summaryrefslogtreecommitdiffstats
path: root/src/yajlpp/yajlpp.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/yajlpp/yajlpp.cc')
-rw-r--r--src/yajlpp/yajlpp.cc1565
1 files changed, 1565 insertions, 0 deletions
diff --git a/src/yajlpp/yajlpp.cc b/src/yajlpp/yajlpp.cc
new file mode 100644
index 0000000..010a7f9
--- /dev/null
+++ b/src/yajlpp/yajlpp.cc
@@ -0,0 +1,1565 @@
+/**
+ * Copyright (c) 2015, Timothy Stack
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * * Neither the name of Timothy Stack nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @file yajlpp.cc
+ */
+
+#include <regex>
+#include <utility>
+
+#include "yajlpp.hh"
+
+#include "base/fs_util.hh"
+#include "base/snippet_highlighters.hh"
+#include "config.h"
+#include "fmt/format.h"
+#include "ghc/filesystem.hpp"
+#include "yajl/api/yajl_parse.h"
+#include "yajlpp_def.hh"
+
+const json_path_handler_base::enum_value_t
+ json_path_handler_base::ENUM_TERMINATOR((const char*) nullptr, 0);
+
+yajl_gen_status
+yajl_gen_tree(yajl_gen hand, yajl_val val)
+{
+ switch (val->type) {
+ case yajl_t_string: {
+ return yajl_gen_string(hand, YAJL_GET_STRING(val));
+ }
+ case yajl_t_number: {
+ if (YAJL_IS_INTEGER(val)) {
+ return yajl_gen_integer(hand, YAJL_GET_INTEGER(val));
+ }
+ if (YAJL_IS_DOUBLE(val)) {
+ return yajl_gen_double(hand, YAJL_GET_DOUBLE(val));
+ }
+ return yajl_gen_number(
+ hand, YAJL_GET_NUMBER(val), strlen(YAJL_GET_NUMBER(val)));
+ }
+ case yajl_t_object: {
+ auto rc = yajl_gen_map_open(hand);
+ if (rc != yajl_gen_status_ok) {
+ return rc;
+ }
+ for (size_t lpc = 0; lpc < YAJL_GET_OBJECT(val)->len; lpc++) {
+ rc = yajl_gen_string(hand, YAJL_GET_OBJECT(val)->keys[lpc]);
+ if (rc != yajl_gen_status_ok) {
+ return rc;
+ }
+ rc = yajl_gen_tree(hand, YAJL_GET_OBJECT(val)->values[lpc]);
+ if (rc != yajl_gen_status_ok) {
+ return rc;
+ }
+ }
+ rc = yajl_gen_map_close(hand);
+ if (rc != yajl_gen_status_ok) {
+ return rc;
+ }
+ return yajl_gen_status_ok;
+ }
+ case yajl_t_array: {
+ auto rc = yajl_gen_array_open(hand);
+ if (rc != yajl_gen_status_ok) {
+ return rc;
+ }
+ for (size_t lpc = 0; lpc < YAJL_GET_ARRAY(val)->len; lpc++) {
+ rc = yajl_gen_tree(hand, YAJL_GET_ARRAY(val)->values[lpc]);
+ if (rc != yajl_gen_status_ok) {
+ return rc;
+ }
+ }
+ rc = yajl_gen_array_close(hand);
+ if (rc != yajl_gen_status_ok) {
+ return rc;
+ }
+ return yajl_gen_status_ok;
+ }
+ case yajl_t_true: {
+ return yajl_gen_bool(hand, true);
+ }
+ case yajl_t_false: {
+ return yajl_gen_bool(hand, false);
+ }
+ case yajl_t_null: {
+ return yajl_gen_null(hand);
+ }
+ default:
+ return yajl_gen_status_ok;
+ }
+}
+
+void
+yajl_cleanup_tree(yajl_val val)
+{
+ if (YAJL_IS_OBJECT(val)) {
+ auto* val_as_obj = YAJL_GET_OBJECT(val);
+
+ for (size_t lpc = 0; lpc < val_as_obj->len;) {
+ auto* child_val = val_as_obj->values[lpc];
+
+ yajl_cleanup_tree(child_val);
+ if (YAJL_IS_OBJECT(child_val)
+ && YAJL_GET_OBJECT(child_val)->len == 0)
+ {
+ free((char*) val_as_obj->keys[lpc]);
+ yajl_tree_free(val_as_obj->values[lpc]);
+ val_as_obj->len -= 1;
+ for (auto lpc2 = lpc; lpc2 < val_as_obj->len; lpc2++) {
+ val_as_obj->keys[lpc2] = val_as_obj->keys[lpc2 + 1];
+ val_as_obj->values[lpc2] = val_as_obj->values[lpc2 + 1];
+ }
+ } else {
+ lpc++;
+ }
+ }
+ }
+}
+
+json_path_handler_base::json_path_handler_base(const std::string& property)
+ : jph_property(property.back() == '#'
+ ? property.substr(0, property.size() - 1)
+ : property),
+ jph_regex(lnav::pcre2pp::code::from(lnav::pcre2pp::quote(property),
+ PCRE2_ANCHORED)
+ .unwrap()
+ .to_shared()),
+ jph_is_array(property.back() == '#')
+{
+ memset(&this->jph_callbacks, 0, sizeof(this->jph_callbacks));
+}
+
+static std::string
+scrub_pattern(const std::string& pattern)
+{
+ static const std::regex CAPTURE(R"(\(\?\<\w+\>)");
+
+ return std::regex_replace(pattern, CAPTURE, "(");
+}
+
+json_path_handler_base::json_path_handler_base(
+ const std::shared_ptr<const lnav::pcre2pp::code>& property)
+ : jph_property(scrub_pattern(property->get_pattern())), jph_regex(property),
+ jph_is_array(property->get_pattern().find('#') != std::string::npos),
+ jph_is_pattern_property(property->get_capture_count() > 0)
+{
+ memset(&this->jph_callbacks, 0, sizeof(this->jph_callbacks));
+}
+
+json_path_handler_base::json_path_handler_base(
+ std::string property,
+ const std::shared_ptr<const lnav::pcre2pp::code>& property_re)
+ : jph_property(std::move(property)), jph_regex(property_re),
+ jph_is_array(property_re->get_pattern().find('#') != std::string::npos)
+{
+ memset(&this->jph_callbacks, 0, sizeof(this->jph_callbacks));
+}
+
+yajl_gen_status
+json_path_handler_base::gen(yajlpp_gen_context& ygc, yajl_gen handle) const
+{
+ if (this->jph_is_array) {
+ auto size = this->jph_size_provider(ygc.ygc_obj_stack.top());
+ auto md = lnav::pcre2pp::match_data::unitialized();
+
+ yajl_gen_string(handle, this->jph_property);
+ yajl_gen_array_open(handle);
+ for (size_t index = 0; index < size; index++) {
+ yajlpp_provider_context ypc{&md, index};
+ yajlpp_gen_context elem_ygc(handle, *this->jph_children);
+ elem_ygc.ygc_depth = 1;
+ elem_ygc.ygc_obj_stack.push(
+ this->jph_obj_provider(ypc, ygc.ygc_obj_stack.top()));
+
+ elem_ygc.gen();
+ }
+ yajl_gen_array_close(handle);
+
+ return yajl_gen_status_ok;
+ }
+
+ std::vector<std::string> local_paths;
+
+ if (this->jph_path_provider) {
+ this->jph_path_provider(ygc.ygc_obj_stack.top(), local_paths);
+ } else {
+ local_paths.emplace_back(this->jph_property);
+ }
+
+ if (this->jph_children) {
+ for (const auto& lpath : local_paths) {
+ std::string full_path = lpath;
+ if (this->jph_path_provider) {
+ full_path += "/";
+ }
+ int start_depth = ygc.ygc_depth;
+
+ yajl_gen_string(handle, lpath);
+ yajl_gen_map_open(handle);
+ ygc.ygc_depth += 1;
+
+ if (this->jph_obj_provider) {
+ static thread_local auto md
+ = lnav::pcre2pp::match_data::unitialized();
+
+ auto find_res = this->jph_regex->capture_from(full_path)
+ .into(md)
+ .matches();
+
+ ygc.ygc_obj_stack.push(this->jph_obj_provider(
+ {&md, yajlpp_provider_context::nindex},
+ ygc.ygc_obj_stack.top()));
+ if (!ygc.ygc_default_stack.empty()) {
+ ygc.ygc_default_stack.push(this->jph_obj_provider(
+ {&md, yajlpp_provider_context::nindex},
+ ygc.ygc_default_stack.top()));
+ }
+ }
+
+ for (const auto& jph : this->jph_children->jpc_children) {
+ yajl_gen_status status = jph.gen(ygc, handle);
+
+ const unsigned char* buf;
+ size_t len;
+ yajl_gen_get_buf(handle, &buf, &len);
+ if (status != yajl_gen_status_ok) {
+ log_error("yajl_gen failure for: %s -- %d",
+ jph.jph_property.c_str(),
+ status);
+ return status;
+ }
+ }
+
+ if (this->jph_obj_provider) {
+ ygc.ygc_obj_stack.pop();
+ if (!ygc.ygc_default_stack.empty()) {
+ ygc.ygc_default_stack.pop();
+ }
+ }
+
+ while (ygc.ygc_depth > start_depth) {
+ yajl_gen_map_close(handle);
+ ygc.ygc_depth -= 1;
+ }
+ }
+ } else if (this->jph_gen_callback != nullptr) {
+ return this->jph_gen_callback(ygc, *this, handle);
+ }
+
+ return yajl_gen_status_ok;
+}
+
+const char* const SCHEMA_TYPE_STRINGS[] = {
+ "any",
+ "boolean",
+ "integer",
+ "number",
+ "string",
+ "array",
+ "object",
+};
+
+yajl_gen_status
+json_path_handler_base::gen_schema(yajlpp_gen_context& ygc) const
+{
+ if (this->jph_children) {
+ {
+ yajlpp_map schema(ygc.ygc_handle);
+
+ if (this->jph_description && this->jph_description[0]) {
+ schema.gen("description");
+ schema.gen(this->jph_description);
+ }
+ if (this->jph_is_pattern_property) {
+ ygc.ygc_path.emplace_back(
+ fmt::format(FMT_STRING("<{}>"),
+ this->jph_regex->get_name_for_capture(1)));
+ } else {
+ ygc.ygc_path.emplace_back(this->jph_property);
+ }
+ if (this->jph_children->jpc_definition_id.empty()) {
+ schema.gen("title");
+ schema.gen(fmt::format(FMT_STRING("/{}"),
+ fmt::join(ygc.ygc_path, "/")));
+ schema.gen("type");
+ if (this->jph_is_array) {
+ if (this->jph_regex->get_pattern().find("#?")
+ == std::string::npos)
+ {
+ schema.gen("array");
+ } else {
+ yajlpp_array type_array(ygc.ygc_handle);
+
+ type_array.gen("array");
+ for (auto schema_type : this->get_types()) {
+ type_array.gen(
+ SCHEMA_TYPE_STRINGS[(int) schema_type]);
+ }
+ }
+ schema.gen("items");
+ yajl_gen_map_open(ygc.ygc_handle);
+ yajl_gen_string(ygc.ygc_handle, "type");
+ this->gen_schema_type(ygc);
+ } else {
+ this->gen_schema_type(ygc);
+ }
+ this->jph_children->gen_schema(ygc);
+ if (this->jph_is_array) {
+ yajl_gen_map_close(ygc.ygc_handle);
+ }
+ } else {
+ schema.gen("title");
+ schema.gen(fmt::format(FMT_STRING("/{}"),
+ fmt::join(ygc.ygc_path, "/")));
+ this->jph_children->gen_schema(ygc);
+ }
+ ygc.ygc_path.pop_back();
+ }
+ } else {
+ yajlpp_map schema(ygc.ygc_handle);
+
+ if (this->jph_is_pattern_property) {
+ ygc.ygc_path.emplace_back(fmt::format(
+ FMT_STRING("<{}>"), this->jph_regex->get_name_for_capture(1)));
+ } else {
+ ygc.ygc_path.emplace_back(this->jph_property);
+ }
+
+ schema.gen("title");
+ schema.gen(
+ fmt::format(FMT_STRING("/{}"), fmt::join(ygc.ygc_path, "/")));
+ if (this->jph_description && this->jph_description[0]) {
+ schema.gen("description");
+ schema.gen(this->jph_description);
+ }
+
+ schema.gen("type");
+
+ if (this->jph_is_array) {
+ if (this->jph_regex->get_pattern().find("#?") == std::string::npos)
+ {
+ schema.gen("array");
+ } else {
+ yajlpp_array type_array(ygc.ygc_handle);
+
+ type_array.gen("array");
+ for (auto schema_type : this->get_types()) {
+ type_array.gen(SCHEMA_TYPE_STRINGS[(int) schema_type]);
+ }
+ }
+ yajl_gen_string(ygc.ygc_handle, "items");
+ yajl_gen_map_open(ygc.ygc_handle);
+ yajl_gen_string(ygc.ygc_handle, "type");
+ }
+
+ this->gen_schema_type(ygc);
+
+ if (!this->jph_examples.empty()) {
+ schema.gen("examples");
+
+ yajlpp_array example_array(ygc.ygc_handle);
+ for (const auto& ex : this->jph_examples) {
+ example_array.gen(ex);
+ }
+ }
+
+ if (this->jph_is_array) {
+ yajl_gen_map_close(ygc.ygc_handle);
+ }
+
+ ygc.ygc_path.pop_back();
+ }
+
+ return yajl_gen_status_ok;
+}
+
+yajl_gen_status
+json_path_handler_base::gen_schema_type(yajlpp_gen_context& ygc) const
+{
+ yajlpp_generator schema(ygc.ygc_handle);
+
+ auto types = this->get_types();
+ if (types.size() == 1) {
+ yajl_gen_string(ygc.ygc_handle, SCHEMA_TYPE_STRINGS[(int) types[0]]);
+ } else {
+ yajlpp_array type_array(ygc.ygc_handle);
+
+ for (auto schema_type : types) {
+ type_array.gen(SCHEMA_TYPE_STRINGS[(int) schema_type]);
+ }
+ }
+
+ for (auto& schema_type : types) {
+ switch (schema_type) {
+ case schema_type_t::STRING:
+ if (this->jph_min_length > 0) {
+ schema("minLength");
+ schema(this->jph_min_length);
+ }
+ if (this->jph_max_length < INT_MAX) {
+ schema("maxLength");
+ schema(this->jph_max_length);
+ }
+ if (this->jph_pattern_re) {
+ schema("pattern");
+ schema(this->jph_pattern_re);
+ }
+ if (this->jph_enum_values) {
+ schema("enum");
+
+ yajlpp_array enum_array(ygc.ygc_handle);
+ for (int lpc = 0; this->jph_enum_values[lpc].first; lpc++) {
+ enum_array.gen(this->jph_enum_values[lpc].first);
+ }
+ }
+ break;
+ case schema_type_t::INTEGER:
+ case schema_type_t::NUMBER:
+ if (this->jph_min_value > LLONG_MIN) {
+ schema("minimum");
+ schema(this->jph_min_value);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ return yajl_gen_keys_must_be_strings;
+}
+
+void
+json_path_handler_base::walk(
+ const std::function<
+ void(const json_path_handler_base&, const std::string&, void*)>& cb,
+ void* root,
+ const std::string& base) const
+{
+ std::vector<std::string> local_paths;
+
+ if (this->jph_path_provider) {
+ this->jph_path_provider(root, local_paths);
+
+ for (auto& lpath : local_paths) {
+ cb(*this,
+ fmt::format(FMT_STRING("{}{}{}"),
+ base,
+ lpath,
+ this->jph_children ? "/" : ""),
+ nullptr);
+ }
+ if (this->jph_obj_deleter) {
+ local_paths.clear();
+ this->jph_path_provider(root, local_paths);
+ }
+ } else {
+ local_paths.emplace_back(this->jph_property);
+
+ std::string full_path = base + this->jph_property;
+ if (this->jph_children) {
+ full_path += "/";
+ }
+ cb(*this, full_path, nullptr);
+ }
+
+ if (this->jph_children) {
+ for (const auto& lpath : local_paths) {
+ for (const auto& jph : this->jph_children->jpc_children) {
+ static const intern_string_t POSS_SRC
+ = intern_string::lookup("possibilities");
+
+ std::string full_path = base + lpath;
+ if (this->jph_children) {
+ full_path += "/";
+ }
+ json_path_container dummy{
+ json_path_handler(this->jph_property, this->jph_regex),
+ };
+
+ yajlpp_parse_context ypc(POSS_SRC, &dummy);
+ void* child_root = root;
+
+ ypc.set_path(full_path).with_obj(root).update_callbacks();
+ if (this->jph_obj_provider) {
+ static thread_local auto md
+ = lnav::pcre2pp::match_data::unitialized();
+
+ std::string full_path = lpath + "/";
+
+ if (!this->jph_regex->capture_from(full_path)
+ .into(md)
+ .matches()
+ .ignore_error())
+ {
+ ensure(false);
+ }
+ child_root = this->jph_obj_provider(
+ {&md, yajlpp_provider_context::nindex}, root);
+ }
+
+ jph.walk(cb, child_root, full_path);
+ }
+ }
+ } else {
+ for (auto& lpath : local_paths) {
+ void* field = nullptr;
+
+ if (this->jph_field_getter) {
+ field = this->jph_field_getter(root, lpath);
+ }
+ cb(*this, base + lpath, field);
+ }
+ }
+}
+
+nonstd::optional<int>
+json_path_handler_base::to_enum_value(const string_fragment& sf) const
+{
+ for (int lpc = 0; this->jph_enum_values[lpc].first; lpc++) {
+ const auto& ev = this->jph_enum_values[lpc];
+
+ if (sf == ev.first) {
+ return ev.second;
+ }
+ }
+
+ return nonstd::nullopt;
+}
+
+const char*
+json_path_handler_base::to_enum_string(int value) const
+{
+ for (int lpc = 0; this->jph_enum_values[lpc].first; lpc++) {
+ const auto& ev = this->jph_enum_values[lpc];
+
+ if (ev.second == value) {
+ return ev.first;
+ }
+ }
+
+ return "";
+}
+
+std::vector<json_path_handler_base::schema_type_t>
+json_path_handler_base::get_types() const
+{
+ std::vector<schema_type_t> retval;
+
+ if (this->jph_callbacks.yajl_boolean) {
+ retval.push_back(schema_type_t::BOOLEAN);
+ }
+ if (this->jph_callbacks.yajl_integer) {
+ retval.push_back(schema_type_t::INTEGER);
+ }
+ if (this->jph_callbacks.yajl_double || this->jph_callbacks.yajl_number) {
+ retval.push_back(schema_type_t::NUMBER);
+ }
+ if (this->jph_callbacks.yajl_string) {
+ retval.push_back(schema_type_t::STRING);
+ }
+ if (this->jph_children) {
+ retval.push_back(schema_type_t::OBJECT);
+ }
+ if (retval.empty()) {
+ retval.push_back(schema_type_t::ANY);
+ }
+ return retval;
+}
+
+yajlpp_parse_context::yajlpp_parse_context(
+ intern_string_t source, const struct json_path_container* handlers)
+ : ypc_source(source), ypc_handlers(handlers)
+{
+ this->ypc_path.reserve(4096);
+ this->ypc_path.push_back('/');
+ this->ypc_path.push_back('\0');
+ this->ypc_callbacks = DEFAULT_CALLBACKS;
+ memset(&this->ypc_alt_callbacks, 0, sizeof(this->ypc_alt_callbacks));
+}
+
+int
+yajlpp_parse_context::map_start(void* ctx)
+{
+ yajlpp_parse_context* ypc = (yajlpp_parse_context*) ctx;
+ int retval = 1;
+
+ require(ypc->ypc_path.size() >= 2);
+
+ ypc->ypc_path_index_stack.push_back(ypc->ypc_path.size() - 1);
+
+ if (ypc->ypc_path.size() > 1
+ && ypc->ypc_path[ypc->ypc_path.size() - 2] == '#')
+ {
+ ypc->ypc_array_index.back() += 1;
+ }
+
+ if (ypc->ypc_alt_callbacks.yajl_start_map != nullptr) {
+ retval = ypc->ypc_alt_callbacks.yajl_start_map(ypc);
+ }
+
+ return retval;
+}
+
+int
+yajlpp_parse_context::map_key(void* ctx, const unsigned char* key, size_t len)
+{
+ yajlpp_parse_context* ypc = (yajlpp_parse_context*) ctx;
+ int retval = 1;
+
+ require(ypc->ypc_path.size() >= 2);
+
+ ypc->ypc_path.resize(ypc->ypc_path_index_stack.back());
+ if (ypc->ypc_path.back() != '/') {
+ ypc->ypc_path.push_back('/');
+ }
+ for (size_t lpc = 0; lpc < len; lpc++) {
+ switch (key[lpc]) {
+ case '~':
+ ypc->ypc_path.push_back('~');
+ ypc->ypc_path.push_back('0');
+ break;
+ case '/':
+ ypc->ypc_path.push_back('~');
+ ypc->ypc_path.push_back('1');
+ break;
+ case '#':
+ ypc->ypc_path.push_back('~');
+ ypc->ypc_path.push_back('2');
+ break;
+ default:
+ ypc->ypc_path.push_back(key[lpc]);
+ break;
+ }
+ }
+ ypc->ypc_path.push_back('\0');
+
+ if (ypc->ypc_alt_callbacks.yajl_map_key != nullptr) {
+ retval = ypc->ypc_alt_callbacks.yajl_map_key(ctx, key, len);
+ }
+
+ if (ypc->ypc_handlers != nullptr) {
+ ypc->update_callbacks();
+ }
+
+ ensure(ypc->ypc_path.size() >= 2);
+
+ return retval;
+}
+
+void
+yajlpp_parse_context::update_callbacks(const json_path_container* orig_handlers,
+ int child_start)
+{
+ const json_path_container* handlers = orig_handlers;
+
+ this->ypc_current_handler = nullptr;
+
+ if (this->ypc_handlers == nullptr) {
+ return;
+ }
+
+ this->ypc_sibling_handlers = orig_handlers;
+ this->ypc_callbacks = DEFAULT_CALLBACKS;
+
+ if (handlers == nullptr) {
+ handlers = this->ypc_handlers;
+ this->ypc_handler_stack.clear();
+ this->ypc_array_handler_count = 0;
+ }
+
+ if (!this->ypc_active_paths.empty()) {
+ std::string curr_path(&this->ypc_path[0], this->ypc_path.size() - 1);
+
+ if (this->ypc_active_paths.find(curr_path)
+ == this->ypc_active_paths.end())
+ {
+ return;
+ }
+ }
+
+ if (child_start == 0 && !this->ypc_obj_stack.empty()) {
+ while (this->ypc_obj_stack.size() > 1) {
+ this->ypc_obj_stack.pop();
+ }
+ }
+
+ auto path_frag = string_fragment::from_byte_range(
+ this->ypc_path.data(), 1 + child_start, this->ypc_path.size() - 1);
+ for (const auto& jph : handlers->jpc_children) {
+ static thread_local auto md = lnav::pcre2pp::match_data::unitialized();
+
+ if (jph.jph_regex->capture_from(path_frag)
+ .into(md)
+ .matches()
+ .ignore_error())
+ {
+ auto cap = md[0].value();
+
+ if (jph.jph_is_array) {
+ this->ypc_array_handler_count += 1;
+ }
+ if (jph.jph_obj_provider) {
+ auto index = this->ypc_array_handler_count == 0
+ ? static_cast<size_t>(-1)
+ : this->ypc_array_index[this->ypc_array_handler_count - 1];
+
+ if ((cap.sf_end != (int) this->ypc_path.size() - 1)
+ && (!jph.is_array()
+ || index != yajlpp_provider_context::nindex))
+ {
+ this->ypc_obj_stack.push(jph.jph_obj_provider(
+ {&md, index, this}, this->ypc_obj_stack.top()));
+ }
+ }
+
+ if (jph.jph_children) {
+ this->ypc_handler_stack.emplace_back(&jph);
+
+ if (cap.sf_end != (int) this->ypc_path.size() - 1) {
+ this->update_callbacks(jph.jph_children, cap.sf_end);
+ return;
+ }
+ } else {
+ if (cap.sf_end != (int) this->ypc_path.size() - 1) {
+ continue;
+ }
+
+ this->ypc_current_handler = &jph;
+ }
+
+ if (jph.jph_callbacks.yajl_null != nullptr) {
+ this->ypc_callbacks.yajl_null = jph.jph_callbacks.yajl_null;
+ }
+ if (jph.jph_callbacks.yajl_boolean != nullptr) {
+ this->ypc_callbacks.yajl_boolean
+ = jph.jph_callbacks.yajl_boolean;
+ }
+ if (jph.jph_callbacks.yajl_integer != nullptr) {
+ this->ypc_callbacks.yajl_integer
+ = jph.jph_callbacks.yajl_integer;
+ }
+ if (jph.jph_callbacks.yajl_double != nullptr) {
+ this->ypc_callbacks.yajl_double = jph.jph_callbacks.yajl_double;
+ }
+ if (jph.jph_callbacks.yajl_string != nullptr) {
+ this->ypc_callbacks.yajl_string = jph.jph_callbacks.yajl_string;
+ }
+ if (jph.jph_is_array) {
+ this->ypc_array_handler_count -= 1;
+ }
+ return;
+ }
+ }
+}
+
+int
+yajlpp_parse_context::map_end(void* ctx)
+{
+ yajlpp_parse_context* ypc = (yajlpp_parse_context*) ctx;
+ int retval = 1;
+
+ ypc->ypc_path.resize(ypc->ypc_path_index_stack.back());
+ ypc->ypc_path.push_back('\0');
+ ypc->ypc_path_index_stack.pop_back();
+
+ if (ypc->ypc_alt_callbacks.yajl_end_map != nullptr) {
+ retval = ypc->ypc_alt_callbacks.yajl_end_map(ctx);
+ }
+
+ ypc->update_callbacks();
+
+ ensure(ypc->ypc_path.size() >= 2);
+
+ return retval;
+}
+
+int
+yajlpp_parse_context::array_start(void* ctx)
+{
+ yajlpp_parse_context* ypc = (yajlpp_parse_context*) ctx;
+ int retval = 1;
+
+ ypc->ypc_path_index_stack.push_back(ypc->ypc_path.size() - 1);
+ ypc->ypc_path[ypc->ypc_path.size() - 1] = '#';
+ ypc->ypc_path.push_back('\0');
+ ypc->ypc_array_index.push_back(-1);
+
+ if (ypc->ypc_alt_callbacks.yajl_start_array != nullptr) {
+ retval = ypc->ypc_alt_callbacks.yajl_start_array(ctx);
+ }
+
+ ypc->update_callbacks();
+
+ ensure(ypc->ypc_path.size() >= 2);
+
+ return retval;
+}
+
+int
+yajlpp_parse_context::array_end(void* ctx)
+{
+ yajlpp_parse_context* ypc = (yajlpp_parse_context*) ctx;
+ int retval = 1;
+
+ ypc->ypc_path.resize(ypc->ypc_path_index_stack.back());
+ ypc->ypc_path.push_back('\0');
+ ypc->ypc_path_index_stack.pop_back();
+ ypc->ypc_array_index.pop_back();
+
+ if (ypc->ypc_alt_callbacks.yajl_end_array != nullptr) {
+ retval = ypc->ypc_alt_callbacks.yajl_end_array(ctx);
+ }
+
+ ypc->update_callbacks();
+
+ ensure(ypc->ypc_path.size() >= 2);
+
+ return retval;
+}
+
+int
+yajlpp_parse_context::handle_unused(void* ctx)
+{
+ yajlpp_parse_context* ypc = (yajlpp_parse_context*) ctx;
+
+ if (ypc->ypc_ignore_unused) {
+ return 1;
+ }
+
+ const json_path_handler_base* handler = ypc->ypc_current_handler;
+ lnav::console::user_message msg;
+
+ if (handler != nullptr && strlen(handler->jph_synopsis) > 0
+ && strlen(handler->jph_description) > 0)
+ {
+ auto help_text = handler->get_help_text(ypc);
+ std::vector<std::string> expected_types;
+
+ if (ypc->ypc_callbacks.yajl_boolean
+ != (int (*)(void*, int)) yajlpp_parse_context::handle_unused)
+ {
+ expected_types.emplace_back("boolean");
+ }
+ if (ypc->ypc_callbacks.yajl_integer
+ != (int (*)(void*, long long)) yajlpp_parse_context::handle_unused)
+ {
+ expected_types.emplace_back("integer");
+ }
+ if (ypc->ypc_callbacks.yajl_double
+ != (int (*)(void*, double)) yajlpp_parse_context::handle_unused)
+ {
+ expected_types.emplace_back("float");
+ }
+ if (ypc->ypc_callbacks.yajl_string
+ != (int (*)(void*, const unsigned char*, size_t))
+ yajlpp_parse_context::handle_unused)
+ {
+ expected_types.emplace_back("string");
+ }
+ if (!expected_types.empty()) {
+ help_text.appendf(
+ FMT_STRING(" expecting one of the following types: {}"),
+ fmt::join(expected_types, ", "));
+ }
+ msg = lnav::console::user_message::warning(
+ attr_line_t("unexpected data for property ")
+ .append_quoted(lnav::roles::symbol(
+ ypc->get_full_path().to_string())))
+ .with_help(help_text);
+ } else if (ypc->ypc_path[1]) {
+ msg = lnav::console::user_message::warning(
+ attr_line_t("unexpected value for property ")
+ .append_quoted(
+ lnav::roles::symbol(ypc->get_full_path().to_string())));
+ } else {
+ msg = lnav::console::user_message::error("unexpected JSON value");
+ }
+
+ if (handler == nullptr) {
+ const json_path_container* accepted_handlers;
+
+ if (ypc->ypc_sibling_handlers) {
+ accepted_handlers = ypc->ypc_sibling_handlers;
+ } else {
+ accepted_handlers = ypc->ypc_handlers;
+ }
+
+ attr_line_t help_text;
+
+ if (accepted_handlers->jpc_children.size() == 1
+ && accepted_handlers->jpc_children.front().jph_is_array)
+ {
+ const auto& jph = accepted_handlers->jpc_children.front();
+
+ help_text.append("expecting an array of ")
+ .append(lnav::roles::variable(jph.jph_synopsis))
+ .append(" values");
+ } else {
+ help_text.append(lnav::roles::h2("Available Properties"))
+ .append("\n");
+ for (const auto& jph : accepted_handlers->jpc_children) {
+ help_text.append(" ")
+ .append(lnav::roles::symbol(jph.jph_property))
+ .append(lnav::roles::symbol(
+ jph.jph_children != nullptr ? "/" : ""))
+ .append(" ")
+ .append(lnav::roles::variable(jph.jph_synopsis))
+ .append("\n");
+ }
+ }
+ msg.with_help(help_text);
+ }
+
+ msg.with_snippet(ypc->get_snippet());
+
+ ypc->report_error(msg);
+
+ return 1;
+}
+
+int
+yajlpp_parse_context::handle_unused_or_delete(void* ctx)
+{
+ yajlpp_parse_context* ypc = (yajlpp_parse_context*) ctx;
+
+ if (!ypc->ypc_handler_stack.empty()
+ && ypc->ypc_handler_stack.back()->jph_obj_deleter)
+ {
+ static thread_local auto md = lnav::pcre2pp::match_data::unitialized();
+
+ auto key_start = ypc->ypc_path_index_stack.back();
+ auto path_frag = string_fragment::from_byte_range(
+ ypc->ypc_path.data(), key_start + 1, ypc->ypc_path.size() - 1);
+ yajlpp_provider_context provider_ctx{&md, static_cast<size_t>(-1)};
+ ypc->ypc_handler_stack.back()
+ ->jph_regex->capture_from(path_frag)
+ .into(md)
+ .matches();
+
+ ypc->ypc_handler_stack.back()->jph_obj_deleter(
+ provider_ctx, ypc->ypc_obj_stack.top());
+ return 1;
+ }
+
+ return handle_unused(ctx);
+}
+
+const yajl_callbacks yajlpp_parse_context::DEFAULT_CALLBACKS = {
+ yajlpp_parse_context::handle_unused_or_delete,
+ (int (*)(void*, int)) yajlpp_parse_context::handle_unused,
+ (int (*)(void*, long long)) yajlpp_parse_context::handle_unused,
+ (int (*)(void*, double)) yajlpp_parse_context::handle_unused,
+ nullptr,
+ (int (*)(void*, const unsigned char*, size_t))
+ yajlpp_parse_context::handle_unused,
+ yajlpp_parse_context::map_start,
+ yajlpp_parse_context::map_key,
+ yajlpp_parse_context::map_end,
+ yajlpp_parse_context::array_start,
+ yajlpp_parse_context::array_end,
+};
+
+yajl_status
+yajlpp_parse_context::parse(const unsigned char* jsonText, size_t jsonTextLen)
+{
+ this->ypc_json_text = jsonText;
+ this->ypc_json_text_len = jsonTextLen;
+
+ yajl_status retval = yajl_parse(this->ypc_handle, jsonText, jsonTextLen);
+
+ size_t consumed = yajl_get_bytes_consumed(this->ypc_handle);
+
+ this->ypc_line_number
+ += std::count(&jsonText[0], &jsonText[consumed], '\n');
+
+ this->ypc_json_text = nullptr;
+
+ if (retval != yajl_status_ok && this->ypc_error_reporter) {
+ auto* msg = yajl_get_error(this->ypc_handle, 1, jsonText, jsonTextLen);
+
+ this->report_error(
+ lnav::console::user_message::error("invalid JSON")
+ .with_snippet(lnav::console::snippet::from(this->ypc_source,
+ (const char*) msg)
+ .with_line(this->get_line_number())));
+ yajl_free_error(this->ypc_handle, msg);
+ }
+
+ return retval;
+}
+
+yajl_status
+yajlpp_parse_context::complete_parse()
+{
+ yajl_status retval = yajl_complete_parse(this->ypc_handle);
+
+ if (retval != yajl_status_ok && this->ypc_error_reporter) {
+ auto* msg = yajl_get_error(this->ypc_handle, 0, nullptr, 0);
+
+ this->report_error(lnav::console::user_message::error("invalid JSON")
+ .with_reason((const char*) msg)
+ .with_snippet(this->get_snippet()));
+ yajl_free_error(this->ypc_handle, msg);
+ }
+
+ return retval;
+}
+
+bool
+yajlpp_parse_context::parse_doc(const string_fragment& sf)
+{
+ bool retval = true;
+
+ this->ypc_json_text = (const unsigned char*) sf.data();
+ this->ypc_json_text_len = sf.length();
+
+ auto rc = yajl_parse(this->ypc_handle, this->ypc_json_text, sf.length());
+ size_t consumed = yajl_get_bytes_consumed(this->ypc_handle);
+ this->ypc_total_consumed += consumed;
+ this->ypc_line_number += std::count(
+ &this->ypc_json_text[0], &this->ypc_json_text[consumed], '\n');
+
+ if (rc != yajl_status_ok) {
+ if (this->ypc_error_reporter) {
+ auto* msg = yajl_get_error(this->ypc_handle,
+ 1,
+ this->ypc_json_text,
+ this->ypc_json_text_len);
+
+ this->report_error(
+ lnav::console::user_message::error("invalid JSON")
+ .with_reason((const char*) msg)
+ .with_snippet(this->get_snippet()));
+ yajl_free_error(this->ypc_handle, msg);
+ }
+ retval = false;
+ } else if (this->complete_parse() != yajl_status_ok) {
+ retval = false;
+ }
+
+ this->ypc_json_text = nullptr;
+
+ return retval;
+}
+
+const intern_string_t
+yajlpp_parse_context::get_path() const
+{
+ if (this->ypc_path.size() <= 1) {
+ return intern_string_t();
+ }
+ return intern_string::lookup(&this->ypc_path[1], this->ypc_path.size() - 2);
+}
+
+const intern_string_t
+yajlpp_parse_context::get_full_path() const
+{
+ if (this->ypc_path.size() <= 1) {
+ static const intern_string_t SLASH = intern_string::lookup("/");
+
+ return SLASH;
+ }
+ return intern_string::lookup(&this->ypc_path[0], this->ypc_path.size() - 1);
+}
+
+void
+yajlpp_parse_context::reset(const struct json_path_container* handlers)
+{
+ this->ypc_handlers = handlers;
+ this->ypc_path.clear();
+ this->ypc_path.push_back('/');
+ this->ypc_path.push_back('\0');
+ this->ypc_path_index_stack.clear();
+ this->ypc_array_index.clear();
+ this->ypc_array_handler_count = 0;
+ this->ypc_callbacks = DEFAULT_CALLBACKS;
+ memset(&this->ypc_alt_callbacks, 0, sizeof(this->ypc_alt_callbacks));
+ this->ypc_sibling_handlers = nullptr;
+ this->ypc_current_handler = nullptr;
+ while (!this->ypc_obj_stack.empty()) {
+ this->ypc_obj_stack.pop();
+ }
+}
+
+void
+yajlpp_parse_context::set_static_handler(const json_path_handler_base& jph)
+{
+ this->ypc_path.clear();
+ this->ypc_path.push_back('/');
+ this->ypc_path.push_back('\0');
+ this->ypc_path_index_stack.clear();
+ this->ypc_array_index.clear();
+ this->ypc_array_handler_count = 0;
+ if (jph.jph_callbacks.yajl_null != nullptr) {
+ this->ypc_callbacks.yajl_null = jph.jph_callbacks.yajl_null;
+ }
+ if (jph.jph_callbacks.yajl_boolean != nullptr) {
+ this->ypc_callbacks.yajl_boolean = jph.jph_callbacks.yajl_boolean;
+ }
+ if (jph.jph_callbacks.yajl_integer != nullptr) {
+ this->ypc_callbacks.yajl_integer = jph.jph_callbacks.yajl_integer;
+ }
+ if (jph.jph_callbacks.yajl_double != nullptr) {
+ this->ypc_callbacks.yajl_double = jph.jph_callbacks.yajl_double;
+ }
+ if (jph.jph_callbacks.yajl_string != nullptr) {
+ this->ypc_callbacks.yajl_string = jph.jph_callbacks.yajl_string;
+ }
+}
+
+yajlpp_parse_context&
+yajlpp_parse_context::set_path(const std::string& path)
+{
+ this->ypc_path.resize(path.size() + 1);
+ std::copy(path.begin(), path.end(), this->ypc_path.begin());
+ this->ypc_path[path.size()] = '\0';
+ for (size_t lpc = 0; lpc < path.size(); lpc++) {
+ switch (path[lpc]) {
+ case '/':
+ this->ypc_path_index_stack.push_back(
+ this->ypc_path_index_stack.empty() ? 1 : 0 + lpc);
+ break;
+ }
+ }
+ return *this;
+}
+
+const char*
+yajlpp_parse_context::get_path_fragment(int offset,
+ char* frag_in,
+ size_t& len_out) const
+{
+ const char* retval;
+ size_t start, end;
+
+ if (offset < 0) {
+ offset = ((int) this->ypc_path_index_stack.size()) + offset;
+ }
+ start = this->ypc_path_index_stack[offset] + ((offset == 0) ? 0 : 1);
+ if ((offset + 1) < (int) this->ypc_path_index_stack.size()) {
+ end = this->ypc_path_index_stack[offset + 1];
+ } else {
+ end = this->ypc_path.size() - 1;
+ }
+ if (this->ypc_handlers) {
+ len_out
+ = json_ptr::decode(frag_in, &this->ypc_path[start], end - start);
+ retval = frag_in;
+ } else {
+ retval = &this->ypc_path[start];
+ len_out = end - start;
+ }
+
+ return retval;
+}
+
+int
+yajlpp_parse_context::get_line_number() const
+{
+ if (this->ypc_handle != nullptr && this->ypc_json_text) {
+ size_t consumed = yajl_get_bytes_consumed(this->ypc_handle);
+ long current_count = std::count(
+ &this->ypc_json_text[0], &this->ypc_json_text[consumed], '\n');
+
+ return this->ypc_line_number + current_count;
+ }
+ return this->ypc_line_number;
+}
+
+void
+yajlpp_gen_context::gen()
+{
+ yajlpp_map root(this->ygc_handle);
+
+ for (const auto& jph : this->ygc_handlers->jpc_children) {
+ jph.gen(*this, this->ygc_handle);
+ }
+}
+
+void
+yajlpp_gen_context::gen_schema(const json_path_container* handlers)
+{
+ if (handlers == nullptr) {
+ handlers = this->ygc_handlers;
+ }
+
+ {
+ yajlpp_map schema(this->ygc_handle);
+
+ if (!handlers->jpc_schema_id.empty()) {
+ schema.gen("$id");
+ schema.gen(handlers->jpc_schema_id);
+ schema.gen("title");
+ schema.gen(handlers->jpc_schema_id);
+ }
+ schema.gen("$schema");
+ schema.gen("http://json-schema.org/draft-07/schema#");
+ if (!handlers->jpc_description.empty()) {
+ schema.gen("description");
+ schema.gen(handlers->jpc_description);
+ }
+ handlers->gen_schema(*this);
+
+ if (!this->ygc_schema_definitions.empty()) {
+ schema.gen("definitions");
+
+ yajlpp_map defs(this->ygc_handle);
+ for (auto& container : this->ygc_schema_definitions) {
+ defs.gen(container.first);
+
+ yajlpp_map def(this->ygc_handle);
+
+ def.gen("title");
+ def.gen(container.first);
+ if (!container.second->jpc_description.empty()) {
+ def.gen("description");
+ def.gen(container.second->jpc_description);
+ }
+ def.gen("type");
+ def.gen("object");
+ def.gen("$$target");
+ def.gen(fmt::format(FMT_STRING("#/definitions/{}"),
+ container.first));
+ container.second->gen_properties(*this);
+ }
+ }
+ }
+}
+
+yajlpp_gen_context&
+yajlpp_gen_context::with_context(yajlpp_parse_context& ypc)
+{
+ this->ygc_obj_stack = ypc.ypc_obj_stack;
+ if (ypc.ypc_current_handler == nullptr && !ypc.ypc_handler_stack.empty()
+ && ypc.ypc_handler_stack.back() != nullptr)
+ {
+ this->ygc_handlers = ypc.ypc_handler_stack.back()->jph_children;
+ this->ygc_depth += 1;
+ }
+ return *this;
+}
+
+json_path_handler&
+json_path_handler::with_children(const json_path_container& container)
+{
+ this->jph_children = &container;
+ return *this;
+}
+
+lnav::console::snippet
+yajlpp_parse_context::get_snippet() const
+{
+ auto line_number = this->get_line_number();
+ attr_line_t content;
+
+ if (this->ypc_json_text != nullptr) {
+ auto in_text_line = line_number - this->ypc_line_number;
+ const auto* line_start = this->ypc_json_text;
+ auto text_len_remaining = this->ypc_json_text_len;
+
+ while (in_text_line > 0) {
+ const auto* line_end = (const unsigned char*) memchr(
+ line_start, '\n', text_len_remaining);
+ if (line_end == nullptr) {
+ break;
+ }
+
+ text_len_remaining -= (line_end - line_start) + 1;
+ line_start = line_end + 1;
+ in_text_line -= 1;
+ }
+
+ if (text_len_remaining > 0) {
+ const auto* line_end = (const unsigned char*) memchr(
+ line_start, '\n', text_len_remaining);
+ if (line_end) {
+ text_len_remaining = (line_end - line_start);
+ }
+ content.append(
+ string_fragment::from_bytes(line_start, text_len_remaining));
+ }
+ }
+
+ content.with_attr_for_all(VC_ROLE.value(role_t::VCR_QUOTED_CODE));
+ return lnav::console::snippet::from(this->ypc_source, content)
+ .with_line(line_number);
+}
+
+void
+json_path_handler_base::validate_string(yajlpp_parse_context& ypc,
+ string_fragment sf) const
+{
+ if (this->jph_pattern) {
+ if (!this->jph_pattern->find_in(sf).ignore_error()) {
+ this->report_pattern_error(&ypc, sf.to_string());
+ }
+ }
+ if (sf.empty() && this->jph_min_length > 0) {
+ ypc.report_error(lnav::console::user_message::error(
+ attr_line_t("invalid value for option ")
+ .append_quoted(lnav::roles::symbol(
+ ypc.get_full_path().to_string())))
+ .with_reason("empty values are not allowed")
+ .with_snippet(ypc.get_snippet())
+ .with_help(this->get_help_text(&ypc)));
+ } else if (sf.length() < this->jph_min_length) {
+ ypc.report_error(
+ lnav::console::user_message::error(
+ attr_line_t()
+ .append_quoted(sf)
+ .append(" is not a valid value for option ")
+ .append_quoted(
+ lnav::roles::symbol(ypc.get_full_path().to_string())))
+ .with_reason(attr_line_t("value must be at least ")
+ .append(lnav::roles::number(
+ fmt::to_string(this->jph_min_length)))
+ .append(" characters long"))
+ .with_snippet(ypc.get_snippet())
+ .with_help(this->get_help_text(&ypc)));
+ }
+}
+
+void
+json_path_handler_base::report_pattern_error(yajlpp_parse_context* ypc,
+ const std::string& value_str) const
+{
+ ypc->report_error(
+ lnav::console::user_message::error(
+ attr_line_t()
+ .append_quoted(value_str)
+ .append(" is not a valid value for option ")
+ .append_quoted(
+ lnav::roles::symbol(ypc->get_full_path().to_string())))
+ .with_snippet(ypc->get_snippet())
+ .with_reason(attr_line_t("value does not match pattern: ")
+ .append(lnav::roles::symbol(this->jph_pattern_re)))
+ .with_help(this->get_help_text(ypc)));
+}
+
+attr_line_t
+json_path_handler_base::get_help_text(const std::string& full_path) const
+{
+ attr_line_t retval;
+
+ retval.append(lnav::roles::h2("Property Synopsis"))
+ .append("\n ")
+ .append(lnav::roles::symbol(full_path))
+ .append(" ")
+ .append(lnav::roles::variable(this->jph_synopsis))
+ .append("\n")
+ .append(lnav::roles::h2("Description"))
+ .append("\n ")
+ .append(this->jph_description)
+ .append("\n");
+
+ if (this->jph_enum_values != nullptr) {
+ retval.append(lnav::roles::h2("Allowed Values")).append("\n ");
+
+ for (int lpc = 0; this->jph_enum_values[lpc].first; lpc++) {
+ const auto& ev = this->jph_enum_values[lpc];
+
+ retval.append(lpc == 0 ? "" : ", ")
+ .append(lnav::roles::symbol(ev.first));
+ }
+ }
+
+ if (!this->jph_examples.empty()) {
+ retval
+ .append(lnav::roles::h2(
+ this->jph_examples.size() == 1 ? "Example" : "Examples"))
+ .append("\n");
+
+ for (const auto& ex : this->jph_examples) {
+ retval.appendf(FMT_STRING(" {}\n"), ex);
+ }
+ }
+
+ return retval;
+}
+
+attr_line_t
+json_path_handler_base::get_help_text(yajlpp_parse_context* ypc) const
+{
+ return this->get_help_text(ypc->get_full_path().to_string());
+}
+
+void
+json_path_handler_base::report_min_value_error(yajlpp_parse_context* ypc,
+ long long value) const
+{
+ ypc->report_error(
+ lnav::console::user_message::error(
+ attr_line_t()
+ .append_quoted(fmt::to_string(value))
+ .append(" is not a valid value for option ")
+ .append_quoted(
+ lnav::roles::symbol(ypc->get_full_path().to_string())))
+ .with_reason(attr_line_t("value must be greater than or equal to ")
+ .append(lnav::roles::number(
+ fmt::to_string(this->jph_min_value))))
+ .with_snippet(ypc->get_snippet())
+ .with_help(this->get_help_text(ypc)));
+}
+
+void
+json_path_handler_base::report_duration_error(
+ yajlpp_parse_context* ypc,
+ const std::string& value_str,
+ const relative_time::parse_error& pe) const
+{
+ ypc->report_error(lnav::console::user_message::error(
+ attr_line_t()
+ .append_quoted(value_str)
+ .append(" is not a valid duration value "
+ "for option ")
+ .append_quoted(lnav::roles::symbol(
+ ypc->get_full_path().to_string())))
+ .with_snippet(ypc->get_snippet())
+ .with_reason(pe.pe_msg)
+ .with_help(this->get_help_text(ypc)));
+}
+
+void
+json_path_handler_base::report_enum_error(yajlpp_parse_context* ypc,
+ const std::string& value_str) const
+{
+ ypc->report_error(lnav::console::user_message::error(
+ attr_line_t()
+ .append_quoted(value_str)
+ .append(" is not a valid value for option ")
+ .append_quoted(lnav::roles::symbol(
+ ypc->get_full_path().to_string())))
+ .with_snippet(ypc->get_snippet())
+ .with_help(this->get_help_text(ypc)));
+}
+
+void
+json_path_handler_base::report_error(yajlpp_parse_context* ypc,
+ const std::string& value,
+ lnav::console::user_message um) const
+{
+ ypc->report_error(um.with_snippet(ypc->get_snippet())
+ .with_help(this->get_help_text(ypc)));
+}
+
+void
+json_path_container::gen_schema(yajlpp_gen_context& ygc) const
+{
+ if (!this->jpc_definition_id.empty()) {
+ ygc.ygc_schema_definitions[this->jpc_definition_id] = this;
+
+ yajl_gen_string(ygc.ygc_handle, "$ref");
+ yajl_gen_string(ygc.ygc_handle,
+ fmt::format(FMT_STRING("#/definitions/{}"),
+ this->jpc_definition_id));
+ return;
+ }
+
+ this->gen_properties(ygc);
+}
+
+void
+json_path_container::gen_properties(yajlpp_gen_context& ygc) const
+{
+ auto pattern_count = count_if(
+ this->jpc_children.begin(), this->jpc_children.end(), [](auto& jph) {
+ return jph.jph_is_pattern_property;
+ });
+ auto plain_count = this->jpc_children.size() - pattern_count;
+
+ if (plain_count > 0) {
+ yajl_gen_string(ygc.ygc_handle, "properties");
+ {
+ yajlpp_map properties(ygc.ygc_handle);
+
+ for (const auto& child_handler : this->jpc_children) {
+ if (child_handler.jph_is_pattern_property) {
+ continue;
+ }
+ properties.gen(child_handler.jph_property);
+ child_handler.gen_schema(ygc);
+ }
+ }
+ }
+ if (pattern_count > 0) {
+ yajl_gen_string(ygc.ygc_handle, "patternProperties");
+ {
+ yajlpp_map properties(ygc.ygc_handle);
+
+ for (const auto& child_handler : this->jpc_children) {
+ if (!child_handler.jph_is_pattern_property) {
+ continue;
+ }
+ properties.gen(child_handler.jph_property);
+ child_handler.gen_schema(ygc);
+ }
+ }
+ }
+
+ yajl_gen_string(ygc.ygc_handle, "additionalProperties");
+ yajl_gen_bool(ygc.ygc_handle, false);
+}
+
+static void
+schema_printer(FILE* file, const char* str, size_t len)
+{
+ fwrite(str, len, 1, file);
+}
+
+void
+dump_schema_to(const json_path_container& jpc, const char* internals_dir)
+{
+ yajlpp_gen genner;
+ yajlpp_gen_context ygc(genner, jpc);
+ auto internals_dir_path = ghc::filesystem::path(internals_dir);
+ auto schema_file_name = ghc::filesystem::path(jpc.jpc_schema_id).filename();
+ auto schema_path = internals_dir_path / schema_file_name;
+ auto file = std::unique_ptr<FILE, decltype(&fclose)>(
+ fopen(schema_path.c_str(), "w+"), fclose);
+
+ if (!file.get()) {
+ return;
+ }
+
+ yajl_gen_config(genner, yajl_gen_beautify, true);
+ yajl_gen_config(
+ genner, yajl_gen_print_callback, schema_printer, file.get());
+
+ ygc.gen_schema();
+}
+
+string_fragment
+yajlpp_gen::to_string_fragment()
+{
+ const unsigned char* buf;
+ size_t len;
+
+ yajl_gen_get_buf(this->yg_handle.in(), &buf, &len);
+
+ return string_fragment::from_bytes(buf, len);
+}