diff --git a/DEPENDENCIES b/DEPENDENCIES index 6869678..967ea8d 100644 --- a/DEPENDENCIES +++ b/DEPENDENCIES @@ -1,4 +1,4 @@ vendorpull https://github.com/sourcemeta/vendorpull dea311b5bfb53b6926a4140267959ae334d3ecf4 noa https://github.com/sourcemeta/noa 7e26abce7a4e31e86a16ef2851702a56773ca527 -jsontoolkit https://github.com/sourcemeta/jsontoolkit c590926d29ee76d00f2bc7d13b2e51ac66cc2d7e +jsontoolkit https://github.com/sourcemeta/jsontoolkit 2b44aee9b2be18a03b375c0b1f15c7623db8b922 hydra https://github.com/sourcemeta/hydra 3c53d3fdef79e9ba603d48470a508cc45472a0dc diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8f35f81..5ff74c6 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -50,7 +50,6 @@ add_jsonschema_test_unix(validate/fail_schema_enoent) add_jsonschema_test_unix(validate/fail_schema_invalid_json) add_jsonschema_test_unix(validate/fail_schema_non_schema) add_jsonschema_test_unix(validate/fail_schema_unknown_dialect) -add_jsonschema_test_unix(validate/fail_schema_unsupported_dialect) add_jsonschema_test_unix(validate/pass_resolve) add_jsonschema_test_unix(validate/pass_resolve_custom_extension) add_jsonschema_test_unix(validate/pass_resolve_verbose) @@ -85,8 +84,6 @@ add_jsonschema_test_unix(test/fail_multi_resolve_verbose) add_jsonschema_test_unix(test/fail_unresolvable) add_jsonschema_test_unix(test/fail_unresolvable_fragment) add_jsonschema_test_unix(test/fail_unresolvable_anchor) -add_jsonschema_test_unix(test/fail_unsupported) -add_jsonschema_test_unix(test/fail_unsupported_verbose) add_jsonschema_test_unix(test/fail_not_object) add_jsonschema_test_unix(test/fail_no_schema) add_jsonschema_test_unix(test/fail_schema_non_string) diff --git a/test/test/fail_unsupported.sh b/test/test/fail_unsupported.sh deleted file mode 100755 index 4f643ce..0000000 --- a/test/test/fail_unsupported.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/bin/sh - -set -o errexit -set -o nounset - -TMP="$(mktemp -d)" -clean() { rm -rf "$TMP"; } -trap clean EXIT - -cat << 'EOF' > "$TMP/test.json" -{ - "target": "https://json-schema.org/draft/2020-12/schema", - "tests": [ - { - "valid": true, - "data": {} - }, - { - "valid": true, - "data": { "type": 1 } - } - ] -} -EOF - -"$1" test "$TMP/test.json" --verbose 1> "$TMP/output.txt" 2>&1 \ - && CODE="$?" || CODE="$?" -test "$CODE" = "1" || exit 1 - -cat << EOF > "$TMP/expected.txt" -$(realpath "$TMP")/test.json: -error: Cannot compile unsupported vocabulary - https://json-schema.org/draft/2020-12/vocab/applicator - -To request support for it, please open an issue at -https://github.com/intelligence-ai/jsonschema -EOF - -diff "$TMP/output.txt" "$TMP/expected.txt" diff --git a/test/test/fail_unsupported_verbose.sh b/test/test/fail_unsupported_verbose.sh deleted file mode 100755 index f98c02d..0000000 --- a/test/test/fail_unsupported_verbose.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/bin/sh - -set -o errexit -set -o nounset - -TMP="$(mktemp -d)" -clean() { rm -rf "$TMP"; } -trap clean EXIT - -cat << 'EOF' > "$TMP/test.json" -{ - "target": "https://json-schema.org/draft/2020-12/schema", - "tests": [ - { - "valid": true, - "data": {} - }, - { - "valid": true, - "data": { "type": 1 } - } - ] -} -EOF - -"$1" test "$TMP/test.json" 1> "$TMP/output.txt" 2>&1 \ - && CODE="$?" || CODE="$?" -test "$CODE" = "1" || exit 1 - -cat << EOF > "$TMP/expected.txt" -$(realpath "$TMP")/test.json: -error: Cannot compile unsupported vocabulary - https://json-schema.org/draft/2020-12/vocab/applicator - -To request support for it, please open an issue at -https://github.com/intelligence-ai/jsonschema -EOF - -diff "$TMP/output.txt" "$TMP/expected.txt" diff --git a/test/validate/fail_schema_unsupported_dialect.sh b/test/validate/fail_schema_unsupported_dialect.sh deleted file mode 100755 index 323beb2..0000000 --- a/test/validate/fail_schema_unsupported_dialect.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/sh - -set -o errexit -set -o nounset - -TMP="$(mktemp -d)" -clean() { rm -rf "$TMP"; } -trap clean EXIT - -cat << 'EOF' > "$TMP/schema.json" -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "string" -} -EOF - -cat << 'EOF' > "$TMP/instance.json" -{ "foo": "bar" } -EOF - -"$1" validate "$TMP/schema.json" "$TMP/instance.json" 2>"$TMP/stderr.txt" \ - && CODE="$?" || CODE="$?" -test "$CODE" = "1" || exit 1 - -cat << EOF > "$TMP/expected.txt" -error: Cannot compile unsupported vocabulary - https://json-schema.org/draft/2020-12/vocab/applicator - -To request support for it, please open an issue at -https://github.com/intelligence-ai/jsonschema -EOF - -diff "$TMP/stderr.txt" "$TMP/expected.txt" diff --git a/vendor/jsontoolkit/src/jsonschema/CMakeLists.txt b/vendor/jsontoolkit/src/jsonschema/CMakeLists.txt index ba9d337..684ed2c 100644 --- a/vendor/jsontoolkit/src/jsonschema/CMakeLists.txt +++ b/vendor/jsontoolkit/src/jsonschema/CMakeLists.txt @@ -11,6 +11,7 @@ noa_library(NAMESPACE sourcemeta PROJECT jsontoolkit NAME jsonschema compile.cc compile_evaluate.cc compile_json.cc compile_describe.cc compile_helpers.h default_compiler.cc explain.cc + default_compiler_2020_12.h default_compiler_2019_09.h default_compiler_draft7.h default_compiler_draft6.h diff --git a/vendor/jsontoolkit/src/jsonschema/compile_evaluate.cc b/vendor/jsontoolkit/src/jsonschema/compile_evaluate.cc index 5c50458..fc0ebc4 100644 --- a/vendor/jsontoolkit/src/jsonschema/compile_evaluate.cc +++ b/vendor/jsontoolkit/src/jsonschema/compile_evaluate.cc @@ -241,18 +241,26 @@ class EvaluationContext { auto find_dynamic_anchor(const std::string &anchor) const -> std::optional { - for (const auto &resource : this->resources()) { + std::optional result; + + // We want to check every schema resource from the current one + // backwards until the dynamic anchor chain is lost. The one + // before its lost is the one we want to jump to + for (auto iterator = this->resources().crbegin(); + iterator < this->resources().crend(); ++iterator) { std::ostringstream name; - name << resource; + name << *iterator; name << '#'; name << anchor; const auto label{std::hash{}(name.str())}; if (this->labels.contains(label)) { - return label; + result = label; + } else { + break; } } - return std::nullopt; + return result; } auto jump(const std::size_t id) const -> const Template & { @@ -1013,9 +1021,10 @@ auto evaluate_step( EVALUATE_CONDITION_GUARD(loop, instance); CALLBACK_PRE(context.instance_location()); const auto &value{context.resolve_value(loop.value, instance)}; - const auto minimum{value.first}; - const auto &maximum{value.second}; + const auto minimum{std::get<0>(value)}; + const auto &maximum{std::get<1>(value)}; assert(!maximum.has_value() || maximum.value() >= minimum); + const auto is_exhaustive{std::get<2>(value)}; const auto &target{context.resolve_target(loop.target, instance)}; assert(target.is_array()); result = minimum == 0 && target.empty(); @@ -1041,6 +1050,10 @@ auto evaluate_step( // 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; } @@ -1049,7 +1062,7 @@ auto evaluate_step( // Exceeding the lower bound when there is no upper bound // is definitely a success - if (!maximum.has_value()) { + if (!maximum.has_value() && !is_exhaustive) { break; } } diff --git a/vendor/jsontoolkit/src/jsonschema/compile_json.cc b/vendor/jsontoolkit/src/jsonschema/compile_json.cc index 20f9d7e..d724a4b 100644 --- a/vendor/jsontoolkit/src/jsonschema/compile_json.cc +++ b/vendor/jsontoolkit/src/jsonschema/compile_json.cc @@ -115,9 +115,11 @@ auto value_to_json(const sourcemeta::jsontoolkit::SchemaCompilerStepValue result.assign("type", JSON{"range"}); JSON values{JSON::make_array()}; const auto &range{std::get(value)}; - values.push_back(JSON{range.first}); - values.push_back(range.second.has_value() ? JSON{range.second.value()} - : JSON{nullptr}); + values.push_back(JSON{std::get<0>(range)}); + values.push_back(std::get<1>(range).has_value() + ? JSON{std::get<1>(range).value()} + : JSON{nullptr}); + values.push_back(JSON{std::get<2>(range)}); result.assign("value", std::move(values)); return result; } else if constexpr (std::is_same_v) { @@ -297,9 +299,10 @@ auto compiler_template_format_compare(const JSON::String &left, {"relativeInstanceLocation", 6}, {"target", 7}, {"location", 8}, - {"dynamic", 9}, - {"condition", 10}, - {"children", 11}}; + {"id", 9}, + {"dynamic", 10}, + {"condition", 11}, + {"children", 12}}; // We define and control all of these keywords, so if we are missing // some here, then we did something wrong? diff --git a/vendor/jsontoolkit/src/jsonschema/default_compiler.cc b/vendor/jsontoolkit/src/jsonschema/default_compiler.cc index 560b269..3303567 100644 --- a/vendor/jsontoolkit/src/jsonschema/default_compiler.cc +++ b/vendor/jsontoolkit/src/jsonschema/default_compiler.cc @@ -2,6 +2,7 @@ #include #include "default_compiler_2019_09.h" +#include "default_compiler_2020_12.h" #include "default_compiler_draft4.h" #include "default_compiler_draft6.h" #include "default_compiler_draft7.h" @@ -19,6 +20,13 @@ auto sourcemeta::jsontoolkit::default_schema_compiler( assert(!dynamic_context.keyword.empty()); static std::set SUPPORTED_VOCABULARIES{ + "https://json-schema.org/draft/2020-12/vocab/core", + "https://json-schema.org/draft/2020-12/vocab/applicator", + "https://json-schema.org/draft/2020-12/vocab/validation", + "https://json-schema.org/draft/2020-12/vocab/meta-data", + "https://json-schema.org/draft/2020-12/vocab/unevaluated", + "https://json-schema.org/draft/2020-12/vocab/format-annotation", + "https://json-schema.org/draft/2020-12/vocab/content", "https://json-schema.org/draft/2019-09/vocab/core", "https://json-schema.org/draft/2019-09/vocab/applicator", "https://json-schema.org/draft/2019-09/vocab/validation", @@ -51,6 +59,109 @@ auto sourcemeta::jsontoolkit::default_schema_compiler( return {}; \ } + // ******************************************** + // 2020-12 + // ******************************************** + + COMPILE("https://json-schema.org/draft/2020-12/vocab/core", "$dynamicRef", + compiler_2020_12_core_dynamicref); + + COMPILE("https://json-schema.org/draft/2020-12/vocab/applicator", + "prefixItems", compiler_2020_12_applicator_prefixitems); + COMPILE("https://json-schema.org/draft/2020-12/vocab/applicator", "items", + compiler_2020_12_applicator_items); + COMPILE("https://json-schema.org/draft/2020-12/vocab/applicator", "contains", + compiler_2020_12_applicator_contains); + + // Same as 2019-09 + + COMPILE("https://json-schema.org/draft/2020-12/vocab/validation", + "dependentRequired", compiler_2019_09_validation_dependentrequired); + + COMPILE("https://json-schema.org/draft/2020-12/vocab/applicator", + "dependentSchemas", compiler_2019_09_applicator_dependentschemas); + COMPILE("https://json-schema.org/draft/2020-12/vocab/applicator", + "additionalProperties", + compiler_2019_09_applicator_additionalproperties); + COMPILE("https://json-schema.org/draft/2020-12/vocab/applicator", "anyOf", + compiler_2019_09_applicator_anyof); + COMPILE("https://json-schema.org/draft/2020-12/vocab/unevaluated", + "unevaluatedProperties", + compiler_2019_09_applicator_unevaluatedproperties); + COMPILE("https://json-schema.org/draft/2020-12/vocab/unevaluated", + "unevaluatedItems", compiler_2019_09_applicator_unevaluateditems); + + // Same as Draft 7 + + COMPILE("https://json-schema.org/draft/2020-12/vocab/applicator", "if", + compiler_draft7_applicator_if); + COMPILE("https://json-schema.org/draft/2020-12/vocab/applicator", "then", + compiler_draft7_applicator_then); + COMPILE("https://json-schema.org/draft/2020-12/vocab/applicator", "else", + compiler_draft7_applicator_else); + + // Same as Draft 6 + + COMPILE("https://json-schema.org/draft/2020-12/vocab/applicator", + "propertyNames", compiler_draft6_validation_propertynames); + + COMPILE("https://json-schema.org/draft/2020-12/vocab/validation", "type", + compiler_draft6_validation_type); + COMPILE("https://json-schema.org/draft/2020-12/vocab/validation", "const", + compiler_draft6_validation_const); + COMPILE("https://json-schema.org/draft/2020-12/vocab/validation", + "exclusiveMaximum", compiler_draft6_validation_exclusivemaximum); + COMPILE("https://json-schema.org/draft/2020-12/vocab/validation", + "exclusiveMinimum", compiler_draft6_validation_exclusiveminimum); + + // Same as Draft 4 + + // As per compatibility optional test + COMPILE("https://json-schema.org/draft/2020-12/vocab/applicator", + "dependencies", compiler_draft4_applicator_dependencies); + + COMPILE("https://json-schema.org/draft/2020-12/vocab/core", "$ref", + compiler_draft4_core_ref); + + COMPILE("https://json-schema.org/draft/2020-12/vocab/applicator", "allOf", + compiler_draft4_applicator_allof); + COMPILE("https://json-schema.org/draft/2020-12/vocab/applicator", "oneOf", + compiler_draft4_applicator_oneof); + COMPILE("https://json-schema.org/draft/2020-12/vocab/applicator", "not", + compiler_draft4_applicator_not); + + COMPILE("https://json-schema.org/draft/2020-12/vocab/applicator", + "properties", compiler_draft4_applicator_properties); + COMPILE("https://json-schema.org/draft/2020-12/vocab/applicator", + "patternProperties", compiler_draft4_applicator_patternproperties); + + COMPILE("https://json-schema.org/draft/2020-12/vocab/validation", "enum", + compiler_draft4_validation_enum); + COMPILE("https://json-schema.org/draft/2020-12/vocab/validation", + "uniqueItems", compiler_draft4_validation_uniqueitems); + COMPILE("https://json-schema.org/draft/2020-12/vocab/validation", "maxItems", + compiler_draft4_validation_maxitems); + COMPILE("https://json-schema.org/draft/2020-12/vocab/validation", "minItems", + compiler_draft4_validation_minitems); + COMPILE("https://json-schema.org/draft/2020-12/vocab/validation", "required", + compiler_draft4_validation_required); + COMPILE("https://json-schema.org/draft/2020-12/vocab/validation", + "maxProperties", compiler_draft4_validation_maxproperties); + COMPILE("https://json-schema.org/draft/2020-12/vocab/validation", + "minProperties", compiler_draft4_validation_minproperties); + COMPILE("https://json-schema.org/draft/2020-12/vocab/validation", "maximum", + compiler_draft4_validation_maximum); + COMPILE("https://json-schema.org/draft/2020-12/vocab/validation", "minimum", + compiler_draft4_validation_minimum); + COMPILE("https://json-schema.org/draft/2020-12/vocab/validation", + "multipleOf", compiler_draft4_validation_multipleof); + COMPILE("https://json-schema.org/draft/2020-12/vocab/validation", "maxLength", + compiler_draft4_validation_maxlength); + COMPILE("https://json-schema.org/draft/2020-12/vocab/validation", "minLength", + compiler_draft4_validation_minlength); + COMPILE("https://json-schema.org/draft/2020-12/vocab/validation", "pattern", + compiler_draft4_validation_pattern); + // ******************************************** // 2019-09 // ******************************************** @@ -386,13 +497,17 @@ auto sourcemeta::jsontoolkit::default_schema_compiler( #undef COMPILE #undef STOP_IF_SIBLING_KEYWORD - if (schema_context.vocabularies.contains( - "https://json-schema.org/draft/2019-09/vocab/core") && + if ((schema_context.vocabularies.contains( + "https://json-schema.org/draft/2019-09/vocab/core") || + schema_context.vocabularies.contains( + "https://json-schema.org/draft/2020-12/vocab/core")) && !dynamic_context.keyword.starts_with('$')) { // We handle these keywords as part of "contains" - if (schema_context.vocabularies.contains( - "https://json-schema.org/draft/2019-09/vocab/validation") && + if ((schema_context.vocabularies.contains( + "https://json-schema.org/draft/2019-09/vocab/validation") || + schema_context.vocabularies.contains( + "https://json-schema.org/draft/2020-12/vocab/validation")) && (dynamic_context.keyword == "minContains" || dynamic_context.keyword == "maxContains")) { return {}; diff --git a/vendor/jsontoolkit/src/jsonschema/default_compiler_2019_09.h b/vendor/jsontoolkit/src/jsonschema/default_compiler_2019_09.h index 2aa4027..36ff223 100644 --- a/vendor/jsontoolkit/src/jsonschema/default_compiler_2019_09.h +++ b/vendor/jsontoolkit/src/jsonschema/default_compiler_2019_09.h @@ -90,11 +90,11 @@ auto compiler_2019_09_core_annotation( SchemaCompilerTargetType::Instance)}; } -auto compiler_2019_09_applicator_contains( +auto compiler_2019_09_applicator_contains_conditional_annotate( const SchemaCompilerContext &context, const SchemaCompilerSchemaContext &schema_context, - const SchemaCompilerDynamicContext &dynamic_context) - -> SchemaCompilerTemplate { + const SchemaCompilerDynamicContext &dynamic_context, + const bool annotate) -> SchemaCompilerTemplate { std::size_t minimum{1}; if (schema_context.schema.defines("minContains")) { @@ -130,63 +130,53 @@ auto compiler_2019_09_applicator_contains( return {}; } + SchemaCompilerTemplate children{compile(context, schema_context, + relative_dynamic_context, + empty_pointer, empty_pointer)}; + + if (annotate) { + children.push_back(make( + context, schema_context, relative_dynamic_context, + SchemaCompilerTarget{SchemaCompilerTargetType::InstanceBasename, + empty_pointer}, + {}, SchemaCompilerTargetType::InstanceParent)); + + // TODO: If after emitting the above annotation, the number of annotations + // for the current schema location + instance location is equal to the + // array size (which means we annotated all of the items), then emit + // an annotation "true" + } + return {make( context, schema_context, dynamic_context, - SchemaCompilerValueRange{minimum, maximum}, - compile(context, schema_context, relative_dynamic_context, empty_pointer, - empty_pointer), + SchemaCompilerValueRange{ + minimum, maximum, + // TODO: We only need to be exhaustive here if `unevaluatedItems` is + // in use on the schema. Can we pre-determine that and speed things up + // if not? + annotate}, + std::move(children), {make( context, schema_context, relative_dynamic_context, JSON::Type::Array, {}, SchemaCompilerTargetType::Instance)})}; } -auto compiler_2019_09_applicator_additionalproperties( +auto compiler_2019_09_applicator_contains( const SchemaCompilerContext &context, const SchemaCompilerSchemaContext &schema_context, const SchemaCompilerDynamicContext &dynamic_context) -> SchemaCompilerTemplate { - // Evaluate the subschema against the current property if it - // was NOT collected as an annotation on either "properties" or - // "patternProperties" - SchemaCompilerTemplate conjunctions{ - make( - context, schema_context, relative_dynamic_context, - SchemaCompilerTarget{SchemaCompilerTargetType::InstanceBasename, - empty_pointer}, - {}, SchemaCompilerTargetType::ParentAdjacentAnnotations, - Pointer{"properties"}), - - make( - context, schema_context, relative_dynamic_context, - SchemaCompilerTarget{SchemaCompilerTargetType::InstanceBasename, - empty_pointer}, - {}, SchemaCompilerTargetType::ParentAdjacentAnnotations, - Pointer{"patternProperties"}), - }; - - SchemaCompilerTemplate children{compile(context, schema_context, - relative_dynamic_context, - empty_pointer, empty_pointer)}; - - children.push_back(make( - context, schema_context, relative_dynamic_context, - SchemaCompilerTarget{SchemaCompilerTargetType::InstanceBasename, - empty_pointer}, - {}, SchemaCompilerTargetType::InstanceParent)); - - SchemaCompilerTemplate wrapper{make( - context, schema_context, relative_dynamic_context, - SchemaCompilerValueNone{}, std::move(children), - {make( - context, schema_context, relative_dynamic_context, - SchemaCompilerValueNone{}, std::move(conjunctions), - SchemaCompilerTemplate{})})}; + return compiler_2019_09_applicator_contains_conditional_annotate( + context, schema_context, dynamic_context, false); +} - return {make( - context, schema_context, dynamic_context, true, {std::move(wrapper)}, - {make( - context, schema_context, relative_dynamic_context, JSON::Type::Object, - {}, SchemaCompilerTargetType::Instance)})}; +auto compiler_2019_09_applicator_additionalproperties( + const SchemaCompilerContext &context, + const SchemaCompilerSchemaContext &schema_context, + const SchemaCompilerDynamicContext &dynamic_context) + -> SchemaCompilerTemplate { + return compiler_draft4_applicator_additionalproperties_conditional_annotation( + context, schema_context, dynamic_context, true); } auto compiler_2019_09_applicator_items( @@ -194,101 +184,8 @@ auto compiler_2019_09_applicator_items( const SchemaCompilerSchemaContext &schema_context, const SchemaCompilerDynamicContext &dynamic_context) -> SchemaCompilerTemplate { - if (is_schema(schema_context.schema.at(dynamic_context.keyword))) { - SchemaCompilerTemplate children; - children.push_back(make( - context, schema_context, relative_dynamic_context, - SchemaCompilerValueUnsignedInteger{0}, - compile(context, schema_context, relative_dynamic_context, - empty_pointer, empty_pointer), - SchemaCompilerTemplate{})); - children.push_back(make( - context, schema_context, relative_dynamic_context, JSON{true}, {}, - SchemaCompilerTargetType::Instance)); - - return {make( - context, schema_context, dynamic_context, SchemaCompilerValueNone{}, - std::move(children), - {make( - context, schema_context, relative_dynamic_context, - JSON::Type::Array, {}, SchemaCompilerTargetType::Instance)})}; - } - - assert(schema_context.schema.at(dynamic_context.keyword).is_array()); - const auto items_size{ - schema_context.schema.at(dynamic_context.keyword).size()}; - if (items_size == 0) { - return {}; - } - - // The idea here is to precompile all possibilities depending on the size - // of the instance array up to the size of the `items` keyword array. - // For example, if `items` is set to `[ {}, {}, {} ]`, we create 3 - // conjunctions: - // - [ {}, {}, {} ] if the instance array size is >= 3 - // - [ {}, {} ] if the instance array size is == 2 - // - [ {} ] if the instance array size is == 1 - - // Precompile subschemas - std::vector subschemas; - subschemas.reserve(items_size); - const auto &array{ - schema_context.schema.at(dynamic_context.keyword).as_array()}; - for (auto iterator{array.cbegin()}; iterator != array.cend(); ++iterator) { - subschemas.push_back(compile(context, schema_context, - relative_dynamic_context, {subschemas.size()}, - {subschemas.size()})); - } - - SchemaCompilerTemplate children; - for (std::size_t cursor = items_size; cursor > 0; cursor--) { - SchemaCompilerTemplate subchildren; - for (std::size_t index = 0; index < cursor; index++) { - for (const auto &substep : subschemas.at(index)) { - subchildren.push_back(substep); - } - } - - // The first entry - if (cursor == items_size) { - subchildren.push_back(make( - context, schema_context, relative_dynamic_context, JSON{true}, - {make( - context, schema_context, relative_dynamic_context, cursor, {}, - SchemaCompilerTargetType::Instance)}, - SchemaCompilerTargetType::Instance)); - subchildren.push_back(make( - context, schema_context, relative_dynamic_context, JSON{cursor - 1}, - {make( - context, schema_context, relative_dynamic_context, cursor, {}, - SchemaCompilerTargetType::Instance)}, - SchemaCompilerTargetType::Instance)); - - children.push_back(make( - context, schema_context, relative_dynamic_context, - SchemaCompilerValueNone{}, std::move(subchildren), - {make( - context, schema_context, relative_dynamic_context, cursor - 1, {}, - SchemaCompilerTargetType::Instance)})); - } else { - subchildren.push_back(make( - context, schema_context, relative_dynamic_context, JSON{cursor - 1}, - {}, SchemaCompilerTargetType::Instance)); - children.push_back(make( - context, schema_context, relative_dynamic_context, - SchemaCompilerValueNone{}, std::move(subchildren), - {make( - context, schema_context, relative_dynamic_context, cursor, {}, - SchemaCompilerTargetType::Instance)})); - } - } - - return {make( - context, schema_context, dynamic_context, SchemaCompilerValueNone{}, - std::move(children), - {make( - context, schema_context, relative_dynamic_context, JSON::Type::Array, - {}, SchemaCompilerTargetType::Instance)})}; + return compiler_draft4_applicator_items_conditional_annotation( + context, schema_context, dynamic_context, true); } auto compiler_2019_09_applicator_additionalitems( @@ -296,39 +193,8 @@ auto compiler_2019_09_applicator_additionalitems( const SchemaCompilerSchemaContext &schema_context, const SchemaCompilerDynamicContext &dynamic_context) -> SchemaCompilerTemplate { - assert(schema_context.schema.is_object()); - - // Nothing to do here - if (!schema_context.schema.defines("items") || - schema_context.schema.at("items").is_object()) { - return {}; - } - - const auto cursor{(schema_context.schema.defines("items") && - schema_context.schema.at("items").is_array()) - ? schema_context.schema.at("items").size() - : 0}; - - SchemaCompilerTemplate condition{make( - context, schema_context, dynamic_context, JSON::Type::Array, {}, - SchemaCompilerTargetType::Instance)}; - condition.push_back(make( - context, schema_context, dynamic_context, cursor, {}, - SchemaCompilerTargetType::Instance)); - - SchemaCompilerTemplate children{make( - context, schema_context, relative_dynamic_context, - SchemaCompilerValueUnsignedInteger{cursor}, - compile(context, schema_context, relative_dynamic_context, empty_pointer, - empty_pointer), - SchemaCompilerTemplate{})}; - children.push_back(make( - context, schema_context, relative_dynamic_context, JSON{true}, {}, - SchemaCompilerTargetType::Instance)); - - return {make( - context, schema_context, dynamic_context, SchemaCompilerValueNone{}, - std::move(children), std::move(condition))}; + return compiler_draft4_applicator_additionalitems_conditional_annotation( + context, schema_context, dynamic_context, true); } auto compiler_2019_09_applicator_unevaluateditems( @@ -337,25 +203,60 @@ auto compiler_2019_09_applicator_unevaluateditems( const SchemaCompilerDynamicContext &dynamic_context) -> SchemaCompilerTemplate { SchemaCompilerValueStrings dependencies{"unevaluatedItems"}; + if (schema_context.vocabularies.contains( "https://json-schema.org/draft/2019-09/vocab/applicator")) { dependencies.emplace("items"); dependencies.emplace("additionalItems"); } - SchemaCompilerTemplate children{compile(context, schema_context, - relative_dynamic_context, - empty_pointer, empty_pointer)}; - children.push_back(make( + if (schema_context.vocabularies.contains( + "https://json-schema.org/draft/2020-12/vocab/applicator")) { + dependencies.emplace("prefixItems"); + dependencies.emplace("items"); + dependencies.emplace("contains"); + } + + SchemaCompilerTemplate children; + + SchemaCompilerTemplate loop_children{compile(context, schema_context, + relative_dynamic_context, + empty_pointer, empty_pointer)}; + loop_children.push_back(make( context, schema_context, relative_dynamic_context, JSON{true}, {}, SchemaCompilerTargetType::InstanceParent)); + if (schema_context.vocabularies.contains( + "https://json-schema.org/draft/2020-12/vocab/applicator")) { + SchemaCompilerTemplate subcondition{ + make( + context, schema_context, relative_dynamic_context, + SchemaCompilerTarget{SchemaCompilerTargetType::InstanceBasename, + empty_pointer}, + {}, SchemaCompilerTargetType::ParentAnnotations, {"contains"})}; + children.push_back(make( + context, schema_context, relative_dynamic_context, + SchemaCompilerValueNone{}, std::move(loop_children), + std::move(subcondition))); + } else { + children = std::move(loop_children); + } + SchemaCompilerTemplate loop; - if (dependencies.contains("items")) { + if (schema_context.vocabularies.contains( + "https://json-schema.org/draft/2019-09/vocab/applicator") && + dependencies.contains("items")) { loop.push_back(make( context, schema_context, relative_dynamic_context, SchemaCompilerValueString{"items"}, std::move(children), SchemaCompilerTemplate{})); + } else if (schema_context.vocabularies.contains( + "https://json-schema.org/draft/2020-12/vocab/applicator") && + dependencies.contains("prefixItems")) { + loop.push_back(make( + context, schema_context, relative_dynamic_context, + SchemaCompilerValueString{"prefixItems"}, std::move(children), + SchemaCompilerTemplate{})); } else { loop.push_back(make( context, schema_context, relative_dynamic_context, @@ -381,6 +282,7 @@ auto compiler_2019_09_applicator_unevaluatedproperties( const SchemaCompilerDynamicContext &dynamic_context) -> SchemaCompilerTemplate { SchemaCompilerValueStrings dependencies{"unevaluatedProperties"}; + if (schema_context.vocabularies.contains( "https://json-schema.org/draft/2019-09/vocab/applicator")) { dependencies.emplace("properties"); @@ -388,6 +290,13 @@ auto compiler_2019_09_applicator_unevaluatedproperties( dependencies.emplace("additionalProperties"); } + if (schema_context.vocabularies.contains( + "https://json-schema.org/draft/2020-12/vocab/applicator")) { + dependencies.emplace("properties"); + dependencies.emplace("patternProperties"); + dependencies.emplace("additionalProperties"); + } + SchemaCompilerTemplate condition{make( context, schema_context, relative_dynamic_context, SchemaCompilerTarget{SchemaCompilerTargetType::InstanceBasename, @@ -437,28 +346,13 @@ auto compiler_2019_09_applicator_anyof( const SchemaCompilerSchemaContext &schema_context, const SchemaCompilerDynamicContext &dynamic_context) -> SchemaCompilerTemplate { - assert(schema_context.schema.at(dynamic_context.keyword).is_array()); - assert(!schema_context.schema.at(dynamic_context.keyword).empty()); - - SchemaCompilerTemplate disjunctors; - for (std::uint64_t index = 0; - index < schema_context.schema.at(dynamic_context.keyword).size(); - index++) { - disjunctors.push_back(make( - context, schema_context, relative_dynamic_context, - SchemaCompilerValueNone{}, - compile(context, schema_context, relative_dynamic_context, - {static_cast(index)}), - SchemaCompilerTemplate{})); - } - - return {make( + return compiler_draft4_applicator_anyof_conditional_exhaustive( context, schema_context, dynamic_context, // TODO: This set to true means that every disjunction of `anyOf` // is always evaluated. In fact, we only need to enable this if // the schema makes any use of `unevaluatedItems` or // `unevaluatedProperties` - true, std::move(disjunctors), SchemaCompilerTemplate{})}; + true); } } // namespace internal diff --git a/vendor/jsontoolkit/src/jsonschema/default_compiler_2020_12.h b/vendor/jsontoolkit/src/jsonschema/default_compiler_2020_12.h new file mode 100644 index 0000000..221cabe --- /dev/null +++ b/vendor/jsontoolkit/src/jsonschema/default_compiler_2020_12.h @@ -0,0 +1,63 @@ +#ifndef SOURCEMETA_JSONTOOLKIT_JSONSCHEMA_DEFAULT_COMPILER_2020_12_H_ +#define SOURCEMETA_JSONTOOLKIT_JSONSCHEMA_DEFAULT_COMPILER_2020_12_H_ + +#include +#include + +#include "compile_helpers.h" +#include "default_compiler_draft4.h" + +namespace internal { +using namespace sourcemeta::jsontoolkit; + +auto compiler_2020_12_applicator_prefixitems( + const SchemaCompilerContext &context, + const SchemaCompilerSchemaContext &schema_context, + const SchemaCompilerDynamicContext &dynamic_context) + -> SchemaCompilerTemplate { + return compiler_draft4_applicator_items_array(context, schema_context, + dynamic_context, true); +} + +auto compiler_2020_12_applicator_items( + const SchemaCompilerContext &context, + const SchemaCompilerSchemaContext &schema_context, + const SchemaCompilerDynamicContext &dynamic_context) + -> SchemaCompilerTemplate { + const auto cursor{(schema_context.schema.defines("prefixItems") && + schema_context.schema.at("prefixItems").is_array()) + ? schema_context.schema.at("prefixItems").size() + : 0}; + + return compiler_draft4_applicator_additionalitems_from_cursor( + context, schema_context, dynamic_context, cursor, true); +} + +auto compiler_2020_12_applicator_contains( + const SchemaCompilerContext &context, + const SchemaCompilerSchemaContext &schema_context, + const SchemaCompilerDynamicContext &dynamic_context) + -> SchemaCompilerTemplate { + return compiler_2019_09_applicator_contains_conditional_annotate( + context, schema_context, dynamic_context, true); +} + +auto compiler_2020_12_core_dynamicref( + const SchemaCompilerContext &context, + const SchemaCompilerSchemaContext &schema_context, + const SchemaCompilerDynamicContext &dynamic_context) + -> SchemaCompilerTemplate { + const auto current{keyword_location(schema_context)}; + assert(context.frame.contains({ReferenceType::Static, current})); + const auto &entry{context.frame.at({ReferenceType::Static, current})}; + // In this case, just behave as a normal static reference + if (!context.references.contains({ReferenceType::Dynamic, entry.pointer})) { + return compiler_draft4_core_ref(context, schema_context, dynamic_context); + } + + // TODO: Implement + return {}; +} + +} // namespace internal +#endif diff --git a/vendor/jsontoolkit/src/jsonschema/default_compiler_draft4.h b/vendor/jsontoolkit/src/jsonschema/default_compiler_draft4.h index 8807050..f804ac4 100644 --- a/vendor/jsontoolkit/src/jsonschema/default_compiler_draft4.h +++ b/vendor/jsontoolkit/src/jsonschema/default_compiler_draft4.h @@ -225,11 +225,11 @@ auto compiler_draft4_applicator_allof( std::move(children), SchemaCompilerTemplate{})}; } -auto compiler_draft4_applicator_anyof( +auto compiler_draft4_applicator_anyof_conditional_exhaustive( const SchemaCompilerContext &context, const SchemaCompilerSchemaContext &schema_context, - const SchemaCompilerDynamicContext &dynamic_context) - -> 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()); @@ -246,8 +246,17 @@ auto compiler_draft4_applicator_anyof( } return {make( - context, schema_context, dynamic_context, false, std::move(disjunctors), - SchemaCompilerTemplate{})}; + context, schema_context, dynamic_context, exhaustive, + std::move(disjunctors), SchemaCompilerTemplate{})}; +} + +auto compiler_draft4_applicator_anyof( + const SchemaCompilerContext &context, + const SchemaCompilerSchemaContext &schema_context, + const SchemaCompilerDynamicContext &dynamic_context) + -> SchemaCompilerTemplate { + return compiler_draft4_applicator_anyof_conditional_exhaustive( + context, schema_context, dynamic_context, false); } auto compiler_draft4_applicator_oneof( @@ -369,19 +378,15 @@ auto compiler_draft4_applicator_patternproperties( {}, SchemaCompilerTargetType::Instance)})}; } -auto compiler_draft4_applicator_additionalproperties( +auto compiler_draft4_applicator_additionalproperties_conditional_annotation( const SchemaCompilerContext &context, const SchemaCompilerSchemaContext &schema_context, - const SchemaCompilerDynamicContext &dynamic_context) - -> SchemaCompilerTemplate { - + const SchemaCompilerDynamicContext &dynamic_context, + const bool annotate) -> SchemaCompilerTemplate { // Evaluate the subschema against the current property if it // was NOT collected as an annotation on either "properties" or // "patternProperties" SchemaCompilerTemplate conjunctions{ - - // TODO: As an optimization, avoid this condition if the subschema does - // not declare `properties` make( context, schema_context, relative_dynamic_context, SchemaCompilerTarget{SchemaCompilerTargetType::InstanceBasename, @@ -389,8 +394,6 @@ auto compiler_draft4_applicator_additionalproperties( {}, SchemaCompilerTargetType::ParentAdjacentAnnotations, Pointer{"properties"}), - // TODO: As an optimization, avoid this condition if the subschema does - // not declare `patternProperties` make( context, schema_context, relative_dynamic_context, SchemaCompilerTarget{SchemaCompilerTargetType::InstanceBasename, @@ -399,11 +402,21 @@ auto compiler_draft4_applicator_additionalproperties( Pointer{"patternProperties"}), }; + SchemaCompilerTemplate children{compile(context, schema_context, + relative_dynamic_context, + empty_pointer, empty_pointer)}; + + if (annotate) { + children.push_back(make( + context, schema_context, relative_dynamic_context, + SchemaCompilerTarget{SchemaCompilerTargetType::InstanceBasename, + empty_pointer}, + {}, SchemaCompilerTargetType::InstanceParent)); + } + SchemaCompilerTemplate wrapper{make( context, schema_context, relative_dynamic_context, - SchemaCompilerValueNone{}, - compile(context, schema_context, relative_dynamic_context, empty_pointer, - empty_pointer), + SchemaCompilerValueNone{}, std::move(children), {make( context, schema_context, relative_dynamic_context, SchemaCompilerValueNone{}, std::move(conjunctions), @@ -411,14 +424,20 @@ auto compiler_draft4_applicator_additionalproperties( return {make( context, schema_context, dynamic_context, true, {std::move(wrapper)}, - - // TODO: As an optimization, avoid this condition if the subschema - // declares `type` to `object` already {make( context, schema_context, relative_dynamic_context, JSON::Type::Object, {}, SchemaCompilerTargetType::Instance)})}; } +auto compiler_draft4_applicator_additionalproperties( + const SchemaCompilerContext &context, + const SchemaCompilerSchemaContext &schema_context, + const SchemaCompilerDynamicContext &dynamic_context) + -> SchemaCompilerTemplate { + return compiler_draft4_applicator_additionalproperties_conditional_annotation( + context, schema_context, dynamic_context, false); +} + auto compiler_draft4_validation_pattern( const SchemaCompilerContext &context, const SchemaCompilerSchemaContext &schema_context, @@ -493,25 +512,11 @@ auto compiler_draft4_applicator_not( SchemaCompilerTemplate{})}; } -auto compiler_draft4_applicator_items( +auto compiler_draft4_applicator_items_array( const SchemaCompilerContext &context, const SchemaCompilerSchemaContext &schema_context, - const SchemaCompilerDynamicContext &dynamic_context) - -> SchemaCompilerTemplate { - if (is_schema(schema_context.schema.at(dynamic_context.keyword))) { - return {make( - context, schema_context, dynamic_context, - SchemaCompilerValueUnsignedInteger{0}, - compile(context, schema_context, relative_dynamic_context, - empty_pointer, empty_pointer), - - // TODO: As an optimization, avoid this condition if the subschema - // declares `type` to `array` already - {make( - context, schema_context, relative_dynamic_context, - JSON::Type::Array, {}, SchemaCompilerTargetType::Instance)})}; - } - + 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()}; @@ -549,6 +554,21 @@ auto compiler_draft4_applicator_items( // The first entry if (cursor == items_size) { + if (annotate) { + subchildren.push_back(make( + context, schema_context, relative_dynamic_context, JSON{true}, + {make( + context, schema_context, relative_dynamic_context, cursor, {}, + SchemaCompilerTargetType::Instance)}, + SchemaCompilerTargetType::Instance)); + subchildren.push_back(make( + context, schema_context, relative_dynamic_context, JSON{cursor - 1}, + {make( + context, schema_context, relative_dynamic_context, cursor, {}, + SchemaCompilerTargetType::Instance)}, + SchemaCompilerTargetType::Instance)); + } + children.push_back(make( context, schema_context, relative_dynamic_context, SchemaCompilerValueNone{}, std::move(subchildren), @@ -556,6 +576,12 @@ auto compiler_draft4_applicator_items( context, schema_context, relative_dynamic_context, cursor - 1, {}, SchemaCompilerTargetType::Instance)})); } else { + if (annotate) { + subchildren.push_back(make( + context, schema_context, relative_dynamic_context, JSON{cursor - 1}, + {}, SchemaCompilerTargetType::Instance)); + } + children.push_back(make( context, schema_context, relative_dynamic_context, SchemaCompilerValueNone{}, std::move(subchildren), @@ -568,19 +594,87 @@ auto compiler_draft4_applicator_items( return {make( context, schema_context, dynamic_context, SchemaCompilerValueNone{}, std::move(children), - - // TODO: As an optimization, avoid this condition if the subschema - // declares `type` to `array` already {make( context, schema_context, relative_dynamic_context, JSON::Type::Array, {}, SchemaCompilerTargetType::Instance)})}; } -auto compiler_draft4_applicator_additionalitems( +auto compiler_draft4_applicator_items_conditional_annotation( + const SchemaCompilerContext &context, + const SchemaCompilerSchemaContext &schema_context, + const SchemaCompilerDynamicContext &dynamic_context, + const bool annotate) -> SchemaCompilerTemplate { + if (is_schema(schema_context.schema.at(dynamic_context.keyword))) { + SchemaCompilerTemplate children; + children.push_back(make( + context, schema_context, relative_dynamic_context, + SchemaCompilerValueUnsignedInteger{0}, + compile(context, schema_context, relative_dynamic_context, + empty_pointer, empty_pointer), + SchemaCompilerTemplate{})); + + if (annotate) { + children.push_back(make( + context, schema_context, relative_dynamic_context, JSON{true}, {}, + SchemaCompilerTargetType::Instance)); + } + + return {make( + context, schema_context, dynamic_context, SchemaCompilerValueNone{}, + std::move(children), + {make( + context, schema_context, relative_dynamic_context, + JSON::Type::Array, {}, SchemaCompilerTargetType::Instance)})}; + } + + return compiler_draft4_applicator_items_array(context, schema_context, + dynamic_context, annotate); +} + +auto compiler_draft4_applicator_items( const SchemaCompilerContext &context, const SchemaCompilerSchemaContext &schema_context, const SchemaCompilerDynamicContext &dynamic_context) -> SchemaCompilerTemplate { + return compiler_draft4_applicator_items_conditional_annotation( + context, schema_context, dynamic_context, false); +} + +auto compiler_draft4_applicator_additionalitems_from_cursor( + const SchemaCompilerContext &context, + const SchemaCompilerSchemaContext &schema_context, + const SchemaCompilerDynamicContext &dynamic_context, + const std::size_t cursor, const bool annotate) -> SchemaCompilerTemplate { + SchemaCompilerTemplate condition{make( + context, schema_context, relative_dynamic_context, JSON::Type::Array, {}, + SchemaCompilerTargetType::Instance)}; + condition.push_back(make( + context, schema_context, relative_dynamic_context, cursor, {}, + SchemaCompilerTargetType::Instance)); + + SchemaCompilerTemplate children{make( + context, schema_context, relative_dynamic_context, + SchemaCompilerValueUnsignedInteger{cursor}, + compile(context, schema_context, relative_dynamic_context, empty_pointer, + empty_pointer), + SchemaCompilerTemplate{})}; + + if (annotate) { + children.push_back(make( + context, schema_context, relative_dynamic_context, JSON{true}, {}, + SchemaCompilerTargetType::Instance)); + } + + return {make( + context, schema_context, dynamic_context, SchemaCompilerValueNone{}, + std::move(children), std::move(condition))}; +} + +auto compiler_draft4_applicator_additionalitems_conditional_annotation( + const SchemaCompilerContext &context, + const SchemaCompilerSchemaContext &schema_context, + const SchemaCompilerDynamicContext &dynamic_context, + const bool annotate) -> SchemaCompilerTemplate { assert(schema_context.schema.is_object()); // Nothing to do here @@ -594,19 +688,17 @@ auto compiler_draft4_applicator_additionalitems( ? schema_context.schema.at("items").size() : 0}; - SchemaCompilerTemplate condition{make( - context, schema_context, dynamic_context, JSON::Type::Array, {}, - SchemaCompilerTargetType::Instance)}; - condition.push_back(make( - context, schema_context, dynamic_context, cursor, {}, - SchemaCompilerTargetType::Instance)); + return compiler_draft4_applicator_additionalitems_from_cursor( + context, schema_context, dynamic_context, cursor, annotate); +} - return {make( - context, schema_context, dynamic_context, - SchemaCompilerValueUnsignedInteger{cursor}, - compile(context, schema_context, relative_dynamic_context, empty_pointer, - empty_pointer), - std::move(condition))}; +auto compiler_draft4_applicator_additionalitems( + const SchemaCompilerContext &context, + const SchemaCompilerSchemaContext &schema_context, + const SchemaCompilerDynamicContext &dynamic_context) + -> SchemaCompilerTemplate { + return compiler_draft4_applicator_additionalitems_conditional_annotation( + context, schema_context, dynamic_context, false); } auto compiler_draft4_applicator_dependencies( diff --git a/vendor/jsontoolkit/src/jsonschema/default_compiler_draft6.h b/vendor/jsontoolkit/src/jsonschema/default_compiler_draft6.h index efcdb75..381c6e7 100644 --- a/vendor/jsontoolkit/src/jsonschema/default_compiler_draft6.h +++ b/vendor/jsontoolkit/src/jsonschema/default_compiler_draft6.h @@ -190,7 +190,7 @@ auto compiler_draft6_applicator_contains( -> SchemaCompilerTemplate { return {make( context, schema_context, dynamic_context, - SchemaCompilerValueRange{1, std::nullopt}, + SchemaCompilerValueRange{1, std::nullopt, false}, compile(context, schema_context, relative_dynamic_context, empty_pointer, empty_pointer), diff --git a/vendor/jsontoolkit/src/jsonschema/default_walker.cc b/vendor/jsontoolkit/src/jsonschema/default_walker.cc index 4d8f7e8..520af6f 100644 --- a/vendor/jsontoolkit/src/jsonschema/default_walker.cc +++ b/vendor/jsontoolkit/src/jsonschema/default_walker.cc @@ -47,7 +47,7 @@ auto sourcemeta::jsontoolkit::default_schema_walker( "patternProperties", "additionalProperties") WALK_MAYBE_DEPENDENT( HTTPS_BASE "2020-12/vocab/unevaluated", "unevaluatedItems", Value, - HTTPS_BASE "2020-12/vocab/applicator", "prefixItems", "items") + HTTPS_BASE "2020-12/vocab/applicator", "prefixItems", "items", "contains") // 2019-09 WALK(HTTPS_BASE "2019-09/vocab/core", "$defs", Members) 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 ed9b02f..56dd49f 100644 --- a/vendor/jsontoolkit/src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema_compile.h +++ b/vendor/jsontoolkit/src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema_compile.h @@ -17,6 +17,7 @@ #include // std::regex #include // std::set #include // std::string +#include // std::tuple #include // std::move, std::pair #include // std::variant #include // std::vector @@ -94,9 +95,11 @@ using SchemaCompilerValueRegex = std::pair; using SchemaCompilerValueUnsignedInteger = std::size_t; /// @ingroup jsonschema -/// Represents a compiler step range value +/// 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::pair>; + std::tuple, bool>; /// @ingroup jsonschema /// Represents a compiler step boolean value diff --git a/vendor/jsontoolkit/src/jsonschema/reference.cc b/vendor/jsontoolkit/src/jsonschema/reference.cc index d6e77f1..72c3998 100644 --- a/vendor/jsontoolkit/src/jsonschema/reference.cc +++ b/vendor/jsontoolkit/src/jsonschema/reference.cc @@ -99,14 +99,17 @@ static auto store(sourcemeta::jsontoolkit::ReferenceFrame &frame, const std::string &base_id, const sourcemeta::jsontoolkit::Pointer &pointer_from_root, const sourcemeta::jsontoolkit::Pointer &pointer_from_base, - const std::string &dialect) -> void { + const std::string &dialect, + const bool ignore_if_present = false) -> void { const auto canonical{ sourcemeta::jsontoolkit::URI{uri}.canonicalize().recompose()}; - if (!frame - .insert({{type, canonical}, - {entry_type, root_id, base_id, pointer_from_root, - pointer_from_base, dialect}}) - .second) { + const auto inserted{ + frame + .insert({{type, canonical}, + {entry_type, root_id, base_id, pointer_from_root, + pointer_from_base, dialect}}) + .second}; + if (!ignore_if_present && !inserted) { std::ostringstream error; error << "Schema identifier already exists: " << uri; throw sourcemeta::jsontoolkit::SchemaError(error.str()); @@ -275,25 +278,42 @@ auto sourcemeta::jsontoolkit::frame( relative_anchor_uri, root_id, "", entry.common.pointer, entry.common.pointer.resolve_from(bases.second), entry.common.dialect.value()); + + // Register a dynamic anchor as a static anchor if possible too + if (entry.common.vocabularies.contains( + "https://json-schema.org/draft/2020-12/vocab/core")) { + store(frame, ReferenceType::Static, ReferenceEntryType::Anchor, + relative_anchor_uri, root_id, "", entry.common.pointer, + entry.common.pointer.resolve_from(bases.second), + entry.common.dialect.value(), true); + } } } else { bool is_first = true; for (const auto &base_string : bases.first) { - auto anchor_uri{sourcemeta::jsontoolkit::URI::from_fragment(name)}; - const sourcemeta::jsontoolkit::URI anchor_base{base_string}; - anchor_uri.resolve_from_if_absolute(anchor_base); - const auto absolute_anchor_uri{anchor_uri.recompose()}; + // TODO: All this dance is necessary because we don't have a + // URI::fragment setter + std::ostringstream anchor_uri_string; + anchor_uri_string << sourcemeta::jsontoolkit::URI{base_string} + .recompose_without_fragment() + .value_or(""); + anchor_uri_string << '#'; + anchor_uri_string << name; + const auto anchor_uri{ + sourcemeta::jsontoolkit::URI{anchor_uri_string.str()} + .canonicalize() + .recompose()}; if (!is_first && - frame.contains({ReferenceType::Static, absolute_anchor_uri})) { + frame.contains({ReferenceType::Static, anchor_uri})) { continue; } if (type == sourcemeta::jsontoolkit::AnchorType::Static || type == sourcemeta::jsontoolkit::AnchorType::All) { store(frame, sourcemeta::jsontoolkit::ReferenceType::Static, - ReferenceEntryType::Anchor, absolute_anchor_uri, root_id, - base_string, entry.common.pointer, + ReferenceEntryType::Anchor, anchor_uri, root_id, base_string, + entry.common.pointer, entry.common.pointer.resolve_from(bases.second), entry.common.dialect.value()); } @@ -301,10 +321,20 @@ auto sourcemeta::jsontoolkit::frame( if (type == sourcemeta::jsontoolkit::AnchorType::Dynamic || type == sourcemeta::jsontoolkit::AnchorType::All) { store(frame, sourcemeta::jsontoolkit::ReferenceType::Dynamic, - ReferenceEntryType::Anchor, absolute_anchor_uri, root_id, - base_string, entry.common.pointer, + ReferenceEntryType::Anchor, anchor_uri, root_id, base_string, + entry.common.pointer, entry.common.pointer.resolve_from(bases.second), entry.common.dialect.value()); + + // Register a dynamic anchor as a static anchor if possible too + if (entry.common.vocabularies.contains( + "https://json-schema.org/draft/2020-12/vocab/core")) { + store(frame, sourcemeta::jsontoolkit::ReferenceType::Static, + ReferenceEntryType::Anchor, anchor_uri, root_id, + base_string, entry.common.pointer, + entry.common.pointer.resolve_from(bases.second), + entry.common.dialect.value(), true); + } } is_first = false; @@ -346,8 +376,6 @@ auto sourcemeta::jsontoolkit::frame( if (entry.common.value.is_object()) { const auto nearest_bases{ find_nearest_bases(base_uris, entry.common.pointer, entry.id)}; - - // TODO: Check that static destinations actually exist in the frame if (entry.common.value.defines("$ref")) { assert(entry.common.value.at("$ref").is_string()); sourcemeta::jsontoolkit::URI ref{ @@ -405,15 +433,27 @@ auto sourcemeta::jsontoolkit::frame( } ref.canonicalize(); - // TODO: Check bookending requirement - const auto destination{ref.recompose()}; - // TODO: We shouldn't need to reparse if the URI handled mutations - const sourcemeta::jsontoolkit::URI destination_uri{destination}; + auto ref_string{ref.recompose()}; + + // Note that here we cannot enforce the bookending requirement, + // as the dynamic reference may point to a schema resource that + // is not part of or bundled within the schema we are analyzing here. + + const auto has_fragment{ref.fragment().has_value()}; + const auto maybe_static_frame{ + frame.find({ReferenceType::Static, ref_string})}; + const auto maybe_dynamic_frame{ + frame.find({ReferenceType::Dynamic, ref_string})}; + const auto behaves_as_static{!has_fragment || + (has_fragment && + maybe_static_frame != frame.end() && + maybe_dynamic_frame == frame.end())}; references.insert( - {{ReferenceType::Dynamic, + {{behaves_as_static ? ReferenceType::Static + : ReferenceType::Dynamic, entry.common.pointer.concat({"$dynamicRef"})}, - {destination, destination_uri.recompose_without_fragment(), - fragment_string(destination_uri)}}); + {std::move(ref_string), ref.recompose_without_fragment(), + fragment_string(ref)}}); } } } diff --git a/vendor/jsontoolkit/vendor/jsonschema-test-suite.mask b/vendor/jsontoolkit/vendor/jsonschema-test-suite.mask index 78c219e..5d301d4 100644 --- a/vendor/jsontoolkit/vendor/jsonschema-test-suite.mask +++ b/vendor/jsontoolkit/vendor/jsonschema-test-suite.mask @@ -9,10 +9,8 @@ test-schema.json tox.ini tests/latest tests/draft-next -tests/draft2020-12 tests/draft3 remotes/draft-next -remotes/draft2020-12 remotes/locationIndependentIdentifier.json remotes/name-defs.json remotes/ref-and-defs.json