diff --git a/src/alterschema/alterschema.cc b/src/alterschema/alterschema.cc index a98d9b6c..58b39683 100644 --- a/src/alterschema/alterschema.cc +++ b/src/alterschema/alterschema.cc @@ -68,16 +68,16 @@ auto WALK_UP(const JSON &root, const SchemaFrame &frame, assert(!relative_pointer.empty() && relative_pointer.at(0).is_property()); const auto parent{frame.traverse(frame.uri(parent_pointer).value().get())}; assert(parent.has_value()); + const auto parent_vocabularies{ + frame.vocabularies(parent.value().get(), resolver)}; const auto keyword_type{ - walker(relative_pointer.at(0).to_property(), - frame.vocabularies(parent.value().get(), resolver)) - .type}; + walker(relative_pointer.at(0).to_property(), parent_vocabularies).type}; if (!should_continue(keyword_type)) { return std::nullopt; } - if (matches(get(root, parent_pointer))) { + if (matches(get(root, parent_pointer), parent_vocabularies)) { return std::cref(parent.value().get().pointer); } diff --git a/src/alterschema/canonicalizer/items_implicit.h b/src/alterschema/canonicalizer/items_implicit.h index f8c58c46..a3068a34 100644 --- a/src/alterschema/canonicalizer/items_implicit.h +++ b/src/alterschema/canonicalizer/items_implicit.h @@ -9,12 +9,12 @@ class ItemsImplicit final : public SchemaTransformRule { [[nodiscard]] auto condition(const sourcemeta::core::JSON &schema, - const sourcemeta::core::JSON &, + const sourcemeta::core::JSON &root, const sourcemeta::core::Vocabularies &vocabularies, - const sourcemeta::core::SchemaFrame &, - const sourcemeta::core::SchemaFrame::Location &, - const sourcemeta::core::SchemaWalker &, - const sourcemeta::core::SchemaResolver &) const + const sourcemeta::core::SchemaFrame &frame, + const sourcemeta::core::SchemaFrame::Location &location, + const sourcemeta::core::SchemaWalker &walker, + const sourcemeta::core::SchemaResolver &resolver) const -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF( ((vocabularies.contains( @@ -31,6 +31,23 @@ class ItemsImplicit final : public SchemaTransformRule { schema.is_object() && schema.defines("type") && schema.at("type").is_string() && schema.at("type").to_string() == "array" && !schema.defines("items")); + ONLY_CONTINUE_IF( + !(schema.defines("unevaluatedItems") && + vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_2020_12_Unevaluated, + Vocabularies::Known::JSON_Schema_2019_09_Applicator}))); + ONLY_CONTINUE_IF( + !WALK_UP_IN_PLACE_APPLICATORS( + root, frame, location, walker, resolver, + [](const JSON &ancestor, + const Vocabularies &ancestor_vocabularies) { + return ancestor.defines("unevaluatedItems") && + ancestor_vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_2020_12_Unevaluated, + Vocabularies::Known:: + JSON_Schema_2019_09_Applicator}); + }) + .has_value()); return true; } diff --git a/src/alterschema/canonicalizer/type_inherit_in_place.h b/src/alterschema/canonicalizer/type_inherit_in_place.h index 9887b9c0..d4dcd192 100644 --- a/src/alterschema/canonicalizer/type_inherit_in_place.h +++ b/src/alterschema/canonicalizer/type_inherit_in_place.h @@ -55,7 +55,7 @@ class TypeInheritInPlace final : public SchemaTransformRule { return IS_IN_PLACE_APPLICATOR(keyword_type) && keyword_type != SchemaKeywordType::ApplicatorElementsInPlace; }, - [](const JSON &ancestor_schema) { + [](const JSON &ancestor_schema, const Vocabularies &) { return ancestor_schema.defines("type"); })}; diff --git a/src/alterschema/common/required_properties_in_properties.h b/src/alterschema/common/required_properties_in_properties.h index 3b285d28..00335df0 100644 --- a/src/alterschema/common/required_properties_in_properties.h +++ b/src/alterschema/common/required_properties_in_properties.h @@ -42,7 +42,7 @@ class RequiredPropertiesInProperties final : public SchemaTransformRule { !this->defined_in_properties_sibling(schema, property.to_string()) && !WALK_UP_IN_PLACE_APPLICATORS( root, frame, location, walker, resolver, - [&](const JSON &ancestor) { + [&](const JSON &ancestor, const Vocabularies &) { return this->defined_in_properties_sibling( ancestor, property.to_string()); }) diff --git a/test/alterschema/alterschema_canonicalize_2019_09_test.cc b/test/alterschema/alterschema_canonicalize_2019_09_test.cc index bcb3e07b..16cf682c 100644 --- a/test/alterschema/alterschema_canonicalize_2019_09_test.cc +++ b/test/alterschema/alterschema_canonicalize_2019_09_test.cc @@ -687,3 +687,107 @@ TEST(AlterSchema_canonicalize_2019_09, items_implicit_1) { EXPECT_EQ(document, expected); } + +TEST(AlterSchema_canonicalize_2019_09, + items_implicit_skipped_with_unevaluated_items) { + auto document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "contains": { "type": "boolean" }, + "unevaluatedItems": false + })JSON"); + + CANONICALIZE(document, result, traces); + + EXPECT_TRUE(result.first); + + const auto expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "anyOf": [ + { "enum": [ null ] }, + { "enum": [ false, true ] }, + { + "type": "object", + "minProperties": 0, + "properties": {} + }, + { + "type": "array", + "minItems": 0, + "contains": { + "enum": [ false, true ] + } + }, + { + "type": "string", + "minLength": 0 + }, + { "type": "number" } + ], + "unevaluatedItems": false + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_canonicalize_2019_09, + items_implicit_skipped_with_direct_unevaluated_items) { + auto document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "array", + "unevaluatedItems": false + })JSON"); + + CANONICALIZE(document, result, traces); + + EXPECT_TRUE(result.first); + + const auto expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "array", + "minItems": 0, + "unevaluatedItems": false + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_canonicalize_2019_09, + items_implicit_skipped_with_anyof_and_unevaluated_items) { + auto document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "anyOf": [ + { "items": { "type": "boolean" } }, + true + ], + "unevaluatedItems": false + })JSON"); + + CANONICALIZE(document, result, traces); + + EXPECT_TRUE(result.first); + + const auto expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "anyOf": [ + { "enum": [ null ] }, + { "enum": [ false, true ] }, + { + "type": "object", + "minProperties": 0, + "properties": {} + }, + { + "type": "array", + "minItems": 0 + }, + { + "type": "string", + "minLength": 0 + }, + { "type": "number" } + ], + "unevaluatedItems": false + })JSON"); + + EXPECT_EQ(document, expected); +} diff --git a/test/alterschema/alterschema_canonicalize_2020_12_test.cc b/test/alterschema/alterschema_canonicalize_2020_12_test.cc index b9000a15..635d8b75 100644 --- a/test/alterschema/alterschema_canonicalize_2020_12_test.cc +++ b/test/alterschema/alterschema_canonicalize_2020_12_test.cc @@ -1697,6 +1697,110 @@ TEST(AlterSchema_canonicalize_2020_12, EXPECT_EQ(document, expected); } +TEST(AlterSchema_canonicalize_2020_12, + items_implicit_skipped_with_direct_unevaluated_items) { + auto document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "array", + "unevaluatedItems": false + })JSON"); + + CANONICALIZE(document, result, traces); + + EXPECT_TRUE(result.first); + + const auto expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "array", + "minItems": 0, + "unevaluatedItems": false + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_canonicalize_2020_12, + items_implicit_skipped_with_anyof_items_and_unevaluated_items) { + auto document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "anyOf": [ + { "items": { "type": "boolean" } }, + true + ], + "unevaluatedItems": false + })JSON"); + + CANONICALIZE(document, result, traces); + + EXPECT_TRUE(result.first); + + const auto expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "anyOf": [ + { "enum": [ null ] }, + { "enum": [ false, true ] }, + { + "type": "object", + "minProperties": 0, + "properties": {} + }, + { + "type": "array", + "minItems": 0 + }, + { + "type": "string", + "minLength": 0 + }, + { "type": "number" } + ], + "unevaluatedItems": false + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_canonicalize_2020_12, + items_implicit_skipped_with_anyof_prefix_items_and_unevaluated_items) { + auto document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "anyOf": [ + { "prefixItems": [ { "type": "boolean" } ] }, + true + ], + "unevaluatedItems": false + })JSON"); + + CANONICALIZE(document, result, traces); + + EXPECT_TRUE(result.first); + + const auto expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "anyOf": [ + { "enum": [ null ] }, + { "enum": [ false, true ] }, + { + "type": "object", + "minProperties": 0, + "properties": {} + }, + { + "type": "array", + "minItems": 0 + }, + { + "type": "string", + "minLength": 0 + }, + { "type": "number" } + ], + "unevaluatedItems": false + })JSON"); + + EXPECT_EQ(document, expected); +} + TEST(AlterSchema_canonicalize_2020_12, ref_into_subschema_via_absolute_uri) { auto document = sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2020-12/schema",