summaryrefslogtreecommitdiffstats
path: root/src/yajlpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 17:44:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 17:44:55 +0000
commit5068d34c08f951a7ea6257d305a1627b09a95817 (patch)
tree08213e2be853396a3b07ce15dbe222644dcd9a89 /src/yajlpp
parentInitial commit. (diff)
downloadlnav-5068d34c08f951a7ea6257d305a1627b09a95817.tar.xz
lnav-5068d34c08f951a7ea6257d305a1627b09a95817.zip
Adding upstream version 0.11.1.upstream/0.11.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--src/yajlpp/CMakeLists.txt27
-rw-r--r--src/yajlpp/Makefile.am81
-rw-r--r--src/yajlpp/README.md5
-rw-r--r--src/yajlpp/drive_json_op.cc210
-rw-r--r--src/yajlpp/drive_json_ptr_walk.cc103
-rw-r--r--src/yajlpp/json_op.cc316
-rw-r--r--src/yajlpp/json_op.hh82
-rw-r--r--src/yajlpp/json_ptr.cc521
-rw-r--r--src/yajlpp/json_ptr.hh136
-rwxr-xr-xsrc/yajlpp/test_json_op.sh114
-rw-r--r--src/yajlpp/test_json_ptr.cc73
-rw-r--r--src/yajlpp/test_json_ptr_walk.sh77
-rw-r--r--src/yajlpp/test_yajlpp.cc166
-rw-r--r--src/yajlpp/yajlpp.cc1565
-rw-r--r--src/yajlpp/yajlpp.hh682
-rw-r--r--src/yajlpp/yajlpp_def.hh1388
16 files changed, 5546 insertions, 0 deletions
diff --git a/src/yajlpp/CMakeLists.txt b/src/yajlpp/CMakeLists.txt
new file mode 100644
index 0000000..73168ba
--- /dev/null
+++ b/src/yajlpp/CMakeLists.txt
@@ -0,0 +1,27 @@
+add_library(
+ yajlpp STATIC
+ ../config.h.in
+ json_op.hh
+ json_ptr.hh
+ yajlpp.hh
+ yajlpp_def.hh
+
+ json_op.cc
+ json_ptr.cc
+ yajlpp.cc
+)
+
+target_include_directories(yajlpp PUBLIC . .. ../fmtlib
+ ${CMAKE_CURRENT_BINARY_DIR}/..)
+target_link_libraries(yajlpp pcrepp yajl ncurses::libcurses)
+
+add_executable(test_yajlpp test_yajlpp.cc)
+target_link_libraries(test_yajlpp yajlpp base ${lnav_LIBS})
+add_test(NAME test_yajlpp COMMAND test_yajlpp)
+
+add_executable(test_json_ptr test_json_ptr.cc)
+target_link_libraries(test_json_ptr yajlpp base ${lnav_LIBS})
+add_test(NAME test_json_ptr COMMAND test_json_ptr)
+
+add_executable(drive_json_op drive_json_op.cc)
+target_link_libraries(drive_json_op base yajlpp ${lnav_LIBS})
diff --git a/src/yajlpp/Makefile.am b/src/yajlpp/Makefile.am
new file mode 100644
index 0000000..cc6bcf6
--- /dev/null
+++ b/src/yajlpp/Makefile.am
@@ -0,0 +1,81 @@
+
+include $(top_srcdir)/aminclude_static.am
+
+TESTS_ENVIRONMENT = $(SHELL) $(top_builddir)/TESTS_ENVIRONMENT
+LOG_COMPILER = $(SHELL) $(top_builddir)/TESTS_ENVIRONMENT
+
+AM_CPPFLAGS = \
+ $(CODE_COVERAGE_CPPFLAGS) \
+ -Wall \
+ $(LIBARCHIVE_CFLAGS) \
+ $(PCRE_CFLAGS) \
+ -I$(top_srcdir)/src/ \
+ -I$(top_srcdir)/src/fmtlib \
+ -I$(top_srcdir)/src/third-party/scnlib/include
+
+AM_LDFLAGS = \
+ $(LIBARCHIVE_LDFLAGS) \
+ $(STATIC_LDFLAGS)
+
+AM_LIBS = $(CODE_COVERAGE_LIBS)
+AM_CFLAGS = $(CODE_COVERAGE_CFLAGS)
+AM_CXXFLAGS = $(CODE_COVERAGE_CXXFLAGS)
+
+noinst_LIBRARIES = libyajlpp.a
+
+noinst_HEADERS = \
+ json_op.hh \
+ json_ptr.hh \
+ yajlpp.hh \
+ yajlpp_def.hh
+
+libyajlpp_a_SOURCES = \
+ json_op.cc \
+ json_ptr.cc \
+ yajlpp.cc
+
+check_PROGRAMS = \
+ drive_json_op \
+ drive_json_ptr_walk \
+ test_json_ptr \
+ test_yajlpp
+
+drive_json_op_SOURCES = drive_json_op.cc
+
+drive_json_ptr_walk_SOURCES = drive_json_ptr_walk.cc
+
+test_json_ptr_SOURCES = test_json_ptr.cc
+
+test_yajlpp_SOURCES = test_yajlpp.cc
+
+LDADD = \
+ $(LIBARCHIVE_LIBS) \
+ libyajlpp.a \
+ $(top_builddir)/src/base/libbase.a \
+ $(top_builddir)/src/fmtlib/libcppfmt.a \
+ $(top_builddir)/src/third-party/scnlib/src/libscnlib.a \
+ $(top_builddir)/src/pcrepp/libpcrepp.a \
+ $(top_builddir)/src/yajl/libyajl.a
+
+dist_noinst_SCRIPTS = \
+ test_json_op.sh \
+ test_json_ptr_walk.sh
+
+TESTS = \
+ test_json_op.sh \
+ test_json_ptr \
+ test_json_ptr_walk.sh \
+ test_yajlpp
+
+DISTCLEANFILES = \
+ *.dat \
+ *.err \
+ *.db \
+ *.dpt \
+ *.diff \
+ *.index \
+ *.tmp \
+ *.errbak \
+ *.tmpbak \
+ *.gz \
+ *.bz2
diff --git a/src/yajlpp/README.md b/src/yajlpp/README.md
new file mode 100644
index 0000000..3a7650c
--- /dev/null
+++ b/src/yajlpp/README.md
@@ -0,0 +1,5 @@
+# YAJLPP
+
+This directory contains a C++ adapter for the YAJL JSON library. The adapter
+provides a usage model that is based around client code mapping internal data
+structures to a JSON schema.
diff --git a/src/yajlpp/drive_json_op.cc b/src/yajlpp/drive_json_op.cc
new file mode 100644
index 0000000..51ef161
--- /dev/null
+++ b/src/yajlpp/drive_json_op.cc
@@ -0,0 +1,210 @@
+/**
+ * Copyright (c) 2014, 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 TIMOTHY STACK 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 drive_json_op.cc
+ */
+
+#include <errno.h>
+#include <stdlib.h>
+
+#include "base/lnav_log.hh"
+#include "config.h"
+#include "yajl/api/yajl_gen.h"
+#include "yajlpp/json_op.hh"
+
+static void
+printer(void* ctx, const char* numberVal, size_t numberLen)
+{
+ log_perror(write(STDOUT_FILENO, numberVal, numberLen));
+}
+
+static int
+handle_start_map(void* ctx)
+{
+ json_op* jo = (json_op*) ctx;
+ yajl_gen gen = (yajl_gen) jo->jo_ptr_data;
+
+ yajl_gen_map_open(gen);
+
+ return 1;
+}
+
+static int
+handle_map_key(void* ctx, const unsigned char* key, size_t len)
+{
+ json_op* jo = (json_op*) ctx;
+ yajl_gen gen = (yajl_gen) jo->jo_ptr_data;
+
+ yajl_gen_string(gen, key, len);
+
+ return 1;
+}
+
+static int
+handle_end_map(void* ctx)
+{
+ json_op* jo = (json_op*) ctx;
+ yajl_gen gen = (yajl_gen) jo->jo_ptr_data;
+
+ yajl_gen_map_close(gen);
+
+ return 1;
+}
+
+static int
+handle_null(void* ctx)
+{
+ json_op* jo = (json_op*) ctx;
+ yajl_gen gen = (yajl_gen) jo->jo_ptr_data;
+
+ yajl_gen_null(gen);
+
+ return 1;
+}
+
+static int
+handle_boolean(void* ctx, int boolVal)
+{
+ json_op* jo = (json_op*) ctx;
+ yajl_gen gen = (yajl_gen) jo->jo_ptr_data;
+
+ yajl_gen_bool(gen, boolVal);
+
+ return 1;
+}
+
+static int
+handle_number(void* ctx, const char* numberVal, size_t numberLen)
+{
+ json_op* jo = (json_op*) ctx;
+ yajl_gen gen = (yajl_gen) jo->jo_ptr_data;
+
+ yajl_gen_number(gen, numberVal, numberLen);
+
+ return 1;
+}
+
+static int
+handle_string(void* ctx, const unsigned char* stringVal, size_t len)
+{
+ json_op* jo = (json_op*) ctx;
+ yajl_gen gen = (yajl_gen) jo->jo_ptr_data;
+
+ yajl_gen_string(gen, stringVal, len);
+
+ return 1;
+}
+
+static int
+handle_start_array(void* ctx)
+{
+ json_op* jo = (json_op*) ctx;
+ yajl_gen gen = (yajl_gen) jo->jo_ptr_data;
+
+ yajl_gen_array_open(gen);
+
+ return 1;
+}
+
+static int
+handle_end_array(void* ctx)
+{
+ json_op* jo = (json_op*) ctx;
+ yajl_gen gen = (yajl_gen) jo->jo_ptr_data;
+
+ yajl_gen_array_close(gen);
+
+ return 1;
+}
+
+int
+main(int argc, char* argv[])
+{
+ int retval = EXIT_SUCCESS;
+
+ log_argv(argc, argv);
+
+ if (argc != 3) {
+ fprintf(stderr, "error: expecting operation and json-pointer\n");
+ retval = EXIT_FAILURE;
+ } else if (strcmp(argv[1], "get") == 0) {
+ unsigned char buffer[1024];
+ json_ptr jptr(argv[2]);
+ json_op jo(jptr);
+ yajl_handle handle;
+ yajl_status status;
+ yajl_gen gen;
+ ssize_t rc;
+
+ gen = yajl_gen_alloc(nullptr);
+ yajl_gen_config(gen, yajl_gen_print_callback, printer, nullptr);
+ yajl_gen_config(gen, yajl_gen_beautify, true);
+
+ jo.jo_ptr_callbacks.yajl_start_map = handle_start_map;
+ jo.jo_ptr_callbacks.yajl_map_key = handle_map_key;
+ jo.jo_ptr_callbacks.yajl_end_map = handle_end_map;
+ jo.jo_ptr_callbacks.yajl_start_array = handle_start_array;
+ jo.jo_ptr_callbacks.yajl_end_array = handle_end_array;
+ jo.jo_ptr_callbacks.yajl_null = handle_null;
+ jo.jo_ptr_callbacks.yajl_boolean = handle_boolean;
+ jo.jo_ptr_callbacks.yajl_number = handle_number;
+ jo.jo_ptr_callbacks.yajl_string = handle_string;
+ jo.jo_ptr_data = gen;
+
+ handle = yajl_alloc(&json_op::ptr_callbacks, nullptr, &jo);
+ while ((rc = read(STDIN_FILENO, buffer, sizeof(buffer))) > 0) {
+ status = yajl_parse(handle, buffer, rc);
+ if (status == yajl_status_error) {
+ auto msg = yajl_get_error(handle, 1, buffer, rc);
+
+ fprintf(stderr, "error:cannot parse JSON input -- %s\n", msg);
+ retval = EXIT_FAILURE;
+ yajl_free_error(handle, msg);
+ break;
+ } else if (status == yajl_status_client_canceled) {
+ fprintf(stderr, "client cancel\n");
+ break;
+ }
+ }
+ status = yajl_complete_parse(handle);
+ if (status == yajl_status_error) {
+ auto msg = yajl_get_error(handle, 1, buffer, rc);
+ fprintf(stderr, "error:cannot parse JSON input -- %s\n", msg);
+ yajl_free_error(handle, msg);
+ retval = EXIT_FAILURE;
+ } else if (status == yajl_status_client_canceled) {
+ fprintf(stderr, "client cancel\n");
+ }
+ yajl_free(handle);
+ } else {
+ fprintf(stderr, "error: unknown operation -- %s\n", argv[1]);
+ retval = EXIT_FAILURE;
+ }
+
+ return retval;
+}
diff --git a/src/yajlpp/drive_json_ptr_walk.cc b/src/yajlpp/drive_json_ptr_walk.cc
new file mode 100644
index 0000000..c6a8f72
--- /dev/null
+++ b/src/yajlpp/drive_json_ptr_walk.cc
@@ -0,0 +1,103 @@
+/**
+ * Copyright (c) 2014, 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 TIMOTHY STACK 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 drive_json_ptr_dump.cc
+ */
+
+#include <iostream>
+
+#include <stdlib.h>
+
+#include "base/lnav_log.hh"
+#include "config.h"
+#include "json_op.hh"
+#include "json_ptr.hh"
+#include "yajl/api/yajl_gen.h"
+#include "yajlpp.hh"
+
+int
+main(int argc, char* argv[])
+{
+ int retval = EXIT_SUCCESS;
+ yajl_status status;
+ json_ptr_walk jpw;
+
+ log_argv(argc, argv);
+
+ std::string json_input(std::istreambuf_iterator<char>(std::cin), {});
+
+ status = jpw.parse(json_input.c_str(), json_input.size());
+ if (status == yajl_status_error) {
+ fprintf(stderr,
+ "error:cannot parse JSON input -- %s\n",
+ jpw.jpw_error_msg.c_str());
+ return EXIT_FAILURE;
+ }
+
+ if (status == yajl_status_client_canceled) {
+ fprintf(stderr, "client cancel\n");
+ }
+
+ status = jpw.complete_parse();
+ if (status == yajl_status_error) {
+ fprintf(stderr,
+ "error:cannot parse JSON input -- %s\n",
+ jpw.jpw_error_msg.c_str());
+ return EXIT_FAILURE;
+ } else if (status == yajl_status_client_canceled) {
+ fprintf(stderr, "client cancel\n");
+ }
+
+ for (json_ptr_walk::walk_list_t::iterator iter = jpw.jpw_values.begin();
+ iter != jpw.jpw_values.end();
+ ++iter)
+ {
+ printf("%s = %s\n", iter->wt_ptr.c_str(), iter->wt_value.c_str());
+
+ {
+ auto_mem<yajl_handle_t> parse_handle(yajl_free);
+ json_ptr jp(iter->wt_ptr.c_str());
+ json_op jo(jp);
+ yajlpp_gen gen;
+
+ jo.jo_ptr_callbacks = json_op::gen_callbacks;
+ jo.jo_ptr_data = gen.get_handle();
+ parse_handle.reset(
+ yajl_alloc(&json_op::ptr_callbacks, nullptr, &jo));
+
+ yajl_parse(parse_handle.in(),
+ (const unsigned char*) json_input.c_str(),
+ json_input.size());
+ yajl_complete_parse(parse_handle.in());
+
+ assert(iter->wt_value == gen.to_string_fragment().to_string());
+ }
+ }
+
+ return retval;
+}
diff --git a/src/yajlpp/json_op.cc b/src/yajlpp/json_op.cc
new file mode 100644
index 0000000..046f223
--- /dev/null
+++ b/src/yajlpp/json_op.cc
@@ -0,0 +1,316 @@
+/**
+ * Copyright (c) 2014, 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 TIMOTHY STACK 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 json_op.cc
+ */
+
+#include "json_op.hh"
+
+#include "base/lnav_log.hh"
+#include "config.h"
+#include "yajl/api/yajl_gen.h"
+
+static int
+gen_handle_start_map(void* ctx)
+{
+ json_op* jo = (json_op*) ctx;
+ yajl_gen gen = (yajl_gen) jo->jo_ptr_data;
+
+ jo->jo_ptr_error_code = yajl_gen_map_open(gen);
+
+ return jo->jo_ptr_error_code == yajl_gen_status_ok;
+}
+
+static int
+gen_handle_map_key(void* ctx, const unsigned char* key, size_t len)
+{
+ json_op* jo = (json_op*) ctx;
+ yajl_gen gen = (yajl_gen) jo->jo_ptr_data;
+
+ jo->jo_ptr_error_code = yajl_gen_string(gen, key, len);
+
+ return jo->jo_ptr_error_code == yajl_gen_status_ok;
+}
+
+static int
+gen_handle_end_map(void* ctx)
+{
+ json_op* jo = (json_op*) ctx;
+ yajl_gen gen = (yajl_gen) jo->jo_ptr_data;
+
+ jo->jo_ptr_error_code = yajl_gen_map_close(gen);
+
+ return jo->jo_ptr_error_code == yajl_gen_status_ok;
+}
+
+static int
+gen_handle_null(void* ctx)
+{
+ json_op* jo = (json_op*) ctx;
+ yajl_gen gen = (yajl_gen) jo->jo_ptr_data;
+
+ jo->jo_ptr_error_code = yajl_gen_null(gen);
+
+ return jo->jo_ptr_error_code == yajl_gen_status_ok;
+}
+
+static int
+gen_handle_boolean(void* ctx, int boolVal)
+{
+ json_op* jo = (json_op*) ctx;
+ yajl_gen gen = (yajl_gen) jo->jo_ptr_data;
+
+ jo->jo_ptr_error_code = yajl_gen_bool(gen, boolVal);
+
+ return jo->jo_ptr_error_code == yajl_gen_status_ok;
+}
+
+static int
+gen_handle_number(void* ctx, const char* numberVal, size_t numberLen)
+{
+ json_op* jo = (json_op*) ctx;
+ yajl_gen gen = (yajl_gen) jo->jo_ptr_data;
+
+ jo->jo_ptr_error_code = yajl_gen_number(gen, numberVal, numberLen);
+
+ return jo->jo_ptr_error_code == yajl_gen_status_ok;
+}
+
+static int
+gen_handle_string(void* ctx, const unsigned char* stringVal, size_t len)
+{
+ json_op* jo = (json_op*) ctx;
+ yajl_gen gen = (yajl_gen) jo->jo_ptr_data;
+
+ jo->jo_ptr_error_code = yajl_gen_string(gen, stringVal, len);
+
+ return jo->jo_ptr_error_code == yajl_gen_status_ok;
+}
+
+static int
+gen_handle_start_array(void* ctx)
+{
+ json_op* jo = (json_op*) ctx;
+ yajl_gen gen = (yajl_gen) jo->jo_ptr_data;
+
+ jo->jo_ptr_error_code = yajl_gen_array_open(gen);
+
+ return jo->jo_ptr_error_code == yajl_gen_status_ok;
+}
+
+static int
+gen_handle_end_array(void* ctx)
+{
+ json_op* jo = (json_op*) ctx;
+ yajl_gen gen = (yajl_gen) jo->jo_ptr_data;
+
+ jo->jo_ptr_error_code = yajl_gen_array_close(gen);
+
+ return jo->jo_ptr_error_code == yajl_gen_status_ok;
+}
+
+const yajl_callbacks json_op::gen_callbacks = {
+ gen_handle_null,
+ gen_handle_boolean,
+ nullptr,
+ nullptr,
+ gen_handle_number,
+ gen_handle_string,
+ gen_handle_start_map,
+ gen_handle_map_key,
+ gen_handle_end_map,
+ gen_handle_start_array,
+ gen_handle_end_array,
+};
+
+const yajl_callbacks json_op::ptr_callbacks = {
+ handle_null,
+ handle_boolean,
+ nullptr,
+ nullptr,
+ handle_number,
+ handle_string,
+ handle_start_map,
+ handle_map_key,
+ handle_end_map,
+ handle_start_array,
+ handle_end_array,
+};
+
+int
+json_op::handle_null(void* ctx)
+{
+ json_op* jo = (json_op*) ctx;
+ int retval = 1;
+
+ if (jo->check_index()) {
+ if (jo->jo_ptr_callbacks.yajl_null != nullptr) {
+ retval = jo->jo_ptr_callbacks.yajl_null(ctx);
+ }
+ }
+
+ return retval;
+}
+
+int
+json_op::handle_boolean(void* ctx, int boolVal)
+{
+ json_op* jo = (json_op*) ctx;
+ int retval = 1;
+
+ if (jo->check_index()) {
+ if (jo->jo_ptr_callbacks.yajl_boolean != nullptr) {
+ retval = jo->jo_ptr_callbacks.yajl_boolean(ctx, boolVal);
+ }
+ }
+
+ return retval;
+}
+
+int
+json_op::handle_number(void* ctx, const char* numberVal, size_t numberLen)
+{
+ json_op* jo = (json_op*) ctx;
+ int retval = 1;
+
+ if (jo->check_index()) {
+ if (jo->jo_ptr_callbacks.yajl_number != nullptr) {
+ retval
+ = jo->jo_ptr_callbacks.yajl_number(ctx, numberVal, numberLen);
+ }
+ }
+
+ return retval;
+}
+
+int
+json_op::handle_string(void* ctx,
+ const unsigned char* stringVal,
+ size_t stringLen)
+{
+ json_op* jo = (json_op*) ctx;
+ int retval = 1;
+
+ if (jo->check_index()) {
+ if (jo->jo_ptr_callbacks.yajl_string != nullptr) {
+ retval
+ = jo->jo_ptr_callbacks.yajl_string(ctx, stringVal, stringLen);
+ }
+ }
+
+ return retval;
+}
+
+int
+json_op::handle_start_map(void* ctx)
+{
+ json_op* jo = (json_op*) ctx;
+ int retval = 1;
+
+ if (jo->check_index(false)) {
+ if (jo->jo_ptr_callbacks.yajl_start_map != nullptr) {
+ retval = jo->jo_ptr_callbacks.yajl_start_map(ctx);
+ }
+ }
+
+ if (!jo->jo_ptr.expect_map(jo->jo_depth, jo->jo_array_index)) {
+ retval = 0;
+ }
+
+ return retval;
+}
+
+int
+json_op::handle_map_key(void* ctx, const unsigned char* key, size_t len)
+{
+ json_op* jo = (json_op*) ctx;
+ int retval = 1;
+
+ if (jo->check_index(false)) {
+ if (jo->jo_ptr_callbacks.yajl_map_key != nullptr) {
+ retval = jo->jo_ptr_callbacks.yajl_map_key(ctx, key, len);
+ }
+ }
+
+ if (!jo->jo_ptr.at_key(jo->jo_depth, (const char*) key, len)) {
+ retval = 0;
+ }
+
+ return retval;
+}
+
+int
+json_op::handle_end_map(void* ctx)
+{
+ json_op* jo = (json_op*) ctx;
+ int retval = 1;
+
+ if (jo->check_index()) {
+ if (jo->jo_ptr_callbacks.yajl_end_map != nullptr) {
+ retval = jo->jo_ptr_callbacks.yajl_end_map(ctx);
+ }
+ }
+
+ jo->jo_ptr.exit_container(jo->jo_depth, jo->jo_array_index);
+
+ return retval;
+}
+
+int
+json_op::handle_start_array(void* ctx)
+{
+ json_op* jo = (json_op*) ctx;
+ int retval = 1;
+
+ if (jo->check_index(false)) {
+ if (jo->jo_ptr_callbacks.yajl_start_array != nullptr) {
+ retval = jo->jo_ptr_callbacks.yajl_start_array(ctx);
+ }
+ }
+
+ jo->jo_ptr.expect_array(jo->jo_depth, jo->jo_array_index);
+
+ return retval;
+}
+
+int
+json_op::handle_end_array(void* ctx)
+{
+ json_op* jo = (json_op*) ctx;
+ int retval = 1;
+
+ if (jo->check_index()) {
+ if (jo->jo_ptr_callbacks.yajl_end_array != nullptr) {
+ retval = jo->jo_ptr_callbacks.yajl_end_array(ctx);
+ }
+ }
+
+ jo->jo_ptr.exit_container(jo->jo_depth, jo->jo_array_index);
+
+ return retval;
+}
diff --git a/src/yajlpp/json_op.hh b/src/yajlpp/json_op.hh
new file mode 100644
index 0000000..4f3399b
--- /dev/null
+++ b/src/yajlpp/json_op.hh
@@ -0,0 +1,82 @@
+/**
+ * Copyright (c) 2014, 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 TIMOTHY STACK 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 json_op.hh
+ */
+
+#ifndef json_op_hh
+#define json_op_hh
+
+#include <string>
+
+#include <sys/types.h>
+
+#include "yajl/api/yajl_parse.h"
+#include "yajlpp/json_ptr.hh"
+
+class json_op {
+ static int handle_null(void* ctx);
+ static int handle_boolean(void* ctx, int boolVal);
+ static int handle_number(void* ctx,
+ const char* numberVal,
+ size_t numberLen);
+ static int handle_string(void* ctx,
+ const unsigned char* stringVal,
+ size_t len);
+ static int handle_start_map(void* ctx);
+ static int handle_map_key(void* ctx, const unsigned char* key, size_t len);
+ static int handle_end_map(void* ctx);
+ static int handle_start_array(void* ctx);
+ static int handle_end_array(void* ctx);
+
+public:
+ const static yajl_callbacks gen_callbacks;
+ const static yajl_callbacks ptr_callbacks;
+
+ explicit json_op(const json_ptr& ptr)
+ : jo_ptr(ptr), jo_ptr_callbacks(gen_callbacks)
+ {
+ }
+
+ bool check_index(bool primitive = true)
+ {
+ return this->jo_ptr.at_index(
+ this->jo_depth, this->jo_array_index, primitive);
+ }
+
+ int jo_depth{0};
+ int jo_array_index{-1};
+
+ json_ptr jo_ptr;
+ yajl_callbacks jo_ptr_callbacks;
+ void* jo_ptr_data{nullptr};
+ std::string jo_ptr_error;
+ int jo_ptr_error_code{0};
+};
+
+#endif
diff --git a/src/yajlpp/json_ptr.cc b/src/yajlpp/json_ptr.cc
new file mode 100644
index 0000000..4cc0273
--- /dev/null
+++ b/src/yajlpp/json_ptr.cc
@@ -0,0 +1,521 @@
+/**
+ * Copyright (c) 2014, 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 TIMOTHY STACK 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 json_ptr.cc
+ */
+
+#ifdef __CYGWIN__
+# include <alloca.h>
+#endif
+
+#include "config.h"
+#include "fmt/format.h"
+#include "yajl/api/yajl_gen.h"
+#include "yajlpp/json_ptr.hh"
+
+static int
+handle_null(void* ctx)
+{
+ json_ptr_walk* jpw = (json_ptr_walk*) ctx;
+
+ jpw->jpw_values.emplace_back(jpw->current_ptr(), yajl_t_null, "null");
+ jpw->inc_array_index();
+
+ return 1;
+}
+
+static int
+handle_boolean(void* ctx, int boolVal)
+{
+ json_ptr_walk* jpw = (json_ptr_walk*) ctx;
+
+ jpw->jpw_values.emplace_back(jpw->current_ptr(),
+ boolVal ? yajl_t_true : yajl_t_false,
+ boolVal ? "true" : "false");
+ jpw->inc_array_index();
+
+ return 1;
+}
+
+static int
+handle_number(void* ctx, const char* numberVal, size_t numberLen)
+{
+ json_ptr_walk* jpw = (json_ptr_walk*) ctx;
+
+ jpw->jpw_values.emplace_back(
+ jpw->current_ptr(), yajl_t_number, std::string(numberVal, numberLen));
+ jpw->inc_array_index();
+
+ return 1;
+}
+
+static void
+appender(void* ctx, const char* strVal, size_t strLen)
+{
+ std::string& str = *(std::string*) ctx;
+
+ str.append(strVal, strLen);
+}
+
+static int
+handle_string(void* ctx, const unsigned char* stringVal, size_t len)
+{
+ json_ptr_walk* jpw = (json_ptr_walk*) ctx;
+ auto_mem<yajl_gen_t> gen(yajl_gen_free);
+ std::string str;
+
+ gen = yajl_gen_alloc(nullptr);
+ yajl_gen_config(gen.in(), yajl_gen_print_callback, appender, &str);
+ yajl_gen_string(gen.in(), stringVal, len);
+ jpw->jpw_values.emplace_back(jpw->current_ptr(), yajl_t_string, str);
+ jpw->inc_array_index();
+
+ return 1;
+}
+
+static int
+handle_start_map(void* ctx)
+{
+ json_ptr_walk* jpw = (json_ptr_walk*) ctx;
+
+ jpw->jpw_keys.emplace_back("");
+ jpw->jpw_array_indexes.push_back(-1);
+
+ return 1;
+}
+
+static int
+handle_map_key(void* ctx, const unsigned char* key, size_t len)
+{
+ json_ptr_walk* jpw = (json_ptr_walk*) ctx;
+ char partially_encoded_key[len + 32];
+ size_t required_len;
+
+ jpw->jpw_keys.pop_back();
+
+ required_len = json_ptr::encode(partially_encoded_key,
+ sizeof(partially_encoded_key),
+ (const char*) key,
+ len);
+ if (required_len < sizeof(partially_encoded_key)) {
+ jpw->jpw_keys.emplace_back(&partially_encoded_key[0], required_len);
+ } else {
+ auto fully_encoded_key = (char*) alloca(required_len);
+
+ json_ptr::encode(
+ fully_encoded_key, required_len, (const char*) key, len);
+ jpw->jpw_keys.emplace_back(&fully_encoded_key[0], required_len);
+ }
+
+ return 1;
+}
+
+static int
+handle_end_map(void* ctx)
+{
+ json_ptr_walk* jpw = (json_ptr_walk*) ctx;
+
+ jpw->jpw_keys.pop_back();
+ jpw->jpw_array_indexes.pop_back();
+
+ jpw->inc_array_index();
+
+ return 1;
+}
+
+static int
+handle_start_array(void* ctx)
+{
+ json_ptr_walk* jpw = (json_ptr_walk*) ctx;
+
+ jpw->jpw_keys.emplace_back("");
+ jpw->jpw_array_indexes.push_back(0);
+
+ return 1;
+}
+
+static int
+handle_end_array(void* ctx)
+{
+ json_ptr_walk* jpw = (json_ptr_walk*) ctx;
+
+ jpw->jpw_keys.pop_back();
+ jpw->jpw_array_indexes.pop_back();
+ jpw->inc_array_index();
+
+ return 1;
+}
+
+const yajl_callbacks json_ptr_walk::callbacks = {handle_null,
+ handle_boolean,
+ nullptr,
+ nullptr,
+ handle_number,
+ handle_string,
+ handle_start_map,
+ handle_map_key,
+ handle_end_map,
+ handle_start_array,
+ handle_end_array};
+
+size_t
+json_ptr::encode(char* dst, size_t dst_len, const char* src, size_t src_len)
+{
+ size_t retval = 0;
+
+ if (src_len == (size_t) -1) {
+ src_len = strlen(src);
+ }
+
+ for (size_t lpc = 0; lpc < src_len; lpc++) {
+ switch (src[lpc]) {
+ case '/':
+ case '~':
+ case '#':
+ if (retval < dst_len) {
+ dst[retval] = '~';
+ retval += 1;
+ if (src[lpc] == '~') {
+ dst[retval] = '0';
+ } else if (src[lpc] == '#') {
+ dst[retval] = '2';
+ } else {
+ dst[retval] = '1';
+ }
+ } else {
+ retval += 1;
+ }
+ break;
+ default:
+ if (retval < dst_len) {
+ dst[retval] = src[lpc];
+ }
+ break;
+ }
+ retval += 1;
+ }
+ if (retval < dst_len) {
+ dst[retval] = '\0';
+ }
+
+ return retval;
+}
+
+size_t
+json_ptr::decode(char* dst, const char* src, ssize_t src_len)
+{
+ size_t retval = 0;
+
+ if (src_len == -1) {
+ src_len = strlen(src);
+ }
+
+ for (int lpc = 0; lpc < src_len; lpc++) {
+ switch (src[lpc]) {
+ case '~':
+ if ((lpc + 1) < src_len) {
+ switch (src[lpc + 1]) {
+ case '0':
+ dst[retval++] = '~';
+ lpc += 1;
+ break;
+ case '1':
+ dst[retval++] = '/';
+ lpc += 1;
+ break;
+ case '2':
+ dst[retval++] = '#';
+ lpc += 1;
+ break;
+ default:
+ break;
+ }
+ }
+ break;
+ default:
+ dst[retval++] = src[lpc];
+ break;
+ }
+ }
+
+ dst[retval] = '\0';
+
+ return retval;
+}
+
+bool
+json_ptr::expect_map(int32_t& depth, int32_t& index)
+{
+ bool retval;
+
+ if (this->jp_state == match_state_t::DONE) {
+ retval = true;
+ } else if (depth != this->jp_depth) {
+ retval = true;
+ } else if (this->reached_end()) {
+ retval = true;
+ } else if (this->jp_state == match_state_t::VALUE
+ && (this->jp_array_index == -1
+ || ((index - 1) == this->jp_array_index)))
+ {
+ if (this->jp_pos[0] == '/') {
+ this->jp_pos += 1;
+ this->jp_depth += 1;
+ this->jp_state = match_state_t::VALUE;
+ this->jp_array_index = -1;
+ index = -1;
+ }
+ retval = true;
+ } else {
+ retval = true;
+ }
+ depth += 1;
+
+ return retval;
+}
+
+bool
+json_ptr::at_key(int32_t depth, const char* component, ssize_t len)
+{
+ const char* component_end;
+ int lpc;
+
+ if (this->jp_state == match_state_t::DONE || depth != this->jp_depth) {
+ return true;
+ }
+
+ if (len == -1) {
+ len = strlen(component);
+ }
+ component_end = component + len;
+
+ for (lpc = 0; component < component_end; lpc++, component++) {
+ char ch = this->jp_pos[lpc];
+
+ if (this->jp_pos[lpc] == '~') {
+ switch (this->jp_pos[lpc + 1]) {
+ case '0':
+ ch = '~';
+ break;
+ case '1':
+ ch = '/';
+ break;
+ default:
+ this->jp_state = match_state_t::ERR_INVALID_ESCAPE;
+ return false;
+ }
+ lpc += 1;
+ } else if (this->jp_pos[lpc] == '/') {
+ ch = '\0';
+ }
+
+ if (ch != *component) {
+ return true;
+ }
+ }
+
+ this->jp_pos += lpc;
+ this->jp_state = match_state_t::VALUE;
+
+ return true;
+}
+
+void
+json_ptr::exit_container(int32_t& depth, int32_t& index)
+{
+ depth -= 1;
+ if (this->jp_state == match_state_t::VALUE && depth == this->jp_depth
+ && (index == -1 || (index - 1 == this->jp_array_index))
+ && this->reached_end())
+ {
+ this->jp_state = match_state_t::DONE;
+ index = -1;
+ }
+}
+
+bool
+json_ptr::expect_array(int32_t& depth, int32_t& index)
+{
+ bool retval;
+
+ if (this->jp_state == match_state_t::DONE) {
+ retval = true;
+ } else if (depth != this->jp_depth) {
+ retval = true;
+ } else if (this->reached_end()) {
+ retval = true;
+ } else if (this->jp_pos[0] == '/' && index == this->jp_array_index) {
+ int offset;
+
+ this->jp_depth += 1;
+
+ if (sscanf(this->jp_pos, "/%d%n", &this->jp_array_index, &offset) != 1)
+ {
+ this->jp_state = match_state_t::ERR_INVALID_INDEX;
+ retval = true;
+ } else if (this->jp_pos[offset] != '\0' && this->jp_pos[offset] != '/')
+ {
+ this->jp_state = match_state_t::ERR_INVALID_INDEX;
+ retval = true;
+ } else {
+ index = 0;
+ this->jp_pos += offset;
+ this->jp_state = match_state_t::VALUE;
+ retval = true;
+ }
+ } else {
+ this->jp_state = match_state_t::ERR_NO_SLASH;
+ retval = true;
+ }
+
+ depth += 1;
+
+ return retval;
+}
+
+bool
+json_ptr::at_index(int32_t& depth, int32_t& index, bool primitive)
+{
+ bool retval;
+
+ if (this->jp_state == match_state_t::DONE) {
+ retval = false;
+ } else if (depth < this->jp_depth) {
+ retval = false;
+ } else if (depth == this->jp_depth) {
+ if (index == -1) {
+ if (this->jp_array_index == -1) {
+ retval = this->reached_end();
+ if (primitive && retval) {
+ this->jp_state = match_state_t::DONE;
+ }
+ } else {
+ retval = false;
+ }
+ } else if (index == this->jp_array_index) {
+ retval = this->reached_end();
+ this->jp_array_index = -1;
+ index = -1;
+ if (primitive && retval) {
+ this->jp_state = match_state_t::DONE;
+ }
+ } else {
+ index += 1;
+ retval = false;
+ }
+ } else if (index == -1) {
+ retval = this->reached_end();
+ } else {
+ retval = false;
+ }
+
+ return retval;
+}
+
+std::string
+json_ptr::error_msg() const
+{
+ switch (this->jp_state) {
+ case match_state_t::ERR_INVALID_ESCAPE:
+ return fmt::format(FMT_STRING("invalid escape sequence near -- {}"),
+ this->jp_pos);
+ case match_state_t::ERR_INVALID_INDEX:
+ return fmt::format(FMT_STRING("expecting array index at -- {}"),
+ this->jp_pos);
+ case match_state_t::ERR_INVALID_TYPE:
+ return fmt::format(FMT_STRING("expecting container at -- {}"),
+ this->jp_pos);
+ default:
+ break;
+ }
+
+ return "";
+}
+
+std::string
+json_ptr_walk::current_ptr()
+{
+ std::string retval;
+
+ for (size_t lpc = 0; lpc < this->jpw_array_indexes.size(); lpc++) {
+ retval.append("/");
+ if (this->jpw_array_indexes[lpc] == -1) {
+ retval.append(this->jpw_keys[lpc]);
+ } else {
+ fmt::format_to(std::back_inserter(retval),
+ FMT_STRING("{}"),
+ this->jpw_array_indexes[lpc]);
+ }
+ }
+
+ this->jpw_max_ptr_len = std::max(this->jpw_max_ptr_len, retval.size());
+
+ return retval;
+}
+
+void
+json_ptr_walk::update_error_msg(yajl_status status,
+ const char* buffer,
+ ssize_t len)
+{
+ switch (status) {
+ case yajl_status_ok:
+ break;
+ case yajl_status_client_canceled:
+ this->jpw_error_msg = "internal error";
+ break;
+ case yajl_status_error: {
+ auto* msg = yajl_get_error(
+ this->jpw_handle, 1, (const unsigned char*) buffer, len);
+ this->jpw_error_msg = std::string((const char*) msg);
+
+ yajl_free_error(this->jpw_handle, msg);
+ break;
+ }
+ }
+}
+
+yajl_status
+json_ptr_walk::complete_parse()
+{
+ yajl_status retval;
+
+ retval = yajl_complete_parse(this->jpw_handle);
+ this->update_error_msg(retval, nullptr, -1);
+ return retval;
+}
+
+yajl_status
+json_ptr_walk::parse(const char* buffer, ssize_t len)
+{
+ yajl_status retval;
+
+ retval = yajl_parse(this->jpw_handle, (const unsigned char*) buffer, len);
+ this->update_error_msg(retval, buffer, len);
+ return retval;
+}
diff --git a/src/yajlpp/json_ptr.hh b/src/yajlpp/json_ptr.hh
new file mode 100644
index 0000000..f7822ab
--- /dev/null
+++ b/src/yajlpp/json_ptr.hh
@@ -0,0 +1,136 @@
+/**
+ * Copyright (c) 2014-2019, 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 TIMOTHY STACK 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 json_ptr.hh
+ */
+
+#ifndef json_ptr_hh
+#define json_ptr_hh
+
+#include <string>
+#include <vector>
+
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include "base/auto_mem.hh"
+#include "yajl/api/yajl_parse.h"
+#include "yajl/api/yajl_tree.h"
+
+class json_ptr_walk {
+public:
+ const static yajl_callbacks callbacks;
+
+ json_ptr_walk()
+ {
+ this->jpw_handle = yajl_alloc(&callbacks, nullptr, this);
+ }
+
+ yajl_status parse(const char* buffer, ssize_t len);
+
+ yajl_status complete_parse();
+
+ void update_error_msg(yajl_status status, const char* buffer, ssize_t len);
+
+ void clear() { this->jpw_values.clear(); }
+
+ void inc_array_index()
+ {
+ if (!this->jpw_array_indexes.empty()
+ && this->jpw_array_indexes.back() != -1) {
+ this->jpw_array_indexes.back() += 1;
+ }
+ }
+
+ std::string current_ptr();
+
+ struct walk_triple {
+ walk_triple(std::string ptr, yajl_type type, std::string value)
+ : wt_ptr(std::move(ptr)), wt_type(type), wt_value(std::move(value))
+ {
+ }
+
+ std::string wt_ptr;
+ yajl_type wt_type;
+ std::string wt_value;
+ };
+
+ using walk_list_t = std::vector<walk_triple>;
+
+ auto_mem<yajl_handle_t> jpw_handle{yajl_free};
+ std::string jpw_error_msg;
+ walk_list_t jpw_values;
+ std::vector<std::string> jpw_keys;
+ std::vector<int32_t> jpw_array_indexes;
+ size_t jpw_max_ptr_len{0};
+};
+
+class json_ptr {
+public:
+ enum class match_state_t {
+ DONE,
+ VALUE,
+ ERR_INVALID_TYPE,
+ ERR_NO_SLASH,
+ ERR_INVALID_ESCAPE,
+ ERR_INVALID_INDEX,
+ };
+
+ static size_t encode(char* dst,
+ size_t dst_len,
+ const char* src,
+ size_t src_len = -1);
+
+ static size_t decode(char* dst, const char* src, ssize_t src_len = -1);
+
+ json_ptr(const char* value) : jp_value(value), jp_pos(value) {}
+
+ bool expect_map(int32_t& depth, int32_t& index);
+
+ bool at_key(int32_t depth, const char* component, ssize_t len = -1);
+
+ void exit_container(int32_t& depth, int32_t& index);
+
+ bool expect_array(int32_t& depth, int32_t& index);
+
+ bool at_index(int32_t& depth, int32_t& index, bool primitive = true);
+
+ bool reached_end() const { return this->jp_pos[0] == '\0'; }
+
+ std::string error_msg() const;
+
+ const char* jp_value;
+ const char* jp_pos;
+ int32_t jp_depth{0};
+ int32_t jp_array_index{-1};
+ match_state_t jp_state{match_state_t::VALUE};
+};
+
+#endif
diff --git a/src/yajlpp/test_json_op.sh b/src/yajlpp/test_json_op.sh
new file mode 100755
index 0000000..91a3849
--- /dev/null
+++ b/src/yajlpp/test_json_op.sh
@@ -0,0 +1,114 @@
+#! /bin/bash
+
+run_test ./drive_json_op get "" <<EOF
+3
+EOF
+
+check_output "cannot read root number value" <<EOF
+3
+EOF
+
+run_test ./drive_json_op get "" <<EOF
+null
+EOF
+
+check_output "cannot read root null value" <<EOF
+null
+EOF
+
+run_test ./drive_json_op get "" <<EOF
+true
+EOF
+
+check_output "cannot read root bool value" <<EOF
+true
+EOF
+
+run_test ./drive_json_op get "" <<EOF
+"str"
+EOF
+
+check_output "cannot read root string value" <<EOF
+"str"
+EOF
+
+run_test ./drive_json_op get "" <<EOF
+{ "val" : 3, "other" : 2 }
+EOF
+
+check_output "cannot read root map value" <<EOF
+{
+ "val": 3,
+ "other": 2
+}
+EOF
+
+run_test ./drive_json_op get /val <<EOF
+{ "val" : 3 }
+EOF
+
+check_output "cannot read top-level value" <<EOF
+3
+EOF
+
+run_test ./drive_json_op get /val <<EOF
+{ "other" : { "val" : 5 }, "val" : 3 }
+EOF
+
+check_output "read wrong value" <<EOF
+3
+EOF
+
+run_test ./drive_json_op get /other <<EOF
+{ "other" : { "val" : 5 }, "val" : 3 }
+EOF
+
+check_output "cannot read map" <<EOF
+{
+ "val": 5
+}
+EOF
+
+run_test ./drive_json_op get /other/val <<EOF
+{ "other" : { "val" : 5 }, "val" : 3 }
+EOF
+
+check_output "cannot read nested map" <<EOF
+5
+EOF
+
+
+run_test ./drive_json_op get "" <<EOF
+[0, 1]
+EOF
+
+check_output "cannot read root array value" <<EOF
+[
+ 0,
+ 1
+]
+EOF
+
+run_test ./drive_json_op get "/6" <<EOF
+[null, true, 1, "str", {"sub":[10, 11]}, [21, [33, 34], 66], 2]
+EOF
+
+check_output "cannot read array value" <<EOF
+2
+EOF
+
+run_test ./drive_json_op get "/ID" <<EOF
+{"ID":"P1","ProcessID":"P1","Name":"VxWorks","CanSuspend":true,"CanResume":1,"IsContainer":true,"WordSize":4,"CanTerminate":true,"CanDetach":true,"RCGroup":"P1","SymbolsGroup":"P1","CPUGroup":"P1","DiagnosticTestProcess":true}
+EOF
+
+check_output "cannot read key value" <<EOF
+"P1"
+EOF
+
+run_test ./drive_json_op get "/arr/1/id" <<EOF
+{"arr": [{"id": 1}, {"id": 2}]}
+EOF
+
+check_output "cannot read key value" <<EOF
+2
+EOF
diff --git a/src/yajlpp/test_json_ptr.cc b/src/yajlpp/test_json_ptr.cc
new file mode 100644
index 0000000..0c530c0
--- /dev/null
+++ b/src/yajlpp/test_json_ptr.cc
@@ -0,0 +1,73 @@
+/**
+ * Copyright (c) 2014, 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 TIMOTHY STACK 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 test_json_ptr.cc
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "config.h"
+#include "yajlpp/json_ptr.hh"
+
+int
+main(int argc, const char* argv[])
+{
+ int32_t depth, index;
+
+ {
+ json_ptr jptr("");
+
+ depth = 0;
+ index = -1;
+ assert(jptr.at_index(depth, index));
+ }
+
+ {
+ json_ptr jptr("/");
+
+ depth = 0;
+ index = -1;
+ assert(!jptr.at_index(depth, index));
+ assert(jptr.expect_map(depth, index));
+ assert(jptr.at_index(depth, index));
+ }
+
+ {
+ json_ptr jptr("/foo/bar");
+
+ depth = 0;
+ index = -1;
+ assert(jptr.expect_map(depth, index));
+ assert(jptr.at_key(depth, "foo"));
+ assert(jptr.expect_map(depth, index));
+ assert(jptr.at_key(depth, "bar"));
+ assert(jptr.at_index(depth, index));
+ }
+}
diff --git a/src/yajlpp/test_json_ptr_walk.sh b/src/yajlpp/test_json_ptr_walk.sh
new file mode 100644
index 0000000..bcaf1ec
--- /dev/null
+++ b/src/yajlpp/test_json_ptr_walk.sh
@@ -0,0 +1,77 @@
+#! /bin/bash
+
+run_test ./drive_json_ptr_walk <<EOF
+{ "foo" : 1 }
+EOF
+
+check_output "simple object" <<EOF
+/foo = 1
+EOF
+
+run_test ./drive_json_ptr_walk <<EOF
+{ "~tstack/julia" : 1 }
+EOF
+
+check_output "escaped object" <<EOF
+/~0tstack~1julia = 1
+EOF
+
+run_test ./drive_json_ptr_walk <<EOF
+1
+EOF
+
+check_output "root value" <<EOF
+ = 1
+EOF
+
+run_test ./drive_json_ptr_walk <<EOF
+[1, 2, 3]
+EOF
+
+check_output "array" <<EOF
+/0 = 1
+/1 = 2
+/2 = 3
+EOF
+
+run_test ./drive_json_ptr_walk <<EOF
+[1, 2, 3, [4, 5, 6], 7, 8, 9, [10, 11, [12, 13, 14], 15], 16]
+EOF
+
+check_output "nested array" <<EOF
+/0 = 1
+/1 = 2
+/2 = 3
+/3/0 = 4
+/3/1 = 5
+/3/2 = 6
+/4 = 7
+/5 = 8
+/6 = 9
+/7/0 = 10
+/7/1 = 11
+/7/2/0 = 12
+/7/2/1 = 13
+/7/2/2 = 14
+/7/3 = 15
+/8 = 16
+EOF
+
+run_test ./drive_json_ptr_walk <<EOF
+[null, true, 123.0, "foo", { "bar" : { "baz" : [1, 2, 3]} }, ["a", null]]
+EOF
+
+check_error_output "" <<EOF
+EOF
+
+check_output "complex" <<EOF
+/0 = null
+/1 = true
+/2 = 123.0
+/3 = "foo"
+/4/bar/baz/0 = 1
+/4/bar/baz/1 = 2
+/4/bar/baz/2 = 3
+/5/0 = "a"
+/5/1 = null
+EOF
diff --git a/src/yajlpp/test_yajlpp.cc b/src/yajlpp/test_yajlpp.cc
new file mode 100644
index 0000000..afa60a3
--- /dev/null
+++ b/src/yajlpp/test_yajlpp.cc
@@ -0,0 +1,166 @@
+/**
+ * Copyright (c) 2013, 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 test_yajlpp.cc
+ */
+
+#include <assert.h>
+#include <stdio.h>
+
+#include "yajlpp.hh"
+#include "yajlpp_def.hh"
+
+const char* TEST_DATA = R"([{ "foo": 0 }, 2, { "foo": 1 }])";
+
+const char* TEST_OBJ_DATA = "{ \"foo\": 0 }";
+
+static int FOO_COUNT = 0;
+static int CONST_COUNT = 0;
+
+static int
+read_foo(yajlpp_parse_context* ypc, long long value)
+{
+ assert(value == FOO_COUNT);
+ assert(ypc->ypc_array_index.empty()
+ || ypc->ypc_array_index.back() == FOO_COUNT);
+
+ FOO_COUNT += 1;
+
+ return 1;
+}
+
+static int
+read_const(yajlpp_parse_context* ypc, long long value)
+{
+ CONST_COUNT += 1;
+
+ return 1;
+}
+
+static int
+dummy_string_handler(void* ctx, const unsigned char* s, size_t len)
+{
+ return 1;
+}
+
+int
+main(int argc, char* argv[])
+{
+ static const auto TEST_SRC = intern_string::lookup("test_data");
+
+ {
+ struct dummy {};
+
+ typed_json_path_container<dummy> dummy_handlers = {
+
+ };
+
+ std::string in1 = "{\"#\":{\"";
+ auto parse_res = dummy_handlers.parser_for(TEST_SRC).of(in1);
+ }
+
+ {
+ static const char UNICODE_BARF[] = "\"\\udb00\\\\0\"\n";
+
+ yajl_callbacks cbs;
+ memset(&cbs, 0, sizeof(cbs));
+ cbs.yajl_string = dummy_string_handler;
+ auto handle = yajl_alloc(&cbs, nullptr, nullptr);
+ auto rc = yajl_parse(handle, (const unsigned char*) UNICODE_BARF, 12);
+ assert(rc == yajl_status_ok);
+ yajl_free(handle);
+ }
+
+ struct json_path_container test_obj_handler = {
+ json_path_handler("foo", read_foo),
+ };
+
+ {
+ struct json_path_container test_array_handlers = {
+ json_path_handler("#")
+ .add_cb(read_const)
+ .with_children(test_obj_handler),
+ };
+
+ yajlpp_parse_context ypc(TEST_SRC, &test_array_handlers);
+ auto handle = yajl_alloc(&ypc.ypc_callbacks, nullptr, &ypc);
+ ypc.with_handle(handle);
+ ypc.parse((const unsigned char*) TEST_DATA, strlen(TEST_DATA));
+ yajl_free(handle);
+
+ assert(FOO_COUNT == 2);
+ assert(CONST_COUNT == 1);
+ }
+
+ {
+ FOO_COUNT = 0;
+
+ yajlpp_parse_context ypc(TEST_SRC, &test_obj_handler);
+ auto handle = yajl_alloc(&ypc.ypc_callbacks, nullptr, &ypc);
+ ypc.with_handle(handle);
+
+ ypc.parse(reinterpret_cast<const unsigned char*>(TEST_OBJ_DATA),
+ strlen(TEST_OBJ_DATA));
+ yajl_free(handle);
+
+ assert(FOO_COUNT == 1);
+ }
+
+ {
+ const char* TEST_INPUT = R"({
+ "msg": "Hello, World!",
+ "parent1": {
+ "child": {}
+ },
+ "parent2": {
+ "child": {"name": "steve"}
+ },
+ "parent3": {
+ "child": {},
+ "sibling": {"name": "mongoose"}
+ }
+})";
+ const std::string EXPECTED_OUTPUT
+ = "{\"msg\":\"Hello, "
+ "World!\",\"parent2\":{\"child\":{\"name\":\"steve\"}},"
+ "\"parent3\":{\"sibling\":{\"name\":\"mongoose\"}}}";
+
+ char errbuf[1024];
+
+ auto tree = yajl_tree_parse(TEST_INPUT, errbuf, sizeof(errbuf));
+ yajl_cleanup_tree(tree);
+
+ yajlpp_gen gen;
+
+ yajl_gen_tree(gen, tree);
+ auto actual = gen.to_string_fragment().to_string();
+ assert(EXPECTED_OUTPUT == actual);
+
+ yajl_tree_free(tree);
+ }
+}
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);
+}
diff --git a/src/yajlpp/yajlpp.hh b/src/yajlpp/yajlpp.hh
new file mode 100644
index 0000000..7632329
--- /dev/null
+++ b/src/yajlpp/yajlpp.hh
@@ -0,0 +1,682 @@
+/**
+ * Copyright (c) 2013-2019, 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.hh
+ */
+
+#ifndef yajlpp_hh
+#define yajlpp_hh
+
+#include <functional>
+#include <map>
+#include <memory>
+#include <set>
+#include <stack>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <limits.h>
+#include <stdarg.h>
+#include <string.h>
+
+#include "base/file_range.hh"
+#include "base/intern_string.hh"
+#include "base/lnav.console.hh"
+#include "base/lnav.console.into.hh"
+#include "base/lnav_log.hh"
+#include "base/opt_util.hh"
+#include "json_ptr.hh"
+#include "optional.hpp"
+#include "pcrepp/pcre2pp.hh"
+#include "relative_time.hh"
+#include "yajl/api/yajl_gen.h"
+#include "yajl/api/yajl_parse.h"
+
+inline yajl_gen_status
+yajl_gen_pstring(yajl_gen hand, const char* str, size_t len)
+{
+ if (len == (size_t) -1) {
+ len = strlen(str);
+ }
+ return yajl_gen_string(hand, (const unsigned char*) str, len);
+}
+
+inline yajl_gen_status
+yajl_gen_string(yajl_gen hand, const std::string& str)
+{
+ return yajl_gen_string(
+ hand, (const unsigned char*) str.c_str(), str.length());
+}
+
+yajl_gen_status yajl_gen_tree(yajl_gen hand, yajl_val val);
+
+void yajl_cleanup_tree(yajl_val val);
+
+template<typename T>
+struct positioned_property {
+ intern_string_t pp_path;
+ source_location pp_location;
+ T pp_value;
+
+ lnav::console::snippet to_snippet() const
+ {
+ return lnav::console::snippet::from(this->pp_location, "");
+ }
+};
+
+template<typename T, typename... Types>
+struct factory_container : public positioned_property<std::shared_ptr<T>> {
+ template<Types... DefaultArgs>
+ struct with_default_args : public positioned_property<std::shared_ptr<T>> {
+ template<typename... Args>
+ static Result<with_default_args, lnav::console::user_message> from(
+ intern_string_t src, source_location loc, Args... args)
+ {
+ auto from_res = T::from(args..., DefaultArgs...);
+
+ if (from_res.isOk()) {
+ with_default_args retval;
+
+ retval.pp_path = src;
+ retval.pp_location = loc;
+ retval.pp_value = from_res.unwrap().to_shared();
+ return Ok(retval);
+ }
+
+ return Err(
+ lnav::console::to_user_message(src, from_res.unwrapErr()));
+ }
+ };
+
+ template<typename... Args>
+ static Result<factory_container, lnav::console::user_message> from(
+ intern_string_t src, source_location loc, Args... args)
+ {
+ auto from_res = T::from(args...);
+
+ if (from_res.isOk()) {
+ factory_container retval;
+
+ retval.pp_path = src;
+ retval.pp_location = loc;
+ retval.pp_value = from_res.unwrap().to_shared();
+ return Ok(retval);
+ }
+
+ return Err(lnav::console::to_user_message(src, from_res.unwrapErr()));
+ }
+};
+
+class yajlpp_gen_context;
+class yajlpp_parse_context;
+
+struct yajlpp_provider_context {
+ lnav::pcre2pp::match_data* ypc_extractor;
+ size_t ypc_index{0};
+ yajlpp_parse_context* ypc_parse_context;
+
+ static constexpr size_t nindex = static_cast<size_t>(-1);
+
+ template<typename T>
+ intern_string_t get_substr_i(T&& name) const
+ {
+ auto cap = (*this->ypc_extractor)[std::forward<T>(name)].value();
+ char path[cap.length() + 1];
+ size_t len = json_ptr::decode(path, cap.data(), cap.length());
+
+ return intern_string::lookup(path, len);
+ }
+
+ template<typename T>
+ std::string get_substr(T&& name) const
+ {
+ auto cap = (*this->ypc_extractor)[std::forward<T>(name)].value();
+ char path[cap.length() + 1];
+ size_t len = json_ptr::decode(path, cap.data(), cap.length());
+
+ return {path, len};
+ }
+};
+
+class yajlpp_error : public std::exception {
+public:
+ yajlpp_error(yajl_handle handle, const char* json, size_t len) noexcept
+ {
+ auto_mem<unsigned char> yajl_msg;
+
+ yajl_msg = yajl_get_error(
+ handle, 1, reinterpret_cast<const unsigned char*>(json), len);
+ this->ye_msg = reinterpret_cast<const char*>(yajl_msg.in());
+ }
+
+ const char* what() const noexcept override { return this->ye_msg.c_str(); }
+
+private:
+ std::string ye_msg;
+};
+
+struct json_path_container;
+
+struct json_path_handler_base {
+ struct enum_value_t {
+ template<typename T>
+ enum_value_t(const char* name, T value)
+ : first(name), second((unsigned int) value)
+ {
+ }
+
+ const char* first;
+ int second;
+ };
+
+ static const enum_value_t ENUM_TERMINATOR;
+
+ explicit json_path_handler_base(const std::string& property);
+
+ explicit json_path_handler_base(
+ const std::shared_ptr<const lnav::pcre2pp::code>& property_re);
+
+ json_path_handler_base(
+ std::string property,
+ const std::shared_ptr<const lnav::pcre2pp::code>& property_re);
+
+ bool is_array() const { return this->jph_is_array; }
+
+ nonstd::optional<int> to_enum_value(const string_fragment& sf) const;
+ const char* to_enum_string(int value) const;
+
+ template<typename T>
+ std::enable_if_t<!detail::is_optional<T>::value, const char*>
+ to_enum_string(T value) const
+ {
+ return this->to_enum_string((int) value);
+ }
+
+ template<typename T>
+ std::enable_if_t<detail::is_optional<T>::value, const char*> to_enum_string(
+ T value) const
+ {
+ return this->to_enum_string((int) value.value());
+ }
+
+ yajl_gen_status gen(yajlpp_gen_context& ygc, yajl_gen handle) const;
+ yajl_gen_status gen_schema(yajlpp_gen_context& ygc) const;
+ yajl_gen_status gen_schema_type(yajlpp_gen_context& ygc) const;
+ void walk(
+ const std::function<
+ void(const json_path_handler_base&, const std::string&, void*)>& cb,
+ void* root = nullptr,
+ const std::string& base = "/") const;
+
+ enum class schema_type_t : std::uint32_t {
+ ANY,
+ BOOLEAN,
+ INTEGER,
+ NUMBER,
+ STRING,
+ ARRAY,
+ OBJECT,
+ };
+
+ std::vector<schema_type_t> get_types() const;
+
+ std::string jph_property;
+ std::shared_ptr<const lnav::pcre2pp::code> jph_regex;
+ yajl_callbacks jph_callbacks{};
+ std::function<yajl_gen_status(
+ yajlpp_gen_context&, const json_path_handler_base&, yajl_gen)>
+ jph_gen_callback;
+ std::function<void(yajlpp_parse_context& ypc,
+ const json_path_handler_base& jph)>
+ jph_validator;
+ std::function<void*(void* root, nonstd::optional<std::string> name)>
+ jph_field_getter;
+ std::function<void*(const yajlpp_provider_context& pe, void* root)>
+ jph_obj_provider;
+ std::function<void(void* root, std::vector<std::string>& paths_out)>
+ jph_path_provider;
+ std::function<void(const yajlpp_provider_context& pe, void* root)>
+ jph_obj_deleter;
+ std::function<size_t(void* root)> jph_size_provider;
+ const char* jph_synopsis{""};
+ const char* jph_description{""};
+ const json_path_container* jph_children{nullptr};
+ std::shared_ptr<const lnav::pcre2pp::code> jph_pattern;
+ const char* jph_pattern_re{nullptr};
+ std::function<void(const string_fragment&)> jph_string_validator;
+ size_t jph_min_length{0};
+ size_t jph_max_length{INT_MAX};
+ const enum_value_t* jph_enum_values{nullptr};
+ long long jph_min_value{LLONG_MIN};
+ bool jph_optional_wrapper{false};
+ bool jph_is_array;
+ bool jph_is_pattern_property{false};
+ std::vector<std::string> jph_examples;
+
+ std::function<int(yajlpp_parse_context*)> jph_null_cb;
+ std::function<int(yajlpp_parse_context*, int)> jph_bool_cb;
+ std::function<int(yajlpp_parse_context*, long long)> jph_integer_cb;
+ std::function<int(yajlpp_parse_context*, double)> jph_double_cb;
+ std::function<int(
+ yajlpp_parse_context*, const unsigned char* str, size_t len)>
+ jph_str_cb;
+
+ void validate_string(yajlpp_parse_context& ypc, string_fragment sf) const;
+
+ void report_pattern_error(yajlpp_parse_context* ypc,
+ const std::string& value_str) const;
+ void report_min_value_error(yajlpp_parse_context* ypc,
+ long long value) const;
+ void report_duration_error(yajlpp_parse_context* ypc,
+ const std::string& value_str,
+ const relative_time::parse_error& pe) const;
+ void report_enum_error(yajlpp_parse_context* ypc,
+ const std::string& value_str) const;
+ void report_error(yajlpp_parse_context* ypc,
+ const std::string& value_str,
+ lnav::console::user_message um) const;
+
+ attr_line_t get_help_text(const std::string& full_path) const;
+ attr_line_t get_help_text(yajlpp_parse_context* ypc) const;
+};
+
+struct json_path_handler;
+
+class yajlpp_parse_context {
+public:
+ using error_reporter_t = std::function<void(
+ const yajlpp_parse_context& ypc, const lnav::console::user_message&)>;
+
+ yajlpp_parse_context(intern_string_t source,
+ const struct json_path_container* handlers = nullptr);
+
+ const char* get_path_fragment(int offset,
+ char* frag_in,
+ size_t& len_out) const;
+
+ intern_string_t get_path_fragment_i(int offset) const
+ {
+ char fragbuf[this->ypc_path.size()];
+ const char* frag;
+ size_t len;
+
+ frag = this->get_path_fragment(offset, fragbuf, len);
+ return intern_string::lookup(frag, len);
+ }
+
+ std::string get_path_fragment(int offset) const
+ {
+ char fragbuf[this->ypc_path.size()];
+ const char* frag;
+ size_t len;
+
+ frag = this->get_path_fragment(offset, fragbuf, len);
+ return std::string(frag, len);
+ }
+
+ const intern_string_t get_path() const;
+
+ const intern_string_t get_full_path() const;
+
+ bool is_level(size_t level) const
+ {
+ return this->ypc_path_index_stack.size() == level;
+ }
+
+ yajlpp_parse_context& set_path(const std::string& path);
+
+ void reset(const struct json_path_container* handlers);
+
+ void set_static_handler(const struct json_path_handler_base& jph);
+
+ template<typename T>
+ yajlpp_parse_context& with_obj(T& obj)
+ {
+ this->ypc_obj_stack.push(&obj);
+ return *this;
+ }
+
+ yajlpp_parse_context& with_handle(yajl_handle handle)
+ {
+ this->ypc_handle = handle;
+ return *this;
+ }
+
+ yajlpp_parse_context& with_error_reporter(error_reporter_t err)
+ {
+ this->ypc_error_reporter = err;
+ return *this;
+ }
+
+ yajlpp_parse_context& with_ignore_unused(bool ignore)
+ {
+ this->ypc_ignore_unused = ignore;
+ return *this;
+ }
+
+ yajl_status parse(const unsigned char* jsonText, size_t jsonTextLen);
+
+ yajl_status parse(const string_fragment& sf)
+ {
+ return this->parse((const unsigned char*) sf.data(), sf.length());
+ }
+
+ int get_line_number() const;
+
+ yajl_status complete_parse();
+
+ bool parse_doc(const string_fragment& sf);
+
+ void report_error(const lnav::console::user_message& msg) const
+ {
+ if (this->ypc_error_reporter) {
+ this->ypc_error_reporter(*this, msg);
+ }
+ }
+
+ lnav::console::snippet get_snippet() const;
+
+ template<typename T>
+ std::vector<T>& get_lvalue(std::map<std::string, std::vector<T>>& value)
+ {
+ return value[this->get_path_fragment(-2)];
+ }
+
+ template<typename T>
+ T& get_lvalue(std::map<std::string, T>& value)
+ {
+ return value[this->get_path_fragment(-1)];
+ }
+
+ template<typename T>
+ T& get_lvalue(T& lvalue)
+ {
+ return lvalue;
+ }
+
+ template<typename T>
+ T& get_rvalue(std::map<std::string, std::vector<T>>& value)
+ {
+ return value[this->get_path_fragment(-2)].back();
+ }
+
+ template<typename T>
+ T& get_rvalue(std::map<std::string, T>& value)
+ {
+ return value[this->get_path_fragment(-1)];
+ }
+
+ template<typename T>
+ T& get_rvalue(std::vector<T>& value)
+ {
+ return value.back();
+ }
+
+ template<typename T>
+ T& get_rvalue(T& lvalue)
+ {
+ return lvalue;
+ }
+
+ template<typename T, typename MEM_T, MEM_T T::*MEM>
+ auto& get_obj_member()
+ {
+ auto obj = (T*) this->ypc_obj_stack.top();
+
+ return obj->*MEM;
+ }
+
+ const intern_string_t ypc_source;
+ int ypc_line_number{1};
+ const struct json_path_container* ypc_handlers;
+ std::stack<void*> ypc_obj_stack;
+ void* ypc_userdata{nullptr};
+ yajl_handle ypc_handle{nullptr};
+ const unsigned char* ypc_json_text{nullptr};
+ size_t ypc_json_text_len{0};
+ size_t ypc_total_consumed{0};
+ yajl_callbacks ypc_callbacks;
+ yajl_callbacks ypc_alt_callbacks;
+ std::vector<char> ypc_path;
+ std::vector<size_t> ypc_path_index_stack;
+ std::vector<size_t> ypc_array_index;
+ std::vector<const json_path_handler_base*> ypc_handler_stack;
+ size_t ypc_array_handler_count{0};
+ bool ypc_ignore_unused{false};
+ const struct json_path_container* ypc_sibling_handlers{nullptr};
+ const struct json_path_handler_base* ypc_current_handler{nullptr};
+ std::set<std::string> ypc_active_paths;
+ error_reporter_t ypc_error_reporter{nullptr};
+ std::map<intern_string_t, source_location>* ypc_locations{nullptr};
+
+ void update_callbacks(const json_path_container* handlers = nullptr,
+ int child_start = 0);
+
+private:
+ static const yajl_callbacks DEFAULT_CALLBACKS;
+
+ static int map_start(void* ctx);
+ static int map_key(void* ctx, const unsigned char* key, size_t len);
+ static int map_end(void* ctx);
+ static int array_start(void* ctx);
+ static int array_end(void* ctx);
+ static int handle_unused(void* ctx);
+ static int handle_unused_or_delete(void* ctx);
+};
+
+class yajlpp_generator {
+public:
+ yajlpp_generator(yajl_gen handle) : yg_handle(handle) {}
+
+ yajl_gen_status operator()(const std::string& str)
+ {
+ return yajl_gen_string(this->yg_handle, str);
+ }
+
+ yajl_gen_status operator()(const char* str)
+ {
+ return yajl_gen_string(
+ this->yg_handle, (const unsigned char*) str, strlen(str));
+ }
+
+ yajl_gen_status operator()(const char* str, size_t len)
+ {
+ return yajl_gen_string(
+ this->yg_handle, (const unsigned char*) str, len);
+ }
+
+ yajl_gen_status operator()(const intern_string_t& str)
+ {
+ return yajl_gen_string(
+ this->yg_handle, (const unsigned char*) str.get(), str.size());
+ }
+
+ yajl_gen_status operator()(const string_fragment& str)
+ {
+ return yajl_gen_string(
+ this->yg_handle, (const unsigned char*) str.data(), str.length());
+ }
+
+ yajl_gen_status operator()(bool value)
+ {
+ return yajl_gen_bool(this->yg_handle, value);
+ }
+
+ yajl_gen_status operator()(double value)
+ {
+ return yajl_gen_double(this->yg_handle, value);
+ }
+
+ template<typename T>
+ yajl_gen_status operator()(
+ T value,
+ typename std::enable_if<std::is_integral<T>::value
+ && !std::is_same<T, bool>::value>::type* dummy
+ = 0)
+ {
+ return yajl_gen_integer(this->yg_handle, value);
+ }
+
+ template<typename T>
+ yajl_gen_status operator()(nonstd::optional<T> value)
+ {
+ if (!value.has_value()) {
+ return yajl_gen_status_ok;
+ }
+
+ return (*this)(value.value());
+ }
+
+ template<typename T>
+ yajl_gen_status operator()(
+ const T& container,
+ typename std::enable_if<!std::is_integral<T>::value>::type* dummy = 0)
+ {
+ yajl_gen_array_open(this->yg_handle);
+ for (const auto& elem : container) {
+ yajl_gen_status rc = (*this)(elem);
+
+ if (rc != yajl_gen_status_ok) {
+ return rc;
+ }
+ }
+
+ yajl_gen_array_close(this->yg_handle);
+
+ return yajl_gen_status_ok;
+ }
+
+ yajl_gen_status operator()() { return yajl_gen_null(this->yg_handle); }
+
+private:
+ yajl_gen yg_handle;
+};
+
+class yajlpp_container_base {
+public:
+ yajlpp_container_base(yajl_gen handle) : gen(handle), ycb_handle(handle) {}
+
+ yajlpp_generator gen;
+
+protected:
+ yajl_gen ycb_handle;
+};
+
+class yajlpp_map : public yajlpp_container_base {
+public:
+ yajlpp_map(yajl_gen handle) : yajlpp_container_base(handle)
+ {
+ yajl_gen_map_open(handle);
+ }
+
+ ~yajlpp_map() { yajl_gen_map_close(this->ycb_handle); }
+};
+
+class yajlpp_array : public yajlpp_container_base {
+public:
+ yajlpp_array(yajl_gen handle) : yajlpp_container_base(handle)
+ {
+ yajl_gen_array_open(handle);
+ }
+
+ ~yajlpp_array() { yajl_gen_array_close(this->ycb_handle); }
+};
+
+class yajlpp_gen_context {
+public:
+ yajlpp_gen_context(yajl_gen handle,
+ const struct json_path_container& handlers)
+ : ygc_handle(handle), ygc_depth(0), ygc_handlers(&handlers)
+ {
+ }
+
+ template<typename T>
+ yajlpp_gen_context& with_default_obj(T& obj)
+ {
+ this->ygc_default_stack.push(&obj);
+ return *this;
+ }
+
+ template<typename T>
+ yajlpp_gen_context& with_obj(const T& obj)
+ {
+ this->ygc_obj_stack.push((void*) &obj);
+ return *this;
+ }
+
+ yajlpp_gen_context& with_context(yajlpp_parse_context& ypc);
+
+ void gen();
+
+ void gen_schema(const json_path_container* handlers = nullptr);
+
+ yajl_gen ygc_handle;
+ int ygc_depth;
+ std::stack<void*> ygc_default_stack;
+ std::stack<void*> ygc_obj_stack;
+ std::vector<std::string> ygc_path;
+ const json_path_container* ygc_handlers;
+ std::map<std::string, const json_path_container*> ygc_schema_definitions;
+};
+
+class yajlpp_gen {
+public:
+ yajlpp_gen() : yg_handle(yajl_gen_free)
+ {
+ this->yg_handle = yajl_gen_alloc(nullptr);
+ }
+
+ yajl_gen get_handle() const { return this->yg_handle.in(); }
+
+ operator yajl_gen() { return this->yg_handle.in(); }
+
+ string_fragment to_string_fragment();
+
+private:
+ auto_mem<yajl_gen_t> yg_handle;
+};
+
+struct json_string {
+ explicit json_string(yajl_gen_t* gen)
+ {
+ const unsigned char* buf;
+
+ yajl_gen_get_buf(gen, &buf, &this->js_len);
+
+ this->js_content = (const unsigned char*) malloc(this->js_len);
+ memcpy((void*) this->js_content.in(), buf, this->js_len);
+ }
+
+ auto_mem<const unsigned char> js_content;
+ size_t js_len{0};
+};
+
+void dump_schema_to(const json_path_container& jpc, const char* internals_dir);
+
+#endif
diff --git a/src/yajlpp/yajlpp_def.hh b/src/yajlpp/yajlpp_def.hh
new file mode 100644
index 0000000..b511fa9
--- /dev/null
+++ b/src/yajlpp/yajlpp_def.hh
@@ -0,0 +1,1388 @@
+/**
+ * Copyright (c) 2018, 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_def.hh
+ */
+
+#ifndef yajlpp_def_hh
+#define yajlpp_def_hh
+
+#include <chrono>
+
+#include "base/date_time_scanner.hh"
+#include "base/time_util.hh"
+#include "config.h"
+#include "mapbox/variant.hpp"
+#include "relative_time.hh"
+#include "yajlpp.hh"
+
+struct json_null_t {
+ bool operator==(const json_null_t& other) const { return true; }
+};
+
+using json_any_t
+ = mapbox::util::variant<json_null_t, bool, int64_t, double, std::string>;
+
+struct json_path_container;
+
+struct json_path_handler : public json_path_handler_base {
+ template<typename P>
+ json_path_handler(P path, int (*null_func)(yajlpp_parse_context*))
+ : json_path_handler_base(path)
+ {
+ this->jph_callbacks.yajl_null = (int (*)(void*)) null_func;
+ }
+
+ template<typename P>
+ json_path_handler(P path, int (*bool_func)(yajlpp_parse_context*, int))
+ : json_path_handler_base(path)
+ {
+ this->jph_callbacks.yajl_boolean = (int (*)(void*, int)) bool_func;
+ }
+
+ template<typename P>
+ json_path_handler(P path, int (*int_func)(yajlpp_parse_context*, long long))
+ : json_path_handler_base(path)
+ {
+ this->jph_callbacks.yajl_integer = (int (*)(void*, long long)) int_func;
+ }
+
+ template<typename P>
+ json_path_handler(P path, int (*double_func)(yajlpp_parse_context*, double))
+ : json_path_handler_base(path)
+ {
+ this->jph_callbacks.yajl_double = (int (*)(void*, double)) double_func;
+ }
+
+ template<typename P>
+ json_path_handler(P path) : json_path_handler_base(path)
+ {
+ }
+
+ template<typename P>
+ json_path_handler(P path,
+ int (*str_func)(yajlpp_parse_context*,
+ const unsigned char*,
+ size_t))
+ : json_path_handler_base(path)
+ {
+ this->jph_callbacks.yajl_string
+ = (int (*)(void*, const unsigned char*, size_t)) str_func;
+ }
+
+ json_path_handler(const std::string& path,
+ const std::shared_ptr<const lnav::pcre2pp::code>& re)
+ : json_path_handler_base(path, re)
+ {
+ }
+
+ json_path_handler& add_cb(int (*null_func)(yajlpp_parse_context*))
+ {
+ this->jph_callbacks.yajl_null = (int (*)(void*)) null_func;
+ return *this;
+ }
+
+ json_path_handler& add_cb(int (*bool_func)(yajlpp_parse_context*, int))
+ {
+ this->jph_callbacks.yajl_boolean = (int (*)(void*, int)) bool_func;
+ return *this;
+ }
+
+ json_path_handler& add_cb(int (*int_func)(yajlpp_parse_context*, long long))
+ {
+ this->jph_callbacks.yajl_integer = (int (*)(void*, long long)) int_func;
+ return *this;
+ }
+
+ json_path_handler& add_cb(int (*double_func)(yajlpp_parse_context*, double))
+ {
+ this->jph_callbacks.yajl_double = (int (*)(void*, double)) double_func;
+ return *this;
+ }
+
+ json_path_handler& add_cb(int (*str_func)(yajlpp_parse_context*,
+ const unsigned char*,
+ size_t))
+ {
+ this->jph_callbacks.yajl_string
+ = (int (*)(void*, const unsigned char*, size_t)) str_func;
+ return *this;
+ }
+
+ json_path_handler& with_synopsis(const char* synopsis)
+ {
+ this->jph_synopsis = synopsis;
+ return *this;
+ }
+
+ json_path_handler& with_description(const char* description)
+ {
+ this->jph_description = description;
+ return *this;
+ }
+
+ json_path_handler& with_min_length(size_t len)
+ {
+ this->jph_min_length = len;
+ return *this;
+ }
+
+ json_path_handler& with_max_length(size_t len)
+ {
+ this->jph_max_length = len;
+ return *this;
+ }
+
+ json_path_handler& with_enum_values(const enum_value_t values[])
+ {
+ this->jph_enum_values = values;
+ return *this;
+ }
+
+ template<typename T, std::size_t N>
+ json_path_handler& with_pattern(const T (&re)[N])
+ {
+ this->jph_pattern_re = re;
+ this->jph_pattern = lnav::pcre2pp::code::from_const(re).to_shared();
+ return *this;
+ }
+
+ json_path_handler& with_min_value(long long val)
+ {
+ this->jph_min_value = val;
+ return *this;
+ }
+
+ template<typename R, typename T>
+ json_path_handler& with_obj_provider(
+ R* (*provider)(const yajlpp_provider_context& pc, T* root))
+ {
+ this->jph_obj_provider
+ = [provider](const yajlpp_provider_context& ypc, void* root) {
+ return (R*) provider(ypc, (T*) root);
+ };
+ return *this;
+ }
+
+ template<typename R>
+ json_path_handler& with_size_provider(size_t (*provider)(const R* root))
+ {
+ this->jph_size_provider
+ = [provider](const void* root) { return provider((R*) root); };
+ return *this;
+ }
+
+ template<typename T>
+ json_path_handler& with_path_provider(
+ void (*provider)(T* root, std::vector<std::string>& paths_out))
+ {
+ this->jph_path_provider
+ = [provider](void* root, std::vector<std::string>& paths_out) {
+ provider((T*) root, paths_out);
+ };
+ return *this;
+ }
+
+ template<typename T>
+ json_path_handler& with_obj_deleter(
+ void (*provider)(const yajlpp_provider_context& pc, T* root))
+ {
+ this->jph_obj_deleter
+ = [provider](const yajlpp_provider_context& ypc, void* root) {
+ provider(ypc, (T*) root);
+ };
+ return *this;
+ }
+
+ static int null_field_cb(yajlpp_parse_context* ypc)
+ {
+ return ypc->ypc_current_handler->jph_null_cb(ypc);
+ }
+
+ static int bool_field_cb(yajlpp_parse_context* ypc, int val)
+ {
+ return ypc->ypc_current_handler->jph_bool_cb(ypc, val);
+ }
+
+ static int str_field_cb2(yajlpp_parse_context* ypc,
+ const unsigned char* str,
+ size_t len)
+ {
+ return ypc->ypc_current_handler->jph_str_cb(ypc, str, len);
+ }
+
+ static int int_field_cb(yajlpp_parse_context* ypc, long long val)
+ {
+ return ypc->ypc_current_handler->jph_integer_cb(ypc, val);
+ }
+
+ static int dbl_field_cb(yajlpp_parse_context* ypc, double val)
+ {
+ return ypc->ypc_current_handler->jph_double_cb(ypc, val);
+ }
+
+ template<typename T, typename U>
+ static inline U& get_field(T& input, U(T::*field))
+ {
+ return input.*field;
+ }
+
+ template<typename T, typename U, typename... V>
+ static inline auto get_field(T& input, U(T::*field), V... args)
+ -> decltype(get_field(input.*field, args...))
+ {
+ return get_field(input.*field, args...);
+ }
+
+ template<typename T, typename U, typename... V>
+ static inline auto get_field(void* input, U(T::*field), V... args)
+ -> decltype(get_field(*((T*) input), field, args...))
+ {
+ return get_field(*((T*) input), field, args...);
+ }
+
+ template<typename R, typename T, typename... Args>
+ struct LastIs {
+ static constexpr bool value = LastIs<R, Args...>::value;
+ };
+
+ template<typename R, typename T>
+ struct LastIs<R, T> {
+ static constexpr bool value = false;
+ };
+
+ template<typename R, typename T>
+ struct LastIs<R, R T::*> {
+ static constexpr bool value = true;
+ };
+
+ template<typename T, typename... Args>
+ struct LastIsEnum {
+ using value_type = typename LastIsEnum<Args...>::value_type;
+ static constexpr bool value = LastIsEnum<Args...>::value;
+ };
+
+ template<typename T, typename U>
+ struct LastIsEnum<U T::*> {
+ using value_type = U;
+
+ static constexpr bool value = std::is_enum<U>::value;
+ };
+
+ template<typename T, typename U>
+ struct LastIsEnum<nonstd::optional<U> T::*> {
+ using value_type = U;
+
+ static constexpr bool value = std::is_enum<U>::value;
+ };
+
+ template<typename T, typename... Args>
+ struct LastIsInteger {
+ static constexpr bool value = LastIsInteger<Args...>::value;
+ };
+
+ template<typename T, typename U>
+ struct LastIsInteger<U T::*> {
+ static constexpr bool value
+ = std::is_integral<U>::value && !std::is_same<U, bool>::value;
+ };
+
+ template<typename T, typename U>
+ struct LastIsInteger<nonstd::optional<U> T::*> {
+ static constexpr bool value
+ = std::is_integral<U>::value && !std::is_same<U, bool>::value;
+ };
+
+ template<typename T, typename... Args>
+ struct LastIsFloat {
+ static constexpr bool value = LastIsFloat<Args...>::value;
+ };
+
+ template<typename T, typename U>
+ struct LastIsFloat<U T::*> {
+ static constexpr bool value = std::is_same<U, double>::value;
+ };
+
+ template<typename T, typename U>
+ struct LastIsFloat<nonstd::optional<U> T::*> {
+ static constexpr bool value = std::is_same<U, double>::value;
+ };
+
+ template<typename T, typename... Args>
+ struct LastIsVector {
+ using value_type = typename LastIsVector<Args...>::value_type;
+ static constexpr bool value = LastIsVector<Args...>::value;
+ };
+
+ template<typename T, typename U>
+ struct LastIsVector<std::vector<U> T::*> {
+ using value_type = U;
+ static constexpr bool value = true;
+ };
+
+ template<typename T, typename U>
+ struct LastIsVector<U T::*> {
+ using value_type = void;
+ static constexpr bool value = false;
+ };
+
+ template<typename T>
+ static bool is_field_set(const nonstd::optional<T>& field)
+ {
+ return field.has_value();
+ }
+
+ template<typename T>
+ static bool is_field_set(const T&)
+ {
+ return true;
+ }
+
+ template<typename... Args,
+ std::enable_if_t<LastIs<bool, Args...>::value, bool> = true>
+ json_path_handler& for_field(Args... args)
+ {
+ this->add_cb(bool_field_cb);
+ this->jph_bool_cb = [args...](yajlpp_parse_context* ypc, int val) {
+ auto* obj = ypc->ypc_obj_stack.top();
+
+ json_path_handler::get_field(obj, args...) = static_cast<bool>(val);
+
+ return 1;
+ };
+ this->jph_gen_callback = [args...](yajlpp_gen_context& ygc,
+ const json_path_handler_base& jph,
+ yajl_gen handle) {
+ const auto& field = json_path_handler::get_field(
+ ygc.ygc_obj_stack.top(), args...);
+
+ if (!ygc.ygc_default_stack.empty()) {
+ const auto& field_def = json_path_handler::get_field(
+ ygc.ygc_default_stack.top(), args...);
+
+ if (field == field_def) {
+ return yajl_gen_status_ok;
+ }
+ }
+
+ if (ygc.ygc_depth) {
+ yajl_gen_string(handle, jph.jph_property);
+ }
+
+ yajlpp_generator gen(handle);
+
+ return gen(field);
+ };
+ this->jph_field_getter
+ = [args...](void* root, nonstd::optional<std::string> name) {
+ return (void*) &json_path_handler::get_field(root, args...);
+ };
+
+ return *this;
+ }
+
+ template<
+ typename... Args,
+ std::enable_if_t<LastIs<std::vector<std::string>, Args...>::value, bool>
+ = true>
+ json_path_handler& for_field(Args... args)
+ {
+ this->add_cb(str_field_cb2);
+ this->jph_str_cb = [args...](yajlpp_parse_context* ypc,
+ const unsigned char* str,
+ size_t len) {
+ auto obj = ypc->ypc_obj_stack.top();
+ auto value_str = std::string((const char*) str, len);
+ auto jph = ypc->ypc_current_handler;
+
+ jph->validate_string(*ypc, value_str);
+ json_path_handler::get_field(obj, args...)
+ .emplace_back(std::move(value_str));
+
+ return 1;
+ };
+ return *this;
+ }
+
+ template<typename... Args,
+ std::enable_if_t<LastIsVector<Args...>::value, bool> = true,
+ std::enable_if_t<
+ !std::is_same<typename LastIsVector<Args...>::value_type,
+ std::string>::value,
+ bool>
+ = true>
+ json_path_handler& for_field(Args... args)
+ {
+ this->jph_obj_provider
+ = [args...](const yajlpp_provider_context& ypc, void* root) {
+ auto& vec = json_path_handler::get_field(root, args...);
+
+ if (ypc.ypc_index >= vec.size()) {
+ vec.resize(ypc.ypc_index + 1);
+ }
+
+ return &vec[ypc.ypc_index];
+ };
+ this->jph_size_provider = [args...](void* root) {
+ auto& vec = json_path_handler::get_field(root, args...);
+
+ return vec.size();
+ };
+
+ return *this;
+ }
+
+ template<typename T, typename U>
+ json_path_handler& for_child(positioned_property<U>(T::*field))
+ {
+ this->jph_obj_provider
+ = [field](const yajlpp_provider_context& ypc, void* root) -> void* {
+ auto& child = json_path_handler::get_field(root, field);
+
+ if (ypc.ypc_parse_context != nullptr && child.pp_path.empty()) {
+ child.pp_path = ypc.ypc_parse_context->get_full_path();
+ }
+ return &child.pp_value;
+ };
+
+ return *this;
+ }
+
+ template<typename... Args>
+ json_path_handler& for_child(Args... args)
+ {
+ this->jph_obj_provider = [args...](const yajlpp_provider_context& ypc,
+ void* root) -> void* {
+ auto& child = json_path_handler::get_field(root, args...);
+
+ return &child;
+ };
+
+ return *this;
+ }
+
+ template<typename... Args,
+ std::enable_if_t<
+ LastIs<std::map<std::string, std::string>, Args...>::value,
+ bool>
+ = true>
+ json_path_handler& for_field(Args... args)
+ {
+ this->add_cb(str_field_cb2);
+ this->jph_str_cb = [args...](yajlpp_parse_context* ypc,
+ const unsigned char* str,
+ size_t len) {
+ auto obj = ypc->ypc_obj_stack.top();
+ auto key = ypc->get_path_fragment(-1);
+
+ json_path_handler::get_field(obj, args...)[key]
+ = std::string((const char*) str, len);
+
+ return 1;
+ };
+ this->jph_gen_callback = [args...](yajlpp_gen_context& ygc,
+ const json_path_handler_base& jph,
+ yajl_gen handle) {
+ const auto& field = json_path_handler::get_field(
+ ygc.ygc_obj_stack.top(), args...);
+
+ if (!ygc.ygc_default_stack.empty()) {
+ const auto& field_def = json_path_handler::get_field(
+ ygc.ygc_default_stack.top(), args...);
+
+ if (field == field_def) {
+ return yajl_gen_status_ok;
+ }
+ }
+
+ {
+ yajlpp_generator gen(handle);
+
+ for (const auto& pair : field) {
+ gen(pair.first);
+ gen(pair.second);
+ }
+ }
+
+ return yajl_gen_status_ok;
+ };
+ return *this;
+ }
+
+ template<typename... Args,
+ std::enable_if_t<
+ LastIs<std::map<std::string, nonstd::optional<std::string>>,
+ Args...>::value,
+ bool>
+ = true>
+ json_path_handler& for_field(Args... args)
+ {
+ this->add_cb(str_field_cb2);
+ this->jph_str_cb = [args...](yajlpp_parse_context* ypc,
+ const unsigned char* str,
+ size_t len) {
+ auto obj = ypc->ypc_obj_stack.top();
+ auto key = ypc->get_path_fragment(-1);
+
+ json_path_handler::get_field(obj, args...)[key]
+ = std::string((const char*) str, len);
+
+ return 1;
+ };
+ this->add_cb(null_field_cb);
+ this->jph_null_cb = [args...](yajlpp_parse_context* ypc) {
+ auto* obj = ypc->ypc_obj_stack.top();
+ auto key = ypc->get_path_fragment(-1);
+
+ json_path_handler::get_field(obj, args...)[key] = nonstd::nullopt;
+
+ return 1;
+ };
+ this->jph_gen_callback = [args...](yajlpp_gen_context& ygc,
+ const json_path_handler_base& jph,
+ yajl_gen handle) {
+ const auto& field = json_path_handler::get_field(
+ ygc.ygc_obj_stack.top(), args...);
+
+ if (!ygc.ygc_default_stack.empty()) {
+ const auto& field_def = json_path_handler::get_field(
+ ygc.ygc_default_stack.top(), args...);
+
+ if (field == field_def) {
+ return yajl_gen_status_ok;
+ }
+ }
+
+ {
+ yajlpp_generator gen(handle);
+
+ for (const auto& pair : field) {
+ gen(pair.first);
+ gen(pair.second);
+ }
+ }
+
+ return yajl_gen_status_ok;
+ };
+ return *this;
+ }
+
+ template<typename... Args,
+ std::enable_if_t<
+ LastIs<std::map<std::string, json_any_t>, Args...>::value,
+ bool>
+ = true>
+ json_path_handler& for_field(Args... args)
+ {
+ this->add_cb(bool_field_cb);
+ this->jph_bool_cb = [args...](yajlpp_parse_context* ypc, int val) {
+ auto* obj = ypc->ypc_obj_stack.top();
+ auto key = ypc->get_path_fragment(-1);
+
+ json_path_handler::get_field(obj, args...)[key] = val ? true
+ : false;
+
+ return 1;
+ };
+ this->add_cb(int_field_cb);
+ this->jph_integer_cb
+ = [args...](yajlpp_parse_context* ypc, long long val) {
+ auto* obj = ypc->ypc_obj_stack.top();
+ auto key = ypc->get_path_fragment(-1);
+
+ json_path_handler::get_field(obj, args...)[key]
+ = static_cast<int64_t>(val);
+
+ return 1;
+ };
+ this->add_cb(str_field_cb2);
+ this->jph_str_cb = [args...](yajlpp_parse_context* ypc,
+ const unsigned char* str,
+ size_t len) {
+ auto* obj = ypc->ypc_obj_stack.top();
+ auto key = ypc->get_path_fragment(-1);
+
+ json_path_handler::get_field(obj, args...)[key]
+ = std::string((const char*) str, len);
+
+ return 1;
+ };
+ this->jph_gen_callback = [args...](yajlpp_gen_context& ygc,
+ const json_path_handler_base& jph,
+ yajl_gen handle) {
+ const auto& field = json_path_handler::get_field(
+ ygc.ygc_obj_stack.top(), args...);
+
+ if (!ygc.ygc_default_stack.empty()) {
+ const auto& field_def = json_path_handler::get_field(
+ ygc.ygc_default_stack.top(), args...);
+
+ if (field == field_def) {
+ return yajl_gen_status_ok;
+ }
+ }
+
+ {
+ yajlpp_generator gen(handle);
+
+ for (const auto& pair : field) {
+ gen(pair.first);
+ pair.second.match([&gen](json_null_t v) { gen(); },
+ [&gen](bool v) { gen(v); },
+ [&gen](int64_t v) { gen(v); },
+ [&gen](double v) { gen(v); },
+ [&gen](const std::string& v) { gen(v); });
+ }
+ }
+
+ return yajl_gen_status_ok;
+ };
+ return *this;
+ }
+
+ template<typename... Args,
+ std::enable_if_t<LastIs<std::string, Args...>::value, bool> = true>
+ json_path_handler& for_field(Args... args)
+ {
+ this->add_cb(str_field_cb2);
+ this->jph_str_cb = [args...](yajlpp_parse_context* ypc,
+ const unsigned char* str,
+ size_t len) {
+ auto obj = ypc->ypc_obj_stack.top();
+ auto value_str = std::string((const char*) str, len);
+ auto jph = ypc->ypc_current_handler;
+
+ jph->validate_string(*ypc, value_str);
+ json_path_handler::get_field(obj, args...) = std::move(value_str);
+
+ return 1;
+ };
+ this->jph_gen_callback = [args...](yajlpp_gen_context& ygc,
+ const json_path_handler_base& jph,
+ yajl_gen handle) {
+ const auto& field = json_path_handler::get_field(
+ ygc.ygc_obj_stack.top(), args...);
+
+ if (!ygc.ygc_default_stack.empty()) {
+ const auto& field_def = json_path_handler::get_field(
+ ygc.ygc_default_stack.top(), args...);
+
+ if (field == field_def) {
+ return yajl_gen_status_ok;
+ }
+ }
+
+ if (ygc.ygc_depth) {
+ yajl_gen_string(handle, jph.jph_property);
+ }
+
+ yajlpp_generator gen(handle);
+
+ return gen(field);
+ };
+ this->jph_field_getter
+ = [args...](void* root, nonstd::optional<std::string> name) {
+ return (void*) &json_path_handler::get_field(root, args...);
+ };
+ return *this;
+ }
+
+ template<typename... Args,
+ std::enable_if_t<LastIs<timeval, Args...>::value, bool> = true>
+ json_path_handler& for_field(Args... args)
+ {
+ this->add_cb(str_field_cb2);
+ this->jph_str_cb = [args...](yajlpp_parse_context* ypc,
+ const unsigned char* str,
+ size_t len) {
+ auto obj = ypc->ypc_obj_stack.top();
+ auto jph = ypc->ypc_current_handler;
+
+ date_time_scanner dts;
+ timeval tv{};
+ exttm tm;
+
+ if (dts.scan((char*) str, len, nullptr, &tm, tv) == nullptr) {
+ ypc->report_error(
+ lnav::console::user_message::error(
+ attr_line_t("unrecognized timestamp ")
+ .append_quoted(
+ string_fragment::from_bytes(str, len)))
+ .with_snippet(ypc->get_snippet())
+ .with_help(jph->get_help_text(ypc)));
+ } else {
+ json_path_handler::get_field(obj, args...) = tv;
+ }
+
+ return 1;
+ };
+ this->jph_gen_callback = [args...](yajlpp_gen_context& ygc,
+ const json_path_handler_base& jph,
+ yajl_gen handle) {
+ const auto& field = json_path_handler::get_field(
+ ygc.ygc_obj_stack.top(), args...);
+
+ if (!ygc.ygc_default_stack.empty()) {
+ const auto& field_def = json_path_handler::get_field(
+ ygc.ygc_default_stack.top(), args...);
+
+ if (field == field_def) {
+ return yajl_gen_status_ok;
+ }
+ }
+
+ if (ygc.ygc_depth) {
+ yajl_gen_string(handle, jph.jph_property);
+ }
+
+ yajlpp_generator gen(handle);
+ char buf[64];
+
+ auto buf_len = lnav::strftime_rfc3339(
+ buf, sizeof(buf), field.tv_sec, field.tv_usec, 'T');
+
+ return gen(string_fragment::from_bytes(buf, buf_len));
+ };
+ this->jph_field_getter
+ = [args...](void* root, nonstd::optional<std::string> name) {
+ return (void*) &json_path_handler::get_field(root, args...);
+ };
+ return *this;
+ }
+
+ template<
+ typename... Args,
+ std::enable_if_t<LastIs<nonstd::optional<std::string>, Args...>::value,
+ bool>
+ = true>
+ json_path_handler& for_field(Args... args)
+ {
+ this->add_cb(str_field_cb2);
+ this->jph_str_cb = [args...](yajlpp_parse_context* ypc,
+ const unsigned char* str,
+ size_t len) {
+ auto obj = ypc->ypc_obj_stack.top();
+ auto value_str = std::string((const char*) str, len);
+ auto jph = ypc->ypc_current_handler;
+
+ jph->validate_string(*ypc, value_str);
+ json_path_handler::get_field(obj, args...) = std::move(value_str);
+
+ return 1;
+ };
+ this->jph_gen_callback = [args...](yajlpp_gen_context& ygc,
+ const json_path_handler_base& jph,
+ yajl_gen handle) {
+ const auto& field = json_path_handler::get_field(
+ ygc.ygc_obj_stack.top(), args...);
+
+ if (!ygc.ygc_default_stack.empty()) {
+ const auto& field_def = json_path_handler::get_field(
+ ygc.ygc_default_stack.top(), args...);
+
+ if (field == field_def) {
+ return yajl_gen_status_ok;
+ }
+ }
+
+ if (!field) {
+ return yajl_gen_status_ok;
+ }
+
+ if (ygc.ygc_depth) {
+ yajl_gen_string(handle, jph.jph_property);
+ }
+
+ yajlpp_generator gen(handle);
+
+ return gen(field.value());
+ };
+ this->jph_field_getter
+ = [args...](void* root, nonstd::optional<std::string> name) {
+ return (void*) &json_path_handler::get_field(root, args...);
+ };
+ return *this;
+ }
+
+ template<typename... Args,
+ std::enable_if_t<
+ LastIs<positioned_property<std::string>, Args...>::value,
+ bool>
+ = true>
+ json_path_handler& for_field(Args... args)
+ {
+ this->add_cb(str_field_cb2);
+ this->jph_str_cb = [args...](yajlpp_parse_context* ypc,
+ const unsigned char* str,
+ size_t len) {
+ auto obj = ypc->ypc_obj_stack.top();
+ auto value_str = std::string((const char*) str, len);
+ auto jph = ypc->ypc_current_handler;
+
+ jph->validate_string(*ypc, value_str);
+ auto& field = json_path_handler::get_field(obj, args...);
+
+ field.pp_path = ypc->get_full_path();
+ field.pp_location.sl_source = ypc->ypc_source;
+ field.pp_location.sl_line_number = ypc->get_line_number();
+ field.pp_value = std::move(value_str);
+
+ return 1;
+ };
+ this->jph_gen_callback = [args...](yajlpp_gen_context& ygc,
+ const json_path_handler_base& jph,
+ yajl_gen handle) {
+ const auto& field = json_path_handler::get_field(
+ ygc.ygc_obj_stack.top(), args...);
+
+ if (!ygc.ygc_default_stack.empty()) {
+ const auto& field_def = json_path_handler::get_field(
+ ygc.ygc_default_stack.top(), args...);
+
+ if (field.pp_value == field_def.pp_value) {
+ return yajl_gen_status_ok;
+ }
+ }
+
+ if (ygc.ygc_depth) {
+ yajl_gen_string(handle, jph.jph_property);
+ }
+
+ yajlpp_generator gen(handle);
+
+ return gen(field.pp_value);
+ };
+ return *this;
+ }
+
+ template<typename... Args,
+ std::enable_if_t<LastIs<intern_string_t, Args...>::value, bool>
+ = true>
+ json_path_handler& for_field(Args... args)
+ {
+ this->add_cb(str_field_cb2);
+ this->jph_str_cb = [args...](yajlpp_parse_context* ypc,
+ const unsigned char* str,
+ size_t len) {
+ auto obj = ypc->ypc_obj_stack.top();
+ auto value_str = std::string((const char*) str, len);
+ auto jph = ypc->ypc_current_handler;
+
+ jph->validate_string(*ypc, value_str);
+ json_path_handler::get_field(obj, args...)
+ = intern_string::lookup(value_str);
+
+ return 1;
+ };
+ this->jph_gen_callback = [args...](yajlpp_gen_context& ygc,
+ const json_path_handler_base& jph,
+ yajl_gen handle) {
+ const auto& field = json_path_handler::get_field(
+ ygc.ygc_obj_stack.top(), args...);
+
+ if (!ygc.ygc_default_stack.empty()) {
+ const auto& field_def = json_path_handler::get_field(
+ ygc.ygc_default_stack.top(), args...);
+
+ if (field == field_def) {
+ return yajl_gen_status_ok;
+ }
+ }
+
+ if (ygc.ygc_depth) {
+ yajl_gen_string(handle, jph.jph_property);
+ }
+
+ yajlpp_generator gen(handle);
+
+ return gen(field);
+ };
+ return *this;
+ }
+
+ template<typename... Args,
+ std::enable_if_t<
+ LastIs<positioned_property<intern_string_t>, Args...>::value,
+ bool>
+ = true>
+ json_path_handler& for_field(Args... args)
+ {
+ this->add_cb(str_field_cb2);
+ this->jph_str_cb = [args...](yajlpp_parse_context* ypc,
+ const unsigned char* str,
+ size_t len) {
+ auto obj = ypc->ypc_obj_stack.top();
+ auto value_str = std::string((const char*) str, len);
+ auto jph = ypc->ypc_current_handler;
+
+ jph->validate_string(*ypc, value_str);
+ auto& field = json_path_handler::get_field(obj, args...);
+ field.pp_path = ypc->get_full_path();
+ field.pp_location.sl_source = ypc->ypc_source;
+ field.pp_location.sl_line_number = ypc->get_line_number();
+ field.pp_value = intern_string::lookup(value_str);
+
+ return 1;
+ };
+ this->jph_gen_callback = [args...](yajlpp_gen_context& ygc,
+ const json_path_handler_base& jph,
+ yajl_gen handle) {
+ const auto& field = json_path_handler::get_field(
+ ygc.ygc_obj_stack.top(), args...);
+
+ if (!ygc.ygc_default_stack.empty()) {
+ const auto& field_def = json_path_handler::get_field(
+ ygc.ygc_default_stack.top(), args...);
+
+ if (field.pp_value == field_def.pp_value) {
+ return yajl_gen_status_ok;
+ }
+ }
+
+ if (ygc.ygc_depth) {
+ yajl_gen_string(handle, jph.jph_property);
+ }
+
+ yajlpp_generator gen(handle);
+
+ return gen(field.pp_value);
+ };
+ return *this;
+ }
+
+ template<typename>
+ struct int_ {
+ typedef int type;
+ };
+ template<
+ typename C,
+ typename T,
+ typename int_<decltype(T::from(
+ intern_string_t{}, source_location{}, string_fragment{}))>::type
+ = 0,
+ typename... Args>
+ json_path_handler& for_field(Args... args, T C::*ptr_arg)
+ {
+ this->add_cb(str_field_cb2);
+ this->jph_str_cb = [args..., ptr_arg](yajlpp_parse_context* ypc,
+ const unsigned char* str,
+ size_t len) {
+ auto* obj = ypc->ypc_obj_stack.top();
+ auto value_frag = string_fragment::from_bytes(str, len);
+ const auto* jph = ypc->ypc_current_handler;
+ auto loc = source_location{ypc->ypc_source, ypc->get_line_number()};
+
+ auto from_res = T::from(ypc->get_full_path(), loc, value_frag);
+ if (from_res.isErr()) {
+ jph->report_error(
+ ypc, value_frag.to_string(), from_res.unwrapErr());
+ } else {
+ json_path_handler::get_field(obj, args..., ptr_arg)
+ = from_res.unwrap();
+ }
+
+ return 1;
+ };
+ return *this;
+ }
+
+ template<typename... Args,
+ std::enable_if_t<LastIsInteger<Args...>::value, bool> = true>
+ json_path_handler& for_field(Args... args)
+ {
+ this->add_cb(int_field_cb);
+ this->jph_integer_cb
+ = [args...](yajlpp_parse_context* ypc, long long val) {
+ auto jph = ypc->ypc_current_handler;
+ auto* obj = ypc->ypc_obj_stack.top();
+
+ if (val < jph->jph_min_value) {
+ jph->report_min_value_error(ypc, val);
+ return 1;
+ }
+
+ json_path_handler::get_field(obj, args...) = val;
+
+ return 1;
+ };
+ this->jph_gen_callback = [args...](yajlpp_gen_context& ygc,
+ const json_path_handler_base& jph,
+ yajl_gen handle) {
+ const auto& field = json_path_handler::get_field(
+ ygc.ygc_obj_stack.top(), args...);
+
+ if (!ygc.ygc_default_stack.empty()) {
+ const auto& field_def = json_path_handler::get_field(
+ ygc.ygc_default_stack.top(), args...);
+
+ if (field == field_def) {
+ return yajl_gen_status_ok;
+ }
+ }
+
+ if (!is_field_set(field)) {
+ return yajl_gen_status_ok;
+ }
+
+ if (ygc.ygc_depth) {
+ yajl_gen_string(handle, jph.jph_property);
+ }
+
+ yajlpp_generator gen(handle);
+
+ return gen(field);
+ };
+ this->jph_field_getter
+ = [args...](void* root, nonstd::optional<std::string> name) {
+ return (void*) &json_path_handler::get_field(root, args...);
+ };
+
+ return *this;
+ }
+
+ template<typename... Args,
+ std::enable_if_t<LastIsFloat<Args...>::value, bool> = true>
+ json_path_handler& for_field(Args... args)
+ {
+ this->add_cb(dbl_field_cb);
+ this->jph_double_cb = [args...](yajlpp_parse_context* ypc, double val) {
+ auto jph = ypc->ypc_current_handler;
+ auto* obj = ypc->ypc_obj_stack.top();
+
+ if (val < jph->jph_min_value) {
+ jph->report_min_value_error(ypc, val);
+ return 1;
+ }
+
+ json_path_handler::get_field(obj, args...) = val;
+
+ return 1;
+ };
+ this->jph_gen_callback = [args...](yajlpp_gen_context& ygc,
+ const json_path_handler_base& jph,
+ yajl_gen handle) {
+ const auto& field = json_path_handler::get_field(
+ ygc.ygc_obj_stack.top(), args...);
+
+ if (!ygc.ygc_default_stack.empty()) {
+ const auto& field_def = json_path_handler::get_field(
+ ygc.ygc_default_stack.top(), args...);
+
+ if (field == field_def) {
+ return yajl_gen_status_ok;
+ }
+ }
+
+ if (!is_field_set(field)) {
+ return yajl_gen_status_ok;
+ }
+
+ if (ygc.ygc_depth) {
+ yajl_gen_string(handle, jph.jph_property);
+ }
+
+ yajlpp_generator gen(handle);
+
+ return gen(field);
+ };
+ this->jph_field_getter
+ = [args...](void* root, nonstd::optional<std::string> name) {
+ return (void*) &json_path_handler::get_field(root, args...);
+ };
+
+ return *this;
+ }
+
+ template<
+ typename... Args,
+ std::enable_if_t<LastIs<std::chrono::seconds, Args...>::value, bool>
+ = true>
+ json_path_handler& for_field(Args... args)
+ {
+ this->add_cb(str_field_cb2);
+ this->jph_str_cb = [args...](yajlpp_parse_context* ypc,
+ const unsigned char* str,
+ size_t len) {
+ auto obj = ypc->ypc_obj_stack.top();
+ auto handler = ypc->ypc_current_handler;
+ auto parse_res = relative_time::from_str(
+ string_fragment::from_bytes(str, len));
+
+ if (parse_res.isErr()) {
+ auto parse_error = parse_res.unwrapErr();
+ auto value_str = std::string((const char*) str, len);
+
+ handler->report_duration_error(ypc, value_str, parse_error);
+ return 1;
+ }
+
+ json_path_handler::get_field(obj, args...) = std::chrono::seconds(
+ parse_res.template unwrap().to_timeval().tv_sec);
+
+ return 1;
+ };
+ this->jph_gen_callback = [args...](yajlpp_gen_context& ygc,
+ const json_path_handler_base& jph,
+ yajl_gen handle) {
+ const auto& field = json_path_handler::get_field(
+ ygc.ygc_obj_stack.top(), args...);
+
+ if (!ygc.ygc_default_stack.empty()) {
+ const auto& field_def = json_path_handler::get_field(
+ ygc.ygc_default_stack.top(), args...);
+
+ if (field == field_def) {
+ return yajl_gen_status_ok;
+ }
+ }
+
+ if (ygc.ygc_depth) {
+ yajl_gen_string(handle, jph.jph_property);
+ }
+
+ yajlpp_generator gen(handle);
+
+ return gen(relative_time::from_timeval(
+ {static_cast<time_t>(field.count()), 0})
+ .to_string());
+ };
+ this->jph_field_getter
+ = [args...](void* root, nonstd::optional<std::string> name) {
+ return (void*) &json_path_handler::get_field(root, args...);
+ };
+ return *this;
+ }
+
+ template<typename... Args,
+ std::enable_if_t<LastIsEnum<Args...>::value, bool> = true>
+ json_path_handler& for_field(Args... args)
+ {
+ this->add_cb(str_field_cb2);
+ this->jph_str_cb = [args...](yajlpp_parse_context* ypc,
+ const unsigned char* str,
+ size_t len) {
+ auto obj = ypc->ypc_obj_stack.top();
+ auto handler = ypc->ypc_current_handler;
+ auto res = handler->to_enum_value(string_fragment(str, 0, len));
+
+ if (res) {
+ json_path_handler::get_field(obj, args...)
+ = (typename LastIsEnum<Args...>::value_type) res.value();
+ } else {
+ handler->report_enum_error(ypc,
+ std::string((const char*) str, len));
+ }
+
+ return 1;
+ };
+ this->jph_gen_callback = [args...](yajlpp_gen_context& ygc,
+ const json_path_handler_base& jph,
+ yajl_gen handle) {
+ const auto& field = json_path_handler::get_field(
+ ygc.ygc_obj_stack.top(), args...);
+
+ if (!ygc.ygc_default_stack.empty()) {
+ const auto& field_def = json_path_handler::get_field(
+ ygc.ygc_default_stack.top(), args...);
+
+ if (field == field_def) {
+ return yajl_gen_status_ok;
+ }
+ }
+
+ if (!is_field_set(field)) {
+ return yajl_gen_status_ok;
+ }
+
+ if (ygc.ygc_depth) {
+ yajl_gen_string(handle, jph.jph_property);
+ }
+
+ yajlpp_generator gen(handle);
+
+ return gen(jph.to_enum_string(field));
+ };
+ this->jph_field_getter
+ = [args...](void* root, nonstd::optional<std::string> name) {
+ return (void*) &json_path_handler::get_field(root, args...);
+ };
+
+ return *this;
+ }
+
+ json_path_handler& with_children(const json_path_container& container);
+
+ json_path_handler& with_example(const std::string& example)
+ {
+ this->jph_examples.emplace_back(example);
+ return *this;
+ }
+};
+
+struct json_path_container {
+ json_path_container(std::initializer_list<json_path_handler> children)
+ : jpc_children(children)
+ {
+ }
+
+ json_path_container& with_definition_id(const std::string& id)
+ {
+ this->jpc_definition_id = id;
+ return *this;
+ }
+
+ json_path_container& with_schema_id(const std::string& id)
+ {
+ this->jpc_schema_id = id;
+ return *this;
+ }
+
+ json_path_container& with_description(std::string desc)
+ {
+ this->jpc_description = std::move(desc);
+ return *this;
+ }
+
+ void gen_schema(yajlpp_gen_context& ygc) const;
+
+ void gen_properties(yajlpp_gen_context& ygc) const;
+
+ std::string jpc_schema_id;
+ std::string jpc_definition_id;
+ std::string jpc_description;
+ std::vector<json_path_handler> jpc_children;
+};
+
+template<typename T>
+class yajlpp_parser {
+public:
+ yajlpp_parser(intern_string_t src, const json_path_container* container)
+ : yp_parse_context(src, container)
+ {
+ this->yp_handle = yajl_alloc(&this->yp_parse_context.ypc_callbacks,
+ nullptr,
+ &this->yp_parse_context);
+ this->yp_parse_context.with_handle(this->yp_handle);
+ this->yp_parse_context.template with_obj(this->yp_obj);
+ this->yp_parse_context.ypc_userdata = this;
+ this->yp_parse_context.with_error_reporter(
+ [](const auto& ypc, const auto& um) {
+ auto* yp = static_cast<yajlpp_parser<T>*>(ypc.ypc_userdata);
+
+ yp->yp_errors.template emplace_back(um);
+ });
+ }
+
+ yajlpp_parser& with_ignore_unused(bool value)
+ {
+ this->yp_parse_context.with_ignore_unused(value);
+
+ return *this;
+ }
+
+ Result<T, std::vector<lnav::console::user_message>> of(
+ const string_fragment& json)
+ {
+ if (this->yp_parse_context.parse_doc(json)) {
+ return Ok(std::move(this->yp_obj));
+ }
+
+ return Err(std::move(this->yp_errors));
+ }
+
+private:
+ yajlpp_parse_context yp_parse_context;
+ auto_mem<yajl_handle_t> yp_handle{yajl_free};
+ std::vector<lnav::console::user_message> yp_errors;
+ T yp_obj;
+};
+
+template<typename T>
+struct typed_json_path_container : public json_path_container {
+ typed_json_path_container(std::initializer_list<json_path_handler> children)
+ : json_path_container(children)
+ {
+ }
+
+ typed_json_path_container<T>& with_schema_id2(const std::string& id)
+ {
+ this->jpc_schema_id = id;
+ return *this;
+ }
+
+ typed_json_path_container<T>& with_description2(std::string desc)
+ {
+ this->jpc_description = std::move(desc);
+ return *this;
+ }
+
+ yajlpp_parser<T> parser_for(intern_string_t src) const
+ {
+ return yajlpp_parser<T>{src, this};
+ }
+
+ std::string to_string(const T& obj) const
+ {
+ yajlpp_gen gen;
+ yajlpp_gen_context ygc(gen, *this);
+ ygc.template with_obj(obj);
+ ygc.ygc_depth = 1;
+ ygc.gen();
+
+ return gen.to_string_fragment().to_string();
+ }
+
+ json_string to_json_string(const T& obj) const
+ {
+ yajlpp_gen gen;
+ yajlpp_gen_context ygc(gen, *this);
+ ygc.template with_obj(obj);
+ ygc.ygc_depth = 1;
+ ygc.gen();
+
+ return json_string{gen.get_handle()};
+ }
+};
+
+namespace yajlpp {
+inline json_path_handler
+property_handler(const std::string& path)
+{
+ return {path};
+}
+
+template<typename T, std::size_t N>
+inline json_path_handler
+pattern_property_handler(const T (&path)[N])
+{
+ return {lnav::pcre2pp::code::from_const(path).to_shared()};
+}
+
+} // namespace yajlpp
+
+#endif