diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1d3ddc9b..6176872d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -38,7 +38,7 @@ jobs: CXX: ${{ matrix.platform.cxx }} steps: - name: Install ClangFormat - run: pip install clang-format==18.1.5 + run: pip install clang-format==19.1.0 - uses: actions/checkout@v4 - name: Install dependencies (macOS) diff --git a/DEPENDENCIES b/DEPENDENCIES index d6a42a3f..1ef6de2d 100644 --- a/DEPENDENCIES +++ b/DEPENDENCIES @@ -1,5 +1,5 @@ vendorpull https://github.com/sourcemeta/vendorpull dea311b5bfb53b6926a4140267959ae334d3ecf4 noa https://github.com/sourcemeta/noa 7e26abce7a4e31e86a16ef2851702a56773ca527 -jsontoolkit https://github.com/sourcemeta/jsontoolkit e86ad1e333c11b6fc2659a0f32ae883ef1040e20 +jsontoolkit https://github.com/sourcemeta/jsontoolkit 3ef19daf7ca042544239111c701a51232f3f5576 hydra https://github.com/sourcemeta/hydra 3c53d3fdef79e9ba603d48470a508cc45472a0dc alterschema https://github.com/sourcemeta/alterschema a31722f04ae2d7e57f2fe5bbb0613670866c0840 diff --git a/src/command_frame.cc b/src/command_frame.cc index 2375010d..b9dd1c7d 100644 --- a/src/command_frame.cc +++ b/src/command_frame.cc @@ -8,8 +8,9 @@ #include "command.h" #include "utils.h" -static auto enum_to_string( - const sourcemeta::jsontoolkit::ReferenceEntryType type) -> std::string { +static auto +enum_to_string(const sourcemeta::jsontoolkit::ReferenceEntryType type) + -> std::string { switch (type) { case sourcemeta::jsontoolkit::ReferenceEntryType::Resource: return "resource"; diff --git a/src/command_test.cc b/src/command_test.cc index f5888cd4..5da1dec2 100644 --- a/src/command_test.cc +++ b/src/command_test.cc @@ -35,8 +35,8 @@ get_schema_object(const sourcemeta::jsontoolkit::URI &identifier, } static auto get_data(const sourcemeta::jsontoolkit::JSON &test_case, - const std::filesystem::path &base, - const bool verbose) -> sourcemeta::jsontoolkit::JSON { + const std::filesystem::path &base, const bool verbose) + -> sourcemeta::jsontoolkit::JSON { assert(base.is_absolute()); assert(test_case.is_object()); assert(test_case.defines("data") || test_case.defines("dataPath")); diff --git a/src/utils.cc b/src/utils.cc index 7c6060ef..2fac99fe 100644 --- a/src/utils.cc +++ b/src/utils.cc @@ -271,8 +271,9 @@ auto log_verbose(const std::map> &options) return null_stream; } -auto parse_extensions(const std::map> - &options) -> std::set { +auto parse_extensions( + const std::map> &options) + -> std::set { std::set result; if (options.contains("extension")) { @@ -296,8 +297,9 @@ auto parse_extensions(const std::map> return result; } -auto parse_ignore(const std::map> - &options) -> std::set { +auto parse_ignore( + const std::map> &options) + -> std::set { std::set result; if (options.contains("ignore")) { diff --git a/src/utils.h b/src/utils.h index 62c3956a..9041f0b3 100644 --- a/src/utils.h +++ b/src/utils.h @@ -38,11 +38,13 @@ auto resolver(const std::map> &options, auto log_verbose(const std::map> &options) -> std::ostream &; -auto parse_extensions(const std::map> - &options) -> std::set; +auto parse_extensions( + const std::map> &options) + -> std::set; -auto parse_ignore(const std::map> - &options) -> std::set; +auto parse_ignore( + const std::map> &options) + -> std::set; } // namespace sourcemeta::jsonschema::cli diff --git a/test/metaschema/fail_directory.sh b/test/metaschema/fail_directory.sh index 92d82701..599d01a1 100755 --- a/test/metaschema/fail_directory.sh +++ b/test/metaschema/fail_directory.sh @@ -32,7 +32,7 @@ error: Schema validation failure The string value "object" was expected to equal one of the following values: "array", "boolean", "integer", "null", "number", "object", and "string" at instance location "/type" at evaluate path "/properties/type/anyOf/0/\$ref/enum" - The value was expected to be of type array but it was of type string + The value was expected to consist of an array of at least 1 item at instance location "/type" at evaluate path "/properties/type/anyOf/1/type" The string value was expected to validate against at least one of the 2 given subschemas diff --git a/test/metaschema/fail_single.sh b/test/metaschema/fail_single.sh index 3a2ed807..8125c728 100755 --- a/test/metaschema/fail_single.sh +++ b/test/metaschema/fail_single.sh @@ -23,7 +23,7 @@ error: Schema validation failure The string value "object" was expected to equal one of the following values: "array", "boolean", "integer", "null", "number", "object", and "string" at instance location "/type" at evaluate path "/properties/type/anyOf/0/\$ref/enum" - The value was expected to be of type array but it was of type string + The value was expected to consist of an array of at least 1 item at instance location "/type" at evaluate path "/properties/type/anyOf/1/type" The string value was expected to validate against at least one of the 2 given subschemas diff --git a/vendor/jsontoolkit/CMakeLists.txt b/vendor/jsontoolkit/CMakeLists.txt index e52fde65..3853039e 100644 --- a/vendor/jsontoolkit/CMakeLists.txt +++ b/vendor/jsontoolkit/CMakeLists.txt @@ -9,6 +9,7 @@ include(vendor/noa/cmake/noa.cmake) option(JSONTOOLKIT_URI "Build the JSON Toolkit URI library" ON) option(JSONTOOLKIT_JSON "Build the JSON Toolkit JSON library" ON) option(JSONTOOLKIT_JSONSCHEMA "Build the JSON Toolkit JSON Schema library" ON) +option(JSONTOOLKIT_EVALUATOR "Build the JSON Toolkit JSON Schema evaluator library" ON) option(JSONTOOLKIT_JSONPOINTER "Build the JSON Toolkit JSON Pointer library" ON) option(JSONTOOLKIT_JSONL "Build the JSON Toolkit JSONL library" ON) option(JSONTOOLKIT_TESTS "Build the JSON Toolkit tests" OFF) @@ -48,7 +49,14 @@ if(JSONTOOLKIT_JSON AND JSONTOOLKIT_JSONPOINTER) add_subdirectory(src/jsonpointer) endif() -if(JSONTOOLKIT_JSON AND JSONTOOLKIT_JSONSCHEMA) +if(JSONTOOLKIT_URI AND JSONTOOLKIT_JSON AND + JSONTOOLKIT_JSONPOINTER AND JSONTOOLKIT_EVALUATOR) + add_subdirectory(src/evaluator) +endif() + +if(JSONTOOLKIT_URI AND JSONTOOLKIT_JSON AND + JSONTOOLKIT_JSONPOINTER AND JSONTOOLKIT_EVALUATOR AND + JSONTOOLKIT_JSONSCHEMA) add_subdirectory(src/jsonschema) endif() @@ -93,7 +101,14 @@ if(JSONTOOLKIT_TESTS) add_subdirectory(test/jsonpointer) endif() - if(JSONTOOLKIT_JSON AND JSONTOOLKIT_JSONSCHEMA) + if(JSONTOOLKIT_URI AND JSONTOOLKIT_JSON AND + JSONTOOLKIT_JSONPOINTER AND JSONTOOLKIT_EVALUATOR) + add_subdirectory(test/evaluator) + endif() + + if(JSONTOOLKIT_URI AND JSONTOOLKIT_JSON AND + JSONTOOLKIT_JSONPOINTER AND JSONTOOLKIT_EVALUATOR AND + JSONTOOLKIT_JSONSCHEMA) add_subdirectory(test/jsonschema) endif() diff --git a/vendor/jsontoolkit/config.cmake.in b/vendor/jsontoolkit/config.cmake.in index 541dc72b..6d611955 100644 --- a/vendor/jsontoolkit/config.cmake.in +++ b/vendor/jsontoolkit/config.cmake.in @@ -9,6 +9,7 @@ if(NOT JSONTOOLKIT_COMPONENTS) list(APPEND JSONTOOLKIT_COMPONENTS jsonl) list(APPEND JSONTOOLKIT_COMPONENTS jsonpointer) list(APPEND JSONTOOLKIT_COMPONENTS jsonschema) + list(APPEND JSONTOOLKIT_COMPONENTS evaluator) endif() foreach(component ${JSONTOOLKIT_COMPONENTS}) @@ -35,6 +36,7 @@ foreach(component ${JSONTOOLKIT_COMPONENTS}) include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_jsontoolkit_uri.cmake") include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_jsontoolkit_json.cmake") include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_jsontoolkit_jsonpointer.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_jsontoolkit_evaluator.cmake") # GCC does not allow the use of std::promise, std::future # without compiling with pthreads support. @@ -46,6 +48,10 @@ foreach(component ${JSONTOOLKIT_COMPONENTS}) endif() include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_jsontoolkit_jsonschema.cmake") + elseif(component STREQUAL "evaluator") + include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_jsontoolkit_uri.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_jsontoolkit_json.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_jsontoolkit_evaluator.cmake") else() message(FATAL_ERROR "Unknown JSON Toolkit component: ${component}") endif() diff --git a/vendor/jsontoolkit/src/evaluator/CMakeLists.txt b/vendor/jsontoolkit/src/evaluator/CMakeLists.txt new file mode 100644 index 00000000..075dadcd --- /dev/null +++ b/vendor/jsontoolkit/src/evaluator/CMakeLists.txt @@ -0,0 +1,15 @@ +noa_library(NAMESPACE sourcemeta PROJECT jsontoolkit NAME evaluator + FOLDER "JSON Toolkit/Evaluator" + PRIVATE_HEADERS error.h value.h template.h context.h + SOURCES evaluator.cc context.cc trace.h) + +if(JSONTOOLKIT_INSTALL) + noa_library_install(NAMESPACE sourcemeta PROJECT jsontoolkit NAME evaluator) +endif() + +target_link_libraries(sourcemeta_jsontoolkit_evaluator PUBLIC + sourcemeta::jsontoolkit::json) +target_link_libraries(sourcemeta_jsontoolkit_evaluator PUBLIC + sourcemeta::jsontoolkit::jsonpointer) +target_link_libraries(sourcemeta_jsontoolkit_evaluator PRIVATE + sourcemeta::jsontoolkit::uri) diff --git a/vendor/jsontoolkit/src/evaluator/context.cc b/vendor/jsontoolkit/src/evaluator/context.cc new file mode 100644 index 00000000..9a9f0fae --- /dev/null +++ b/vendor/jsontoolkit/src/evaluator/context.cc @@ -0,0 +1,327 @@ +#include +#include + +#include // assert + +namespace sourcemeta::jsontoolkit { + +auto EvaluationContext::prepare(const JSON &instance) -> void { + // Do a full reset for the next run + assert(this->evaluate_path_.empty()); + assert(this->instance_location_.empty()); + assert(this->frame_sizes.empty()); + assert(this->resources_.empty()); + this->instances_.clear(); + this->instances_.emplace_back(instance); + this->annotation_blacklist.clear(); + this->annotations_.clear(); + this->labels.clear(); + this->property_as_instance = false; +} + +auto EvaluationContext::push_without_traverse( + const Pointer &relative_schema_location, + const Pointer &relative_instance_location, + const std::string &schema_resource, const bool dynamic) -> void { + // Guard against infinite recursion in a cheap manner, as + // infinite recursion will manifest itself through huge + // ever-growing evaluate paths + constexpr auto EVALUATE_PATH_LIMIT{300}; + if (this->evaluate_path_.size() > EVALUATE_PATH_LIMIT) [[unlikely]] { + throw sourcemeta::jsontoolkit::SchemaEvaluationError( + "The evaluation path depth limit was reached " + "likely due to infinite recursion"); + } + + this->frame_sizes.emplace_back(relative_schema_location.size(), + relative_instance_location.size()); + this->evaluate_path_.push_back(relative_schema_location); + this->instance_location_.push_back(relative_instance_location); + + if (dynamic) { + // Note that we are potentially repeatedly pushing back the + // same schema resource over and over again. However, the + // logic for making sure this list is "pure" takes a lot of + // computation power. Being silly seems faster. + this->resources_.push_back(schema_resource); + } +} + +auto EvaluationContext::push(const Pointer &relative_schema_location, + const Pointer &relative_instance_location, + const std::string &schema_resource, + const bool dynamic) -> void { + this->push_without_traverse(relative_schema_location, + relative_instance_location, schema_resource, + dynamic); + if (!relative_instance_location.empty()) { + assert(!this->instances_.empty()); + this->instances_.emplace_back( + get(this->instances_.back().get(), relative_instance_location)); + } +} + +auto EvaluationContext::push(const Pointer &relative_schema_location, + const Pointer &relative_instance_location, + const std::string &schema_resource, + const bool dynamic, + std::reference_wrapper &&new_instance) + -> void { + this->push_without_traverse(relative_schema_location, + relative_instance_location, schema_resource, + dynamic); + assert(!relative_instance_location.empty()); + this->instances_.emplace_back(std::move(new_instance)); +} + +auto EvaluationContext::pop(const bool dynamic) -> void { + assert(!this->frame_sizes.empty()); + const auto &sizes{this->frame_sizes.back()}; + this->evaluate_path_.pop_back(sizes.first); + this->instance_location_.pop_back(sizes.second); + if (sizes.second > 0) { + this->instances_.pop_back(); + } + + this->frame_sizes.pop_back(); + + // TODO: Do schema resource management using hashes to avoid + // expensive string comparisons + if (dynamic) { + assert(!this->resources_.empty()); + this->resources_.pop_back(); + } +} + +auto EvaluationContext::annotate(const WeakPointer ¤t_instance_location, + const JSON &value) + -> std::pair, bool> { + const auto result{this->annotations_.insert({current_instance_location, {}}) + .first->second.insert({this->evaluate_path(), {}}) + .first->second.insert(value)}; + return {*(result.first), result.second}; +} + +auto EvaluationContext::annotations( + const WeakPointer ¤t_instance_location, + const WeakPointer &schema_location) const -> const std::set & { + static const decltype(this->annotations_)::mapped_type::mapped_type + placeholder; + // Use `.find()` instead of `.contains()` and `.at()` for performance + // reasons + const auto instance_location_result{ + this->annotations_.find(current_instance_location)}; + if (instance_location_result == this->annotations_.end()) { + return placeholder; + } + + const auto schema_location_result{ + instance_location_result->second.find(schema_location)}; + if (schema_location_result == instance_location_result->second.end()) { + return placeholder; + } + + return schema_location_result->second; +} + +auto EvaluationContext::annotations( + const WeakPointer ¤t_instance_location) const + -> const std::map> & { + static const decltype(this->annotations_)::mapped_type placeholder; + // Use `.find()` instead of `.contains()` and `.at()` for performance + // reasons + const auto instance_location_result{ + this->annotations_.find(current_instance_location)}; + if (instance_location_result == this->annotations_.end()) { + return placeholder; + } + + return instance_location_result->second; +} + +auto EvaluationContext::defines_any_adjacent_annotation( + const WeakPointer &expected_instance_location, + const WeakPointer &base_evaluate_path, const std::string &keyword) const + -> bool { + // TODO: We should be taking masks into account + // TODO: How can we avoid this expensive pointer manipulation? + auto expected_evaluate_path{base_evaluate_path}; + expected_evaluate_path.push_back({keyword}); + return !this->annotations(expected_instance_location, expected_evaluate_path) + .empty(); +} + +auto EvaluationContext::defines_any_adjacent_annotation( + const WeakPointer &expected_instance_location, + const WeakPointer &base_evaluate_path, + const std::vector &keywords) const -> bool { + for (const auto &keyword : keywords) { + if (this->defines_any_adjacent_annotation(expected_instance_location, + base_evaluate_path, keyword)) { + return true; + } + } + + return false; +} + +auto EvaluationContext::defines_annotation( + const WeakPointer &expected_instance_location, + const WeakPointer &base_evaluate_path, + const std::vector &keywords, const JSON &value) const -> bool { + if (keywords.empty()) { + return false; + } + + const auto instance_annotations{ + this->annotations(expected_instance_location)}; + for (const auto &[schema_location, schema_annotations] : + instance_annotations) { + assert(!schema_location.empty()); + const auto &keyword{schema_location.back()}; + + if (keyword.is_property() && + std::find(keywords.cbegin(), keywords.cend(), keyword.to_property()) != + keywords.cend() && + schema_annotations.contains(value) && + schema_location.initial().starts_with(base_evaluate_path)) { + bool blacklisted = false; + for (const auto &masked : this->annotation_blacklist) { + if (schema_location.starts_with(masked) && + !this->evaluate_path_.starts_with(masked)) { + blacklisted = true; + break; + } + } + + if (!blacklisted) { + return true; + } + } + } + + return false; +} + +auto EvaluationContext::largest_annotation_index( + const WeakPointer &expected_instance_location, + const std::vector &keywords, + const std::uint64_t default_value) const -> std::uint64_t { + // TODO: We should be taking masks into account + + std::uint64_t result{default_value}; + for (const auto &[schema_location, schema_annotations] : + this->annotations(expected_instance_location)) { + assert(!schema_location.empty()); + const auto &keyword{schema_location.back()}; + if (!keyword.is_property()) { + continue; + } + + if (std::find(keywords.cbegin(), keywords.cend(), keyword.to_property()) == + keywords.cend()) { + continue; + } + + for (const auto &annotation : schema_annotations) { + if (annotation.is_integer() && annotation.is_positive()) { + result = std::max( + result, static_cast(annotation.to_integer()) + 1); + } + } + } + + return result; +} + +auto EvaluationContext::enter(const WeakPointer::Token::Property &property) + -> void { + this->instance_location_.push_back(property); + this->instances_.emplace_back(this->instances_.back().get().at(property)); +} + +auto EvaluationContext::enter(const WeakPointer::Token::Index &index) -> void { + this->instance_location_.push_back(index); + this->instances_.emplace_back(this->instances_.back().get().at(index)); +} + +auto EvaluationContext::leave() -> void { + this->instance_location_.pop_back(); + this->instances_.pop_back(); +} + +auto EvaluationContext::instances() const noexcept + -> const std::vector> & { + return this->instances_; +} + +auto EvaluationContext::resources() const noexcept + -> const std::vector & { + return this->resources_; +} + +auto EvaluationContext::evaluate_path() const noexcept -> const WeakPointer & { + return this->evaluate_path_; +} + +auto EvaluationContext::instance_location() const noexcept + -> const WeakPointer & { + return this->instance_location_; +} + +auto EvaluationContext::target_type(const TargetType type) noexcept -> void { + this->property_as_instance = (type == TargetType::Key); +} + +auto EvaluationContext::resolve_target() -> const JSON & { + if (this->property_as_instance) [[unlikely]] { + assert(!this->instance_location().empty()); + assert(this->instance_location().back().is_property()); + // For efficiency, as we likely reference the same JSON values + // over and over again + // TODO: Get rid of this once we have weak pointers + static std::set property_values; + return *( + property_values.emplace(this->instance_location().back().to_property()) + .first); + } + + return this->instances_.back().get(); +} + +auto EvaluationContext::mark(const std::size_t id, + const SchemaCompilerTemplate &children) -> void { + this->labels.try_emplace(id, children); +} + +// TODO: At least currently, we only need to mask if a schema +// makes use of `unevaluatedProperties` or `unevaluatedItems` +// Detect if a schema does need this so if not, we avoid +// an unnecessary copy +auto EvaluationContext::mask() -> void { + this->annotation_blacklist.push_back(this->evaluate_path_); +} + +auto EvaluationContext::jump(const std::size_t id) const noexcept + -> const SchemaCompilerTemplate & { + assert(this->labels.contains(id)); + return this->labels.at(id).get(); +} + +auto EvaluationContext::find_dynamic_anchor(const std::string &anchor) const + -> std::optional { + for (const auto &resource : this->resources()) { + std::ostringstream name; + name << resource; + name << '#'; + name << anchor; + const auto label{std::hash{}(name.str())}; + if (this->labels.contains(label)) { + return label; + } + } + + return std::nullopt; +} + +} // namespace sourcemeta::jsontoolkit diff --git a/vendor/jsontoolkit/src/evaluator/evaluator.cc b/vendor/jsontoolkit/src/evaluator/evaluator.cc new file mode 100644 index 00000000..b54048c3 --- /dev/null +++ b/vendor/jsontoolkit/src/evaluator/evaluator.cc @@ -0,0 +1,1199 @@ +#include +#include + +#include "trace.h" + +#include // std::min, std::any_of +#include // assert +#include // std::distance, std::advance +#include // std::numeric_limits +#include // std::optional + +namespace { + +auto evaluate_step( + const sourcemeta::jsontoolkit::SchemaCompilerTemplate::value_type &step, + const sourcemeta::jsontoolkit::SchemaCompilerEvaluationMode mode, + const std::optional< + sourcemeta::jsontoolkit::SchemaCompilerEvaluationCallback> &callback, + sourcemeta::jsontoolkit::EvaluationContext &context) -> bool { + SOURCEMETA_TRACE_REGISTER_ID(trace_dispatch_id); + SOURCEMETA_TRACE_REGISTER_ID(trace_id); + SOURCEMETA_TRACE_START(trace_dispatch_id, "Dispatch"); + using namespace sourcemeta::jsontoolkit; + +#define STRINGIFY(x) #x + +#define EVALUATE_BEGIN(step_category, step_type, precondition) \ + SOURCEMETA_TRACE_END(trace_dispatch_id, "Dispatch"); \ + SOURCEMETA_TRACE_START(trace_id, STRINGIFY(step_type)); \ + const auto &step_category{std::get(step)}; \ + context.push(step_category.relative_schema_location, \ + step_category.relative_instance_location, \ + step_category.schema_resource, step_category.dynamic); \ + const auto &target{context.resolve_target()}; \ + if (!(precondition)) { \ + context.pop(step_category.dynamic); \ + SOURCEMETA_TRACE_END(trace_id, STRINGIFY(step_type)); \ + return true; \ + } \ + if (step_category.report && callback.has_value()) { \ + callback.value()(SchemaCompilerEvaluationType::Pre, true, step, \ + context.evaluate_path(), context.instance_location(), \ + context.null); \ + } \ + bool result{false}; + +#define EVALUATE_BEGIN_NO_TARGET(step_category, step_type, precondition) \ + SOURCEMETA_TRACE_END(trace_dispatch_id, "Dispatch"); \ + SOURCEMETA_TRACE_START(trace_id, STRINGIFY(step_type)); \ + const auto &step_category{std::get(step)}; \ + if (!(precondition)) { \ + SOURCEMETA_TRACE_END(trace_id, STRINGIFY(step_type)); \ + return true; \ + } \ + context.push(step_category.relative_schema_location, \ + step_category.relative_instance_location, \ + step_category.schema_resource, step_category.dynamic); \ + if (step_category.report && callback.has_value()) { \ + callback.value()(SchemaCompilerEvaluationType::Pre, true, step, \ + context.evaluate_path(), context.instance_location(), \ + context.null); \ + } \ + bool result{false}; + + // This is a slightly complicated dance to avoid traversing the relative + // instance location twice. We first need to traverse it to check if its + // valid in the document as part of the condition, but if it is, we can + // pass it to `.push()` so that it doesn't need to traverse it again. +#define EVALUATE_BEGIN_TRY_TARGET(step_category, step_type, precondition) \ + SOURCEMETA_TRACE_END(trace_dispatch_id, "Dispatch"); \ + SOURCEMETA_TRACE_START(trace_id, STRINGIFY(step_type)); \ + const auto &target{context.resolve_target()}; \ + const auto &step_category{std::get(step)}; \ + if (!(precondition)) { \ + SOURCEMETA_TRACE_END(trace_id, STRINGIFY(step_type)); \ + return true; \ + } \ + auto target_check{ \ + try_get(target, step_category.relative_instance_location)}; \ + if (!target_check.has_value()) { \ + SOURCEMETA_TRACE_END(trace_id, STRINGIFY(step_type)); \ + return true; \ + } \ + context.push(step_category.relative_schema_location, \ + step_category.relative_instance_location, \ + step_category.schema_resource, step_category.dynamic, \ + std::move(target_check.value())); \ + if (step_category.report && callback.has_value()) { \ + callback.value()(SchemaCompilerEvaluationType::Pre, true, step, \ + context.evaluate_path(), context.instance_location(), \ + context.null); \ + } \ + bool result{false}; + +#define EVALUATE_BEGIN_NO_PRECONDITION(step_category, step_type) \ + SOURCEMETA_TRACE_END(trace_dispatch_id, "Dispatch"); \ + SOURCEMETA_TRACE_START(trace_id, STRINGIFY(step_type)); \ + const auto &step_category{std::get(step)}; \ + context.push(step_category.relative_schema_location, \ + step_category.relative_instance_location, \ + step_category.schema_resource, step_category.dynamic); \ + if (step_category.report && callback.has_value()) { \ + callback.value()(SchemaCompilerEvaluationType::Pre, true, step, \ + context.evaluate_path(), context.instance_location(), \ + context.null); \ + } \ + bool result{false}; + +#define EVALUATE_END(step_category, step_type) \ + if (step_category.report && callback.has_value()) { \ + callback.value()(SchemaCompilerEvaluationType::Post, result, step, \ + context.evaluate_path(), context.instance_location(), \ + context.null); \ + } \ + context.pop(step_category.dynamic); \ + SOURCEMETA_TRACE_END(trace_id, STRINGIFY(step_type)); \ + return result; + + // As a safety guard, only emit the annotation if it didn't exist already. + // Otherwise we risk confusing consumers + +#define EVALUATE_ANNOTATION(step_category, step_type, precondition, \ + destination, annotation_value) \ + SOURCEMETA_TRACE_START(trace_id, STRINGIFY(step_type)); \ + const auto &step_category{std::get(step)}; \ + assert(step_category.relative_instance_location.empty()); \ + const auto &target{context.resolve_target()}; \ + if (!(precondition)) { \ + SOURCEMETA_TRACE_END(trace_id, STRINGIFY(step_type)); \ + return true; \ + } \ + const auto annotation_result{ \ + context.annotate(destination, annotation_value)}; \ + context.push(step_category.relative_schema_location, \ + step_category.relative_instance_location, \ + step_category.schema_resource, step_category.dynamic); \ + if (annotation_result.second && step_category.report && \ + callback.has_value()) { \ + callback.value()(SchemaCompilerEvaluationType::Pre, true, step, \ + context.evaluate_path(), destination, context.null); \ + callback.value()(SchemaCompilerEvaluationType::Post, true, step, \ + context.evaluate_path(), destination, \ + annotation_result.first); \ + } \ + context.pop(step_category.dynamic); \ + SOURCEMETA_TRACE_END(trace_id, STRINGIFY(step_type)); \ + return true; + +#define EVALUATE_ANNOTATION_NO_PRECONDITION(step_category, step_type, \ + destination, annotation_value) \ + SOURCEMETA_TRACE_START(trace_id, STRINGIFY(step_type)); \ + const auto &step_category{std::get(step)}; \ + const auto annotation_result{ \ + context.annotate(destination, annotation_value)}; \ + context.push(step_category.relative_schema_location, \ + step_category.relative_instance_location, \ + step_category.schema_resource, step_category.dynamic); \ + if (annotation_result.second && step_category.report && \ + callback.has_value()) { \ + callback.value()(SchemaCompilerEvaluationType::Pre, true, step, \ + context.evaluate_path(), destination, context.null); \ + callback.value()(SchemaCompilerEvaluationType::Post, true, step, \ + context.evaluate_path(), destination, \ + annotation_result.first); \ + } \ + context.pop(step_category.dynamic); \ + SOURCEMETA_TRACE_END(trace_id, STRINGIFY(step_type)); \ + return true; + +#define IS_STEP(step_type) SchemaCompilerTemplateIndex::step_type + switch (static_cast(step.index())) { + case IS_STEP(SchemaCompilerAssertionFail): { + EVALUATE_BEGIN_NO_PRECONDITION(assertion, SchemaCompilerAssertionFail); + EVALUATE_END(assertion, SchemaCompilerAssertionFail); + } + + case IS_STEP(SchemaCompilerAssertionDefines): { + EVALUATE_BEGIN(assertion, SchemaCompilerAssertionDefines, + target.is_object()); + result = target.defines(assertion.value); + EVALUATE_END(assertion, SchemaCompilerAssertionDefines); + } + + case IS_STEP(SchemaCompilerAssertionDefinesAll): { + EVALUATE_BEGIN(assertion, SchemaCompilerAssertionDefinesAll, + target.is_object()); + + // Otherwise we are we even emitting this instruction? + assert(assertion.value.size() > 1); + result = true; + for (const auto &property : assertion.value) { + if (!target.defines(property)) { + result = false; + break; + } + } + + EVALUATE_END(assertion, SchemaCompilerAssertionDefinesAll); + } + + case IS_STEP(SchemaCompilerAssertionPropertyDependencies): { + EVALUATE_BEGIN(assertion, SchemaCompilerAssertionPropertyDependencies, + target.is_object()); + // Otherwise we are we even emitting this instruction? + assert(!assertion.value.empty()); + result = true; + for (const auto &[property, dependencies] : assertion.value) { + if (!target.defines(property)) { + continue; + } + + assert(!dependencies.empty()); + for (const auto &dependency : dependencies) { + if (!target.defines(dependency)) { + result = false; + // For efficiently breaking from the outer loop too + goto evaluate_assertion_property_dependencies_end; + } + } + } + + evaluate_assertion_property_dependencies_end: + EVALUATE_END(assertion, SchemaCompilerAssertionPropertyDependencies); + } + + case IS_STEP(SchemaCompilerAssertionType): { + EVALUATE_BEGIN_NO_PRECONDITION(assertion, SchemaCompilerAssertionType); + const auto &target{context.resolve_target()}; + // In non-strict mode, we consider a real number that represents an + // integer to be an integer + result = + target.type() == assertion.value || + (assertion.value == JSON::Type::Integer && target.is_integer_real()); + EVALUATE_END(assertion, SchemaCompilerAssertionType); + } + + case IS_STEP(SchemaCompilerAssertionTypeAny): { + EVALUATE_BEGIN_NO_PRECONDITION(assertion, SchemaCompilerAssertionTypeAny); + // Otherwise we are we even emitting this instruction? + assert(assertion.value.size() > 1); + const auto &target{context.resolve_target()}; + // In non-strict mode, we consider a real number that represents an + // integer to be an integer + for (const auto type : assertion.value) { + if (type == JSON::Type::Integer && target.is_integer_real()) { + result = true; + break; + } else if (type == target.type()) { + result = true; + break; + } + } + + EVALUATE_END(assertion, SchemaCompilerAssertionTypeAny); + } + + case IS_STEP(SchemaCompilerAssertionTypeStrict): { + EVALUATE_BEGIN_NO_PRECONDITION(assertion, + SchemaCompilerAssertionTypeStrict); + result = context.resolve_target().type() == assertion.value; + EVALUATE_END(assertion, SchemaCompilerAssertionTypeStrict); + } + + case IS_STEP(SchemaCompilerAssertionTypeStrictAny): { + EVALUATE_BEGIN_NO_PRECONDITION(assertion, + SchemaCompilerAssertionTypeStrictAny); + // Otherwise we are we even emitting this instruction? + assert(assertion.value.size() > 1); + result = (std::find(assertion.value.cbegin(), assertion.value.cend(), + context.resolve_target().type()) != + assertion.value.cend()); + EVALUATE_END(assertion, SchemaCompilerAssertionTypeStrictAny); + } + + case IS_STEP(SchemaCompilerAssertionTypeStringBounded): { + EVALUATE_BEGIN_NO_PRECONDITION(assertion, + SchemaCompilerAssertionTypeStringBounded); + const auto &target{context.resolve_target()}; + const auto minimum{std::get<0>(assertion.value)}; + const auto maximum{std::get<1>(assertion.value)}; + assert(!maximum.has_value() || maximum.value() >= minimum); + // Require early breaking + assert(!std::get<2>(assertion.value)); + result = target.type() == JSON::Type::String && + target.size() >= minimum && + (!maximum.has_value() || target.size() <= maximum.value()); + EVALUATE_END(assertion, SchemaCompilerAssertionTypeStringBounded); + } + + case IS_STEP(SchemaCompilerAssertionTypeArrayBounded): { + EVALUATE_BEGIN_NO_PRECONDITION(assertion, + SchemaCompilerAssertionTypeArrayBounded); + const auto &target{context.resolve_target()}; + const auto minimum{std::get<0>(assertion.value)}; + const auto maximum{std::get<1>(assertion.value)}; + assert(!maximum.has_value() || maximum.value() >= minimum); + // Require early breaking + assert(!std::get<2>(assertion.value)); + result = target.type() == JSON::Type::Array && target.size() >= minimum && + (!maximum.has_value() || target.size() <= maximum.value()); + EVALUATE_END(assertion, SchemaCompilerAssertionTypeArrayBounded); + } + + case IS_STEP(SchemaCompilerAssertionTypeObjectBounded): { + EVALUATE_BEGIN_NO_PRECONDITION(assertion, + SchemaCompilerAssertionTypeObjectBounded); + const auto &target{context.resolve_target()}; + const auto minimum{std::get<0>(assertion.value)}; + const auto maximum{std::get<1>(assertion.value)}; + assert(!maximum.has_value() || maximum.value() >= minimum); + // Require early breaking + assert(!std::get<2>(assertion.value)); + result = target.type() == JSON::Type::Object && + target.size() >= minimum && + (!maximum.has_value() || target.size() <= maximum.value()); + EVALUATE_END(assertion, SchemaCompilerAssertionTypeObjectBounded); + } + + case IS_STEP(SchemaCompilerAssertionRegex): { + EVALUATE_BEGIN(assertion, SchemaCompilerAssertionRegex, + target.is_string()); + result = std::regex_search(target.to_string(), assertion.value.first); + EVALUATE_END(assertion, SchemaCompilerAssertionRegex); + } + + case IS_STEP(SchemaCompilerAssertionStringSizeLess): { + EVALUATE_BEGIN(assertion, SchemaCompilerAssertionStringSizeLess, + target.is_string()); + result = (target.size() < assertion.value); + EVALUATE_END(assertion, SchemaCompilerAssertionStringSizeLess); + } + + case IS_STEP(SchemaCompilerAssertionStringSizeGreater): { + EVALUATE_BEGIN(assertion, SchemaCompilerAssertionStringSizeGreater, + target.is_string()); + result = (target.size() > assertion.value); + EVALUATE_END(assertion, SchemaCompilerAssertionStringSizeGreater); + } + + case IS_STEP(SchemaCompilerAssertionArraySizeLess): { + EVALUATE_BEGIN(assertion, SchemaCompilerAssertionArraySizeLess, + target.is_array()); + result = (target.size() < assertion.value); + EVALUATE_END(assertion, SchemaCompilerAssertionArraySizeLess); + } + + case IS_STEP(SchemaCompilerAssertionArraySizeGreater): { + EVALUATE_BEGIN(assertion, SchemaCompilerAssertionArraySizeGreater, + target.is_array()); + result = (target.size() > assertion.value); + EVALUATE_END(assertion, SchemaCompilerAssertionArraySizeGreater); + } + + case IS_STEP(SchemaCompilerAssertionObjectSizeLess): { + EVALUATE_BEGIN(assertion, SchemaCompilerAssertionObjectSizeLess, + target.is_object()); + result = (target.size() < assertion.value); + EVALUATE_END(assertion, SchemaCompilerAssertionObjectSizeLess); + } + + case IS_STEP(SchemaCompilerAssertionObjectSizeGreater): { + EVALUATE_BEGIN(assertion, SchemaCompilerAssertionObjectSizeGreater, + target.is_object()); + result = (target.size() > assertion.value); + EVALUATE_END(assertion, SchemaCompilerAssertionObjectSizeGreater); + } + + case IS_STEP(SchemaCompilerAssertionEqual): { + EVALUATE_BEGIN_NO_PRECONDITION(assertion, SchemaCompilerAssertionEqual); + result = (context.resolve_target() == assertion.value); + EVALUATE_END(assertion, SchemaCompilerAssertionEqual); + } + + case IS_STEP(SchemaCompilerAssertionEqualsAny): { + EVALUATE_BEGIN_NO_PRECONDITION(assertion, + SchemaCompilerAssertionEqualsAny); + result = (std::find(assertion.value.cbegin(), assertion.value.cend(), + context.resolve_target()) != assertion.value.cend()); + EVALUATE_END(assertion, SchemaCompilerAssertionEqualsAny); + } + + case IS_STEP(SchemaCompilerAssertionGreaterEqual): { + EVALUATE_BEGIN(assertion, SchemaCompilerAssertionGreaterEqual, + target.is_number()); + result = target >= assertion.value; + EVALUATE_END(assertion, SchemaCompilerAssertionGreaterEqual); + } + + case IS_STEP(SchemaCompilerAssertionLessEqual): { + EVALUATE_BEGIN(assertion, SchemaCompilerAssertionLessEqual, + target.is_number()); + result = target <= assertion.value; + EVALUATE_END(assertion, SchemaCompilerAssertionLessEqual); + } + + case IS_STEP(SchemaCompilerAssertionGreater): { + EVALUATE_BEGIN(assertion, SchemaCompilerAssertionGreater, + target.is_number()); + result = target > assertion.value; + EVALUATE_END(assertion, SchemaCompilerAssertionGreater); + } + + case IS_STEP(SchemaCompilerAssertionLess): { + EVALUATE_BEGIN(assertion, SchemaCompilerAssertionLess, + target.is_number()); + result = target < assertion.value; + EVALUATE_END(assertion, SchemaCompilerAssertionLess); + } + + case IS_STEP(SchemaCompilerAssertionUnique): { + EVALUATE_BEGIN(assertion, SchemaCompilerAssertionUnique, + target.is_array()); + result = target.unique(); + EVALUATE_END(assertion, SchemaCompilerAssertionUnique); + } + + case IS_STEP(SchemaCompilerAssertionDivisible): { + EVALUATE_BEGIN(assertion, SchemaCompilerAssertionDivisible, + target.is_number()); + assert(assertion.value.is_number()); + result = target.divisible_by(assertion.value); + EVALUATE_END(assertion, SchemaCompilerAssertionDivisible); + } + + case IS_STEP(SchemaCompilerAssertionStringType): { + EVALUATE_BEGIN(assertion, SchemaCompilerAssertionStringType, + target.is_string()); + switch (assertion.value) { + case SchemaCompilerValueStringType::URI: + try { + // TODO: This implies a string copy + result = URI{target.to_string()}.is_absolute(); + } catch (const URIParseError &) { + result = false; + } + + break; + default: + // We should never get here + assert(false); + } + + EVALUATE_END(assertion, SchemaCompilerAssertionStringType); + } + + case IS_STEP(SchemaCompilerAssertionPropertyType): { + EVALUATE_BEGIN_TRY_TARGET( + assertion, SchemaCompilerAssertionPropertyType, + // Note that here are are referring to the parent + // object that might hold the given property, + // before traversing into the actual property + target.is_object()); + // Now here we refer to the actual property + const auto &effective_target{context.resolve_target()}; + // In non-strict mode, we consider a real number that represents an + // integer to be an integer + result = effective_target.type() == assertion.value || + (assertion.value == JSON::Type::Integer && + effective_target.is_integer_real()); + EVALUATE_END(assertion, SchemaCompilerAssertionPropertyType); + } + + case IS_STEP(SchemaCompilerAssertionPropertyTypeStrict): { + EVALUATE_BEGIN_TRY_TARGET( + assertion, SchemaCompilerAssertionPropertyTypeStrict, + // Note that here are are referring to the parent + // object that might hold the given property, + // before traversing into the actual property + target.is_object()); + // Now here we refer to the actual property + result = context.resolve_target().type() == assertion.value; + EVALUATE_END(assertion, SchemaCompilerAssertionPropertyTypeStrict); + } + + case IS_STEP(SchemaCompilerLogicalOr): { + EVALUATE_BEGIN_NO_PRECONDITION(logical, SchemaCompilerLogicalOr); + result = logical.children.empty(); + for (const auto &child : logical.children) { + if (evaluate_step(child, mode, callback, context)) { + result = true; + // This boolean value controls whether we should still evaluate + // every disjunction even on fast mode + if (mode == SchemaCompilerEvaluationMode::Fast && !logical.value) { + break; + } + } + } + + EVALUATE_END(logical, SchemaCompilerLogicalOr); + } + + case IS_STEP(SchemaCompilerLogicalAnd): { + EVALUATE_BEGIN_NO_PRECONDITION(logical, SchemaCompilerLogicalAnd); + result = true; + for (const auto &child : logical.children) { + if (!evaluate_step(child, mode, callback, context)) { + result = false; + break; + } + } + + EVALUATE_END(logical, SchemaCompilerLogicalAnd); + } + + case IS_STEP(SchemaCompilerLogicalWhenType): { + EVALUATE_BEGIN(logical, SchemaCompilerLogicalWhenType, + target.type() == logical.value); + result = true; + for (const auto &child : logical.children) { + if (!evaluate_step(child, mode, callback, context)) { + result = false; + break; + } + } + + EVALUATE_END(logical, SchemaCompilerLogicalWhenType); + } + + case IS_STEP(SchemaCompilerLogicalWhenDefines): { + EVALUATE_BEGIN(logical, SchemaCompilerLogicalWhenDefines, + target.is_object() && target.defines(logical.value)); + result = true; + for (const auto &child : logical.children) { + if (!evaluate_step(child, mode, callback, context)) { + result = false; + break; + } + } + + EVALUATE_END(logical, SchemaCompilerLogicalWhenDefines); + } + + case IS_STEP(SchemaCompilerLogicalWhenAdjacentUnmarked): { + EVALUATE_BEGIN_NO_TARGET( + logical, SchemaCompilerLogicalWhenAdjacentUnmarked, + !context.defines_any_adjacent_annotation(context.instance_location(), + context.evaluate_path(), + logical.value)); + result = true; + for (const auto &child : logical.children) { + if (!evaluate_step(child, mode, callback, context)) { + result = false; + break; + } + } + + EVALUATE_END(logical, SchemaCompilerLogicalWhenAdjacentUnmarked); + } + + case IS_STEP(SchemaCompilerLogicalWhenAdjacentMarked): { + EVALUATE_BEGIN_NO_TARGET(logical, SchemaCompilerLogicalWhenAdjacentMarked, + context.defines_any_adjacent_annotation( + context.instance_location(), + context.evaluate_path(), logical.value)); + result = true; + for (const auto &child : logical.children) { + if (!evaluate_step(child, mode, callback, context)) { + result = false; + break; + } + } + + EVALUATE_END(logical, SchemaCompilerLogicalWhenAdjacentMarked); + } + + case IS_STEP(SchemaCompilerLogicalWhenArraySizeGreater): { + EVALUATE_BEGIN(logical, SchemaCompilerLogicalWhenArraySizeGreater, + target.is_array() && target.size() > logical.value); + result = true; + for (const auto &child : logical.children) { + if (!evaluate_step(child, mode, callback, context)) { + result = false; + break; + } + } + + EVALUATE_END(logical, SchemaCompilerLogicalWhenArraySizeGreater); + } + + case IS_STEP(SchemaCompilerLogicalWhenArraySizeEqual): { + EVALUATE_BEGIN(logical, SchemaCompilerLogicalWhenArraySizeEqual, + target.is_array() && target.size() == logical.value); + result = true; + for (const auto &child : logical.children) { + if (!evaluate_step(child, mode, callback, context)) { + result = false; + break; + } + } + + EVALUATE_END(logical, SchemaCompilerLogicalWhenArraySizeEqual); + } + + case IS_STEP(SchemaCompilerLogicalXor): { + EVALUATE_BEGIN_NO_PRECONDITION(logical, SchemaCompilerLogicalXor); + result = false; + + // TODO: Cache results of a given branch so we can avoid + // computing it multiple times + for (auto iterator{logical.children.cbegin()}; + iterator != logical.children.cend(); ++iterator) { + if (!evaluate_step(*iterator, mode, callback, context)) { + continue; + } + + // Check if another one matches + bool subresult{true}; + for (auto subiterator{logical.children.cbegin()}; + subiterator != logical.children.cend(); ++subiterator) { + // Don't compare the element against itself + if (std::distance(logical.children.cbegin(), iterator) == + std::distance(logical.children.cbegin(), subiterator)) { + continue; + } + + // We don't need to report traces that part of the exhaustive + // XOR search. We can treat those as internal + if (evaluate_step(*subiterator, mode, std::nullopt, context)) { + subresult = false; + break; + } + } + + result = result || subresult; + if (result && mode == SchemaCompilerEvaluationMode::Fast) { + break; + } + } + + EVALUATE_END(logical, SchemaCompilerLogicalXor); + } + + case IS_STEP(SchemaCompilerLogicalTryMark): { + EVALUATE_BEGIN_NO_PRECONDITION(logical, SchemaCompilerLogicalTryMark); + result = true; + for (const auto &child : logical.children) { + if (!evaluate_step(child, mode, callback, context)) { + result = false; + break; + } + } + + if (result) { + // TODO: This implies an allocation of a JSON boolean + context.annotate(context.instance_location(), JSON{true}); + } else { + result = true; + } + + EVALUATE_END(logical, SchemaCompilerLogicalTryMark); + } + + case IS_STEP(SchemaCompilerLogicalNot): { + EVALUATE_BEGIN_NO_PRECONDITION(logical, SchemaCompilerLogicalNot); + // Ignore annotations produced inside "not" + context.mask(); + result = false; + for (const auto &child : logical.children) { + if (!evaluate_step(child, mode, callback, context)) { + result = true; + if (mode == SchemaCompilerEvaluationMode::Fast) { + break; + } + } + } + + EVALUATE_END(logical, SchemaCompilerLogicalNot); + } + + case IS_STEP(SchemaCompilerControlLabel): { + EVALUATE_BEGIN_NO_PRECONDITION(control, SchemaCompilerControlLabel); + context.mark(control.value, control.children); + result = true; + for (const auto &child : control.children) { + if (!evaluate_step(child, mode, callback, context)) { + result = false; + break; + } + } + + EVALUATE_END(control, SchemaCompilerControlLabel); + } + + case IS_STEP(SchemaCompilerControlMark): { + SOURCEMETA_TRACE_START(trace_id, "SchemaCompilerControlMark"); + const auto &control{std::get(step)}; + context.mark(control.value, control.children); + SOURCEMETA_TRACE_END(trace_id, "SchemaCompilerControlMark"); + return true; + } + + case IS_STEP(SchemaCompilerControlJump): { + EVALUATE_BEGIN_NO_PRECONDITION(control, SchemaCompilerControlJump); + result = true; + for (const auto &child : context.jump(control.value)) { + if (!evaluate_step(child, mode, callback, context)) { + result = false; + break; + } + } + + EVALUATE_END(control, SchemaCompilerControlJump); + } + + case IS_STEP(SchemaCompilerControlDynamicAnchorJump): { + EVALUATE_BEGIN_NO_PRECONDITION(control, + SchemaCompilerControlDynamicAnchorJump); + const auto id{context.find_dynamic_anchor(control.value)}; + result = id.has_value(); + if (id.has_value()) { + for (const auto &child : context.jump(id.value())) { + if (!evaluate_step(child, mode, callback, context)) { + result = false; + break; + } + } + } + + EVALUATE_END(control, SchemaCompilerControlDynamicAnchorJump); + } + + case IS_STEP(SchemaCompilerAnnotationEmit): { + EVALUATE_ANNOTATION_NO_PRECONDITION( + annotation, SchemaCompilerAnnotationEmit, context.instance_location(), + annotation.value); + } + + case IS_STEP(SchemaCompilerAnnotationWhenArraySizeEqual): { + EVALUATE_ANNOTATION( + annotation, SchemaCompilerAnnotationWhenArraySizeEqual, + target.is_array() && target.size() == annotation.value.first, + context.instance_location(), annotation.value.second); + } + + case IS_STEP(SchemaCompilerAnnotationWhenArraySizeGreater): { + EVALUATE_ANNOTATION( + annotation, SchemaCompilerAnnotationWhenArraySizeGreater, + target.is_array() && target.size() > annotation.value.first, + context.instance_location(), annotation.value.second); + } + + case IS_STEP(SchemaCompilerAnnotationToParent): { + EVALUATE_ANNOTATION_NO_PRECONDITION( + annotation, SchemaCompilerAnnotationToParent, + // TODO: Can we avoid a copy of the instance location here? + context.instance_location().initial(), annotation.value); + } + + case IS_STEP(SchemaCompilerAnnotationBasenameToParent): { + EVALUATE_ANNOTATION_NO_PRECONDITION( + annotation, SchemaCompilerAnnotationBasenameToParent, + // TODO: Can we avoid a copy of the instance location here? + context.instance_location().initial(), + context.instance_location().back().to_json()); + } + + case IS_STEP(SchemaCompilerLoopPropertiesMatch): { + EVALUATE_BEGIN(loop, SchemaCompilerLoopPropertiesMatch, + target.is_object()); + assert(!loop.value.empty()); + result = true; + for (const auto &entry : target.as_object()) { + const auto index{loop.value.find(entry.first)}; + if (index == loop.value.cend()) { + continue; + } + + const auto &substep{loop.children[index->second]}; + assert(std::holds_alternative(substep)); + for (const auto &child : + std::get(substep).children) { + if (!evaluate_step(child, mode, callback, context)) { + result = false; + // For efficiently breaking from the outer loop too + goto evaluate_loop_properties_match_end; + } + } + } + + evaluate_loop_properties_match_end: + EVALUATE_END(loop, SchemaCompilerLoopPropertiesMatch); + } + + case IS_STEP(SchemaCompilerLoopProperties): { + EVALUATE_BEGIN(loop, SchemaCompilerLoopProperties, target.is_object()); + result = true; + for (const auto &entry : target.as_object()) { + context.enter(entry.first); + for (const auto &child : loop.children) { + if (!evaluate_step(child, mode, callback, context)) { + result = false; + context.leave(); + // For efficiently breaking from the outer loop too + goto evaluate_loop_properties_end; + } + } + + context.leave(); + } + + evaluate_loop_properties_end: + EVALUATE_END(loop, SchemaCompilerLoopProperties); + } + + case IS_STEP(SchemaCompilerLoopPropertiesRegex): { + EVALUATE_BEGIN(loop, SchemaCompilerLoopPropertiesRegex, + target.is_object()); + result = true; + for (const auto &entry : target.as_object()) { + if (!std::regex_search(entry.first, loop.value.first)) { + continue; + } + + context.enter(entry.first); + for (const auto &child : loop.children) { + if (!evaluate_step(child, mode, callback, context)) { + result = false; + context.leave(); + // For efficiently breaking from the outer loop too + goto evaluate_loop_properties_regex_end; + } + } + + context.leave(); + } + + evaluate_loop_properties_regex_end: + EVALUATE_END(loop, SchemaCompilerLoopPropertiesRegex); + } + + case IS_STEP(SchemaCompilerLoopPropertiesNoAnnotation): { + EVALUATE_BEGIN(loop, SchemaCompilerLoopPropertiesNoAnnotation, + target.is_object()); + result = true; + assert(!loop.value.empty()); + + for (const auto &entry : target.as_object()) { + // TODO: It might be more efficient to get all the annotations we + // potentially care about as a set first, and the make the loop + // check for O(1) containment in that set? + if (context.defines_annotation( + context.instance_location(), + // TODO: Can we avoid doing this expensive operation on a loop? + context.evaluate_path().initial(), loop.value, + // TODO: This conversion implies a string copy + JSON{entry.first})) { + continue; + } + + context.enter(entry.first); + for (const auto &child : loop.children) { + if (!evaluate_step(child, mode, callback, context)) { + result = false; + context.leave(); + // For efficiently breaking from the outer loop too + goto evaluate_loop_properties_no_annotation_end; + } + } + + context.leave(); + } + + evaluate_loop_properties_no_annotation_end: + EVALUATE_END(loop, SchemaCompilerLoopPropertiesNoAnnotation); + } + + case IS_STEP(SchemaCompilerLoopPropertiesExcept): { + EVALUATE_BEGIN(loop, SchemaCompilerLoopPropertiesExcept, + target.is_object()); + result = true; + // Otherwise why emit this instruction? + assert(!loop.value.first.empty() || !loop.value.second.empty()); + + for (const auto &entry : target.as_object()) { + if (loop.value.first.contains(entry.first) || + std::any_of(loop.value.second.cbegin(), loop.value.second.cend(), + [&entry](const auto &pattern) { + return std::regex_search(entry.first, pattern.first); + })) { + continue; + } + + context.enter(entry.first); + for (const auto &child : loop.children) { + if (!evaluate_step(child, mode, callback, context)) { + result = false; + context.leave(); + // For efficiently breaking from the outer loop too + goto evaluate_loop_properties_except_end; + } + } + + context.leave(); + } + + evaluate_loop_properties_except_end: + EVALUATE_END(loop, SchemaCompilerLoopPropertiesExcept); + } + + case IS_STEP(SchemaCompilerLoopPropertiesType): { + EVALUATE_BEGIN(loop, SchemaCompilerLoopPropertiesType, + target.is_object()); + result = true; + for (const auto &entry : target.as_object()) { + context.enter(entry.first); + const auto &entry_target{context.resolve_target()}; + // In non-strict mode, we consider a real number that represents an + // integer to be an integer + if (entry_target.type() != loop.value && + (loop.value != JSON::Type::Integer || + entry_target.is_integer_real())) { + result = false; + context.leave(); + break; + } + + context.leave(); + } + + EVALUATE_END(loop, SchemaCompilerLoopPropertiesType); + } + + case IS_STEP(SchemaCompilerLoopPropertiesTypeStrict): { + EVALUATE_BEGIN(loop, SchemaCompilerLoopPropertiesTypeStrict, + target.is_object()); + result = true; + for (const auto &entry : target.as_object()) { + context.enter(entry.first); + if (context.resolve_target().type() != loop.value) { + result = false; + context.leave(); + break; + } + + context.leave(); + } + + EVALUATE_END(loop, SchemaCompilerLoopPropertiesTypeStrict); + } + + case IS_STEP(SchemaCompilerLoopKeys): { + EVALUATE_BEGIN(loop, SchemaCompilerLoopKeys, target.is_object()); + result = true; + context.target_type( + sourcemeta::jsontoolkit::EvaluationContext::TargetType::Key); + for (const auto &entry : target.as_object()) { + context.enter(entry.first); + for (const auto &child : loop.children) { + if (!evaluate_step(child, mode, callback, context)) { + result = false; + context.leave(); + goto evaluate_loop_keys_end; + } + } + + context.leave(); + } + + evaluate_loop_keys_end: + context.target_type( + sourcemeta::jsontoolkit::EvaluationContext::TargetType::Value); + EVALUATE_END(loop, SchemaCompilerLoopKeys); + } + + case IS_STEP(SchemaCompilerLoopItems): { + EVALUATE_BEGIN(loop, SchemaCompilerLoopItems, target.is_array()); + const auto &array{target.as_array()}; + result = true; + auto iterator{array.cbegin()}; + + // We need this check, as advancing an iterator past its bounds + // is considered undefined behavior + // See https://en.cppreference.com/w/cpp/iterator/advance + std::advance(iterator, + std::min(static_cast(loop.value), + static_cast(target.size()))); + + for (; iterator != array.cend(); ++iterator) { + const auto index{std::distance(array.cbegin(), iterator)}; + context.enter(static_cast(index)); + for (const auto &child : loop.children) { + if (!evaluate_step(child, mode, callback, context)) { + result = false; + context.leave(); + goto evaluate_compiler_loop_items_end; + } + } + + context.leave(); + } + + evaluate_compiler_loop_items_end: + EVALUATE_END(loop, SchemaCompilerLoopItems); + } + + case IS_STEP(SchemaCompilerLoopItemsUnmarked): { + EVALUATE_BEGIN(loop, SchemaCompilerLoopItemsUnmarked, + target.is_array() && + !context.defines_annotation( + context.instance_location(), + context.evaluate_path(), loop.value, JSON{true})); + // Otherwise you shouldn't be using this step? + assert(!loop.value.empty()); + const auto &array{target.as_array()}; + result = true; + + for (auto iterator = array.cbegin(); iterator != array.cend(); + ++iterator) { + const auto index{std::distance(array.cbegin(), iterator)}; + context.enter(static_cast(index)); + for (const auto &child : loop.children) { + if (!evaluate_step(child, mode, callback, context)) { + result = false; + context.leave(); + goto evaluate_compiler_loop_items_unmarked_end; + } + } + + context.leave(); + } + + evaluate_compiler_loop_items_unmarked_end: + EVALUATE_END(loop, SchemaCompilerLoopItemsUnmarked); + } + + case IS_STEP(SchemaCompilerLoopItemsUnevaluated): { + // TODO: This precondition is very expensive due to pointer manipulation + EVALUATE_BEGIN(loop, SchemaCompilerLoopItemsUnevaluated, + target.is_array() && !context.defines_annotation( + context.instance_location(), + context.evaluate_path().initial(), + loop.value.mask, JSON{true})); + const auto &array{target.as_array()}; + result = true; + auto iterator{array.cbegin()}; + + // Determine the proper start based on integer annotations collected for + // the current instance location by the keyword requested by the user. + const std::uint64_t start{context.largest_annotation_index( + context.instance_location(), {loop.value.index}, 0)}; + + // We need this check, as advancing an iterator past its bounds + // is considered undefined behavior + // See https://en.cppreference.com/w/cpp/iterator/advance + std::advance(iterator, + std::min(static_cast(start), + static_cast(target.size()))); + + for (; iterator != array.cend(); ++iterator) { + const auto index{std::distance(array.cbegin(), iterator)}; + + if (context.defines_annotation( + context.instance_location(), + // TODO: Can we avoid doing this expensive operation on a loop? + context.evaluate_path().initial(), loop.value.filter, + JSON{static_cast(index)})) { + continue; + } + + context.enter(static_cast(index)); + for (const auto &child : loop.children) { + if (!evaluate_step(child, mode, callback, context)) { + result = false; + context.leave(); + goto evaluate_compiler_loop_items_unevaluated_end; + } + } + + context.leave(); + } + + evaluate_compiler_loop_items_unevaluated_end: + EVALUATE_END(loop, SchemaCompilerLoopItemsUnevaluated); + } + + case IS_STEP(SchemaCompilerLoopContains): { + EVALUATE_BEGIN(loop, SchemaCompilerLoopContains, target.is_array()); + const auto minimum{std::get<0>(loop.value)}; + const auto &maximum{std::get<1>(loop.value)}; + assert(!maximum.has_value() || maximum.value() >= minimum); + const auto is_exhaustive{std::get<2>(loop.value)}; + result = minimum == 0 && target.empty(); + const auto &array{target.as_array()}; + auto match_count{std::numeric_limits::min()}; + for (auto iterator = array.cbegin(); iterator != array.cend(); + ++iterator) { + const auto index{std::distance(array.cbegin(), iterator)}; + context.enter(static_cast(index)); + bool subresult{true}; + for (const auto &child : loop.children) { + if (!evaluate_step(child, mode, callback, context)) { + subresult = false; + break; + } + } + + context.leave(); + + if (subresult) { + match_count += 1; + + // Exceeding the upper bound is definitely a failure + if (maximum.has_value() && match_count > maximum.value()) { + result = false; + + // Note that here we don't want to consider whether to run + // exhaustively or not. At this point, its already a failure, + // and anything that comes after would not run at all anyway + break; + } + + if (match_count >= minimum) { + result = true; + + // Exceeding the lower bound when there is no upper bound + // is definitely a success + if (!maximum.has_value() && !is_exhaustive) { + break; + } + } + } + } + + EVALUATE_END(loop, SchemaCompilerLoopContains); + } + +#undef IS_STEP +#undef STRINGIFY +#undef EVALUATE_BEGIN +#undef EVALUATE_BEGIN_NO_TARGET +#undef EVALUATE_BEGIN_TRY_TARGET +#undef EVALUATE_BEGIN_NO_PRECONDITION +#undef EVALUATE_END +#undef EVALUATE_ANNOTATION +#undef EVALUATE_ANNOTATION_NO_PRECONDITION + + default: + // We should never get here + assert(false); + return false; + } +} + +inline auto evaluate_internal( + sourcemeta::jsontoolkit::EvaluationContext &context, + const sourcemeta::jsontoolkit::SchemaCompilerTemplate &steps, + const sourcemeta::jsontoolkit::SchemaCompilerEvaluationMode mode, + const std::optional< + sourcemeta::jsontoolkit::SchemaCompilerEvaluationCallback> &callback) + -> bool { + bool overall{true}; + for (const auto &step : steps) { + if (!evaluate_step(step, mode, callback, context)) { + overall = false; + break; + } + } + + // The evaluation path and instance location must be empty by the time + // we are done, otherwise there was a frame push/pop mismatch + assert(context.evaluate_path().empty()); + assert(context.instance_location().empty()); + assert(context.resources().empty()); + // We should end up at the root of the instance + assert(context.instances().size() == 1); + return overall; +} + +} // namespace + +namespace sourcemeta::jsontoolkit { + +auto evaluate(const SchemaCompilerTemplate &steps, const JSON &instance, + const SchemaCompilerEvaluationMode mode, + const SchemaCompilerEvaluationCallback &callback) -> bool { + EvaluationContext context; + context.prepare(instance); + return evaluate_internal(context, steps, mode, callback); +} + +auto evaluate(const SchemaCompilerTemplate &steps, const JSON &instance) + -> bool { + EvaluationContext context; + context.prepare(instance); + return evaluate_internal(context, steps, + // Otherwise what's the point of an exhaustive + // evaluation if you don't get the results? + SchemaCompilerEvaluationMode::Fast, std::nullopt); +} + +auto evaluate(const SchemaCompilerTemplate &steps, EvaluationContext &context) + -> bool { + return evaluate_internal(context, steps, + // Otherwise what's the point of an exhaustive + // evaluation if you don't get the results? + SchemaCompilerEvaluationMode::Fast, std::nullopt); +} + +} // namespace sourcemeta::jsontoolkit diff --git a/vendor/jsontoolkit/src/evaluator/include/sourcemeta/jsontoolkit/evaluator.h b/vendor/jsontoolkit/src/evaluator/include/sourcemeta/jsontoolkit/evaluator.h new file mode 100644 index 00000000..b7d0f78a --- /dev/null +++ b/vendor/jsontoolkit/src/evaluator/include/sourcemeta/jsontoolkit/evaluator.h @@ -0,0 +1,181 @@ +#ifndef SOURCEMETA_JSONTOOLKIT_EVALUATOR_H_ +#define SOURCEMETA_JSONTOOLKIT_EVALUATOR_H_ + +#include "evaluator_export.h" + +#include +#include +#include + +#include +#include + +#include // std::uint8_t +#include // std::function + +/// @defgroup evaluator Evaluator +/// @brief A high-performance JSON Schema evaluator +/// +/// This functionality is included as follows: +/// +/// ```cpp +/// #include +/// ``` + +namespace sourcemeta::jsontoolkit { + +/// @ingroup evaluator +/// Represents the mode of evalution +enum class SchemaCompilerEvaluationMode : std::uint8_t { + /// Attempt to get to a boolean result as fast as possible + Fast, + /// Perform a full schema evaluation + Exhaustive +}; + +/// @ingroup evaluator +/// Represents the state of a step evaluation +enum class SchemaCompilerEvaluationType : std::uint8_t { Pre, Post }; + +/// @ingroup evaluator +/// A callback of this type is invoked after evaluating any keyword. The +/// arguments go as follows: +/// +/// - The stage at which the step in question is +/// - Whether the evaluation was successful or not (always true before +/// evaluation) +/// - The step that was just evaluated +/// - The evaluation path +/// - The instance location +/// - The annotation result, if any (otherwise null) +/// +/// You can use this callback mechanism to implement arbitrary output formats. +using SchemaCompilerEvaluationCallback = + std::function; + +/// @ingroup evaluator +/// +/// This function evaluates a schema compiler template in validation mode, +/// returning a boolean without error information. For example: +/// +/// ```cpp +/// #include +/// #include +/// #include +/// #include +/// +/// const sourcemeta::jsontoolkit::JSON schema = +/// sourcemeta::jsontoolkit::parse(R"JSON({ +/// "$schema": "https://json-schema.org/draft/2020-12/schema", +/// "type": "string" +/// })JSON"); +/// +/// const auto schema_template{sourcemeta::jsontoolkit::compile( +/// schema, sourcemeta::jsontoolkit::default_schema_walker, +/// sourcemeta::jsontoolkit::official_resolver, +/// sourcemeta::jsontoolkit::default_schema_compiler)}; +/// +/// const sourcemeta::jsontoolkit::JSON instance{"foo bar"}; +/// const auto result{sourcemeta::jsontoolkit::evaluate( +/// schema_template, instance)}; +/// assert(result); +/// ``` +auto SOURCEMETA_JSONTOOLKIT_EVALUATOR_EXPORT +evaluate(const SchemaCompilerTemplate &steps, const JSON &instance) -> bool; + +/// @ingroup evaluator +/// +/// This function evaluates a schema compiler template, executing the given +/// callback at every step of the way. For example: +/// +/// ```cpp +/// #include +/// #include +/// #include +/// #include +/// #include +/// +/// const sourcemeta::jsontoolkit::JSON schema = +/// sourcemeta::jsontoolkit::parse(R"JSON({ +/// "$schema": "https://json-schema.org/draft/2020-12/schema", +/// "type": "string" +/// })JSON"); +/// +/// const auto schema_template{sourcemeta::jsontoolkit::compile( +/// schema, sourcemeta::jsontoolkit::default_schema_walker, +/// sourcemeta::jsontoolkit::official_resolver, +/// sourcemeta::jsontoolkit::default_schema_compiler)}; +/// +/// static auto callback( +/// bool result, +/// const sourcemeta::jsontoolkit::SchemaCompilerTemplate::value_type &step, +/// const sourcemeta::jsontoolkit::Pointer &evaluate_path, +/// const sourcemeta::jsontoolkit::Pointer &instance_location, +/// const sourcemeta::jsontoolkit::JSON &document, +/// const sourcemeta::jsontoolkit::JSON &annotation) -> void { +/// std::cout << "TYPE: " << (result ? "Success" : "Failure") << "\n"; +/// std::cout << "STEP:\n"; +/// sourcemeta::jsontoolkit::prettify(sourcemeta::jsontoolkit::to_json({step}), +/// std::cout); +/// std::cout << "\nEVALUATE PATH:"; +/// sourcemeta::jsontoolkit::stringify(evaluate_path, std::cout); +/// std::cout << "\nINSTANCE LOCATION:"; +/// sourcemeta::jsontoolkit::stringify(instance_location, std::cout); +/// std::cout << "\nANNOTATION:\n"; +/// sourcemeta::jsontoolkit::prettify(annotation, std::cout); +/// std::cout << "\n"; +/// } +/// +/// const sourcemeta::jsontoolkit::JSON instance{"foo bar"}; +/// const auto result{sourcemeta::jsontoolkit::evaluate( +/// schema_template, instance, +/// sourcemeta::jsontoolkit::SchemaCompilerEvaluationMode::Fast, +/// callback)}; +/// +/// assert(result); +/// ``` +auto SOURCEMETA_JSONTOOLKIT_EVALUATOR_EXPORT +evaluate(const SchemaCompilerTemplate &steps, const JSON &instance, + const SchemaCompilerEvaluationMode mode, + const SchemaCompilerEvaluationCallback &callback) -> bool; + +/// @ingroup evaluator +/// +/// This function evaluates a schema compiler template from an evaluation +/// context, returning a boolean without error information. The evaluation +/// context can be re-used among evaluations (as long as its always loaded with +/// the new instance first) for performance reasons. For example: +/// +/// ```cpp +/// #include +/// #include +/// #include +/// #include +/// +/// const sourcemeta::jsontoolkit::JSON schema = +/// sourcemeta::jsontoolkit::parse(R"JSON({ +/// "$schema": "https://json-schema.org/draft/2020-12/schema", +/// "type": "string" +/// })JSON"); +/// +/// const auto schema_template{sourcemeta::jsontoolkit::compile( +/// schema, sourcemeta::jsontoolkit::default_schema_walker, +/// sourcemeta::jsontoolkit::official_resolver, +/// sourcemeta::jsontoolkit::default_schema_compiler)}; +/// +/// const sourcemeta::jsontoolkit::JSON instance{"foo bar"}; +/// sourcemeta::jsontoolkit::EvaluationContext context; +/// context.prepare(instance); +/// +/// const auto result{sourcemeta::jsontoolkit::evaluate( +/// schema_template, context)}; +/// assert(result); +/// ``` +auto SOURCEMETA_JSONTOOLKIT_EVALUATOR_EXPORT evaluate( + const SchemaCompilerTemplate &steps, EvaluationContext &context) -> bool; + +} // namespace sourcemeta::jsontoolkit + +#endif diff --git a/vendor/jsontoolkit/src/evaluator/include/sourcemeta/jsontoolkit/evaluator_context.h b/vendor/jsontoolkit/src/evaluator/include/sourcemeta/jsontoolkit/evaluator_context.h new file mode 100644 index 00000000..c83b2318 --- /dev/null +++ b/vendor/jsontoolkit/src/evaluator/include/sourcemeta/jsontoolkit/evaluator_context.h @@ -0,0 +1,151 @@ +#ifndef SOURCEMETA_JSONTOOLKIT_EVALUATOR_CONTEXT_H +#define SOURCEMETA_JSONTOOLKIT_EVALUATOR_CONTEXT_H + +#include "evaluator_export.h" + +#include + +#include +#include + +#include // assert +#include // std::uint8_t +#include // std::reference_wrapper +#include // std::map +#include // std::optional +#include // std::set +#include // std::vector + +namespace sourcemeta::jsontoolkit { + +/// @ingroup evaluator +/// Represents a stateful schema evaluation context +class SOURCEMETA_JSONTOOLKIT_EVALUATOR_EXPORT EvaluationContext { +public: + /// Prepare the schema evaluation context with a given instance. + /// Performing evaluation on a context without preparing it with + /// an instance is undefined behavior. + auto prepare(const JSON &instance) -> void; + + // All of these methods are considered internal and no + // client must depend on them +#ifndef DOXYGEN + + /////////////////////////////////////////////// + // Evaluation stack + /////////////////////////////////////////////// + + auto evaluate_path() const noexcept -> const WeakPointer &; + auto instance_location() const noexcept -> const WeakPointer &; + auto push(const Pointer &relative_schema_location, + const Pointer &relative_instance_location, + const std::string &schema_resource, const bool dynamic) -> void; + // A performance shortcut for pushing without re-traversing the target + // if we already know that the destination target will be + auto push(const Pointer &relative_schema_location, + const Pointer &relative_instance_location, + const std::string &schema_resource, const bool dynamic, + std::reference_wrapper &&new_instance) -> void; + auto pop(const bool dynamic) -> void; + auto enter(const WeakPointer::Token::Property &property) -> void; + auto enter(const WeakPointer::Token::Index &index) -> void; + auto leave() -> void; + +private: + auto push_without_traverse(const Pointer &relative_schema_location, + const Pointer &relative_instance_location, + const std::string &schema_resource, + const bool dynamic) -> void; + +public: + /////////////////////////////////////////////// + // Target resolution + /////////////////////////////////////////////// + + auto instances() const noexcept + -> const std::vector> &; + enum class TargetType : std::uint8_t { Key, Value }; + auto target_type(const TargetType type) noexcept -> void; + auto resolve_target() -> const JSON &; + + /////////////////////////////////////////////// + // References and anchors + /////////////////////////////////////////////// + + auto resources() const noexcept -> const std::vector &; + auto mark(const std::size_t id, const SchemaCompilerTemplate &children) + -> void; + // TODO: At least currently, we only need to mask if a schema + // makes use of `unevaluatedProperties` or `unevaluatedItems` + // Detect if a schema does need this so if not, we avoid + // an unnecessary copy + auto mask() -> void; + auto jump(const std::size_t id) const noexcept + -> const SchemaCompilerTemplate &; + auto find_dynamic_anchor(const std::string &anchor) const + -> std::optional; + + /////////////////////////////////////////////// + // Annotations + /////////////////////////////////////////////// + + auto annotate(const WeakPointer ¤t_instance_location, const JSON &value) + -> std::pair, bool>; + auto + defines_any_adjacent_annotation(const WeakPointer &expected_instance_location, + const WeakPointer &base_evaluate_path, + const std::string &keyword) const -> bool; + auto defines_any_adjacent_annotation( + const WeakPointer &expected_instance_location, + const WeakPointer &base_evaluate_path, + const std::vector &keywords) const -> bool; + auto defines_annotation(const WeakPointer &expected_instance_location, + const WeakPointer &base_evaluate_path, + const std::vector &keywords, + const JSON &value) const -> bool; + auto largest_annotation_index(const WeakPointer &expected_instance_location, + const std::vector &keywords, + const std::uint64_t default_value) const + -> std::uint64_t; + +private: + auto annotations(const WeakPointer ¤t_instance_location, + const WeakPointer &schema_location) const + -> const std::set &; + auto annotations(const WeakPointer ¤t_instance_location) const + -> const std::map> &; + +public: + // TODO: Remove this + const JSON null{nullptr}; + +private: +// Exporting symbols that depends on the standard C++ library is considered +// safe. +// https://learn.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-2-c4275?view=msvc-170&redirectedfrom=MSDN +#if defined(_MSC_VER) +#pragma warning(disable : 4251 4275) +#endif + std::vector> instances_; + WeakPointer evaluate_path_; + WeakPointer instance_location_; + std::vector> frame_sizes; + // TODO: Keep hashes of schema resources URI instead for performance reasons + std::vector resources_; + std::vector annotation_blacklist; + // We don't use a pair for holding the two pointers for runtime + // efficiency when resolving keywords like `unevaluatedProperties` + std::map>> annotations_; + std::map> + labels; + bool property_as_instance{false}; +#if defined(_MSC_VER) +#pragma warning(default : 4251 4275) +#endif +#endif +}; + +} // namespace sourcemeta::jsontoolkit + +#endif diff --git a/vendor/jsontoolkit/src/evaluator/include/sourcemeta/jsontoolkit/evaluator_error.h b/vendor/jsontoolkit/src/evaluator/include/sourcemeta/jsontoolkit/evaluator_error.h new file mode 100644 index 00000000..46df2644 --- /dev/null +++ b/vendor/jsontoolkit/src/evaluator/include/sourcemeta/jsontoolkit/evaluator_error.h @@ -0,0 +1,39 @@ +#ifndef SOURCEMETA_JSONTOOLKIT_EVALUATOR_ERROR_H +#define SOURCEMETA_JSONTOOLKIT_EVALUATOR_ERROR_H + +#include "evaluator_export.h" + +#include // std::exception +#include // std::string +#include // std::move + +namespace sourcemeta::jsontoolkit { + +// Exporting symbols that depends on the standard C++ library is considered +// safe. +// https://learn.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-2-c4275?view=msvc-170&redirectedfrom=MSDN +#if defined(_MSC_VER) +#pragma warning(disable : 4251 4275) +#endif + +/// @ingroup jsonschema +/// An error that represents a schema evaluation error event +class SOURCEMETA_JSONTOOLKIT_EVALUATOR_EXPORT SchemaEvaluationError + : public std::exception { +public: + SchemaEvaluationError(std::string message) : message_{std::move(message)} {} + [[nodiscard]] auto what() const noexcept -> const char * override { + return this->message_.c_str(); + } + +private: + std::string message_; +}; + +#if defined(_MSC_VER) +#pragma warning(default : 4251 4275) +#endif + +} // namespace sourcemeta::jsontoolkit + +#endif diff --git a/vendor/jsontoolkit/src/evaluator/include/sourcemeta/jsontoolkit/evaluator_template.h b/vendor/jsontoolkit/src/evaluator/include/sourcemeta/jsontoolkit/evaluator_template.h new file mode 100644 index 00000000..f824f692 --- /dev/null +++ b/vendor/jsontoolkit/src/evaluator/include/sourcemeta/jsontoolkit/evaluator_template.h @@ -0,0 +1,533 @@ +#ifndef SOURCEMETA_JSONTOOLKIT_EVALUATOR_TEMPLATE_H +#define SOURCEMETA_JSONTOOLKIT_EVALUATOR_TEMPLATE_H + +#include + +#include + +#include // std::uint8_t +#include // std::string +#include // std::vector + +namespace sourcemeta::jsontoolkit { + +// Forward declarations for the sole purpose of being bale to define circular +// structures +#ifndef DOXYGEN +struct SchemaCompilerAssertionFail; +struct SchemaCompilerAssertionDefines; +struct SchemaCompilerAssertionDefinesAll; +struct SchemaCompilerAssertionPropertyDependencies; +struct SchemaCompilerAssertionType; +struct SchemaCompilerAssertionTypeAny; +struct SchemaCompilerAssertionTypeStrict; +struct SchemaCompilerAssertionTypeStrictAny; +struct SchemaCompilerAssertionTypeStringBounded; +struct SchemaCompilerAssertionTypeArrayBounded; +struct SchemaCompilerAssertionTypeObjectBounded; +struct SchemaCompilerAssertionRegex; +struct SchemaCompilerAssertionStringSizeLess; +struct SchemaCompilerAssertionStringSizeGreater; +struct SchemaCompilerAssertionArraySizeLess; +struct SchemaCompilerAssertionArraySizeGreater; +struct SchemaCompilerAssertionObjectSizeLess; +struct SchemaCompilerAssertionObjectSizeGreater; +struct SchemaCompilerAssertionEqual; +struct SchemaCompilerAssertionEqualsAny; +struct SchemaCompilerAssertionGreaterEqual; +struct SchemaCompilerAssertionLessEqual; +struct SchemaCompilerAssertionGreater; +struct SchemaCompilerAssertionLess; +struct SchemaCompilerAssertionUnique; +struct SchemaCompilerAssertionDivisible; +struct SchemaCompilerAssertionStringType; +struct SchemaCompilerAssertionPropertyType; +struct SchemaCompilerAssertionPropertyTypeStrict; +struct SchemaCompilerAnnotationEmit; +struct SchemaCompilerAnnotationWhenArraySizeEqual; +struct SchemaCompilerAnnotationWhenArraySizeGreater; +struct SchemaCompilerAnnotationToParent; +struct SchemaCompilerAnnotationBasenameToParent; +struct SchemaCompilerLogicalOr; +struct SchemaCompilerLogicalAnd; +struct SchemaCompilerLogicalXor; +struct SchemaCompilerLogicalTryMark; +struct SchemaCompilerLogicalNot; +struct SchemaCompilerLogicalWhenType; +struct SchemaCompilerLogicalWhenDefines; +struct SchemaCompilerLogicalWhenAdjacentUnmarked; +struct SchemaCompilerLogicalWhenAdjacentMarked; +struct SchemaCompilerLogicalWhenArraySizeGreater; +struct SchemaCompilerLogicalWhenArraySizeEqual; +struct SchemaCompilerLoopPropertiesMatch; +struct SchemaCompilerLoopProperties; +struct SchemaCompilerLoopPropertiesRegex; +struct SchemaCompilerLoopPropertiesNoAnnotation; +struct SchemaCompilerLoopPropertiesExcept; +struct SchemaCompilerLoopPropertiesType; +struct SchemaCompilerLoopPropertiesTypeStrict; +struct SchemaCompilerLoopKeys; +struct SchemaCompilerLoopItems; +struct SchemaCompilerLoopItemsUnmarked; +struct SchemaCompilerLoopItemsUnevaluated; +struct SchemaCompilerLoopContains; +struct SchemaCompilerControlLabel; +struct SchemaCompilerControlMark; +struct SchemaCompilerControlJump; +struct SchemaCompilerControlDynamicAnchorJump; +#endif + +/// @ingroup evaluator +/// Represents a schema compilation step that can be evaluated +using SchemaCompilerTemplate = std::vector>; + +#if !defined(DOXYGEN) +// For fast internal instruction dispatching. It must stay +// in sync with the variant ordering above +enum class SchemaCompilerTemplateIndex : std::uint8_t { + SchemaCompilerAssertionFail = 0, + SchemaCompilerAssertionDefines, + SchemaCompilerAssertionDefinesAll, + SchemaCompilerAssertionPropertyDependencies, + SchemaCompilerAssertionType, + SchemaCompilerAssertionTypeAny, + SchemaCompilerAssertionTypeStrict, + SchemaCompilerAssertionTypeStrictAny, + SchemaCompilerAssertionTypeStringBounded, + SchemaCompilerAssertionTypeArrayBounded, + SchemaCompilerAssertionTypeObjectBounded, + SchemaCompilerAssertionRegex, + SchemaCompilerAssertionStringSizeLess, + SchemaCompilerAssertionStringSizeGreater, + SchemaCompilerAssertionArraySizeLess, + SchemaCompilerAssertionArraySizeGreater, + SchemaCompilerAssertionObjectSizeLess, + SchemaCompilerAssertionObjectSizeGreater, + SchemaCompilerAssertionEqual, + SchemaCompilerAssertionEqualsAny, + SchemaCompilerAssertionGreaterEqual, + SchemaCompilerAssertionLessEqual, + SchemaCompilerAssertionGreater, + SchemaCompilerAssertionLess, + SchemaCompilerAssertionUnique, + SchemaCompilerAssertionDivisible, + SchemaCompilerAssertionStringType, + SchemaCompilerAssertionPropertyType, + SchemaCompilerAssertionPropertyTypeStrict, + SchemaCompilerAnnotationEmit, + SchemaCompilerAnnotationWhenArraySizeEqual, + SchemaCompilerAnnotationWhenArraySizeGreater, + SchemaCompilerAnnotationToParent, + SchemaCompilerAnnotationBasenameToParent, + SchemaCompilerLogicalOr, + SchemaCompilerLogicalAnd, + SchemaCompilerLogicalXor, + SchemaCompilerLogicalTryMark, + SchemaCompilerLogicalNot, + SchemaCompilerLogicalWhenType, + SchemaCompilerLogicalWhenDefines, + SchemaCompilerLogicalWhenAdjacentUnmarked, + SchemaCompilerLogicalWhenAdjacentMarked, + SchemaCompilerLogicalWhenArraySizeGreater, + SchemaCompilerLogicalWhenArraySizeEqual, + SchemaCompilerLoopPropertiesMatch, + SchemaCompilerLoopProperties, + SchemaCompilerLoopPropertiesRegex, + SchemaCompilerLoopPropertiesNoAnnotation, + SchemaCompilerLoopPropertiesExcept, + SchemaCompilerLoopPropertiesType, + SchemaCompilerLoopPropertiesTypeStrict, + SchemaCompilerLoopKeys, + SchemaCompilerLoopItems, + SchemaCompilerLoopItemsUnmarked, + SchemaCompilerLoopItemsUnevaluated, + SchemaCompilerLoopContains, + SchemaCompilerControlLabel, + SchemaCompilerControlMark, + SchemaCompilerControlJump, + SchemaCompilerControlDynamicAnchorJump +}; +#endif + +#define DEFINE_STEP_WITH_VALUE(category, name, type) \ + struct SchemaCompiler##category##name { \ + const Pointer relative_schema_location; \ + const Pointer relative_instance_location; \ + const std::string keyword_location; \ + const std::string schema_resource; \ + const bool dynamic; \ + const bool report; \ + const type value; \ + }; + +#define DEFINE_STEP_APPLICATOR(category, name, type) \ + struct SchemaCompiler##category##name { \ + const Pointer relative_schema_location; \ + const Pointer relative_instance_location; \ + const std::string keyword_location; \ + const std::string schema_resource; \ + const bool dynamic; \ + const bool report; \ + const type value; \ + const SchemaCompilerTemplate children; \ + }; + +/// @defgroup evaluator_instructions Instruction Set +/// @ingroup evaluator +/// @brief The set of instructions supported by the evaluator. +/// @details +/// +/// Every instruction operates at a specific instance location and with the +/// given value, whose type depends on the instruction. + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that always fails +DEFINE_STEP_WITH_VALUE(Assertion, Fail, SchemaCompilerValueNone) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks if an object defines +/// a given property +DEFINE_STEP_WITH_VALUE(Assertion, Defines, SchemaCompilerValueString) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks if an object defines +/// a set of properties +DEFINE_STEP_WITH_VALUE(Assertion, DefinesAll, SchemaCompilerValueStrings) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks if an object defines +/// a set of properties if it defines other set of properties +DEFINE_STEP_WITH_VALUE(Assertion, PropertyDependencies, + SchemaCompilerValueStringMap) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks if a document is of +/// the given type +DEFINE_STEP_WITH_VALUE(Assertion, Type, SchemaCompilerValueType) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks if a document is of +/// any of the given types +DEFINE_STEP_WITH_VALUE(Assertion, TypeAny, SchemaCompilerValueTypes) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks if a document is of +/// the given type (strict version) +DEFINE_STEP_WITH_VALUE(Assertion, TypeStrict, SchemaCompilerValueType) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks if a document is of +/// any of the given types (strict version) +DEFINE_STEP_WITH_VALUE(Assertion, TypeStrictAny, SchemaCompilerValueTypes) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks if a document is of +/// type string and adheres to the given bounds +DEFINE_STEP_WITH_VALUE(Assertion, TypeStringBounded, SchemaCompilerValueRange) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks if a document is of +/// type array and adheres to the given bounds +DEFINE_STEP_WITH_VALUE(Assertion, TypeArrayBounded, SchemaCompilerValueRange) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks if a document is of +/// type object and adheres to the given bounds +DEFINE_STEP_WITH_VALUE(Assertion, TypeObjectBounded, SchemaCompilerValueRange) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks a string against an +/// ECMA regular expression +DEFINE_STEP_WITH_VALUE(Assertion, Regex, SchemaCompilerValueRegex) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks a given string has +/// less than a certain number of characters +DEFINE_STEP_WITH_VALUE(Assertion, StringSizeLess, + SchemaCompilerValueUnsignedInteger) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks a given string has +/// greater than a certain number of characters +DEFINE_STEP_WITH_VALUE(Assertion, StringSizeGreater, + SchemaCompilerValueUnsignedInteger) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks a given array has +/// less than a certain number of items +DEFINE_STEP_WITH_VALUE(Assertion, ArraySizeLess, + SchemaCompilerValueUnsignedInteger) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks a given array has +/// greater than a certain number of items +DEFINE_STEP_WITH_VALUE(Assertion, ArraySizeGreater, + SchemaCompilerValueUnsignedInteger) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks a given object has +/// less than a certain number of properties +DEFINE_STEP_WITH_VALUE(Assertion, ObjectSizeLess, + SchemaCompilerValueUnsignedInteger) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks a given object has +/// greater than a certain number of properties +DEFINE_STEP_WITH_VALUE(Assertion, ObjectSizeGreater, + SchemaCompilerValueUnsignedInteger) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks the instance equals +/// a given JSON document +DEFINE_STEP_WITH_VALUE(Assertion, Equal, SchemaCompilerValueJSON) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks that a JSON document +/// is equal to at least one of the given elements +DEFINE_STEP_WITH_VALUE(Assertion, EqualsAny, SchemaCompilerValueArray) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks a JSON document is +/// greater than or equal to another JSON document +DEFINE_STEP_WITH_VALUE(Assertion, GreaterEqual, SchemaCompilerValueJSON) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks a JSON document is +/// less than or equal to another JSON document +DEFINE_STEP_WITH_VALUE(Assertion, LessEqual, SchemaCompilerValueJSON) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks a JSON document is +/// greater than another JSON document +DEFINE_STEP_WITH_VALUE(Assertion, Greater, SchemaCompilerValueJSON) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks a JSON document is +/// less than another JSON document +DEFINE_STEP_WITH_VALUE(Assertion, Less, SchemaCompilerValueJSON) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks a given JSON array +/// does not contain duplicate items +DEFINE_STEP_WITH_VALUE(Assertion, Unique, SchemaCompilerValueNone) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks a number is +/// divisible by another number +DEFINE_STEP_WITH_VALUE(Assertion, Divisible, SchemaCompilerValueJSON) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks that a string is of +/// a certain type +DEFINE_STEP_WITH_VALUE(Assertion, StringType, SchemaCompilerValueStringType) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks that an instance +/// property is of a given type if present +DEFINE_STEP_WITH_VALUE(Assertion, PropertyType, SchemaCompilerValueType) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks that an instance +/// property is of a given type if present (strict mode) +DEFINE_STEP_WITH_VALUE(Assertion, PropertyTypeStrict, SchemaCompilerValueType) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler step that emits an annotation +DEFINE_STEP_WITH_VALUE(Annotation, Emit, SchemaCompilerValueJSON) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler step that emits an annotation when the size of +/// the array instance is equal to the given size +DEFINE_STEP_WITH_VALUE(Annotation, WhenArraySizeEqual, + SchemaCompilerValueIndexedJSON) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler step that emits an annotation when the size of +/// the array instance is greater than the given size +DEFINE_STEP_WITH_VALUE(Annotation, WhenArraySizeGreater, + SchemaCompilerValueIndexedJSON) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler step that emits an annotation to the parent +DEFINE_STEP_WITH_VALUE(Annotation, ToParent, SchemaCompilerValueJSON) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler step that emits the current basename as an +/// annotation to the parent +DEFINE_STEP_WITH_VALUE(Annotation, BasenameToParent, SchemaCompilerValueNone) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler logical step that represents a disjunction +DEFINE_STEP_APPLICATOR(Logical, Or, SchemaCompilerValueBoolean) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler logical step that represents a conjunction +DEFINE_STEP_APPLICATOR(Logical, And, SchemaCompilerValueNone) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler logical step that represents an exclusive +/// disjunction +DEFINE_STEP_APPLICATOR(Logical, Xor, SchemaCompilerValueNone) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler logical step that represents a conjunction that +/// always reports success and marks its outcome for other steps +DEFINE_STEP_APPLICATOR(Logical, TryMark, SchemaCompilerValueNone) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler logical step that represents a negation +DEFINE_STEP_APPLICATOR(Logical, Not, SchemaCompilerValueNone) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler logical step that represents a conjunction when +/// the instance is of a given type +DEFINE_STEP_APPLICATOR(Logical, WhenType, SchemaCompilerValueType) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler logical step that represents a conjunction when +/// the instance is an object and defines a given property +DEFINE_STEP_APPLICATOR(Logical, WhenDefines, SchemaCompilerValueString) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler logical step that represents a conjunction when +/// the instance and desired evaluation path was not marked +DEFINE_STEP_APPLICATOR(Logical, WhenAdjacentUnmarked, SchemaCompilerValueString) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler logical step that represents a conjunction when +/// the instance and desired evaluation path was marked +DEFINE_STEP_APPLICATOR(Logical, WhenAdjacentMarked, SchemaCompilerValueString) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler logical step that represents a conjunction when +/// the array instance size is greater than the given number +DEFINE_STEP_APPLICATOR(Logical, WhenArraySizeGreater, + SchemaCompilerValueUnsignedInteger) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler logical step that represents a conjunction when +/// the array instance size is equal to the given number +DEFINE_STEP_APPLICATOR(Logical, WhenArraySizeEqual, + SchemaCompilerValueUnsignedInteger) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler step that matches steps to object properties +DEFINE_STEP_APPLICATOR(Loop, PropertiesMatch, SchemaCompilerValueNamedIndexes) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler step that loops over object properties +DEFINE_STEP_APPLICATOR(Loop, Properties, SchemaCompilerValueNone) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler step that loops over object properties that +/// match a given ECMA regular expression +DEFINE_STEP_APPLICATOR(Loop, PropertiesRegex, SchemaCompilerValueRegex) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler step that loops over object properties that +/// were not collected as annotations +DEFINE_STEP_APPLICATOR(Loop, PropertiesNoAnnotation, SchemaCompilerValueStrings) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler step that loops over object properties that +/// do not match the given property filters +DEFINE_STEP_APPLICATOR(Loop, PropertiesExcept, + SchemaCompilerValuePropertyFilter) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler step that checks every object property is of a +/// given type +DEFINE_STEP_WITH_VALUE(Loop, PropertiesType, SchemaCompilerValueType) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler step that checks every object property is of a +/// given type (strict mode) +DEFINE_STEP_WITH_VALUE(Loop, PropertiesTypeStrict, SchemaCompilerValueType) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler step that loops over object property keys +DEFINE_STEP_APPLICATOR(Loop, Keys, SchemaCompilerValueNone) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler step that loops over array items starting from +/// a given index +DEFINE_STEP_APPLICATOR(Loop, Items, SchemaCompilerValueUnsignedInteger) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler step that loops over array items when the array +/// is considered unmarked +DEFINE_STEP_APPLICATOR(Loop, ItemsUnmarked, SchemaCompilerValueStrings) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler step that loops over unevaluated array items +DEFINE_STEP_APPLICATOR(Loop, ItemsUnevaluated, + SchemaCompilerValueItemsAnnotationKeywords) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler step that checks array items match a given +/// criteria +DEFINE_STEP_APPLICATOR(Loop, Contains, SchemaCompilerValueRange) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler step that consists of a mark to jump to while +/// executing children instructions +DEFINE_STEP_APPLICATOR(Control, Label, SchemaCompilerValueUnsignedInteger) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler step that consists of a mark to jump to, but +/// without executing children instructions +DEFINE_STEP_APPLICATOR(Control, Mark, SchemaCompilerValueUnsignedInteger) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler step that consists of jumping into a +/// pre-registered label +DEFINE_STEP_WITH_VALUE(Control, Jump, SchemaCompilerValueUnsignedInteger) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler step that consists of jump to a dynamic anchor +DEFINE_STEP_WITH_VALUE(Control, DynamicAnchorJump, SchemaCompilerValueString) + +#undef DEFINE_STEP_WITH_VALUE +#undef DEFINE_STEP_APPLICATOR + +} // namespace sourcemeta::jsontoolkit + +#endif diff --git a/vendor/jsontoolkit/src/evaluator/include/sourcemeta/jsontoolkit/evaluator_value.h b/vendor/jsontoolkit/src/evaluator/include/sourcemeta/jsontoolkit/evaluator_value.h new file mode 100644 index 00000000..513df80a --- /dev/null +++ b/vendor/jsontoolkit/src/evaluator/include/sourcemeta/jsontoolkit/evaluator_value.h @@ -0,0 +1,111 @@ +#ifndef SOURCEMETA_JSONTOOLKIT_EVALUATOR_VALUE_H +#define SOURCEMETA_JSONTOOLKIT_EVALUATOR_VALUE_H + +#include +#include + +#include // std::uint8_t +#include // std::optional, std::nullopt +#include // std::regex +#include // std::set +#include // std::string +#include // std::tuple +#include // std::unordered_map +#include // std::pair +#include // std::vector + +namespace sourcemeta::jsontoolkit { + +/// @ingroup evaluator +/// @brief Represents a compiler step empty value +struct SchemaCompilerValueNone {}; + +/// @ingroup evaluator +/// Represents a compiler step JSON value +using SchemaCompilerValueJSON = JSON; + +// Note that for these steps, we prefer vectors over sets as the former performs +// better for small collections, where we can even guarantee uniqueness when +// generating the instructions + +/// @ingroup evaluator +/// Represents a set of JSON values +using SchemaCompilerValueArray = std::vector; + +/// @ingroup evaluator +/// Represents a compiler step string values +using SchemaCompilerValueStrings = std::vector; + +/// @ingroup evaluator +/// Represents a compiler step JSON types value +using SchemaCompilerValueTypes = std::vector; + +/// @ingroup evaluator +/// Represents a compiler step string value +using SchemaCompilerValueString = JSON::String; + +/// @ingroup evaluator +/// Represents a compiler step JSON type value +using SchemaCompilerValueType = JSON::Type; + +/// @ingroup evaluator +/// Represents a compiler step ECMA regular expression value. We store both the +/// original string and the regular expression as standard regular expressions +/// do not keep a copy of their original value (which we need for serialization +/// purposes) +using SchemaCompilerValueRegex = std::pair; + +/// @ingroup evaluator +/// Represents a compiler step JSON unsigned integer value +using SchemaCompilerValueUnsignedInteger = std::size_t; + +/// @ingroup evaluator +/// Represents a compiler step range value. The boolean option +/// modifies whether the range is considered exhaustively or +/// if the evaluator is allowed to break early +using SchemaCompilerValueRange = + std::tuple, bool>; + +/// @ingroup evaluator +/// Represents a compiler step boolean value +using SchemaCompilerValueBoolean = bool; + +/// @ingroup evaluator +/// Represents a compiler step string to index map +using SchemaCompilerValueNamedIndexes = + std::unordered_map; + +/// @ingroup evaluator +/// Represents a compiler step string logical type +enum class SchemaCompilerValueStringType : std::uint8_t { URI }; + +/// @ingroup evaluator +/// Represents an array loop compiler step annotation keywords +struct SchemaCompilerValueItemsAnnotationKeywords { + const SchemaCompilerValueString index; + const SchemaCompilerValueStrings filter; + const SchemaCompilerValueStrings mask; +}; + +/// @ingroup evaluator +/// Represents an compiler step that maps strings to strings +using SchemaCompilerValueStringMap = + std::unordered_map; + +/// @ingroup evaluator +/// Represents a compiler step JSON value accompanied with an index +using SchemaCompilerValueIndexedJSON = + std::pair; + +// Note that while we generally avoid sets, in this case, we want +// hash-based lookups on string collections that might get large. +/// @ingroup evaluator +/// Represents a compiler step value that consist of object property filters +using SchemaCompilerValuePropertyFilter = + std::pair, + std::vector>; + +} // namespace sourcemeta::jsontoolkit + +#endif diff --git a/vendor/jsontoolkit/src/jsonschema/trace.h b/vendor/jsontoolkit/src/evaluator/trace.h similarity index 91% rename from vendor/jsontoolkit/src/jsonschema/trace.h rename to vendor/jsontoolkit/src/evaluator/trace.h index e9331daa..770967f2 100644 --- a/vendor/jsontoolkit/src/jsonschema/trace.h +++ b/vendor/jsontoolkit/src/evaluator/trace.h @@ -1,5 +1,5 @@ -#ifndef SOURCEMETA_JSONTOOLKIT_JSONSCHEMA_TRACE_H_ -#define SOURCEMETA_JSONTOOLKIT_JSONSCHEMA_TRACE_H_ +#ifndef SOURCEMETA_JSONTOOLKIT_EVALUATOR_TRACE_H_ +#define SOURCEMETA_JSONTOOLKIT_EVALUATOR_TRACE_H_ // We only perform tracing on debugging builds, at least for now #if !defined(NDEBUG) && defined(__APPLE__) && defined(__clang__) diff --git a/vendor/jsontoolkit/src/json/include/sourcemeta/jsontoolkit/json_value.h b/vendor/jsontoolkit/src/json/include/sourcemeta/jsontoolkit/json_value.h index 69dc9d62..b33ccd5c 100644 --- a/vendor/jsontoolkit/src/json/include/sourcemeta/jsontoolkit/json_value.h +++ b/vendor/jsontoolkit/src/json/include/sourcemeta/jsontoolkit/json_value.h @@ -12,6 +12,7 @@ #include // std::less, std::reference_wrapper #include // std::initializer_list #include // std::allocator +#include // std::optional #include // std::set #include // std::basic_istringstream #include // std::basic_string, std::char_traits @@ -650,8 +651,8 @@ class SOURCEMETA_JSONTOOLKIT_JSON_EXPORT JSON { /// sourcemeta::jsontoolkit::parse("{ \"1\": "foo" }"); /// assert(my_array.at(1).to_string() == "foo"); /// ``` - [[nodiscard]] auto - at(const typename Array::size_type index) const -> const JSON &; + [[nodiscard]] auto at(const typename Array::size_type index) const + -> const JSON &; /// This method retrieves a element by its index. If the input JSON instance /// is an object, a property that corresponds to the stringified integer will @@ -847,6 +848,22 @@ class SOURCEMETA_JSONTOOLKIT_JSON_EXPORT JSON { /// ``` [[nodiscard]] auto empty() const -> bool; + /// This method checks whether an input JSON object defines a specific key + /// and returns the value if it does. For example: + /// + /// ```cpp + /// #include + /// #include + /// + /// const sourcemeta::jsontoolkit::JSON document = + /// sourcemeta::jsontoolkit::parse("{ \"foo\": 1 }"); + /// EXPECT_TRUE(document.is_object()); + /// const auto result = document.try_at("foo"); + /// EXPECT_TRUE(result.has_value()); + /// EXPECT_EQ(result.value().get().to_integer(), 1); + [[nodiscard]] auto try_at(const String &key) const + -> std::optional>; + /// This method checks whether an input JSON object defines a specific key. /// For example: /// @@ -873,8 +890,8 @@ class SOURCEMETA_JSONTOOLKIT_JSON_EXPORT JSON { /// assert(document.defines(0)); /// assert(!document.defines(1)); /// ``` - [[nodiscard]] auto - defines(const typename Array::size_type index) const -> bool; + [[nodiscard]] auto defines(const typename Array::size_type index) const + -> bool; /// This method checks whether an input JSON object defines at least one given /// key. @@ -910,8 +927,8 @@ class SOURCEMETA_JSONTOOLKIT_JSON_EXPORT JSON { /// /// assert(document.defines_any({ "foo", "qux" })); /// ``` - [[nodiscard]] auto - defines_any(std::initializer_list keys) const -> bool; + [[nodiscard]] auto defines_any(std::initializer_list keys) const + -> bool; /// This method checks if an JSON array contains a given JSON instance. For /// example: diff --git a/vendor/jsontoolkit/src/json/json_value.cc b/vendor/jsontoolkit/src/json/json_value.cc index 60e3d75f..a66b61b7 100644 --- a/vendor/jsontoolkit/src/json/json_value.cc +++ b/vendor/jsontoolkit/src/json/json_value.cc @@ -284,8 +284,8 @@ auto JSON::operator-=(const JSON &substractive) -> JSON & { } } -[[nodiscard]] auto -JSON::at(const typename JSON::Array::size_type index) const -> const JSON & { +[[nodiscard]] auto JSON::at(const typename JSON::Array::size_type index) const + -> const JSON & { // In practice, this case only applies in some edge cases when // using JSON Pointers if (this->is_object()) [[unlikely]] { @@ -297,8 +297,8 @@ JSON::at(const typename JSON::Array::size_type index) const -> const JSON & { return std::get(this->data).data.at(index); } -[[nodiscard]] auto -JSON::at(const typename JSON::Array::size_type index) -> JSON & { +[[nodiscard]] auto JSON::at(const typename JSON::Array::size_type index) + -> JSON & { // In practice, this case only applies in some edge cases when // using JSON Pointers if (this->is_object()) [[unlikely]] { @@ -450,6 +450,19 @@ JSON::at(const typename JSON::Array::size_type index) -> JSON & { } } +[[nodiscard]] auto JSON::try_at(const JSON::String &key) const + -> std::optional> { + assert(this->is_object()); + + const auto &object{std::get(this->data)}; + const auto value{object.data.find(key)}; + + if (value == object.data.cend()) { + return std::nullopt; + } + return value->second; +} + [[nodiscard]] auto JSON::defines(const JSON::String &key) const -> bool { assert(this->is_object()); return std::get(this->data).data.contains(key); @@ -474,6 +487,10 @@ JSON::defines_any(std::initializer_list keys) const -> bool { [[nodiscard]] auto JSON::unique() const -> bool { assert(this->is_array()); const auto &items{std::get(this->data).data}; + // Arrays of 0 or 1 item are unique by definition + if (items.size() <= 1) { + return true; + } // Otherwise std::unique would require us to create a copy of the contents for (auto iterator = items.cbegin(); iterator != items.cend(); ++iterator) { @@ -534,8 +551,8 @@ auto JSON::assign(const JSON::String &key, JSON &&value) -> void { std::get(this->data).data.insert_or_assign(key, std::move(value)); } -auto JSON::assign_if_missing(const JSON::String &key, - const JSON &value) -> void { +auto JSON::assign_if_missing(const JSON::String &key, const JSON &value) + -> void { assert(this->is_object()); if (!this->defines(key)) { this->assign(key, value); diff --git a/vendor/jsontoolkit/src/jsonl/include/sourcemeta/jsontoolkit/jsonl_iterator.h b/vendor/jsontoolkit/src/jsonl/include/sourcemeta/jsontoolkit/jsonl_iterator.h index d6d7d41b..654098ad 100644 --- a/vendor/jsontoolkit/src/jsonl/include/sourcemeta/jsontoolkit/jsonl_iterator.h +++ b/vendor/jsontoolkit/src/jsonl/include/sourcemeta/jsontoolkit/jsonl_iterator.h @@ -30,8 +30,8 @@ class SOURCEMETA_JSONTOOLKIT_JSONL_EXPORT ConstJSONLIterator { auto operator++() -> ConstJSONLIterator &; SOURCEMETA_JSONTOOLKIT_JSONL_EXPORT friend auto - operator==(const ConstJSONLIterator &left, - const ConstJSONLIterator &right) -> bool; + operator==(const ConstJSONLIterator &left, const ConstJSONLIterator &right) + -> bool; private: std::uint64_t line{1}; diff --git a/vendor/jsontoolkit/src/jsonl/iterator.cc b/vendor/jsontoolkit/src/jsonl/iterator.cc index 10dbdb1e..1d26b804 100644 --- a/vendor/jsontoolkit/src/jsonl/iterator.cc +++ b/vendor/jsontoolkit/src/jsonl/iterator.cc @@ -102,8 +102,8 @@ ConstJSONLIterator::ConstJSONLIterator( ConstJSONLIterator::~ConstJSONLIterator() {} -auto operator==(const ConstJSONLIterator &left, - const ConstJSONLIterator &right) -> bool { +auto operator==(const ConstJSONLIterator &left, const ConstJSONLIterator &right) + -> bool { return (!left.data && !right.data) || (left.data && right.data && left.internal->current == right.internal->current); diff --git a/vendor/jsontoolkit/src/jsonpointer/include/sourcemeta/jsontoolkit/jsonpointer.h b/vendor/jsontoolkit/src/jsonpointer/include/sourcemeta/jsontoolkit/jsonpointer.h index 73196bb3..cedda358 100644 --- a/vendor/jsontoolkit/src/jsonpointer/include/sourcemeta/jsontoolkit/jsonpointer.h +++ b/vendor/jsontoolkit/src/jsonpointer/include/sourcemeta/jsontoolkit/jsonpointer.h @@ -13,6 +13,7 @@ #include // std::reference_wrapper #include // std::allocator +#include // std::optional #include // std::basic_ostream #include // std::basic_string @@ -88,8 +89,8 @@ SOURCEMETA_JSONTOOLKIT_JSONPOINTER_EXPORT auto get(const JSON &document, const WeakPointer &pointer) -> const JSON &; /// @ingroup jsonpointer -/// Check that a path represented by a JSON Pointer exists in the given -/// document. For example: +/// Get a value from a JSON document using a Pointer, returning an optional that +/// is not set if the path does not exist in the document. For example: /// /// ```cpp /// #include @@ -100,14 +101,17 @@ auto get(const JSON &document, const WeakPointer &pointer) -> const JSON &; /// std::istringstream stream{"[ { \"foo\": 1 }, { \"bar\": 2 } ]"}; /// const auto document{sourcemeta::jsontoolkit::parse(stream)}; /// const sourcemeta::jsontoolkit::Pointer pointer{1, "bar"}; -/// assert(sourcemeta::jsontoolkit::has(document, pointer)); +/// const auto result{sourcemeta::jsontoolkit::try_get(document, pointer)}; +/// assert(result.has_value()); +/// assert(result.value().get() == document.at(1).at("bar")); /// ``` SOURCEMETA_JSONTOOLKIT_JSONPOINTER_EXPORT -auto has(const JSON &document, const Pointer &pointer) -> bool; +auto try_get(const JSON &document, const Pointer &pointer) + -> std::optional>; /// @ingroup jsonpointer -/// Check that a path represented by a JSON WeakPointer exists in the given -/// document. For example: +/// Get a value from a JSON document using a WeakPointer, returning an optional +/// that is not set if the path does not exist in the document. For example: /// /// ```cpp /// #include @@ -119,10 +123,13 @@ auto has(const JSON &document, const Pointer &pointer) -> bool; /// const auto document{sourcemeta::jsontoolkit::parse(stream)}; /// const std::string bar = "bar"; /// const sourcemeta::jsontoolkit::WeakPointer pointer{1, std::cref(bar)}; -/// assert(sourcemeta::jsontoolkit::has(document, pointer)); +/// const auto result{sourcemeta::jsontoolkit::try_get(document, pointer)}; +/// assert(result.has_value()); +/// assert(result.value().get() == document.at(1).at("bar")); /// ``` SOURCEMETA_JSONTOOLKIT_JSONPOINTER_EXPORT -auto has(const JSON &document, const WeakPointer &pointer) -> bool; +auto try_get(const JSON &document, const WeakPointer &pointer) + -> std::optional>; /// @ingroup jsonpointer /// Get a value from a JSON document using a JSON Pointer (non-`const` diff --git a/vendor/jsontoolkit/src/jsonpointer/include/sourcemeta/jsontoolkit/jsonpointer_pointer.h b/vendor/jsontoolkit/src/jsonpointer/include/sourcemeta/jsontoolkit/jsonpointer_pointer.h index 801021d4..0427dccc 100644 --- a/vendor/jsontoolkit/src/jsonpointer/include/sourcemeta/jsontoolkit/jsonpointer_pointer.h +++ b/vendor/jsontoolkit/src/jsonpointer/include/sourcemeta/jsontoolkit/jsonpointer_pointer.h @@ -519,15 +519,15 @@ template class GenericPointer { } /// Compare JSON Pointer instances - auto - operator==(const GenericPointer &other) const noexcept -> bool { + auto operator==(const GenericPointer &other) const noexcept + -> bool { return this->data == other.data; } /// Overload to support ordering of JSON Pointers. Typically for sorting /// reasons. - auto - operator<(const GenericPointer &other) const noexcept -> bool { + auto operator<(const GenericPointer &other) const noexcept + -> bool { return this->data < other.data; } diff --git a/vendor/jsontoolkit/src/jsonpointer/jsonpointer.cc b/vendor/jsontoolkit/src/jsonpointer/jsonpointer.cc index 9664ea0d..8530a6a1 100644 --- a/vendor/jsontoolkit/src/jsonpointer/jsonpointer.cc +++ b/vendor/jsontoolkit/src/jsonpointer/jsonpointer.cc @@ -55,36 +55,35 @@ auto traverse(V &document, typename PointerT::const_iterator begin, } template -auto check(const sourcemeta::jsontoolkit::JSON &document, - typename PointerT::const_iterator begin, - typename PointerT::const_iterator end) -> bool { +auto try_traverse(const sourcemeta::jsontoolkit::JSON &document, + typename PointerT::const_iterator begin, + typename PointerT::const_iterator end) + -> std::optional< + std::reference_wrapper> { std::reference_wrapper current{document}; for (auto iterator = begin; iterator != end; ++iterator) { const auto &token{*iterator}; const auto &instance{current.get()}; - const bool is_last{std::next(iterator) == end}; if (token.is_property()) { const auto &property{token.to_property()}; - if (is_last) { - return instance.defines(property); - } else if (instance.defines(property)) { - current = instance.at(property); + auto json_value{instance.try_at(property)}; + if (json_value.has_value()) { + current = std::move(json_value.value()); + } else { - return false; + return std::nullopt; } } else { const auto index{token.to_index()}; - if (is_last) { - return index < instance.size(); - } else if (index < instance.size()) { + if (index < instance.size()) { current = instance.at(index); } else { - return false; + return std::nullopt; } } } - return true; + return current; } } // namespace @@ -106,12 +105,16 @@ auto get(JSON &document, const Pointer &pointer) -> JSON & { std::cend(pointer)); } -auto has(const JSON &document, const Pointer &pointer) -> bool { - return check(document, std::cbegin(pointer), std::cend(pointer)); +auto try_get(const JSON &document, const Pointer &pointer) + -> std::optional> { + return try_traverse(document, std::cbegin(pointer), + std::cend(pointer)); } -auto has(const JSON &document, const WeakPointer &pointer) -> bool { - return check(document, std::cbegin(pointer), std::cend(pointer)); +auto try_get(const JSON &document, const WeakPointer &pointer) + -> std::optional> { + return try_traverse(document, std::cbegin(pointer), + std::cend(pointer)); } auto get(const JSON &document, const Pointer::Token &token) -> const JSON & { @@ -122,8 +125,8 @@ auto get(const JSON &document, const Pointer::Token &token) -> const JSON & { } } -auto get(const JSON &document, - const WeakPointer::Token &token) -> const JSON & { +auto get(const JSON &document, const WeakPointer::Token &token) + -> const JSON & { if (token.is_property()) { return document.at(token.to_property()); } else { diff --git a/vendor/jsontoolkit/src/jsonpointer/parser.h b/vendor/jsontoolkit/src/jsonpointer/parser.h index 15cd39a5..6db2196f 100644 --- a/vendor/jsontoolkit/src/jsonpointer/parser.h +++ b/vendor/jsontoolkit/src/jsonpointer/parser.h @@ -14,8 +14,9 @@ namespace sourcemeta::jsontoolkit::internal { template typename Allocator> -inline auto reset( - std::basic_stringstream> &stream) -> void { +inline auto +reset(std::basic_stringstream> &stream) + -> void { stream.str(""); stream.clear(); } diff --git a/vendor/jsontoolkit/src/jsonpointer/stringify.h b/vendor/jsontoolkit/src/jsonpointer/stringify.h index bc04594a..937a8996 100644 --- a/vendor/jsontoolkit/src/jsonpointer/stringify.h +++ b/vendor/jsontoolkit/src/jsonpointer/stringify.h @@ -14,8 +14,8 @@ namespace sourcemeta::jsontoolkit::internal { inline auto write_character(std::basic_ostream &stream, - const JSON::Char character, - const bool perform_uri_escaping) -> void { + const JSON::Char character, const bool perform_uri_escaping) + -> void { // The dollar sign does not need to be encoded in URI fragments // See `fragment` in https://www.rfc-editor.org/rfc/rfc3986#appendix-A if (perform_uri_escaping && character != '$') { diff --git a/vendor/jsontoolkit/src/jsonschema/CMakeLists.txt b/vendor/jsontoolkit/src/jsonschema/CMakeLists.txt index a1be00db..d7ae24d4 100644 --- a/vendor/jsontoolkit/src/jsonschema/CMakeLists.txt +++ b/vendor/jsontoolkit/src/jsonschema/CMakeLists.txt @@ -8,9 +8,9 @@ noa_library(NAMESPACE sourcemeta PROJECT jsontoolkit NAME jsonschema walker.h reference.h error.h compile.h SOURCES jsonschema.cc default_walker.cc reference.cc anchor.cc resolver.cc - walker.cc bundle.cc compile.cc compile_evaluate.cc + walker.cc bundle.cc compile.cc compile_json.cc compile_describe.cc - compile_helpers.h default_compiler.cc trace.h + compile_helpers.h default_compiler.cc default_compiler_2020_12.h default_compiler_2019_09.h @@ -30,6 +30,8 @@ target_link_libraries(sourcemeta_jsontoolkit_jsonschema PUBLIC sourcemeta::jsontoolkit::jsonpointer) target_link_libraries(sourcemeta_jsontoolkit_jsonschema PRIVATE sourcemeta::jsontoolkit::uri) +target_link_libraries(sourcemeta_jsontoolkit_jsonschema PUBLIC + sourcemeta::jsontoolkit::evaluator) # GCC does not allow the use of std::promise, std::future # without compiling with pthreads support. diff --git a/vendor/jsontoolkit/src/jsonschema/compile.cc b/vendor/jsontoolkit/src/jsonschema/compile.cc index 7bb2c58e..d2b06f98 100644 --- a/vendor/jsontoolkit/src/jsonschema/compile.cc +++ b/vendor/jsontoolkit/src/jsonschema/compile.cc @@ -184,23 +184,29 @@ auto compile(const SchemaCompilerContext &context, .canonicalize() .recompose()}; - const Pointer destination_pointer{ - dynamic_context.keyword.empty() - ? dynamic_context.base_schema_location.concat(schema_suffix) - : dynamic_context.base_schema_location - .concat({dynamic_context.keyword}) - .concat(schema_suffix)}; - // Otherwise the recursion attempt is non-sense if (!context.frame.contains({ReferenceType::Static, destination})) { throw SchemaReferenceError( - destination, destination_pointer, + destination, schema_context.relative_pointer, "The target of the reference does not exist in the schema"); } const auto &entry{context.frame.at({ReferenceType::Static, destination})}; - const auto &new_schema{get(context.root, entry.pointer)}; + + if (!is_schema(new_schema)) { + throw SchemaReferenceError( + destination, schema_context.relative_pointer, + "The target of the reference is not a valid schema"); + } + + const Pointer destination_pointer{ + dynamic_context.keyword.empty() + ? dynamic_context.base_schema_location.concat(schema_suffix) + : dynamic_context.base_schema_location + .concat({dynamic_context.keyword}) + .concat(schema_suffix)}; + return compile_subschema( context, {entry.relative_pointer, new_schema, diff --git a/vendor/jsontoolkit/src/jsonschema/compile_describe.cc b/vendor/jsontoolkit/src/jsonschema/compile_describe.cc index 32633599..04354c2d 100644 --- a/vendor/jsontoolkit/src/jsonschema/compile_describe.cc +++ b/vendor/jsontoolkit/src/jsonschema/compile_describe.cc @@ -45,8 +45,8 @@ auto escape_string(const std::string &input) -> std::string { } auto describe_type_check(const bool valid, const JSON::Type current, - const JSON::Type expected, - std::ostringstream &message) -> void { + const JSON::Type expected, std::ostringstream &message) + -> void { message << "The value was expected to be of type "; message << to_string(expected); if (!valid) { @@ -524,8 +524,8 @@ struct DescribeVisitor { return unknown(); } - auto - operator()(const SchemaCompilerAnnotationToParent &) const -> std::string { + auto operator()(const SchemaCompilerAnnotationToParent &) const + -> std::string { if (this->keyword == "unevaluatedItems" && this->annotation.is_boolean() && this->annotation.to_boolean()) { assert(this->target.is_array()); @@ -583,8 +583,8 @@ struct DescribeVisitor { return unknown(); } - auto - operator()(const SchemaCompilerLoopProperties &step) const -> std::string { + auto operator()(const SchemaCompilerLoopProperties &step) const + -> std::string { assert(this->keyword == "additionalProperties"); std::ostringstream message; if (step.children.size() == 1 && @@ -637,6 +637,22 @@ struct DescribeVisitor { return message.str(); } + auto operator()(const SchemaCompilerLoopPropertiesType &step) const + -> std::string { + std::ostringstream message; + message << "The object properties were expected to be of type " + << to_string(step.value); + return message.str(); + } + + auto operator()(const SchemaCompilerLoopPropertiesTypeStrict &step) const + -> std::string { + std::ostringstream message; + message << "The object properties were expected to be of type " + << to_string(step.value); + return message.str(); + } + auto operator()(const SchemaCompilerLoopKeys &) const -> std::string { assert(this->keyword == "propertyNames"); assert(this->target.is_object()); @@ -682,8 +698,8 @@ struct DescribeVisitor { return message.str(); } - auto - operator()(const SchemaCompilerLoopItemsUnmarked &) const -> std::string { + auto operator()(const SchemaCompilerLoopItemsUnmarked &) const + -> std::string { return unknown(); } @@ -742,8 +758,8 @@ struct DescribeVisitor { return message.str(); } - auto - operator()(const SchemaCompilerAssertionDefines &step) const -> std::string { + auto operator()(const SchemaCompilerAssertionDefines &step) const + -> std::string { std::ostringstream message; message << "The object value was expected to define the property " << escape_string(step_value(step)); @@ -795,8 +811,8 @@ struct DescribeVisitor { return message.str(); } - auto - operator()(const SchemaCompilerAssertionType &step) const -> std::string { + auto operator()(const SchemaCompilerAssertionType &step) const + -> std::string { std::ostringstream message; describe_type_check(this->valid, this->target.type(), step_value(step), message); @@ -822,8 +838,8 @@ struct DescribeVisitor { return message.str(); } - auto - operator()(const SchemaCompilerAssertionTypeAny &step) const -> std::string { + auto operator()(const SchemaCompilerAssertionTypeAny &step) const + -> std::string { std::ostringstream message; describe_types_check(this->valid, this->target.type(), step_value(step), message); @@ -838,8 +854,73 @@ struct DescribeVisitor { return message.str(); } - auto - operator()(const SchemaCompilerAssertionRegex &step) const -> std::string { + auto operator()(const SchemaCompilerAssertionTypeStringBounded &step) const + -> std::string { + std::ostringstream message; + + const auto minimum{std::get<0>(step.value)}; + const auto maximum{std::get<1>(step.value)}; + if (minimum == 0 && maximum.has_value()) { + message << "The value was expected to consist of a string of at most " + << maximum.value() + << (maximum.value() == 1 ? " character" : " characters"); + } else if (maximum.has_value()) { + message << "The value was expected to consist of a string of " << minimum + << " to " << maximum.value() + << (maximum.value() == 1 ? " character" : " characters"); + } else { + message << "The value was expected to consist of a string of at least " + << minimum << (minimum == 1 ? " character" : " characters"); + } + + return message.str(); + } + + auto operator()(const SchemaCompilerAssertionTypeArrayBounded &step) const + -> std::string { + std::ostringstream message; + + const auto minimum{std::get<0>(step.value)}; + const auto maximum{std::get<1>(step.value)}; + if (minimum == 0 && maximum.has_value()) { + message << "The value was expected to consist of an array of at most " + << maximum.value() << (maximum.value() == 1 ? " item" : " items"); + } else if (maximum.has_value()) { + message << "The value was expected to consist of an array of " << minimum + << " to " << maximum.value() + << (maximum.value() == 1 ? " item" : " items"); + } else { + message << "The value was expected to consist of an array of at least " + << minimum << (minimum == 1 ? " item" : " items"); + } + + return message.str(); + } + + auto operator()(const SchemaCompilerAssertionTypeObjectBounded &step) const + -> std::string { + std::ostringstream message; + + const auto minimum{std::get<0>(step.value)}; + const auto maximum{std::get<1>(step.value)}; + if (minimum == 0 && maximum.has_value()) { + message << "The value was expected to consist of an object of at most " + << maximum.value() + << (maximum.value() == 1 ? " property" : " properties"); + } else if (maximum.has_value()) { + message << "The value was expected to consist of an object of " << minimum + << " to " << maximum.value() + << (maximum.value() == 1 ? " property" : " properties"); + } else { + message << "The value was expected to consist of an object of at least " + << minimum << (minimum == 1 ? " property" : " properties"); + } + + return message.str(); + } + + auto operator()(const SchemaCompilerAssertionRegex &step) const + -> std::string { assert(this->target.is_string()); std::ostringstream message; message << "The string value " << escape_string(this->target.to_string()) @@ -1096,8 +1177,8 @@ struct DescribeVisitor { return unknown(); } - auto - operator()(const SchemaCompilerAssertionEqual &step) const -> std::string { + auto operator()(const SchemaCompilerAssertionEqual &step) const + -> std::string { std::ostringstream message; const auto &value{step_value(step)}; message << "The " << to_string(this->target.type()) << " value "; @@ -1131,8 +1212,8 @@ struct DescribeVisitor { return message.str(); } - auto - operator()(const SchemaCompilerAssertionGreater &step) const -> std::string { + auto operator()(const SchemaCompilerAssertionGreater &step) const + -> std::string { std::ostringstream message; const auto &value{step_value(step)}; message << "The " << to_string(this->target.type()) << " value "; @@ -1147,8 +1228,8 @@ struct DescribeVisitor { return message.str(); } - auto - operator()(const SchemaCompilerAssertionLess &step) const -> std::string { + auto operator()(const SchemaCompilerAssertionLess &step) const + -> std::string { std::ostringstream message; const auto &value{step_value(step)}; message << "The " << to_string(this->target.type()) << " value "; @@ -1320,8 +1401,8 @@ struct DescribeVisitor { return message.str(); } - auto - operator()(const SchemaCompilerLogicalWhenType &step) const -> std::string { + auto operator()(const SchemaCompilerLogicalWhenType &step) const + -> std::string { if (this->keyword == "patternProperties") { assert(!step.children.empty()); assert(this->target.is_object()); @@ -1677,12 +1758,12 @@ struct DescribeVisitor { -> std::string { return unknown(); } - auto - operator()(const SchemaCompilerLogicalWhenDefines &) const -> std::string { + auto operator()(const SchemaCompilerLogicalWhenDefines &) const + -> std::string { return unknown(); } - auto - operator()(const SchemaCompilerLoopPropertiesRegex &) const -> std::string { + auto operator()(const SchemaCompilerLoopPropertiesRegex &) const + -> std::string { return unknown(); } }; diff --git a/vendor/jsontoolkit/src/jsonschema/compile_evaluate.cc b/vendor/jsontoolkit/src/jsonschema/compile_evaluate.cc deleted file mode 100644 index 9f539c50..00000000 --- a/vendor/jsontoolkit/src/jsonschema/compile_evaluate.cc +++ /dev/null @@ -1,1238 +0,0 @@ -#include -#include -#include - -#include "trace.h" - -#include // std::min, std::any_of -#include // assert -#include // std::reference_wrapper -#include // std::distance, std::advance -#include // std::numeric_limits -#include // std::map -#include // std::optional -#include // std::set -#include // std::is_same_v -#include // std::vector - -namespace { - -class EvaluationContext { -public: - using Pointer = sourcemeta::jsontoolkit::WeakPointer; - using JSON = sourcemeta::jsontoolkit::JSON; - using Template = sourcemeta::jsontoolkit::SchemaCompilerTemplate; - EvaluationContext(const JSON &instance) : instances_{instance} {}; - - auto annotate(const Pointer ¤t_instance_location, const JSON &value) - -> std::pair, bool> { - const auto result{this->annotations_.insert({current_instance_location, {}}) - .first->second.insert({this->evaluate_path(), {}}) - .first->second.insert(value)}; - return {*(result.first), result.second}; - } - -private: - auto annotations(const Pointer ¤t_instance_location, - const Pointer &schema_location) const -> const auto & { - static const decltype(this->annotations_)::mapped_type::mapped_type - placeholder; - // Use `.find()` instead of `.contains()` and `.at()` for performance - // reasons - const auto instance_location_result{ - this->annotations_.find(current_instance_location)}; - if (instance_location_result == this->annotations_.end()) { - return placeholder; - } - - const auto schema_location_result{ - instance_location_result->second.find(schema_location)}; - if (schema_location_result == instance_location_result->second.end()) { - return placeholder; - } - - return schema_location_result->second; - } - - auto annotations(const Pointer ¤t_instance_location) const -> const - auto & { - static const decltype(this->annotations_)::mapped_type placeholder; - // Use `.find()` instead of `.contains()` and `.at()` for performance - // reasons - const auto instance_location_result{ - this->annotations_.find(current_instance_location)}; - if (instance_location_result == this->annotations_.end()) { - return placeholder; - } - - return instance_location_result->second; - } - -public: - auto - defines_any_adjacent_annotation(const Pointer &expected_instance_location, - const Pointer &base_evaluate_path, - const std::string &keyword) const -> bool { - // TODO: We should be taking masks into account - // TODO: How can we avoid this expensive pointer manipulation? - auto expected_evaluate_path{base_evaluate_path}; - expected_evaluate_path.push_back({keyword}); - return !this->annotations(expected_instance_location, - expected_evaluate_path) - .empty(); - } - - auto defines_any_adjacent_annotation( - const Pointer &expected_instance_location, - const Pointer &base_evaluate_path, - const std::vector &keywords) const -> bool { - for (const auto &keyword : keywords) { - if (this->defines_any_adjacent_annotation(expected_instance_location, - base_evaluate_path, keyword)) { - return true; - } - } - - return false; - } - - auto defines_annotation(const Pointer &expected_instance_location, - const Pointer &base_evaluate_path, - const std::vector &keywords, - const JSON &value) const -> bool { - if (keywords.empty()) { - return false; - } - - const auto instance_annotations{ - this->annotations(expected_instance_location)}; - for (const auto &[schema_location, schema_annotations] : - instance_annotations) { - assert(!schema_location.empty()); - const auto &keyword{schema_location.back()}; - - if (keyword.is_property() && - std::find(keywords.cbegin(), keywords.cend(), - keyword.to_property()) != keywords.cend() && - schema_annotations.contains(value) && - schema_location.initial().starts_with(base_evaluate_path)) { - bool blacklisted = false; - for (const auto &masked : this->annotation_blacklist) { - if (schema_location.starts_with(masked) && - !this->evaluate_path_.starts_with(masked)) { - blacklisted = true; - break; - } - } - - if (!blacklisted) { - return true; - } - } - } - - return false; - } - - auto largest_annotation_index(const Pointer &expected_instance_location, - const std::vector &keywords, - const std::uint64_t default_value) const - -> std::uint64_t { - // TODO: We should be taking masks into account - - std::uint64_t result{default_value}; - for (const auto &[schema_location, schema_annotations] : - this->annotations(expected_instance_location)) { - assert(!schema_location.empty()); - const auto &keyword{schema_location.back()}; - if (!keyword.is_property()) { - continue; - } - - if (std::find(keywords.cbegin(), keywords.cend(), - keyword.to_property()) == keywords.cend()) { - continue; - } - - for (const auto &annotation : schema_annotations) { - if (annotation.is_integer() && annotation.is_positive()) { - result = std::max( - result, static_cast(annotation.to_integer()) + 1); - } - } - } - - return result; - } - - template auto push(const T &step) -> void { - // Guard against infinite recursion in a cheap manner, as - // infinite recursion will manifest itself through huge - // ever-growing evaluate paths - constexpr auto EVALUATE_PATH_LIMIT{400}; - if (this->evaluate_path_.size() > EVALUATE_PATH_LIMIT) [[unlikely]] { - throw sourcemeta::jsontoolkit::SchemaEvaluationError( - "The evaluation path depth limit was reached " - "likely due to infinite recursion"); - } - - this->frame_sizes.emplace_back(step.relative_schema_location.size(), - step.relative_instance_location.size()); - this->evaluate_path_.push_back(step.relative_schema_location); - this->instance_location_.push_back(step.relative_instance_location); - if (!step.relative_instance_location.empty()) { - this->instances_.emplace_back( - get(this->instances_.back().get(), step.relative_instance_location)); - } - - if (step.dynamic) { - // Note that we are potentially repeatedly pushing back the - // same schema resource over and over again. However, the - // logic for making sure this list is "pure" takes a lot of - // computation power. Being silly seems faster. - this->resources_.push_back(step.schema_resource); - } - } - - template auto pop(const T &step) -> void { - assert(!this->frame_sizes.empty()); - const auto &sizes{this->frame_sizes.back()}; - this->evaluate_path_.pop_back(sizes.first); - this->instance_location_.pop_back(sizes.second); - if (sizes.second > 0) { - this->instances_.pop_back(); - } - - this->frame_sizes.pop_back(); - - // TODO: Do schema resource management using hashes to avoid - // expensive string comparisons - if (step.dynamic) { - assert(!this->resources_.empty()); - this->resources_.pop_back(); - } - } - - auto enter(const Pointer::Token::Property &property) -> void { - this->instance_location_.push_back(property); - this->instances_.emplace_back(this->instances_.back().get().at(property)); - } - - auto enter(const Pointer::Token::Index &index) -> void { - this->instance_location_.push_back(index); - this->instances_.emplace_back(this->instances_.back().get().at(index)); - } - - auto leave() -> void { - this->instance_location_.pop_back(); - this->instances_.pop_back(); - } - - auto instances() const noexcept -> const auto & { return this->instances_; } - - auto resources() const noexcept -> const std::vector & { - return this->resources_; - } - - auto evaluate_path() const noexcept -> const Pointer & { - return this->evaluate_path_; - } - - auto instance_location() const noexcept -> const Pointer & { - return this->instance_location_; - } - - enum class TargetType { Key, Value }; - auto target_type(const TargetType type) noexcept -> void { - this->property_as_instance = (type == TargetType::Key); - } - - auto resolve_target() -> const JSON & { - if (this->property_as_instance) [[unlikely]] { - assert(!this->instance_location().empty()); - assert(this->instance_location().back().is_property()); - // For efficiency, as we likely reference the same JSON values - // over and over again - // TODO: Get rid of this once we have weak pointers - static std::set property_values; - return *(property_values - .emplace(this->instance_location().back().to_property()) - .first); - } - - return this->instances_.back().get(); - } - - auto mark(const std::size_t id, const Template &children) -> void { - this->labels.try_emplace(id, children); - } - - // TODO: At least currently, we only need to mask if a schema - // makes use of `unevaluatedProperties` or `unevaluatedItems` - // Detect if a schema does need this so if not, we avoid - // an unnecessary copy - auto mask() -> void { - this->annotation_blacklist.push_back(this->evaluate_path_); - } - - auto jump(const std::size_t id) const noexcept -> const Template & { - assert(this->labels.contains(id)); - return this->labels.at(id).get(); - } - - auto find_dynamic_anchor(const std::string &anchor) const - -> std::optional { - for (const auto &resource : this->resources()) { - std::ostringstream name; - name << resource; - name << '#'; - name << anchor; - const auto label{std::hash{}(name.str())}; - if (this->labels.contains(label)) { - return label; - } - } - - return std::nullopt; - } - -public: - const JSON null{nullptr}; - -private: - std::vector> instances_; - Pointer evaluate_path_; - Pointer instance_location_; - std::vector> frame_sizes; - // TODO: Keep hashes of schema resources URI instead for performance reasons - std::vector resources_; - std::vector annotation_blacklist; - // We don't use a pair for holding the two pointers for runtime - // efficiency when resolving keywords like `unevaluatedProperties` - std::map>> annotations_; - std::map> labels; - bool property_as_instance{false}; -}; - -auto evaluate_step( - const sourcemeta::jsontoolkit::SchemaCompilerTemplate::value_type &step, - const sourcemeta::jsontoolkit::SchemaCompilerEvaluationMode mode, - const std::optional< - sourcemeta::jsontoolkit::SchemaCompilerEvaluationCallback> &callback, - EvaluationContext &context) -> bool { - SOURCEMETA_TRACE_REGISTER_ID(trace_id); - using namespace sourcemeta::jsontoolkit; - -#define STRINGIFY(x) #x -#define IS_STEP(step_type) std::holds_alternative(step) - -#define EVALUATE_BEGIN(step_category, step_type, precondition) \ - SOURCEMETA_TRACE_START(trace_id, STRINGIFY(step_type)); \ - const auto &step_category{std::get(step)}; \ - context.push(step_category); \ - const auto &target{context.resolve_target()}; \ - if (!(precondition)) { \ - context.pop(step_category); \ - SOURCEMETA_TRACE_END(trace_id, STRINGIFY(step_type)); \ - return true; \ - } \ - if (step_category.report && callback.has_value()) { \ - callback.value()(SchemaCompilerEvaluationType::Pre, true, step, \ - context.evaluate_path(), context.instance_location(), \ - context.null); \ - } \ - bool result{false}; - -#define EVALUATE_BEGIN_NO_TARGET(step_category, step_type, precondition) \ - SOURCEMETA_TRACE_START(trace_id, STRINGIFY(step_type)); \ - const auto &step_category{std::get(step)}; \ - if (!(precondition)) { \ - SOURCEMETA_TRACE_END(trace_id, STRINGIFY(step_type)); \ - return true; \ - } \ - context.push(step_category); \ - if (step_category.report && callback.has_value()) { \ - callback.value()(SchemaCompilerEvaluationType::Pre, true, step, \ - context.evaluate_path(), context.instance_location(), \ - context.null); \ - } \ - bool result{false}; - -#define EVALUATE_BEGIN_NO_PRECONDITION(step_category, step_type) \ - SOURCEMETA_TRACE_START(trace_id, STRINGIFY(step_type)); \ - const auto &step_category{std::get(step)}; \ - context.push(step_category); \ - if (step_category.report && callback.has_value()) { \ - callback.value()(SchemaCompilerEvaluationType::Pre, true, step, \ - context.evaluate_path(), context.instance_location(), \ - context.null); \ - } \ - bool result{false}; - -#define EVALUATE_END(step_category, step_type) \ - if (step_category.report && callback.has_value()) { \ - callback.value()(SchemaCompilerEvaluationType::Post, result, step, \ - context.evaluate_path(), context.instance_location(), \ - context.null); \ - } \ - context.pop(step_category); \ - SOURCEMETA_TRACE_END(trace_id, STRINGIFY(step_type)); \ - return result; - - // As a safety guard, only emit the annotation if it didn't exist already. - // Otherwise we risk confusing consumers - -#define EVALUATE_ANNOTATION(step_category, step_type, precondition, \ - destination, annotation_value) \ - SOURCEMETA_TRACE_START(trace_id, STRINGIFY(step_type)); \ - const auto &step_category{std::get(step)}; \ - assert(step_category.relative_instance_location.empty()); \ - const auto &target{context.resolve_target()}; \ - if (!(precondition)) { \ - SOURCEMETA_TRACE_END(trace_id, STRINGIFY(step_type)); \ - return true; \ - } \ - const auto annotation_result{ \ - context.annotate(destination, annotation_value)}; \ - context.push(step_category); \ - if (annotation_result.second && step_category.report && \ - callback.has_value()) { \ - callback.value()(SchemaCompilerEvaluationType::Pre, true, step, \ - context.evaluate_path(), destination, context.null); \ - callback.value()(SchemaCompilerEvaluationType::Post, true, step, \ - context.evaluate_path(), destination, \ - annotation_result.first); \ - } \ - context.pop(step_category); \ - SOURCEMETA_TRACE_END(trace_id, STRINGIFY(step_type)); \ - return true; - -#define EVALUATE_ANNOTATION_NO_PRECONDITION(step_category, step_type, \ - destination, annotation_value) \ - SOURCEMETA_TRACE_START(trace_id, STRINGIFY(step_type)); \ - const auto &step_category{std::get(step)}; \ - const auto annotation_result{ \ - context.annotate(destination, annotation_value)}; \ - context.push(step_category); \ - if (annotation_result.second && step_category.report && \ - callback.has_value()) { \ - callback.value()(SchemaCompilerEvaluationType::Pre, true, step, \ - context.evaluate_path(), destination, context.null); \ - callback.value()(SchemaCompilerEvaluationType::Post, true, step, \ - context.evaluate_path(), destination, \ - annotation_result.first); \ - } \ - context.pop(step_category); \ - SOURCEMETA_TRACE_END(trace_id, STRINGIFY(step_type)); \ - return true; - - if (IS_STEP(SchemaCompilerAssertionFail)) { - EVALUATE_BEGIN_NO_PRECONDITION(assertion, SchemaCompilerAssertionFail); - EVALUATE_END(assertion, SchemaCompilerAssertionFail); - } else if (IS_STEP(SchemaCompilerAssertionDefines)) { - EVALUATE_BEGIN(assertion, SchemaCompilerAssertionDefines, - target.is_object()); - result = target.defines(assertion.value); - EVALUATE_END(assertion, SchemaCompilerAssertionDefines); - } else if (IS_STEP(SchemaCompilerAssertionDefinesAll)) { - EVALUATE_BEGIN(assertion, SchemaCompilerAssertionDefinesAll, - target.is_object()); - - // Otherwise we are we even emitting this instruction? - assert(assertion.value.size() > 1); - result = true; - for (const auto &property : assertion.value) { - if (!target.defines(property)) { - result = false; - break; - } - } - - EVALUATE_END(assertion, SchemaCompilerAssertionDefinesAll); - } else if (IS_STEP(SchemaCompilerAssertionPropertyDependencies)) { - EVALUATE_BEGIN(assertion, SchemaCompilerAssertionPropertyDependencies, - target.is_object()); - // Otherwise we are we even emitting this instruction? - assert(!assertion.value.empty()); - result = true; - for (const auto &[property, dependencies] : assertion.value) { - if (!target.defines(property)) { - continue; - } - - assert(!dependencies.empty()); - for (const auto &dependency : dependencies) { - if (!target.defines(dependency)) { - result = false; - // For efficiently breaking from the outer loop too - goto evaluate_assertion_property_dependencies_end; - } - } - } - - evaluate_assertion_property_dependencies_end: - EVALUATE_END(assertion, SchemaCompilerAssertionPropertyDependencies); - } else if (IS_STEP(SchemaCompilerAssertionType)) { - EVALUATE_BEGIN_NO_PRECONDITION(assertion, SchemaCompilerAssertionType); - const auto &target{context.resolve_target()}; - // In non-strict mode, we consider a real number that represents an - // integer to be an integer - result = - target.type() == assertion.value || - (assertion.value == JSON::Type::Integer && target.is_integer_real()); - EVALUATE_END(assertion, SchemaCompilerAssertionType); - } else if (IS_STEP(SchemaCompilerAssertionTypeAny)) { - EVALUATE_BEGIN_NO_PRECONDITION(assertion, SchemaCompilerAssertionTypeAny); - // Otherwise we are we even emitting this instruction? - assert(assertion.value.size() > 1); - const auto &target{context.resolve_target()}; - // In non-strict mode, we consider a real number that represents an - // integer to be an integer - for (const auto type : assertion.value) { - if (type == JSON::Type::Integer && target.is_integer_real()) { - result = true; - break; - } else if (type == target.type()) { - result = true; - break; - } - } - - EVALUATE_END(assertion, SchemaCompilerAssertionTypeAny); - } else if (IS_STEP(SchemaCompilerAssertionTypeStrict)) { - EVALUATE_BEGIN_NO_PRECONDITION(assertion, - SchemaCompilerAssertionTypeStrict); - result = context.resolve_target().type() == assertion.value; - EVALUATE_END(assertion, SchemaCompilerAssertionTypeStrict); - } else if (IS_STEP(SchemaCompilerAssertionTypeStrictAny)) { - EVALUATE_BEGIN_NO_PRECONDITION(assertion, - SchemaCompilerAssertionTypeStrictAny); - // Otherwise we are we even emitting this instruction? - assert(assertion.value.size() > 1); - result = - (std::find(assertion.value.cbegin(), assertion.value.cend(), - context.resolve_target().type()) != assertion.value.cend()); - EVALUATE_END(assertion, SchemaCompilerAssertionTypeStrictAny); - } else if (IS_STEP(SchemaCompilerAssertionRegex)) { - EVALUATE_BEGIN(assertion, SchemaCompilerAssertionRegex, target.is_string()); - result = std::regex_search(target.to_string(), assertion.value.first); - EVALUATE_END(assertion, SchemaCompilerAssertionRegex); - } else if (IS_STEP(SchemaCompilerAssertionStringSizeLess)) { - EVALUATE_BEGIN(assertion, SchemaCompilerAssertionStringSizeLess, - target.is_string()); - result = (target.size() < assertion.value); - EVALUATE_END(assertion, SchemaCompilerAssertionStringSizeLess); - } else if (IS_STEP(SchemaCompilerAssertionStringSizeGreater)) { - EVALUATE_BEGIN(assertion, SchemaCompilerAssertionStringSizeGreater, - target.is_string()); - result = (target.size() > assertion.value); - EVALUATE_END(assertion, SchemaCompilerAssertionStringSizeGreater); - } else if (IS_STEP(SchemaCompilerAssertionArraySizeLess)) { - EVALUATE_BEGIN(assertion, SchemaCompilerAssertionArraySizeLess, - target.is_array()); - result = (target.size() < assertion.value); - EVALUATE_END(assertion, SchemaCompilerAssertionArraySizeLess); - } else if (IS_STEP(SchemaCompilerAssertionArraySizeGreater)) { - EVALUATE_BEGIN(assertion, SchemaCompilerAssertionArraySizeGreater, - target.is_array()); - result = (target.size() > assertion.value); - EVALUATE_END(assertion, SchemaCompilerAssertionArraySizeGreater); - } else if (IS_STEP(SchemaCompilerAssertionObjectSizeLess)) { - EVALUATE_BEGIN(assertion, SchemaCompilerAssertionObjectSizeLess, - target.is_object()); - result = (target.size() < assertion.value); - EVALUATE_END(assertion, SchemaCompilerAssertionObjectSizeLess); - } else if (IS_STEP(SchemaCompilerAssertionObjectSizeGreater)) { - EVALUATE_BEGIN(assertion, SchemaCompilerAssertionObjectSizeGreater, - target.is_object()); - result = (target.size() > assertion.value); - EVALUATE_END(assertion, SchemaCompilerAssertionObjectSizeGreater); - } else if (IS_STEP(SchemaCompilerAssertionEqual)) { - EVALUATE_BEGIN_NO_PRECONDITION(assertion, SchemaCompilerAssertionEqual); - result = (context.resolve_target() == assertion.value); - EVALUATE_END(assertion, SchemaCompilerAssertionEqual); - } else if (IS_STEP(SchemaCompilerAssertionEqualsAny)) { - EVALUATE_BEGIN_NO_PRECONDITION(assertion, SchemaCompilerAssertionEqualsAny); - result = (std::find(assertion.value.cbegin(), assertion.value.cend(), - context.resolve_target()) != assertion.value.cend()); - EVALUATE_END(assertion, SchemaCompilerAssertionEqualsAny); - } else if (IS_STEP(SchemaCompilerAssertionGreaterEqual)) { - EVALUATE_BEGIN(assertion, SchemaCompilerAssertionGreaterEqual, - target.is_number()); - result = target >= assertion.value; - EVALUATE_END(assertion, SchemaCompilerAssertionGreaterEqual); - } else if (IS_STEP(SchemaCompilerAssertionLessEqual)) { - EVALUATE_BEGIN(assertion, SchemaCompilerAssertionLessEqual, - target.is_number()); - result = target <= assertion.value; - EVALUATE_END(assertion, SchemaCompilerAssertionLessEqual); - } else if (IS_STEP(SchemaCompilerAssertionGreater)) { - EVALUATE_BEGIN(assertion, SchemaCompilerAssertionGreater, - target.is_number()); - result = target > assertion.value; - EVALUATE_END(assertion, SchemaCompilerAssertionGreater); - } else if (IS_STEP(SchemaCompilerAssertionLess)) { - EVALUATE_BEGIN(assertion, SchemaCompilerAssertionLess, target.is_number()); - result = target < assertion.value; - EVALUATE_END(assertion, SchemaCompilerAssertionLess); - } else if (IS_STEP(SchemaCompilerAssertionUnique)) { - EVALUATE_BEGIN(assertion, SchemaCompilerAssertionUnique, target.is_array()); - result = target.unique(); - EVALUATE_END(assertion, SchemaCompilerAssertionUnique); - } else if (IS_STEP(SchemaCompilerAssertionDivisible)) { - EVALUATE_BEGIN(assertion, SchemaCompilerAssertionDivisible, - target.is_number()); - assert(assertion.value.is_number()); - result = target.divisible_by(assertion.value); - EVALUATE_END(assertion, SchemaCompilerAssertionDivisible); - } else if (IS_STEP(SchemaCompilerAssertionStringType)) { - EVALUATE_BEGIN(assertion, SchemaCompilerAssertionStringType, - target.is_string()); - switch (assertion.value) { - case SchemaCompilerValueStringType::URI: - try { - // TODO: This implies a string copy - result = URI{target.to_string()}.is_absolute(); - } catch (const URIParseError &) { - result = false; - } - - break; - default: - // We should never get here - assert(false); - } - - EVALUATE_END(assertion, SchemaCompilerAssertionStringType); - } else if (IS_STEP(SchemaCompilerAssertionPropertyType)) { - EVALUATE_BEGIN_NO_TARGET(assertion, SchemaCompilerAssertionPropertyType, - // Note that here are are referring to the parent - // object that might hold the given property, - // before traversing into the actual property - context.resolve_target().is_object() && - has(context.resolve_target(), - assertion.relative_instance_location)); - // Now here we refer to the actual property - const auto &target{context.resolve_target()}; - // In non-strict mode, we consider a real number that represents an - // integer to be an integer - result = - target.type() == assertion.value || - (assertion.value == JSON::Type::Integer && target.is_integer_real()); - EVALUATE_END(assertion, SchemaCompilerAssertionPropertyType); - } else if (IS_STEP(SchemaCompilerAssertionPropertyTypeStrict)) { - EVALUATE_BEGIN_NO_TARGET(assertion, - SchemaCompilerAssertionPropertyTypeStrict, - // Note that here are are referring to the parent - // object that might hold the given property, - // before traversing into the actual property - context.resolve_target().is_object() && - has(context.resolve_target(), - assertion.relative_instance_location)); - // Now here we refer to the actual property - result = context.resolve_target().type() == assertion.value; - EVALUATE_END(assertion, SchemaCompilerAssertionPropertyTypeStrict); - } else if (IS_STEP(SchemaCompilerLogicalOr)) { - EVALUATE_BEGIN_NO_PRECONDITION(logical, SchemaCompilerLogicalOr); - result = logical.children.empty(); - for (const auto &child : logical.children) { - if (evaluate_step(child, mode, callback, context)) { - result = true; - // This boolean value controls whether we should still evaluate - // every disjunction even on fast mode - if (mode == SchemaCompilerEvaluationMode::Fast && !logical.value) { - break; - } - } - } - - EVALUATE_END(logical, SchemaCompilerLogicalOr); - } else if (IS_STEP(SchemaCompilerLogicalAnd)) { - EVALUATE_BEGIN_NO_PRECONDITION(logical, SchemaCompilerLogicalAnd); - result = true; - for (const auto &child : logical.children) { - if (!evaluate_step(child, mode, callback, context)) { - result = false; - break; - } - } - - EVALUATE_END(logical, SchemaCompilerLogicalAnd); - } else if (IS_STEP(SchemaCompilerLogicalWhenType)) { - EVALUATE_BEGIN(logical, SchemaCompilerLogicalWhenType, - target.type() == logical.value); - result = true; - for (const auto &child : logical.children) { - if (!evaluate_step(child, mode, callback, context)) { - result = false; - break; - } - } - - EVALUATE_END(logical, SchemaCompilerLogicalWhenType); - } else if (IS_STEP(SchemaCompilerLogicalWhenDefines)) { - EVALUATE_BEGIN(logical, SchemaCompilerLogicalWhenDefines, - target.is_object() && target.defines(logical.value)); - result = true; - for (const auto &child : logical.children) { - if (!evaluate_step(child, mode, callback, context)) { - result = false; - break; - } - } - - EVALUATE_END(logical, SchemaCompilerLogicalWhenDefines); - } else if (IS_STEP(SchemaCompilerLogicalWhenAdjacentUnmarked)) { - EVALUATE_BEGIN_NO_TARGET(logical, SchemaCompilerLogicalWhenAdjacentUnmarked, - !context.defines_any_adjacent_annotation( - context.instance_location(), - context.evaluate_path(), logical.value)); - result = true; - for (const auto &child : logical.children) { - if (!evaluate_step(child, mode, callback, context)) { - result = false; - break; - } - } - - EVALUATE_END(logical, SchemaCompilerLogicalWhenAdjacentUnmarked); - } else if (IS_STEP(SchemaCompilerLogicalWhenAdjacentMarked)) { - EVALUATE_BEGIN_NO_TARGET(logical, SchemaCompilerLogicalWhenAdjacentMarked, - context.defines_any_adjacent_annotation( - context.instance_location(), - context.evaluate_path(), logical.value)); - result = true; - for (const auto &child : logical.children) { - if (!evaluate_step(child, mode, callback, context)) { - result = false; - break; - } - } - - EVALUATE_END(logical, SchemaCompilerLogicalWhenAdjacentMarked); - } else if (IS_STEP(SchemaCompilerLogicalWhenArraySizeGreater)) { - EVALUATE_BEGIN(logical, SchemaCompilerLogicalWhenArraySizeGreater, - target.is_array() && target.size() > logical.value); - result = true; - for (const auto &child : logical.children) { - if (!evaluate_step(child, mode, callback, context)) { - result = false; - break; - } - } - - EVALUATE_END(logical, SchemaCompilerLogicalWhenArraySizeGreater); - } else if (IS_STEP(SchemaCompilerLogicalWhenArraySizeEqual)) { - EVALUATE_BEGIN(logical, SchemaCompilerLogicalWhenArraySizeEqual, - target.is_array() && target.size() == logical.value); - result = true; - for (const auto &child : logical.children) { - if (!evaluate_step(child, mode, callback, context)) { - result = false; - break; - } - } - - EVALUATE_END(logical, SchemaCompilerLogicalWhenArraySizeEqual); - } else if (IS_STEP(SchemaCompilerLogicalXor)) { - EVALUATE_BEGIN_NO_PRECONDITION(logical, SchemaCompilerLogicalXor); - result = false; - - // TODO: Cache results of a given branch so we can avoid - // computing it multiple times - for (auto iterator{logical.children.cbegin()}; - iterator != logical.children.cend(); ++iterator) { - if (!evaluate_step(*iterator, mode, callback, context)) { - continue; - } - - // Check if another one matches - bool subresult{true}; - for (auto subiterator{logical.children.cbegin()}; - subiterator != logical.children.cend(); ++subiterator) { - // Don't compare the element against itself - if (std::distance(logical.children.cbegin(), iterator) == - std::distance(logical.children.cbegin(), subiterator)) { - continue; - } - - // We don't need to report traces that part of the exhaustive - // XOR search. We can treat those as internal - if (evaluate_step(*subiterator, mode, std::nullopt, context)) { - subresult = false; - break; - } - } - - result = result || subresult; - if (result && mode == SchemaCompilerEvaluationMode::Fast) { - break; - } - } - - EVALUATE_END(logical, SchemaCompilerLogicalXor); - } else if (IS_STEP(SchemaCompilerLogicalTryMark)) { - EVALUATE_BEGIN_NO_PRECONDITION(logical, SchemaCompilerLogicalTryMark); - result = true; - for (const auto &child : logical.children) { - if (!evaluate_step(child, mode, callback, context)) { - result = false; - break; - } - } - - if (result) { - // TODO: This implies an allocation of a JSON boolean - context.annotate(context.instance_location(), JSON{true}); - } else { - result = true; - } - - EVALUATE_END(logical, SchemaCompilerLogicalTryMark); - } else if (IS_STEP(SchemaCompilerLogicalNot)) { - EVALUATE_BEGIN_NO_PRECONDITION(logical, SchemaCompilerLogicalNot); - // Ignore annotations produced inside "not" - context.mask(); - result = false; - for (const auto &child : logical.children) { - if (!evaluate_step(child, mode, callback, context)) { - result = true; - if (mode == SchemaCompilerEvaluationMode::Fast) { - break; - } - } - } - - EVALUATE_END(logical, SchemaCompilerLogicalNot); - } else if (IS_STEP(SchemaCompilerControlLabel)) { - EVALUATE_BEGIN_NO_PRECONDITION(control, SchemaCompilerControlLabel); - context.mark(control.value, control.children); - result = true; - for (const auto &child : control.children) { - if (!evaluate_step(child, mode, callback, context)) { - result = false; - break; - } - } - - EVALUATE_END(control, SchemaCompilerControlLabel); - } else if (IS_STEP(SchemaCompilerControlMark)) { - SOURCEMETA_TRACE_START(trace_id, "SchemaCompilerControlMark"); - const auto &control{std::get(step)}; - context.mark(control.value, control.children); - SOURCEMETA_TRACE_END(trace_id, "SchemaCompilerControlMark"); - return true; - } else if (IS_STEP(SchemaCompilerControlJump)) { - EVALUATE_BEGIN_NO_PRECONDITION(control, SchemaCompilerControlJump); - result = true; - for (const auto &child : context.jump(control.value)) { - if (!evaluate_step(child, mode, callback, context)) { - result = false; - break; - } - } - - EVALUATE_END(control, SchemaCompilerControlJump); - } else if (IS_STEP(SchemaCompilerControlDynamicAnchorJump)) { - EVALUATE_BEGIN_NO_PRECONDITION(control, - SchemaCompilerControlDynamicAnchorJump); - const auto id{context.find_dynamic_anchor(control.value)}; - result = id.has_value(); - if (id.has_value()) { - for (const auto &child : context.jump(id.value())) { - if (!evaluate_step(child, mode, callback, context)) { - result = false; - break; - } - } - } - - EVALUATE_END(control, SchemaCompilerControlDynamicAnchorJump); - } else if (IS_STEP(SchemaCompilerAnnotationEmit)) { - EVALUATE_ANNOTATION_NO_PRECONDITION( - annotation, SchemaCompilerAnnotationEmit, context.instance_location(), - annotation.value); - } else if (IS_STEP(SchemaCompilerAnnotationWhenArraySizeEqual)) { - EVALUATE_ANNOTATION(annotation, SchemaCompilerAnnotationWhenArraySizeEqual, - target.is_array() && - target.size() == annotation.value.first, - context.instance_location(), annotation.value.second); - } else if (IS_STEP(SchemaCompilerAnnotationWhenArraySizeGreater)) { - EVALUATE_ANNOTATION( - annotation, SchemaCompilerAnnotationWhenArraySizeGreater, - target.is_array() && target.size() > annotation.value.first, - context.instance_location(), annotation.value.second); - } else if (IS_STEP(SchemaCompilerAnnotationToParent)) { - EVALUATE_ANNOTATION_NO_PRECONDITION( - annotation, SchemaCompilerAnnotationToParent, - // TODO: Can we avoid a copy of the instance location here? - context.instance_location().initial(), annotation.value); - } else if (IS_STEP(SchemaCompilerAnnotationBasenameToParent)) { - EVALUATE_ANNOTATION_NO_PRECONDITION( - annotation, SchemaCompilerAnnotationBasenameToParent, - // TODO: Can we avoid a copy of the instance location here? - context.instance_location().initial(), - context.instance_location().back().to_json()); - } else if (IS_STEP(SchemaCompilerLoopPropertiesMatch)) { - EVALUATE_BEGIN(loop, SchemaCompilerLoopPropertiesMatch, target.is_object()); - assert(!loop.value.empty()); - result = true; - for (const auto &entry : target.as_object()) { - const auto index{loop.value.find(entry.first)}; - if (index == loop.value.cend()) { - continue; - } - - const auto &substep{loop.children[index->second]}; - assert(std::holds_alternative(substep)); - for (const auto &child : - std::get(substep).children) { - if (!evaluate_step(child, mode, callback, context)) { - result = false; - // For efficiently breaking from the outer loop too - goto evaluate_loop_properties_match_end; - } - } - } - - evaluate_loop_properties_match_end: - EVALUATE_END(loop, SchemaCompilerLoopPropertiesMatch); - } else if (IS_STEP(SchemaCompilerLoopProperties)) { - EVALUATE_BEGIN(loop, SchemaCompilerLoopProperties, target.is_object()); - result = true; - for (const auto &entry : target.as_object()) { - context.enter(entry.first); - for (const auto &child : loop.children) { - if (!evaluate_step(child, mode, callback, context)) { - result = false; - context.leave(); - // For efficiently breaking from the outer loop too - goto evaluate_loop_properties_end; - } - } - - context.leave(); - } - - evaluate_loop_properties_end: - EVALUATE_END(loop, SchemaCompilerLoopProperties); - } else if (IS_STEP(SchemaCompilerLoopPropertiesRegex)) { - EVALUATE_BEGIN(loop, SchemaCompilerLoopPropertiesRegex, target.is_object()); - result = true; - for (const auto &entry : target.as_object()) { - if (!std::regex_search(entry.first, loop.value.first)) { - continue; - } - - context.enter(entry.first); - for (const auto &child : loop.children) { - if (!evaluate_step(child, mode, callback, context)) { - result = false; - context.leave(); - // For efficiently breaking from the outer loop too - goto evaluate_loop_properties_regex_end; - } - } - - context.leave(); - } - - evaluate_loop_properties_regex_end: - EVALUATE_END(loop, SchemaCompilerLoopPropertiesRegex); - } else if (IS_STEP(SchemaCompilerLoopPropertiesNoAnnotation)) { - EVALUATE_BEGIN(loop, SchemaCompilerLoopPropertiesNoAnnotation, - target.is_object()); - result = true; - assert(!loop.value.empty()); - - for (const auto &entry : target.as_object()) { - // TODO: It might be more efficient to get all the annotations we - // potentially care about as a set first, and the make the loop - // check for O(1) containment in that set? - if (context.defines_annotation( - context.instance_location(), - // TODO: Can we avoid doing this expensive operation on a loop? - context.evaluate_path().initial(), loop.value, - // TODO: This conversion implies a string copy - JSON{entry.first})) { - continue; - } - - context.enter(entry.first); - for (const auto &child : loop.children) { - if (!evaluate_step(child, mode, callback, context)) { - result = false; - context.leave(); - // For efficiently breaking from the outer loop too - goto evaluate_loop_properties_no_annotation_end; - } - } - - context.leave(); - } - - evaluate_loop_properties_no_annotation_end: - EVALUATE_END(loop, SchemaCompilerLoopPropertiesNoAnnotation); - } else if (IS_STEP(SchemaCompilerLoopPropertiesExcept)) { - EVALUATE_BEGIN(loop, SchemaCompilerLoopPropertiesExcept, - target.is_object()); - result = true; - // Otherwise why emit this instruction? - assert(!loop.value.first.empty() || !loop.value.second.empty()); - - for (const auto &entry : target.as_object()) { - if (loop.value.first.contains(entry.first) || - std::any_of(loop.value.second.cbegin(), loop.value.second.cend(), - [&entry](const auto &pattern) { - return std::regex_search(entry.first, pattern.first); - })) { - continue; - } - - context.enter(entry.first); - for (const auto &child : loop.children) { - if (!evaluate_step(child, mode, callback, context)) { - result = false; - context.leave(); - // For efficiently breaking from the outer loop too - goto evaluate_loop_properties_except_end; - } - } - - context.leave(); - } - - evaluate_loop_properties_except_end: - EVALUATE_END(loop, SchemaCompilerLoopPropertiesExcept); - } else if (IS_STEP(SchemaCompilerLoopKeys)) { - EVALUATE_BEGIN(loop, SchemaCompilerLoopKeys, target.is_object()); - result = true; - context.target_type(EvaluationContext::TargetType::Key); - for (const auto &entry : target.as_object()) { - context.enter(entry.first); - for (const auto &child : loop.children) { - if (!evaluate_step(child, mode, callback, context)) { - result = false; - context.leave(); - goto evaluate_loop_keys_end; - } - } - - context.leave(); - } - - evaluate_loop_keys_end: - context.target_type(EvaluationContext::TargetType::Value); - EVALUATE_END(loop, SchemaCompilerLoopKeys); - } else if (IS_STEP(SchemaCompilerLoopItems)) { - EVALUATE_BEGIN(loop, SchemaCompilerLoopItems, target.is_array()); - const auto &array{target.as_array()}; - result = true; - auto iterator{array.cbegin()}; - - // We need this check, as advancing an iterator past its bounds - // is considered undefined behavior - // See https://en.cppreference.com/w/cpp/iterator/advance - std::advance(iterator, - std::min(static_cast(loop.value), - static_cast(target.size()))); - - for (; iterator != array.cend(); ++iterator) { - const auto index{std::distance(array.cbegin(), iterator)}; - context.enter(static_cast(index)); - for (const auto &child : loop.children) { - if (!evaluate_step(child, mode, callback, context)) { - result = false; - context.leave(); - goto evaluate_compiler_loop_items_end; - } - } - - context.leave(); - } - - evaluate_compiler_loop_items_end: - EVALUATE_END(loop, SchemaCompilerLoopItems); - } else if (IS_STEP(SchemaCompilerLoopItemsUnmarked)) { - EVALUATE_BEGIN(loop, SchemaCompilerLoopItemsUnmarked, - target.is_array() && - !context.defines_annotation(context.instance_location(), - context.evaluate_path(), - loop.value, JSON{true})); - // Otherwise you shouldn't be using this step? - assert(!loop.value.empty()); - const auto &array{target.as_array()}; - result = true; - - for (auto iterator = array.cbegin(); iterator != array.cend(); ++iterator) { - const auto index{std::distance(array.cbegin(), iterator)}; - context.enter(static_cast(index)); - for (const auto &child : loop.children) { - if (!evaluate_step(child, mode, callback, context)) { - result = false; - context.leave(); - goto evaluate_compiler_loop_items_unmarked_end; - } - } - - context.leave(); - } - - evaluate_compiler_loop_items_unmarked_end: - EVALUATE_END(loop, SchemaCompilerLoopItemsUnmarked); - } else if (IS_STEP(SchemaCompilerLoopItemsUnevaluated)) { - // TODO: This precondition is very expensive due to pointer manipulation - EVALUATE_BEGIN(loop, SchemaCompilerLoopItemsUnevaluated, - target.is_array() && !context.defines_annotation( - context.instance_location(), - context.evaluate_path().initial(), - loop.value.mask, JSON{true})); - const auto &array{target.as_array()}; - result = true; - auto iterator{array.cbegin()}; - - // Determine the proper start based on integer annotations collected for the - // current instance location by the keyword requested by the user. - const std::uint64_t start{context.largest_annotation_index( - context.instance_location(), {loop.value.index}, 0)}; - - // We need this check, as advancing an iterator past its bounds - // is considered undefined behavior - // See https://en.cppreference.com/w/cpp/iterator/advance - std::advance(iterator, - std::min(static_cast(start), - static_cast(target.size()))); - - for (; iterator != array.cend(); ++iterator) { - const auto index{std::distance(array.cbegin(), iterator)}; - - if (context.defines_annotation( - context.instance_location(), - // TODO: Can we avoid doing this expensive operation on a loop? - context.evaluate_path().initial(), loop.value.filter, - JSON{static_cast(index)})) { - continue; - } - - context.enter(static_cast(index)); - for (const auto &child : loop.children) { - if (!evaluate_step(child, mode, callback, context)) { - result = false; - context.leave(); - goto evaluate_compiler_loop_items_unevaluated_end; - } - } - - context.leave(); - } - - evaluate_compiler_loop_items_unevaluated_end: - EVALUATE_END(loop, SchemaCompilerLoopItemsUnevaluated); - } else if (IS_STEP(SchemaCompilerLoopContains)) { - EVALUATE_BEGIN(loop, SchemaCompilerLoopContains, target.is_array()); - const auto minimum{std::get<0>(loop.value)}; - const auto &maximum{std::get<1>(loop.value)}; - assert(!maximum.has_value() || maximum.value() >= minimum); - const auto is_exhaustive{std::get<2>(loop.value)}; - result = minimum == 0 && target.empty(); - const auto &array{target.as_array()}; - auto match_count{std::numeric_limits::min()}; - for (auto iterator = array.cbegin(); iterator != array.cend(); ++iterator) { - const auto index{std::distance(array.cbegin(), iterator)}; - context.enter(static_cast(index)); - bool subresult{true}; - for (const auto &child : loop.children) { - if (!evaluate_step(child, mode, callback, context)) { - subresult = false; - break; - } - } - - context.leave(); - - if (subresult) { - match_count += 1; - - // Exceeding the upper bound is definitely a failure - if (maximum.has_value() && match_count > maximum.value()) { - result = false; - - // Note that here we don't want to consider whether to run - // exhaustively or not. At this point, its already a failure, - // and anything that comes after would not run at all anyway - break; - } - - if (match_count >= minimum) { - result = true; - - // Exceeding the lower bound when there is no upper bound - // is definitely a success - if (!maximum.has_value() && !is_exhaustive) { - break; - } - } - } - } - - EVALUATE_END(loop, SchemaCompilerLoopContains); - } - -#undef STRINGIFY -#undef IS_STEP -#undef EVALUATE_BEGIN -#undef EVALUATE_BEGIN_NO_TARGET -#undef EVALUATE_BEGIN_NO_PRECONDITION -#undef EVALUATE_END -#undef EVALUATE_ANNOTATION -#undef EVALUATE_ANNOTATION_NO_PRECONDITION - // We should never get here - assert(false); - return false; -} - -inline auto evaluate_internal( - const sourcemeta::jsontoolkit::SchemaCompilerTemplate &steps, - const sourcemeta::jsontoolkit::JSON &instance, - const sourcemeta::jsontoolkit::SchemaCompilerEvaluationMode mode, - const std::optional< - sourcemeta::jsontoolkit::SchemaCompilerEvaluationCallback> &callback) - -> bool { - EvaluationContext context{instance}; - bool overall{true}; - for (const auto &step : steps) { - if (!evaluate_step(step, mode, callback, context)) { - overall = false; - break; - } - } - - // The evaluation path and instance location must be empty by the time - // we are done, otherwise there was a frame push/pop mismatch - assert(context.evaluate_path().empty()); - assert(context.instance_location().empty()); - assert(context.resources().empty()); - // We should end up at the root of the instance - assert(context.instances().size() == 1); - return overall; -} - -} // namespace - -namespace sourcemeta::jsontoolkit { - -auto evaluate(const SchemaCompilerTemplate &steps, const JSON &instance, - const SchemaCompilerEvaluationMode mode, - const SchemaCompilerEvaluationCallback &callback) -> bool { - return evaluate_internal(steps, instance, mode, callback); -} - -auto evaluate(const SchemaCompilerTemplate &steps, - const JSON &instance) -> bool { - return evaluate_internal(steps, instance, - // Otherwise what's the point of an exhaustive - // evaluation if you don't get the results? - SchemaCompilerEvaluationMode::Fast, std::nullopt); -} - -} // namespace sourcemeta::jsontoolkit diff --git a/vendor/jsontoolkit/src/jsonschema/compile_helpers.h b/vendor/jsontoolkit/src/jsonschema/compile_helpers.h index 89e15b90..e0d66256 100644 --- a/vendor/jsontoolkit/src/jsonschema/compile_helpers.h +++ b/vendor/jsontoolkit/src/jsonschema/compile_helpers.h @@ -17,7 +17,7 @@ auto make(const bool report, const SchemaCompilerContext &context, const SchemaCompilerSchemaContext &schema_context, const SchemaCompilerDynamicContext &dynamic_context, // Take the value type from the "type" property of the step struct - decltype(std::declval().value) &&value) -> Step { + const decltype(std::declval().value) &value) -> Step { return { dynamic_context.keyword.empty() ? dynamic_context.base_schema_location @@ -28,7 +28,7 @@ auto make(const bool report, const SchemaCompilerContext &context, schema_context.base.recompose(), context.uses_dynamic_scopes, report, - std::move(value)}; + value}; } // Instantiate an applicator step @@ -72,6 +72,25 @@ auto unroll(const SchemaCompilerDynamicContext &dynamic_context, std::get(step).value}; } +inline auto unsigned_integer_property(const JSON &document, + const JSON::String &property) + -> std::optional { + if (document.defines(property) && document.at(property).is_integer()) { + const auto value{document.at(property).to_integer()}; + assert(value >= 0); + return static_cast(value); + } + + return std::nullopt; +} + +inline auto unsigned_integer_property(const JSON &document, + const JSON::String &property, + const std::size_t otherwise) + -> std::size_t { + return unsigned_integer_property(document, property).value_or(otherwise); +} + } // namespace sourcemeta::jsontoolkit #endif diff --git a/vendor/jsontoolkit/src/jsonschema/compile_json.cc b/vendor/jsontoolkit/src/jsonschema/compile_json.cc index 6903eab5..c7071002 100644 --- a/vendor/jsontoolkit/src/jsonschema/compile_json.cc +++ b/vendor/jsontoolkit/src/jsonschema/compile_json.cc @@ -217,6 +217,12 @@ struct StepVisitor { HANDLE_STEP("assertion", "type-strict", SchemaCompilerAssertionTypeStrict) HANDLE_STEP("assertion", "type-strict-any", SchemaCompilerAssertionTypeStrictAny) + HANDLE_STEP("assertion", "type-string-bounded", + SchemaCompilerAssertionTypeStringBounded) + HANDLE_STEP("assertion", "type-array-bounded", + SchemaCompilerAssertionTypeArrayBounded) + HANDLE_STEP("assertion", "type-object-bounded", + SchemaCompilerAssertionTypeObjectBounded) HANDLE_STEP("assertion", "regex", SchemaCompilerAssertionRegex) HANDLE_STEP("assertion", "string-size-less", SchemaCompilerAssertionStringSizeLess) @@ -271,6 +277,9 @@ struct StepVisitor { HANDLE_STEP("loop", "properties-no-annotation", SchemaCompilerLoopPropertiesNoAnnotation) HANDLE_STEP("loop", "properties-except", SchemaCompilerLoopPropertiesExcept) + HANDLE_STEP("loop", "properties-type", SchemaCompilerLoopPropertiesType) + HANDLE_STEP("loop", "properties-type-strict", + SchemaCompilerLoopPropertiesTypeStrict) HANDLE_STEP("loop", "keys", SchemaCompilerLoopKeys) HANDLE_STEP("loop", "items", SchemaCompilerLoopItems) HANDLE_STEP("loop", "items-unmarked", SchemaCompilerLoopItemsUnmarked) @@ -310,17 +319,14 @@ auto compiler_template_format_compare(const JSON::String &left, {"absoluteKeywordLocation", 4}, {"relativeSchemaLocation", 5}, {"relativeInstanceLocation", 6}, - {"location", 7}, - {"report", 8}, - {"dynamic", 9}, - {"children", 10}}; + {"report", 7}, + {"dynamic", 8}, + {"children", 9}}; - // We define and control all of these keywords, so if we are missing - // some here, then we did something wrong? - assert(rank.contains(left)); - assert(rank.contains(right)); - - return rank.at(left) < rank.at(right); + constexpr std::uint64_t DEFAULT_RANK{999}; + const auto left_rank{rank.contains(left) ? rank.at(left) : DEFAULT_RANK}; + const auto right_rank{rank.contains(right) ? rank.at(right) : DEFAULT_RANK}; + return left_rank < right_rank; } } // namespace sourcemeta::jsontoolkit diff --git a/vendor/jsontoolkit/src/jsonschema/default_compiler_2019_09.h b/vendor/jsontoolkit/src/jsonschema/default_compiler_2019_09.h index fc66bc3e..36c4957b 100644 --- a/vendor/jsontoolkit/src/jsonschema/default_compiler_2019_09.h +++ b/vendor/jsontoolkit/src/jsonschema/default_compiler_2019_09.h @@ -108,8 +108,8 @@ auto compiler_2019_09_core_annotation( auto compiler_2019_09_applicator_contains_conditional_annotate( const SchemaCompilerContext &context, const SchemaCompilerSchemaContext &schema_context, - const SchemaCompilerDynamicContext &dynamic_context, - const bool annotate) -> SchemaCompilerTemplate { + const SchemaCompilerDynamicContext &dynamic_context, const bool annotate) + -> SchemaCompilerTemplate { if (schema_context.schema.defines("type") && schema_context.schema.at("type").is_string() && schema_context.schema.at("type").to_string() != "array") { diff --git a/vendor/jsontoolkit/src/jsonschema/default_compiler_draft4.h b/vendor/jsontoolkit/src/jsonschema/default_compiler_draft4.h index b3af97ec..79aada2e 100644 --- a/vendor/jsontoolkit/src/jsonschema/default_compiler_draft4.h +++ b/vendor/jsontoolkit/src/jsonschema/default_compiler_draft4.h @@ -15,10 +15,11 @@ namespace internal { using namespace sourcemeta::jsontoolkit; -auto compiler_draft4_core_ref(const SchemaCompilerContext &context, - const SchemaCompilerSchemaContext &schema_context, - const SchemaCompilerDynamicContext - &dynamic_context) -> SchemaCompilerTemplate { +auto compiler_draft4_core_ref( + const SchemaCompilerContext &context, + const SchemaCompilerSchemaContext &schema_context, + const SchemaCompilerDynamicContext &dynamic_context) + -> SchemaCompilerTemplate { // Determine the label const auto type{ReferenceType::Static}; const auto current{ @@ -84,7 +85,7 @@ auto compiler_draft4_core_ref(const SchemaCompilerContext &context, return {make( true, context, schema_context, dynamic_context, SchemaCompilerValueUnsignedInteger{label}, - compile(context, std::move(new_schema_context), relative_dynamic_context, + compile(context, new_schema_context, relative_dynamic_context, empty_pointer, empty_pointer, reference.destination))}; } @@ -103,9 +104,29 @@ auto compiler_draft4_validation_type( return {make( true, context, schema_context, dynamic_context, JSON::Type::Boolean)}; } else if (type == "object") { + const auto minimum{ + unsigned_integer_property(schema_context.schema, "minProperties", 0)}; + const auto maximum{ + unsigned_integer_property(schema_context.schema, "maxProperties")}; + if (minimum > 0 || maximum.has_value()) { + return {make( + true, context, schema_context, dynamic_context, + {minimum, maximum, false})}; + } + return {make( true, context, schema_context, dynamic_context, JSON::Type::Object)}; } else if (type == "array") { + const auto minimum{ + unsigned_integer_property(schema_context.schema, "minItems", 0)}; + const auto maximum{ + unsigned_integer_property(schema_context.schema, "maxItems")}; + if (minimum > 0 || maximum.has_value()) { + return {make( + true, context, schema_context, dynamic_context, + {minimum, maximum, false})}; + } + return {make( true, context, schema_context, dynamic_context, JSON::Type::Array)}; } else if (type == "number") { @@ -116,6 +137,16 @@ auto compiler_draft4_validation_type( return {make( true, context, schema_context, dynamic_context, JSON::Type::Integer)}; } else if (type == "string") { + const auto minimum{ + unsigned_integer_property(schema_context.schema, "minLength", 0)}; + const auto maximum{ + unsigned_integer_property(schema_context.schema, "maxLength")}; + if (minimum > 0 || maximum.has_value()) { + return {make( + true, context, schema_context, dynamic_context, + {minimum, maximum, false})}; + } + return {make( true, context, schema_context, dynamic_context, JSON::Type::String)}; } else { @@ -257,8 +288,8 @@ auto compiler_draft4_applicator_allof( auto compiler_draft4_applicator_anyof_conditional_exhaustive( const SchemaCompilerContext &context, const SchemaCompilerSchemaContext &schema_context, - const SchemaCompilerDynamicContext &dynamic_context, - const bool exhaustive) -> SchemaCompilerTemplate { + const SchemaCompilerDynamicContext &dynamic_context, const bool exhaustive) + -> SchemaCompilerTemplate { assert(schema_context.schema.at(dynamic_context.keyword).is_array()); assert(!schema_context.schema.at(dynamic_context.keyword).empty()); @@ -314,8 +345,8 @@ auto compiler_draft4_applicator_oneof( auto compiler_draft4_applicator_properties_conditional_annotation( const SchemaCompilerContext &context, const SchemaCompilerSchemaContext &schema_context, - const SchemaCompilerDynamicContext &dynamic_context, - const bool annotate) -> SchemaCompilerTemplate { + const SchemaCompilerDynamicContext &dynamic_context, const bool annotate) + -> SchemaCompilerTemplate { assert(schema_context.schema.at(dynamic_context.keyword).is_object()); if (schema_context.schema.at(dynamic_context.keyword).empty()) { return {}; @@ -376,7 +407,7 @@ auto compiler_draft4_applicator_properties_conditional_annotation( is_required <= (size / 2) && // If `properties` only defines a relatively small amount of properties, // then its probably still faster to unroll - schema_context.schema.at(dynamic_context.keyword).size() > 3}; + schema_context.schema.at(dynamic_context.keyword).size() > 5}; if (prefer_loop_over_instance) { SchemaCompilerValueNamedIndexes indexes; @@ -497,8 +528,8 @@ auto compiler_draft4_applicator_properties( auto compiler_draft4_applicator_patternproperties_conditional_annotation( const SchemaCompilerContext &context, const SchemaCompilerSchemaContext &schema_context, - const SchemaCompilerDynamicContext &dynamic_context, - const bool annotate) -> SchemaCompilerTemplate { + const SchemaCompilerDynamicContext &dynamic_context, const bool annotate) + -> SchemaCompilerTemplate { assert(schema_context.schema.at(dynamic_context.keyword).is_object()); if (schema_context.schema.at(dynamic_context.keyword).empty()) { return {}; @@ -570,8 +601,8 @@ auto compiler_draft4_applicator_patternproperties( auto compiler_draft4_applicator_additionalproperties_conditional_annotation( const SchemaCompilerContext &context, const SchemaCompilerSchemaContext &schema_context, - const SchemaCompilerDynamicContext &dynamic_context, - const bool annotate) -> SchemaCompilerTemplate { + const SchemaCompilerDynamicContext &dynamic_context, const bool annotate) + -> SchemaCompilerTemplate { if (schema_context.schema.defines("type") && schema_context.schema.at("type").is_string() && schema_context.schema.at("type").to_string() != "object") { @@ -612,6 +643,24 @@ auto compiler_draft4_applicator_additionalproperties_conditional_annotation( true, context, schema_context, dynamic_context, std::move(filter), std::move(children))}; } else { + if (children.size() == 1) { + // Optimize `additionalProperties` set to just `type`, which is a + // pretty common pattern + if (std::holds_alternative( + children.front())) { + const auto &type_step{ + std::get(children.front())}; + return {make( + true, context, schema_context, dynamic_context, type_step.value)}; + } else if (std::holds_alternative( + children.front())) { + const auto &type_step{ + std::get(children.front())}; + return {make( + true, context, schema_context, dynamic_context, type_step.value)}; + } + } + return {make( true, context, schema_context, dynamic_context, SchemaCompilerValueNone{}, std::move(children))}; @@ -709,8 +758,8 @@ auto compiler_draft4_applicator_not( auto compiler_draft4_applicator_items_array( const SchemaCompilerContext &context, const SchemaCompilerSchemaContext &schema_context, - const SchemaCompilerDynamicContext &dynamic_context, - const bool annotate) -> SchemaCompilerTemplate { + const SchemaCompilerDynamicContext &dynamic_context, const bool annotate) + -> SchemaCompilerTemplate { assert(schema_context.schema.at(dynamic_context.keyword).is_array()); const auto items_size{ schema_context.schema.at(dynamic_context.keyword).size()}; @@ -789,8 +838,8 @@ auto compiler_draft4_applicator_items_array( auto compiler_draft4_applicator_items_conditional_annotation( const SchemaCompilerContext &context, const SchemaCompilerSchemaContext &schema_context, - const SchemaCompilerDynamicContext &dynamic_context, - const bool annotate) -> SchemaCompilerTemplate { + const SchemaCompilerDynamicContext &dynamic_context, const bool annotate) + -> SchemaCompilerTemplate { if (schema_context.schema.defines("type") && schema_context.schema.at("type").is_string() && schema_context.schema.at("type").to_string() != "array") { @@ -863,8 +912,8 @@ auto compiler_draft4_applicator_additionalitems_from_cursor( auto compiler_draft4_applicator_additionalitems_conditional_annotation( const SchemaCompilerContext &context, const SchemaCompilerSchemaContext &schema_context, - const SchemaCompilerDynamicContext &dynamic_context, - const bool annotate) -> SchemaCompilerTemplate { + const SchemaCompilerDynamicContext &dynamic_context, const bool annotate) + -> SchemaCompilerTemplate { if (schema_context.schema.defines("type") && schema_context.schema.at("type").is_string() && schema_context.schema.at("type").to_string() != "array") { @@ -1005,8 +1054,13 @@ auto compiler_draft4_validation_maxlength( return {}; } - // TODO: As an optimization, if `minLength` is set to the same number, do - // a single size equality assertion + // We'll handle it at the type level as an optimization + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() == "string") { + return {}; + } + return {make( true, context, schema_context, dynamic_context, SchemaCompilerValueUnsignedInteger{ @@ -1030,8 +1084,13 @@ auto compiler_draft4_validation_minlength( return {}; } - // TODO: As an optimization, if `maxLength` is set to the same number, do - // a single size equality assertion + // We'll handle it at the type level as an optimization + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() == "string") { + return {}; + } + return {make( true, context, schema_context, dynamic_context, SchemaCompilerValueUnsignedInteger{ @@ -1055,8 +1114,13 @@ auto compiler_draft4_validation_maxitems( return {}; } - // TODO: As an optimization, if `minItems` is set to the same number, do - // a single size equality assertion + // We'll handle it at the type level as an optimization + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() == "array") { + return {}; + } + return {make( true, context, schema_context, dynamic_context, SchemaCompilerValueUnsignedInteger{ @@ -1080,8 +1144,13 @@ auto compiler_draft4_validation_minitems( return {}; } - // TODO: As an optimization, if `maxItems` is set to the same number, do - // a single size equality assertion + // We'll handle it at the type level as an optimization + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() == "array") { + return {}; + } + return {make( true, context, schema_context, dynamic_context, SchemaCompilerValueUnsignedInteger{ @@ -1105,8 +1174,13 @@ auto compiler_draft4_validation_maxproperties( return {}; } - // TODO: As an optimization, if `minProperties` is set to the same number, do - // a single size equality assertion + // We'll handle it at the type level as an optimization + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() == "object") { + return {}; + } + return {make( true, context, schema_context, dynamic_context, SchemaCompilerValueUnsignedInteger{ @@ -1130,8 +1204,13 @@ auto compiler_draft4_validation_minproperties( return {}; } - // TODO: As an optimization, if `maxProperties` is set to the same number, do - // a single size equality assertion + // We'll handle it at the type level as an optimization + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() == "object") { + return {}; + } + return {make( true, context, schema_context, dynamic_context, SchemaCompilerValueUnsignedInteger{ diff --git a/vendor/jsontoolkit/src/jsonschema/default_compiler_draft6.h b/vendor/jsontoolkit/src/jsonschema/default_compiler_draft6.h index f94fac3a..977202cd 100644 --- a/vendor/jsontoolkit/src/jsonschema/default_compiler_draft6.h +++ b/vendor/jsontoolkit/src/jsonschema/default_compiler_draft6.h @@ -24,9 +24,29 @@ auto compiler_draft6_validation_type( return {make( true, context, schema_context, dynamic_context, JSON::Type::Boolean)}; } else if (type == "object") { + const auto minimum{ + unsigned_integer_property(schema_context.schema, "minProperties", 0)}; + const auto maximum{ + unsigned_integer_property(schema_context.schema, "maxProperties")}; + if (minimum > 0 || maximum.has_value()) { + return {make( + true, context, schema_context, dynamic_context, + {minimum, maximum, false})}; + } + return {make( true, context, schema_context, dynamic_context, JSON::Type::Object)}; } else if (type == "array") { + const auto minimum{ + unsigned_integer_property(schema_context.schema, "minItems", 0)}; + const auto maximum{ + unsigned_integer_property(schema_context.schema, "maxItems")}; + if (minimum > 0 || maximum.has_value()) { + return {make( + true, context, schema_context, dynamic_context, + {minimum, maximum, false})}; + } + return {make( true, context, schema_context, dynamic_context, JSON::Type::Array)}; } else if (type == "number") { @@ -37,6 +57,16 @@ auto compiler_draft6_validation_type( return {make( true, context, schema_context, dynamic_context, JSON::Type::Integer)}; } else if (type == "string") { + const auto minimum{ + unsigned_integer_property(schema_context.schema, "minLength", 0)}; + const auto maximum{ + unsigned_integer_property(schema_context.schema, "maxLength")}; + if (minimum > 0 || maximum.has_value()) { + return {make( + true, context, schema_context, dynamic_context, + {minimum, maximum, false})}; + } + return {make( true, context, schema_context, dynamic_context, JSON::Type::String)}; } else { diff --git a/vendor/jsontoolkit/src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema.h b/vendor/jsontoolkit/src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema.h index 502385c7..4218c8fe 100644 --- a/vendor/jsontoolkit/src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema.h +++ b/vendor/jsontoolkit/src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema.h @@ -46,7 +46,7 @@ auto is_schema(const JSON &schema) -> bool; /// @ingroup jsonschema /// The strategy to follow when attempting to identify a schema -enum class IdentificationStrategy { +enum class IdentificationStrategy : std::uint8_t { /// Only proceed if we can guarantee the identifier is valid Strict, @@ -316,8 +316,8 @@ auto vocabularies(const SchemaResolver &resolver, /// std::cout << stream.str() << std::endl; /// ``` SOURCEMETA_JSONTOOLKIT_JSONSCHEMA_EXPORT -auto schema_format_compare(const JSON::String &left, - const JSON::String &right) -> bool; +auto schema_format_compare(const JSON::String &left, const JSON::String &right) + -> bool; } // namespace sourcemeta::jsontoolkit diff --git a/vendor/jsontoolkit/src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema_anchor.h b/vendor/jsontoolkit/src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema_anchor.h index c6e23af3..5a0dcea9 100644 --- a/vendor/jsontoolkit/src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema_anchor.h +++ b/vendor/jsontoolkit/src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema_anchor.h @@ -6,6 +6,7 @@ #include #include +#include // std::uint8_t #include // std::promise, std::future #include // std::map #include // std::optional @@ -15,7 +16,7 @@ namespace sourcemeta::jsontoolkit { /// @ingroup jsonschema /// The anchor type -enum class AnchorType { Static, Dynamic, All }; +enum class AnchorType : std::uint8_t { Static, Dynamic, All }; /// @ingroup jsonschema /// diff --git a/vendor/jsontoolkit/src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema_bundle.h b/vendor/jsontoolkit/src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema_bundle.h index 1a815191..97822e53 100644 --- a/vendor/jsontoolkit/src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema_bundle.h +++ b/vendor/jsontoolkit/src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema_bundle.h @@ -7,6 +7,7 @@ #include #include +#include // std::uint8_t #include // std::future #include // std::optional, std::nullopt #include // std::string @@ -17,7 +18,7 @@ namespace sourcemeta::jsontoolkit { /// @ingroup jsonschema /// A set of options that modify the behavior of bundling -enum class BundleOptions { +enum class BundleOptions : std::uint8_t { /// Perform standard JSON Schema bundling Default, diff --git a/vendor/jsontoolkit/src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema_compile.h b/vendor/jsontoolkit/src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema_compile.h index 1b7b6199..3f7b3690 100644 --- a/vendor/jsontoolkit/src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema_compile.h +++ b/vendor/jsontoolkit/src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema_compile.h @@ -3,6 +3,7 @@ #include "jsonschema_export.h" +#include #include #include #include @@ -11,17 +12,12 @@ #include #include -#include // std::function -#include // std::map -#include // std::optional, std::nullopt -#include // std::regex -#include // std::set -#include // std::string -#include // std::tuple -#include // std::unordered_map -#include // std::move, std::pair -#include // std::variant -#include // std::vector +#include // std::function +#include // std::map +#include // std::optional, std::nullopt +#include // std::set +#include // std::string +#include // std::vector /// @ingroup jsonschema /// @defgroup jsonschema_compiler Compiler @@ -30,511 +26,6 @@ namespace sourcemeta::jsontoolkit { -/// @ingroup jsonschema_compiler -/// @brief Represents a compiler step empty value -struct SchemaCompilerValueNone {}; - -/// @ingroup jsonschema_compiler -/// Represents a compiler step JSON value -using SchemaCompilerValueJSON = JSON; - -// Note that for these steps, we prefer vectors over sets as the former performs -// better for small collections, where we can even guarantee uniqueness when -// generating the instructions - -/// @ingroup jsonschema_compiler -/// Represents a set of JSON values -using SchemaCompilerValueArray = std::vector; - -/// @ingroup jsonschema_compiler -/// Represents a compiler step string values -using SchemaCompilerValueStrings = std::vector; - -/// @ingroup jsonschema_compiler -/// Represents a compiler step JSON types value -using SchemaCompilerValueTypes = std::vector; - -/// @ingroup jsonschema_compiler -/// Represents a compiler step string value -using SchemaCompilerValueString = JSON::String; - -/// @ingroup jsonschema_compiler -/// Represents a compiler step JSON type value -using SchemaCompilerValueType = JSON::Type; - -/// @ingroup jsonschema_compiler -/// Represents a compiler step ECMA regular expression value. We store both the -/// original string and the regular expression as standard regular expressions -/// do not keep a copy of their original value (which we need for serialization -/// purposes) -using SchemaCompilerValueRegex = std::pair; - -/// @ingroup jsonschema_compiler -/// Represents a compiler step JSON unsigned integer value -using SchemaCompilerValueUnsignedInteger = std::size_t; - -/// @ingroup jsonschema_compiler -/// Represents a compiler step range value. The boolean option -/// modifies whether the range is considered exhaustively or -/// if the evaluator is allowed to break early -using SchemaCompilerValueRange = - std::tuple, bool>; - -/// @ingroup jsonschema_compiler -/// Represents a compiler step boolean value -using SchemaCompilerValueBoolean = bool; - -/// @ingroup jsonschema_compiler -/// Represents a compiler step string to index map -using SchemaCompilerValueNamedIndexes = - std::unordered_map; - -/// @ingroup jsonschema_compiler -/// Represents a compiler step string logical type -enum class SchemaCompilerValueStringType { URI }; - -/// @ingroup jsonschema_compiler -/// Represents an array loop compiler step annotation keywords -struct SchemaCompilerValueItemsAnnotationKeywords { - const SchemaCompilerValueString index; - const SchemaCompilerValueStrings filter; - const SchemaCompilerValueStrings mask; -}; - -/// @ingroup jsonschema_compiler -/// Represents an compiler step that maps strings to strings -using SchemaCompilerValueStringMap = - std::unordered_map; - -/// @ingroup jsonschema_compiler -/// Represents a compiler step JSON value accompanied with an index -using SchemaCompilerValueIndexedJSON = - std::pair; - -// Note that while we generally avoid sets, in this case, we want -// hash-based lookups on string collections that might get large. -/// @ingroup jsonschema_compiler -/// Represents a compiler step value that consist of object property filters -using SchemaCompilerValuePropertyFilter = - std::pair, - std::vector>; - -// Forward declarations for the sole purpose of being bale to define circular -// structures -#ifndef DOXYGEN -struct SchemaCompilerAssertionFail; -struct SchemaCompilerAssertionDefines; -struct SchemaCompilerAssertionDefinesAll; -struct SchemaCompilerAssertionPropertyDependencies; -struct SchemaCompilerAssertionType; -struct SchemaCompilerAssertionTypeAny; -struct SchemaCompilerAssertionTypeStrict; -struct SchemaCompilerAssertionTypeStrictAny; -struct SchemaCompilerAssertionRegex; -struct SchemaCompilerAssertionStringSizeLess; -struct SchemaCompilerAssertionStringSizeGreater; -struct SchemaCompilerAssertionArraySizeLess; -struct SchemaCompilerAssertionArraySizeGreater; -struct SchemaCompilerAssertionObjectSizeLess; -struct SchemaCompilerAssertionObjectSizeGreater; -struct SchemaCompilerAssertionEqual; -struct SchemaCompilerAssertionEqualsAny; -struct SchemaCompilerAssertionGreaterEqual; -struct SchemaCompilerAssertionLessEqual; -struct SchemaCompilerAssertionGreater; -struct SchemaCompilerAssertionLess; -struct SchemaCompilerAssertionUnique; -struct SchemaCompilerAssertionDivisible; -struct SchemaCompilerAssertionStringType; -struct SchemaCompilerAssertionPropertyType; -struct SchemaCompilerAssertionPropertyTypeStrict; -struct SchemaCompilerAnnotationEmit; -struct SchemaCompilerAnnotationWhenArraySizeEqual; -struct SchemaCompilerAnnotationWhenArraySizeGreater; -struct SchemaCompilerAnnotationToParent; -struct SchemaCompilerAnnotationBasenameToParent; -struct SchemaCompilerLogicalOr; -struct SchemaCompilerLogicalAnd; -struct SchemaCompilerLogicalXor; -struct SchemaCompilerLogicalTryMark; -struct SchemaCompilerLogicalNot; -struct SchemaCompilerLogicalWhenType; -struct SchemaCompilerLogicalWhenDefines; -struct SchemaCompilerLogicalWhenAdjacentUnmarked; -struct SchemaCompilerLogicalWhenAdjacentMarked; -struct SchemaCompilerLogicalWhenArraySizeGreater; -struct SchemaCompilerLogicalWhenArraySizeEqual; -struct SchemaCompilerLoopPropertiesMatch; -struct SchemaCompilerLoopProperties; -struct SchemaCompilerLoopPropertiesRegex; -struct SchemaCompilerLoopPropertiesNoAnnotation; -struct SchemaCompilerLoopPropertiesExcept; -struct SchemaCompilerLoopKeys; -struct SchemaCompilerLoopItems; -struct SchemaCompilerLoopItemsUnmarked; -struct SchemaCompilerLoopItemsUnevaluated; -struct SchemaCompilerLoopContains; -struct SchemaCompilerControlLabel; -struct SchemaCompilerControlMark; -struct SchemaCompilerControlJump; -struct SchemaCompilerControlDynamicAnchorJump; -#endif - -/// @ingroup jsonschema_compiler -/// Represents a schema compilation step that can be evaluated -using SchemaCompilerTemplate = std::vector>; - -#define DEFINE_STEP_WITH_VALUE(category, name, type) \ - struct SchemaCompiler##category##name { \ - const Pointer relative_schema_location; \ - const Pointer relative_instance_location; \ - const std::string keyword_location; \ - const std::string schema_resource; \ - const bool dynamic; \ - const bool report; \ - const type value; \ - }; - -#define DEFINE_STEP_APPLICATOR(category, name, type) \ - struct SchemaCompiler##category##name { \ - const Pointer relative_schema_location; \ - const Pointer relative_instance_location; \ - const std::string keyword_location; \ - const std::string schema_resource; \ - const bool dynamic; \ - const bool report; \ - const type value; \ - const SchemaCompilerTemplate children; \ - }; - -/// @defgroup jsonschema_compiler_instructions Instruction Set -/// @ingroup jsonschema_compiler -/// @brief The set of instructions supported by the compiler. -/// @details -/// -/// Every instruction operates at a specific instance location and with the -/// given value, whose type depends on the instruction. - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler assertion step that always fails -DEFINE_STEP_WITH_VALUE(Assertion, Fail, SchemaCompilerValueNone) - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler assertion step that checks if an object defines -/// a given property -DEFINE_STEP_WITH_VALUE(Assertion, Defines, SchemaCompilerValueString) - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler assertion step that checks if an object defines -/// a set of properties -DEFINE_STEP_WITH_VALUE(Assertion, DefinesAll, SchemaCompilerValueStrings) - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler assertion step that checks if an object defines -/// a set of properties if it defines other set of properties -DEFINE_STEP_WITH_VALUE(Assertion, PropertyDependencies, - SchemaCompilerValueStringMap) - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler assertion step that checks if a document is of -/// the given type -DEFINE_STEP_WITH_VALUE(Assertion, Type, SchemaCompilerValueType) - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler assertion step that checks if a document is of -/// any of the given types -DEFINE_STEP_WITH_VALUE(Assertion, TypeAny, SchemaCompilerValueTypes) - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler assertion step that checks if a document is of -/// the given type (strict version) -DEFINE_STEP_WITH_VALUE(Assertion, TypeStrict, SchemaCompilerValueType) - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler assertion step that checks if a document is of -/// any of the given types (strict version) -DEFINE_STEP_WITH_VALUE(Assertion, TypeStrictAny, SchemaCompilerValueTypes) - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler assertion step that checks a string against an -/// ECMA regular expression -DEFINE_STEP_WITH_VALUE(Assertion, Regex, SchemaCompilerValueRegex) - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler assertion step that checks a given string has -/// less than a certain number of characters -DEFINE_STEP_WITH_VALUE(Assertion, StringSizeLess, - SchemaCompilerValueUnsignedInteger) - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler assertion step that checks a given string has -/// greater than a certain number of characters -DEFINE_STEP_WITH_VALUE(Assertion, StringSizeGreater, - SchemaCompilerValueUnsignedInteger) - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler assertion step that checks a given array has -/// less than a certain number of items -DEFINE_STEP_WITH_VALUE(Assertion, ArraySizeLess, - SchemaCompilerValueUnsignedInteger) - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler assertion step that checks a given array has -/// greater than a certain number of items -DEFINE_STEP_WITH_VALUE(Assertion, ArraySizeGreater, - SchemaCompilerValueUnsignedInteger) - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler assertion step that checks a given object has -/// less than a certain number of properties -DEFINE_STEP_WITH_VALUE(Assertion, ObjectSizeLess, - SchemaCompilerValueUnsignedInteger) - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler assertion step that checks a given object has -/// greater than a certain number of properties -DEFINE_STEP_WITH_VALUE(Assertion, ObjectSizeGreater, - SchemaCompilerValueUnsignedInteger) - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler assertion step that checks the instance equals -/// a given JSON document -DEFINE_STEP_WITH_VALUE(Assertion, Equal, SchemaCompilerValueJSON) - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler assertion step that checks that a JSON document -/// is equal to at least one of the given elements -DEFINE_STEP_WITH_VALUE(Assertion, EqualsAny, SchemaCompilerValueArray) - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler assertion step that checks a JSON document is -/// greater than or equal to another JSON document -DEFINE_STEP_WITH_VALUE(Assertion, GreaterEqual, SchemaCompilerValueJSON) - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler assertion step that checks a JSON document is -/// less than or equal to another JSON document -DEFINE_STEP_WITH_VALUE(Assertion, LessEqual, SchemaCompilerValueJSON) - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler assertion step that checks a JSON document is -/// greater than another JSON document -DEFINE_STEP_WITH_VALUE(Assertion, Greater, SchemaCompilerValueJSON) - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler assertion step that checks a JSON document is -/// less than another JSON document -DEFINE_STEP_WITH_VALUE(Assertion, Less, SchemaCompilerValueJSON) - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler assertion step that checks a given JSON array -/// does not contain duplicate items -DEFINE_STEP_WITH_VALUE(Assertion, Unique, SchemaCompilerValueNone) - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler assertion step that checks a number is -/// divisible by another number -DEFINE_STEP_WITH_VALUE(Assertion, Divisible, SchemaCompilerValueJSON) - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler assertion step that checks that a string is of -/// a certain type -DEFINE_STEP_WITH_VALUE(Assertion, StringType, SchemaCompilerValueStringType) - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler assertion step that checks that an instance -/// property is of a given type if present -DEFINE_STEP_WITH_VALUE(Assertion, PropertyType, SchemaCompilerValueType) - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler assertion step that checks that an instance -/// property is of a given type if present (strict mode) -DEFINE_STEP_WITH_VALUE(Assertion, PropertyTypeStrict, SchemaCompilerValueType) - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler step that emits an annotation -DEFINE_STEP_WITH_VALUE(Annotation, Emit, SchemaCompilerValueJSON) - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler step that emits an annotation when the size of -/// the array instance is equal to the given size -DEFINE_STEP_WITH_VALUE(Annotation, WhenArraySizeEqual, - SchemaCompilerValueIndexedJSON) - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler step that emits an annotation when the size of -/// the array instance is greater than the given size -DEFINE_STEP_WITH_VALUE(Annotation, WhenArraySizeGreater, - SchemaCompilerValueIndexedJSON) - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler step that emits an annotation to the parent -DEFINE_STEP_WITH_VALUE(Annotation, ToParent, SchemaCompilerValueJSON) - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler step that emits the current basename as an -/// annotation to the parent -DEFINE_STEP_WITH_VALUE(Annotation, BasenameToParent, SchemaCompilerValueNone) - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler logical step that represents a disjunction -DEFINE_STEP_APPLICATOR(Logical, Or, SchemaCompilerValueBoolean) - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler logical step that represents a conjunction -DEFINE_STEP_APPLICATOR(Logical, And, SchemaCompilerValueNone) - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler logical step that represents an exclusive -/// disjunction -DEFINE_STEP_APPLICATOR(Logical, Xor, SchemaCompilerValueNone) - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler logical step that represents a conjunction that -/// always reports success and marks its outcome for other steps -DEFINE_STEP_APPLICATOR(Logical, TryMark, SchemaCompilerValueNone) - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler logical step that represents a negation -DEFINE_STEP_APPLICATOR(Logical, Not, SchemaCompilerValueNone) - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler logical step that represents a conjunction when -/// the instance is of a given type -DEFINE_STEP_APPLICATOR(Logical, WhenType, SchemaCompilerValueType) - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler logical step that represents a conjunction when -/// the instance is an object and defines a given property -DEFINE_STEP_APPLICATOR(Logical, WhenDefines, SchemaCompilerValueString) - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler logical step that represents a conjunction when -/// the instance and desired evaluation path was not marked -DEFINE_STEP_APPLICATOR(Logical, WhenAdjacentUnmarked, SchemaCompilerValueString) - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler logical step that represents a conjunction when -/// the instance and desired evaluation path was marked -DEFINE_STEP_APPLICATOR(Logical, WhenAdjacentMarked, SchemaCompilerValueString) - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler logical step that represents a conjunction when -/// the array instance size is greater than the given number -DEFINE_STEP_APPLICATOR(Logical, WhenArraySizeGreater, - SchemaCompilerValueUnsignedInteger) - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler logical step that represents a conjunction when -/// the array instance size is equal to the given number -DEFINE_STEP_APPLICATOR(Logical, WhenArraySizeEqual, - SchemaCompilerValueUnsignedInteger) - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler step that matches steps to object properties -DEFINE_STEP_APPLICATOR(Loop, PropertiesMatch, SchemaCompilerValueNamedIndexes) - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler step that loops over object properties -DEFINE_STEP_APPLICATOR(Loop, Properties, SchemaCompilerValueNone) - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler step that loops over object properties that -/// match a given ECMA regular expression -DEFINE_STEP_APPLICATOR(Loop, PropertiesRegex, SchemaCompilerValueRegex) - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler step that loops over object properties that -/// were not collected as annotations -DEFINE_STEP_APPLICATOR(Loop, PropertiesNoAnnotation, SchemaCompilerValueStrings) - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler step that loops over object properties that -/// do not match the given property filters -DEFINE_STEP_APPLICATOR(Loop, PropertiesExcept, - SchemaCompilerValuePropertyFilter) - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler step that loops over object property keys -DEFINE_STEP_APPLICATOR(Loop, Keys, SchemaCompilerValueNone) - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler step that loops over array items starting from -/// a given index -DEFINE_STEP_APPLICATOR(Loop, Items, SchemaCompilerValueUnsignedInteger) - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler step that loops over array items when the array -/// is considered unmarked -DEFINE_STEP_APPLICATOR(Loop, ItemsUnmarked, SchemaCompilerValueStrings) - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler step that loops over unevaluated array items -DEFINE_STEP_APPLICATOR(Loop, ItemsUnevaluated, - SchemaCompilerValueItemsAnnotationKeywords) - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler step that checks array items match a given -/// criteria -DEFINE_STEP_APPLICATOR(Loop, Contains, SchemaCompilerValueRange) - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler step that consists of a mark to jump to while -/// executing children instructions -DEFINE_STEP_APPLICATOR(Control, Label, SchemaCompilerValueUnsignedInteger) - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler step that consists of a mark to jump to, but -/// without executing children instructions -DEFINE_STEP_APPLICATOR(Control, Mark, SchemaCompilerValueUnsignedInteger) - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler step that consists of jumping into a -/// pre-registered label -DEFINE_STEP_WITH_VALUE(Control, Jump, SchemaCompilerValueUnsignedInteger) - -/// @ingroup jsonschema_compiler_instructions -/// @brief Represents a compiler step that consists of jump to a dynamic anchor -DEFINE_STEP_WITH_VALUE(Control, DynamicAnchorJump, SchemaCompilerValueString) - -#undef DEFINE_STEP_WITH_VALUE -#undef DEFINE_STEP_APPLICATOR - /// @ingroup jsonschema_compiler /// The schema compiler context is the current subschema information you have at /// your disposal to implement a keyword @@ -598,39 +89,6 @@ struct SchemaCompilerContext { const bool uses_dynamic_scopes; }; -/// @ingroup jsonschema_compiler -/// Represents the mode of evalution -enum class SchemaCompilerEvaluationMode { - /// Attempt to get to a boolean result as fast as possible - Fast, - /// Perform a full schema evaluation - Exhaustive -}; - -/// @ingroup jsonschema_compiler -/// Represents the state of a step evaluation -enum class SchemaCompilerEvaluationType { Pre, Post }; - -/// @ingroup jsonschema_compiler -/// A callback of this type is invoked after evaluating any keyword. The -/// arguments go as follows: -/// -/// - The stage at which the step in question is -/// - Whether the evaluation was successful or not (always true before -/// evaluation) -/// - The step that was just evaluated -/// - The evaluation path -/// - The instance location -/// - The annotation result, if any (otherwise null) -/// -/// You can use this callback mechanism to implement arbitrary output formats. -using SchemaCompilerEvaluationCallback = - std::function; - -// TODO: Support standard output formats too - /// @ingroup jsonschema_compiler /// /// A simple evaluation callback that reports a stack trace in the case of @@ -691,8 +149,8 @@ class SOURCEMETA_JSONTOOLKIT_JSONSCHEMA_EXPORT SchemaCompilerErrorTraceOutput { auto operator()(const SchemaCompilerEvaluationType type, const bool result, const SchemaCompilerTemplate::value_type &step, const WeakPointer &evaluate_path, - const WeakPointer &instance_location, - const JSON &annotation) -> void; + const WeakPointer &instance_location, const JSON &annotation) + -> void; using container_type = typename std::vector; using const_iterator = typename container_type::const_iterator; @@ -726,90 +184,6 @@ describe(const bool valid, const SchemaCompilerTemplate::value_type &step, const WeakPointer &evaluate_path, const WeakPointer &instance_location, const JSON &instance, const JSON &annotation) -> std::string; -/// @ingroup jsonschema_compiler -/// -/// This function evaluates a schema compiler template in validation mode, -/// returning a boolean without error information. For example: -/// -/// ```cpp -/// #include -/// #include -/// #include -/// -/// const sourcemeta::jsontoolkit::JSON schema = -/// sourcemeta::jsontoolkit::parse(R"JSON({ -/// "$schema": "https://json-schema.org/draft/2020-12/schema", -/// "type": "string" -/// })JSON"); -/// -/// const auto schema_template{sourcemeta::jsontoolkit::compile( -/// schema, sourcemeta::jsontoolkit::default_schema_walker, -/// sourcemeta::jsontoolkit::official_resolver, -/// sourcemeta::jsontoolkit::default_schema_compiler)}; -/// -/// const sourcemeta::jsontoolkit::JSON instance{"foo bar"}; -/// const auto result{sourcemeta::jsontoolkit::evaluate( -/// schema_template, instance)}; -/// assert(result); -/// ``` -auto SOURCEMETA_JSONTOOLKIT_JSONSCHEMA_EXPORT -evaluate(const SchemaCompilerTemplate &steps, const JSON &instance) -> bool; - -/// @ingroup jsonschema_compiler -/// -/// This function evaluates a schema compiler template, executing the given -/// callback at every step of the way. For example: -/// -/// ```cpp -/// #include -/// #include -/// #include -/// #include -/// -/// const sourcemeta::jsontoolkit::JSON schema = -/// sourcemeta::jsontoolkit::parse(R"JSON({ -/// "$schema": "https://json-schema.org/draft/2020-12/schema", -/// "type": "string" -/// })JSON"); -/// -/// const auto schema_template{sourcemeta::jsontoolkit::compile( -/// schema, sourcemeta::jsontoolkit::default_schema_walker, -/// sourcemeta::jsontoolkit::official_resolver, -/// sourcemeta::jsontoolkit::default_schema_compiler)}; -/// -/// static auto callback( -/// bool result, -/// const sourcemeta::jsontoolkit::SchemaCompilerTemplate::value_type &step, -/// const sourcemeta::jsontoolkit::Pointer &evaluate_path, -/// const sourcemeta::jsontoolkit::Pointer &instance_location, -/// const sourcemeta::jsontoolkit::JSON &document, -/// const sourcemeta::jsontoolkit::JSON &annotation) -> void { -/// std::cout << "TYPE: " << (result ? "Success" : "Failure") << "\n"; -/// std::cout << "STEP:\n"; -/// sourcemeta::jsontoolkit::prettify(sourcemeta::jsontoolkit::to_json({step}), -/// std::cout); -/// std::cout << "\nEVALUATE PATH:"; -/// sourcemeta::jsontoolkit::stringify(evaluate_path, std::cout); -/// std::cout << "\nINSTANCE LOCATION:"; -/// sourcemeta::jsontoolkit::stringify(instance_location, std::cout); -/// std::cout << "\nANNOTATION:\n"; -/// sourcemeta::jsontoolkit::prettify(annotation, std::cout); -/// std::cout << "\n"; -/// } -/// -/// const sourcemeta::jsontoolkit::JSON instance{"foo bar"}; -/// const auto result{sourcemeta::jsontoolkit::evaluate( -/// schema_template, instance, -/// sourcemeta::jsontoolkit::SchemaCompilerEvaluationMode::Fast, -/// callback)}; -/// -/// assert(result); -/// ``` -auto SOURCEMETA_JSONTOOLKIT_JSONSCHEMA_EXPORT -evaluate(const SchemaCompilerTemplate &steps, const JSON &instance, - const SchemaCompilerEvaluationMode mode, - const SchemaCompilerEvaluationCallback &callback) -> bool; - /// @ingroup jsonschema_compiler /// A default compiler that aims to implement every keyword for official JSON /// Schema dialects. diff --git a/vendor/jsontoolkit/src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema_error.h b/vendor/jsontoolkit/src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema_error.h index 8eef6f8a..76f1b10c 100644 --- a/vendor/jsontoolkit/src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema_error.h +++ b/vendor/jsontoolkit/src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema_error.h @@ -99,20 +99,6 @@ class SOURCEMETA_JSONTOOLKIT_JSONSCHEMA_EXPORT SchemaReferenceError std::string message_; }; -/// @ingroup jsonschema -/// An error that represents a schema evaluation error event -class SOURCEMETA_JSONTOOLKIT_JSONSCHEMA_EXPORT SchemaEvaluationError - : public std::exception { -public: - SchemaEvaluationError(std::string message) : message_{std::move(message)} {} - [[nodiscard]] auto what() const noexcept -> const char * override { - return this->message_.c_str(); - } - -private: - std::string message_; -}; - #if defined(_MSC_VER) #pragma warning(default : 4251 4275) #endif diff --git a/vendor/jsontoolkit/src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema_reference.h b/vendor/jsontoolkit/src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema_reference.h index 2781b456..9a42e68f 100644 --- a/vendor/jsontoolkit/src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema_reference.h +++ b/vendor/jsontoolkit/src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema_reference.h @@ -7,6 +7,7 @@ #include #include +#include // std::uint8_t #include // std::future #include // std::map #include // std::optional @@ -18,7 +19,7 @@ namespace sourcemeta::jsontoolkit { /// @ingroup jsonschema /// The reference type -enum class ReferenceType { Static, Dynamic }; +enum class ReferenceType : std::uint8_t { Static, Dynamic }; #if defined(__GNUC__) #pragma GCC diagnostic push @@ -28,7 +29,7 @@ enum class ReferenceType { Static, Dynamic }; #endif /// @ingroup jsonschema /// The reference entry type -enum class ReferenceEntryType { Resource, Anchor, Pointer }; +enum class ReferenceEntryType : std::uint8_t { Resource, Anchor, Pointer }; #if defined(__GNUC__) #pragma GCC diagnostic pop #endif diff --git a/vendor/jsontoolkit/src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema_walker.h b/vendor/jsontoolkit/src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema_walker.h index cbd7bb58..de8e77b8 100644 --- a/vendor/jsontoolkit/src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema_walker.h +++ b/vendor/jsontoolkit/src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema_walker.h @@ -7,6 +7,7 @@ #include #include +#include // std::uint8_t #include // std::function #include // std::map #include // std::optional @@ -26,7 +27,7 @@ namespace sourcemeta::jsontoolkit { #endif /// @ingroup jsonschema /// Determines the possible states of a schema walk strategy -enum class SchemaWalkerStrategy { +enum class SchemaWalkerStrategy : std::uint8_t { /// The JSON Schema keyword is not an applicator None, /// The JSON Schema keyword is an applicator that potentially diff --git a/vendor/jsontoolkit/src/jsonschema/jsonschema.cc b/vendor/jsontoolkit/src/jsonschema/jsonschema.cc index ff71b8af..0d1c3d9b 100644 --- a/vendor/jsontoolkit/src/jsonschema/jsonschema.cc +++ b/vendor/jsontoolkit/src/jsonschema/jsonschema.cc @@ -155,8 +155,9 @@ auto sourcemeta::jsontoolkit::identify( return identifier.to_string(); } -auto sourcemeta::jsontoolkit::anonymize( - JSON &schema, const std::string &base_dialect) -> void { +auto sourcemeta::jsontoolkit::anonymize(JSON &schema, + const std::string &base_dialect) + -> void { if (schema.is_object()) { schema.erase(id_keyword(base_dialect)); } @@ -176,9 +177,10 @@ auto sourcemeta::jsontoolkit::reidentify( reidentify(schema, new_identifier, base_dialect.value()); } -auto sourcemeta::jsontoolkit::reidentify( - JSON &schema, const std::string &new_identifier, - const std::string &base_dialect) -> void { +auto sourcemeta::jsontoolkit::reidentify(JSON &schema, + const std::string &new_identifier, + const std::string &base_dialect) + -> void { assert(is_schema(schema)); assert(schema.is_object()); schema.assign(id_keyword(base_dialect), JSON{new_identifier}); @@ -335,9 +337,10 @@ auto sourcemeta::jsontoolkit::vocabularies( maybe_dialect.value()); } -auto sourcemeta::jsontoolkit::vocabularies( - const SchemaResolver &resolver, const std::string &base_dialect, - const std::string &dialect) -> std::future> { +auto sourcemeta::jsontoolkit::vocabularies(const SchemaResolver &resolver, + const std::string &base_dialect, + const std::string &dialect) + -> std::future> { // As a performance optimization shortcut if (base_dialect == dialect) { if (dialect == "https://json-schema.org/draft/2020-12/schema") { diff --git a/vendor/jsontoolkit/src/jsonschema/reference.cc b/vendor/jsontoolkit/src/jsonschema/reference.cc index 8dbe5ad1..303ded4f 100644 --- a/vendor/jsontoolkit/src/jsonschema/reference.cc +++ b/vendor/jsontoolkit/src/jsonschema/reference.cc @@ -55,8 +55,8 @@ static auto find_every_base(const std::map bool { +static auto ref_overrides_adjacent_keywords(const std::string &base_dialect) + -> bool { // In older drafts, the presence of `$ref` would override any sibling // keywords // See @@ -80,7 +80,7 @@ static auto supports_id_anchors(const std::string &base_dialect) -> bool { base_dialect == "http://json-schema.org/draft-04/hyper-schema#"; } -static auto fragment_string(const sourcemeta::jsontoolkit::URI uri) +static auto fragment_string(const sourcemeta::jsontoolkit::URI &uri) -> std::optional { const auto fragment{uri.fragment()}; if (fragment.has_value()) { @@ -178,8 +178,7 @@ auto sourcemeta::jsontoolkit::frame( entry.pointer.empty() ? default_id : std::nullopt)}; // Store information - subschema_entries.emplace_back( - InternalEntry{std::move(entry), std::move(id)}); + subschema_entries.emplace_back(InternalEntry{entry, std::move(id)}); } for (const auto &entry : subschema_entries) { diff --git a/vendor/jsontoolkit/src/jsonschema/resolver.cc b/vendor/jsontoolkit/src/jsonschema/resolver.cc index 990e32af..4622ba2f 100644 --- a/vendor/jsontoolkit/src/jsonschema/resolver.cc +++ b/vendor/jsontoolkit/src/jsonschema/resolver.cc @@ -11,9 +11,10 @@ MapSchemaResolver::MapSchemaResolver() {} MapSchemaResolver::MapSchemaResolver(const SchemaResolver &resolver) : default_resolver{resolver} {} -auto MapSchemaResolver::add( - const JSON &schema, const std::optional &default_dialect, - const std::optional &default_id) -> void { +auto MapSchemaResolver::add(const JSON &schema, + const std::optional &default_dialect, + const std::optional &default_id) + -> void { assert(sourcemeta::jsontoolkit::is_schema(schema)); // Registering the top-level schema is not enough. We need to check diff --git a/vendor/jsontoolkit/src/jsonschema/walker.cc b/vendor/jsontoolkit/src/jsonschema/walker.cc index 714e2123..1119bc19 100644 --- a/vendor/jsontoolkit/src/jsonschema/walker.cc +++ b/vendor/jsontoolkit/src/jsonschema/walker.cc @@ -20,7 +20,7 @@ auto sourcemeta::jsontoolkit::keyword_priority( } namespace { -enum class SchemaWalkerType_t { Deep, Flat }; +enum class SchemaWalkerType_t : std::uint8_t { Deep, Flat }; auto walk(sourcemeta::jsontoolkit::Pointer &pointer, std::vector &subschemas, diff --git a/vendor/jsontoolkit/src/uri/include/sourcemeta/jsontoolkit/uri.h b/vendor/jsontoolkit/src/uri/include/sourcemeta/jsontoolkit/uri.h index 3067a2f0..8c5ef1d4 100644 --- a/vendor/jsontoolkit/src/uri/include/sourcemeta/jsontoolkit/uri.h +++ b/vendor/jsontoolkit/src/uri/include/sourcemeta/jsontoolkit/uri.h @@ -264,8 +264,8 @@ class SOURCEMETA_JSONTOOLKIT_URI_EXPORT URI { /// assert(uri.recompose_without_fragment().value() == /// "https://sourcemeta.com/foo"); /// ``` - [[nodiscard]] auto - recompose_without_fragment() const -> std::optional; + [[nodiscard]] auto recompose_without_fragment() const + -> std::optional; /// Recompose and canonicalize a URI. For example: ///