diff --git a/src/core/jsonschema/jsonschema.cc b/src/core/jsonschema/jsonschema.cc index 5c23d5369..0c523e084 100644 --- a/src/core/jsonschema/jsonschema.cc +++ b/src/core/jsonschema/jsonschema.cc @@ -443,7 +443,8 @@ auto sourcemeta::core::vocabularies(const SchemaResolver &resolver, // complexity of the generic `id` function. assert(schema_dialect.defines("$id") && schema_dialect.at("$id").is_string() && - schema_dialect.at("$id").to_string() == dialect); + URI::canonicalize(schema_dialect.at("$id").to_string()) == + URI::canonicalize(dialect)); /* * (4) Retrieve the vocabularies explicitly or implicitly declared by the diff --git a/src/core/jsonschema/official_resolver.in.cc b/src/core/jsonschema/official_resolver.in.cc index fb9ed8c9a..d3796f9a3 100644 --- a/src/core/jsonschema/official_resolver.in.cc +++ b/src/core/jsonschema/official_resolver.in.cc @@ -56,6 +56,10 @@ auto sourcemeta::core::schema_official_resolver(std::string_view identifier) } else if (identifier == "https://json-schema.org/draft/2020-12/schema#") { return sourcemeta::core::parse_json( R"EOF(@METASCHEMA_JSONSCHEMA_2020_12@)EOF"); + } else if (identifier == + "https://json-schema.org/draft/2020-12/hyper-schema#") { + return sourcemeta::core::parse_json( + R"EOF(@METASCHEMA_HYPERSCHEMA_2020_12@)EOF"); // JSON Schema 2019-09 } else if (identifier == "https://json-schema.org/draft/2019-09/schema") { @@ -107,6 +111,10 @@ auto sourcemeta::core::schema_official_resolver(std::string_view identifier) } else if (identifier == "https://json-schema.org/draft/2019-09/schema#") { return sourcemeta::core::parse_json( R"EOF(@METASCHEMA_JSONSCHEMA_2019_09@)EOF"); + } else if (identifier == + "https://json-schema.org/draft/2019-09/hyper-schema#") { + return sourcemeta::core::parse_json( + R"EOF(@METASCHEMA_HYPERSCHEMA_2019_09@)EOF"); // JSON Schema Draft7 } else if (identifier == "http://json-schema.org/draft-07/schema#" || diff --git a/src/extension/alterschema/CMakeLists.txt b/src/extension/alterschema/CMakeLists.txt index a019c2489..995e528cd 100644 --- a/src/extension/alterschema/CMakeLists.txt +++ b/src/extension/alterschema/CMakeLists.txt @@ -59,6 +59,7 @@ sourcemeta_library(NAMESPACE sourcemeta PROJECT core NAME alterschema linter/if_without_then_else.h linter/max_contains_without_contains.h linter/min_contains_without_contains.h + linter/modern_official_dialect_with_empty_fragment.h linter/then_without_if.h) if(SOURCEMETA_CORE_INSTALL) diff --git a/src/extension/alterschema/alterschema.cc b/src/extension/alterschema/alterschema.cc index 21a7d310a..46933d4ef 100644 --- a/src/extension/alterschema/alterschema.cc +++ b/src/extension/alterschema/alterschema.cc @@ -63,6 +63,7 @@ contains_any(const Vocabularies &container, #include "linter/maximum_real_for_integer.h" #include "linter/min_contains_without_contains.h" #include "linter/minimum_real_for_integer.h" +#include "linter/modern_official_dialect_with_empty_fragment.h" #include "linter/multiple_of_default.h" #include "linter/non_applicable_type_specific_keywords.h" #include "linter/pattern_properties_default.h" @@ -109,6 +110,7 @@ auto add(SchemaTransformer &bundle, const AlterSchemaMode mode) bundle.add(); bundle.add(); bundle.add(); + bundle.add(); bundle.add(); bundle.add(); diff --git a/src/extension/alterschema/linter/modern_official_dialect_with_empty_fragment.h b/src/extension/alterschema/linter/modern_official_dialect_with_empty_fragment.h new file mode 100644 index 000000000..8188f4985 --- /dev/null +++ b/src/extension/alterschema/linter/modern_official_dialect_with_empty_fragment.h @@ -0,0 +1,36 @@ +class ModernOfficialDialectWithEmptyFragment final + : public SchemaTransformRule { +public: + ModernOfficialDialectWithEmptyFragment() + : SchemaTransformRule{ + "modern_official_dialect_with_empty_fragment", + "The official dialect URI of 2019-09 and newer versions must " + "not contain the empty fragment"} {}; + + [[nodiscard]] auto condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, + const sourcemeta::core::Vocabularies &, + const sourcemeta::core::SchemaFrame &, + const sourcemeta::core::SchemaFrame::Location &, + const sourcemeta::core::SchemaWalker &, + const sourcemeta::core::SchemaResolver &) const + -> sourcemeta::core::SchemaTransformRule::Result override { + if (!schema.is_object() || !schema.defines("$schema") || + !schema.at("$schema").is_string()) { + return false; + } + + const auto &schema_value = schema.at("$schema").to_string(); + return ( + schema_value == "https://json-schema.org/draft/2019-09/schema#" || + schema_value == "https://json-schema.org/draft/2019-09/hyper-schema#" || + schema_value == "https://json-schema.org/draft/2020-12/schema#" || + schema_value == "https://json-schema.org/draft/2020-12/hyper-schema#"); + } + + auto transform(sourcemeta::core::JSON &schema) const -> void override { + auto schema_value = schema.at("$schema").to_string(); + schema_value.pop_back(); + schema.at("$schema").into(sourcemeta::core::JSON{std::move(schema_value)}); + } +}; diff --git a/test/alterschema/alterschema_lint_2019_09_test.cc b/test/alterschema/alterschema_lint_2019_09_test.cc index 8edb8f8ab..f8b26ac7c 100644 --- a/test/alterschema/alterschema_lint_2019_09_test.cc +++ b/test/alterschema/alterschema_lint_2019_09_test.cc @@ -1984,3 +1984,51 @@ TEST(AlterSchema_lint_2019_09, additional_items_with_schema_items_array_items) { EXPECT_EQ(document, expected); } + +TEST(AlterSchema_lint_2019_09, modern_official_dialect_with_empty_fragment_1) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema#", + "type": "string" + })JSON"); + + LINT_AND_FIX_FOR_READABILITY(document); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "string" + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_2019_09, modern_official_dialect_with_empty_fragment_2) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/hyper-schema#", + "type": "string" + })JSON"); + + LINT_AND_FIX_FOR_READABILITY(document); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/hyper-schema", + "type": "string" + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_2019_09, modern_official_dialect_with_empty_fragment_3) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "string" + })JSON"); + + LINT_AND_FIX_FOR_READABILITY(document); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "string" + })JSON"); + + EXPECT_EQ(document, expected); +} diff --git a/test/alterschema/alterschema_lint_2020_12_test.cc b/test/alterschema/alterschema_lint_2020_12_test.cc index 120c03407..cde0a7955 100644 --- a/test/alterschema/alterschema_lint_2020_12_test.cc +++ b/test/alterschema/alterschema_lint_2020_12_test.cc @@ -2158,3 +2158,52 @@ TEST(AlterSchema_lint_2020_12, unnecessary_allof_wrapper_properties_4) { EXPECT_EQ(document, expected); } + +TEST(AlterSchema_lint_2020_12, modern_official_dialect_with_empty_fragment_1) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema#", + "type": "string" + })JSON"); + + LINT_AND_FIX_FOR_READABILITY(document); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "string" + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_2020_12, modern_official_dialect_with_empty_fragment_2) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/hyper-schema#", + "type": "string" + })JSON"); + + LINT_AND_FIX_FOR_READABILITY(document); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/hyper-schema", + "type": "string" + })JSON"); + + EXPECT_EQ(document, expected); +} + +TEST(AlterSchema_lint_2020_12, modern_official_dialect_with_empty_fragment_3) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "string" + })JSON"); + + LINT_AND_FIX_FOR_READABILITY(document); + + // Should remain unchanged since there's no empty fragment + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "string" + })JSON"); + + EXPECT_EQ(document, expected); +}