/** * 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 #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; struct json_path_container; struct json_path_handler : public json_path_handler_base { template 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 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 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 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 json_path_handler(P path, int (*number_func)(yajlpp_parse_context*, const char* numberVal, size_t numberLen)) : json_path_handler_base(path) { this->jph_callbacks.yajl_number = (int (*)(void*, const char*, size_t)) number_func; } template json_path_handler(P path) : json_path_handler_base(path) { } template 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& 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 (*number_func)(yajlpp_parse_context*, const char*, size_t)) { this->jph_callbacks.yajl_number = (int (*)(void*, const char*, size_t)) number_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 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 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 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 json_path_handler& with_path_provider( void (*provider)(T* root, std::vector& paths_out)) { this->jph_path_provider = [provider](void* root, std::vector& paths_out) { provider((T*) root, paths_out); }; return *this; } template 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) { ypc->fill_in_source(); return ypc->ypc_current_handler->jph_null_cb(ypc); } static int bool_field_cb(yajlpp_parse_context* ypc, int val) { ypc->fill_in_source(); 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) { ypc->fill_in_source(); return ypc->ypc_current_handler->jph_str_cb( ypc, string_fragment::from_bytes(str, len)); } static int int_field_cb(yajlpp_parse_context* ypc, long long val) { ypc->fill_in_source(); return ypc->ypc_current_handler->jph_integer_cb(ypc, val); } static int dbl_field_cb(yajlpp_parse_context* ypc, double val) { ypc->fill_in_source(); return ypc->ypc_current_handler->jph_double_cb(ypc, val); } template static inline U& get_field(T& input, std::shared_ptr(T::*field)) { auto& ptr = input.*field; if (ptr.get() == nullptr) { ptr = std::make_shared(); } return *ptr; } template static inline U& get_field(T& input, U(T::*field)) { return input.*field; } template 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 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 struct LastIs { static constexpr bool value = LastIs::value; }; template struct LastIs { static constexpr bool value = false; }; template struct LastIs { static constexpr bool value = true; }; template struct LastIsEnum { using value_type = typename LastIsEnum::value_type; static constexpr bool value = LastIsEnum::value; }; template struct LastIsEnum { using value_type = U; static constexpr bool value = std::is_enum::value; }; template struct LastIsEnum T::*> { using value_type = U; static constexpr bool value = std::is_enum::value; }; template struct LastIsInteger { static constexpr bool value = LastIsInteger::value; }; template struct LastIsInteger { static constexpr bool value = std::is_integral::value && !std::is_same::value; }; template struct LastIsInteger T::*> { static constexpr bool value = std::is_integral::value && !std::is_same::value; }; template struct LastIsFloat { static constexpr bool value = LastIsFloat::value; }; template struct LastIsFloat { static constexpr bool value = std::is_same::value; }; template struct LastIsFloat T::*> { static constexpr bool value = std::is_same::value; }; template struct LastIsVector { using value_type = typename LastIsVector::value_type; static constexpr bool value = LastIsVector::value; }; template struct LastIsVector T::*> { using value_type = U; static constexpr bool value = true; }; template struct LastIsVector> T::*> { using value_type = U; static constexpr bool value = true; }; template struct LastIsVector { using value_type = void; static constexpr bool value = false; }; template struct LastIsIntegerVector { using value_type = typename LastIsIntegerVector::value_type; static constexpr bool value = LastIsIntegerVector::value; }; template struct LastIsIntegerVector T::*> { using value_type = U; static constexpr bool value = std::is_integral::value && !std::is_same::value; }; template struct LastIsIntegerVector { using value_type = void; static constexpr bool value = false; }; template struct LastIsMap { using key_type = typename LastIsMap::key_type; using value_type = typename LastIsMap::value_type; static constexpr bool value = LastIsMap::value; }; template struct LastIsMap> T::*> { using key_type = K; using value_type = U; static constexpr bool value = true; }; template struct LastIsMap T::*> { using key_type = K; using value_type = U; static constexpr bool value = true; }; template struct LastIsMap { using key_type = void; using value_type = void; static constexpr bool value = false; }; template static bool is_field_set(const nonstd::optional& field) { return field.has_value(); } template static bool is_field_set(const T&) { return true; } template::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(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 name) { return (void*) &json_path_handler::get_field(root, args...); }; return *this; } template< typename... Args, std::enable_if_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 string_fragment& value_str) { auto* obj = ypc->ypc_obj_stack.top(); const auto* jph = ypc->ypc_current_handler; jph->validate_string(*ypc, value_str); json_path_handler::get_field(obj, args...) .emplace_back(value_str.to_string()); return 1; }; return *this; } template::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) { const 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...).emplace_back(val); return 1; }; return *this; } template::value, bool> = true, std::enable_if_t< !std::is_same::value_type, std::string>::value && !LastIsIntegerVector::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 json_path_handler& for_child(positioned_property(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 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, 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 string_fragment& value_str) { auto* obj = ypc->ypc_obj_stack.top(); auto key = ypc->get_path_fragment(-1); json_path_handler::get_field(obj, args...)[key] = value_str.to_string(); return 1; }; this->jph_path_provider = [args...](void* root, std::vector& paths_out) { const auto& field = json_path_handler::get_field(root, args...); for (const auto& pair : field) { paths_out.emplace_back(pair.first); } }; this->jph_field_getter = [args...](void* root, nonstd::optional name) -> const void* { const auto& field = json_path_handler::get_field(root, args...); if (!name) { return &field; } auto iter = field.find(name.value()); if (iter == field.end()) { return nullptr; } return (void*) &iter->second; }; 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::value, bool> = true, std::enable_if_t< std::is_same::key_type>::value, bool> = true> json_path_handler& for_field(Args... args) { this->jph_path_provider = [args...](void* root, std::vector& paths_out) { const auto& field = json_path_handler::get_field(root, args...); for (const auto& pair : field) { paths_out.emplace_back(std::to_string(pair.first)); } }; this->jph_obj_provider = [args...](const yajlpp_provider_context& ypc, void* root) { auto& field = json_path_handler::get_field(root, args...); return &(field[ypc.get_substr_i(0)]); }; return *this; } template< typename... Args, std::enable_if_t::value, bool> = true, std::enable_if_t< std::is_same::key_type>::value, bool> = true, std::enable_if_t< !std::is_same::value_type>::value && !std::is_same::value_type>::value && !std::is_same< nonstd::optional, typename LastIsMap::value_type>::value, bool> = true> json_path_handler& for_field(Args... args) { this->jph_path_provider = [args...](void* root, std::vector& paths_out) { const auto& field = json_path_handler::get_field(root, args...); for (const auto& pair : field) { paths_out.emplace_back(pair.first); } }; this->jph_obj_provider = [args...](const yajlpp_provider_context& ypc, void* root) { auto& field = json_path_handler::get_field(root, args...); auto key = ypc.get_substr(0); return &(field[key]); }; return *this; } template>, 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 string_fragment& value_str) { auto obj = ypc->ypc_obj_stack.top(); auto key = ypc->get_path_fragment(-1); json_path_handler::get_field(obj, args...)[key] = value_str.to_string(); 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, 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(val); return 1; }; this->add_cb(str_field_cb2); this->jph_str_cb = [args...](yajlpp_parse_context* ypc, const string_fragment& value_str) { auto* obj = ypc->ypc_obj_stack.top(); auto key = ypc->get_path_fragment(-1); json_path_handler::get_field(obj, args...)[key] = value_str.to_string(); 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::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 string_fragment& value_str) { auto* obj = ypc->ypc_obj_stack.top(); const auto* jph = ypc->ypc_current_handler; jph->validate_string(*ypc, value_str); json_path_handler::get_field(obj, args...) = value_str.to_string(); 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 name) { return (void*) &json_path_handler::get_field(root, args...); }; return *this; } template::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 string_fragment& value_str) { auto* obj = ypc->ypc_obj_stack.top(); const auto* jph = ypc->ypc_current_handler; date_time_scanner dts; timeval tv{}; exttm tm; if (dts.scan(value_str.data(), value_str.length(), nullptr, &tm, tv) == nullptr) { ypc->report_error(lnav::console::user_message::error( attr_line_t("unrecognized timestamp ") .append_quoted(value_str)) .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 name) { return (void*) &json_path_handler::get_field(root, args...); }; return *this; } template< typename... Args, std::enable_if_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 string_fragment& value_str) { auto* obj = ypc->ypc_obj_stack.top(); const auto* jph = ypc->ypc_current_handler; jph->validate_string(*ypc, value_str); json_path_handler::get_field(obj, args...) = value_str.to_string(); return 1; }; this->add_cb(null_field_cb); this->jph_null_cb = [args...](yajlpp_parse_context* ypc) { auto* obj = ypc->ypc_obj_stack.top(); json_path_handler::get_field(obj, args...) = 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; } } 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 name) { return (void*) &json_path_handler::get_field(root, args...); }; return *this; } template, 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 string_fragment& value_str) { auto* obj = ypc->ypc_obj_stack.top(); const 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 = value_str.to_string(); 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); }; this->jph_field_getter = [args...](void* root, nonstd::optional name) { return (void*) &json_path_handler::get_field(root, args...); }; return *this; } template::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 string_fragment& value_str) { auto* obj = ypc->ypc_obj_stack.top(); const 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, 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 string_fragment& value_str) { auto* obj = ypc->ypc_obj_stack.top(); const auto* jph = ypc->ypc_current_handler; try { const auto* tz = date::get_tzdb().locate_zone(value_str.to_string()); 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 = tz; } catch (const std::runtime_error& e) { jph->report_tz_error(ypc, value_str.to_string(), e.what()); } 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->name()); }; return *this; } template, 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 string_fragment& value_str) { auto* obj = ypc->ypc_obj_stack.top(); const 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 struct int_ { using type = int; }; template< typename C, typename T, typename int_::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 string_fragment& value_frag) { auto* obj = ypc->ypc_obj_stack.top(); 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; }; this->jph_gen_callback = [args..., ptr_arg](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..., ptr_arg); if (!ygc.ygc_default_stack.empty()) { const auto& field_def = json_path_handler::get_field( ygc.ygc_default_stack.top(), args..., ptr_arg); 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.to_string()); }; return *this; } template::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) { const 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 name) { return (void*) &json_path_handler::get_field(root, args...); }; return *this; } template::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) { const 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 name) { return (void*) &json_path_handler::get_field(root, args...); }; return *this; } template< typename... Args, std::enable_if_t::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 string_fragment& value_str) { auto* obj = ypc->ypc_obj_stack.top(); const auto* handler = ypc->ypc_current_handler; auto parse_res = relative_time::from_str(value_str); if (parse_res.isErr()) { auto parse_error = parse_res.unwrapErr(); handler->report_duration_error( ypc, value_str.to_string(), 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(field.count()), 0}) .to_string()); }; this->jph_field_getter = [args...](void* root, nonstd::optional name) { return (void*) &json_path_handler::get_field(root, args...); }; return *this; } template::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 string_fragment& value_str) { auto* obj = ypc->ypc_obj_stack.top(); const auto* handler = ypc->ypc_current_handler; auto res = handler->to_enum_value(value_str); if (res) { json_path_handler::get_field(obj, args...) = (typename LastIsEnum::value_type) res.value(); } else { handler->report_enum_error(ypc, value_str.to_string()); } 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 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 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 jpc_children; }; template 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*>(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> consume( const string_fragment& json) { if (this->yp_parse_context.parse(json) == yajl_status_ok) { if (this->yp_errors.empty()) { return Ok(); } } return Err(std::move(this->yp_errors)); } Result> complete() { if (this->yp_parse_context.complete_parse() == yajl_status_ok) { return Ok(std::move(this->yp_obj)); } return Err(std::move(this->yp_errors)); } Result> of( const string_fragment& json) { if (this->yp_parse_context.parse_doc(json)) { if (this->yp_errors.empty()) { return Ok(std::move(this->yp_obj)); } } return Err(std::move(this->yp_errors)); } private: yajlpp_parse_context yp_parse_context; auto_mem yp_handle{yajl_free}; std::vector yp_errors; T yp_obj; }; template struct typed_json_path_container; template struct yajlpp_formatter { const T& yf_obj; const typed_json_path_container& yf_container; yajlpp_gen yf_gen; template yajlpp_formatter with_config(Args... args) && { yajl_gen_config(this->yf_gen.get_handle(), args...); return std::move(*this); } std::string to_string() && { yajlpp_gen_context ygc(this->yf_gen, this->yf_container); ygc.template with_obj(this->yf_obj); ygc.ygc_depth = 1; ygc.gen(); return this->yf_gen.to_string_fragment().to_string(); } }; template struct typed_json_path_container : public json_path_container { typed_json_path_container(std::initializer_list children) : json_path_container(children) { } typed_json_path_container& with_schema_id2(const std::string& id) { this->jpc_schema_id = id; return *this; } typed_json_path_container& with_description2(std::string desc) { this->jpc_description = std::move(desc); return *this; } yajlpp_parser parser_for(intern_string_t src) const { return yajlpp_parser{src, this}; } yajlpp_formatter formatter_for(const T& obj) const { return yajlpp_formatter{obj, *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 inline json_path_handler pattern_property_handler(const T (&path)[N]) { return {lnav::pcre2pp::code::from_const(path, PCRE2_ANCHORED).to_shared()}; } } // namespace yajlpp #endif