From 35497e61176f018167f05956eb0a828902c96ebb Mon Sep 17 00:00:00 2001 From: Juan Cruz Viotti Date: Fri, 10 Apr 2026 14:21:44 -0400 Subject: [PATCH 1/5] Implement `rereference` for `type_array_to_any_of` Signed-off-by: Juan Cruz Viotti --- .../canonicalizer/type_array_to_any_of.h | 26 +++++++++ src/alterschema/transformer.cc | 12 ++++- .../alterschema_canonicalize_2020_12_test.cc | 54 +++++++++++++++++++ .../alterschema_transformer_test.cc | 51 ++++++++++++++++++ 4 files changed, 142 insertions(+), 1 deletion(-) diff --git a/src/alterschema/canonicalizer/type_array_to_any_of.h b/src/alterschema/canonicalizer/type_array_to_any_of.h index 8b414ecca..b89a63e53 100644 --- a/src/alterschema/canonicalizer/type_array_to_any_of.h +++ b/src/alterschema/canonicalizer/type_array_to_any_of.h @@ -46,7 +46,9 @@ class TypeArrayToAnyOf final : public SchemaTransformRule { } auto transform(JSON &schema, const Result &) const -> void override { + this->keyword_branch_index_.clear(); auto disjunctors{sourcemeta::core::JSON::make_array()}; + std::size_t branch_index{0}; for (const auto &type : schema.at("type").as_array()) { auto branch{sourcemeta::core::JSON::make_object()}; branch.assign("type", type); @@ -54,10 +56,14 @@ class TypeArrayToAnyOf final : public SchemaTransformRule { for (const auto &[keyword, instances] : this->keyword_instances_) { if ((instances & current_type_set).any()) { branch.assign(keyword, schema.at(keyword)); + if (!this->keyword_branch_index_.contains(keyword)) { + this->keyword_branch_index_[keyword] = branch_index; + } } } disjunctors.push_back(std::move(branch)); + branch_index++; } for (const auto &[keyword, instances] : this->keyword_instances_) { @@ -88,7 +94,27 @@ class TypeArrayToAnyOf final : public SchemaTransformRule { } } + [[nodiscard]] auto rereference(const std::string_view reference, + const Pointer &origin, const Pointer &target, + const Pointer ¤t) const + -> Pointer override { + assert(!target.empty() && target.at(0).is_property()); + const auto &keyword{target.at(0).to_property()}; + const auto match{this->keyword_branch_index_.find(keyword)}; + if (match == this->keyword_branch_index_.end()) { + return SchemaTransformRule::rereference(reference, origin, target, + current); + } + + static const std::string anyof_keyword{"anyOf"}; + const Pointer old_prefix{current.concat({keyword})}; + const Pointer new_prefix{ + current.concat({anyof_keyword, match->second, keyword})}; + return target.rebase(old_prefix, new_prefix); + } + private: mutable std::unordered_map keyword_instances_; + mutable std::unordered_map keyword_branch_index_; }; diff --git a/src/alterschema/transformer.cc b/src/alterschema/transformer.cc index 43434dad2..5d056de97 100644 --- a/src/alterschema/transformer.cc +++ b/src/alterschema/transformer.cc @@ -305,12 +305,22 @@ auto SchemaTransformer::apply(core::JSON &schema, continue; } - const auto new_fragment{rule->rereference( + const auto resource_prefix{saved_reference.target_pointer.slice( + 0, saved_reference.target_relative_pointer)}; + const auto new_relative{rule->rereference( saved_reference.destination, saved_reference.origin, saved_reference.target_pointer.slice( saved_reference.target_relative_pointer), entry_pointer.slice( new_location.value().get().relative_pointer))}; + // Rereference operates in resource-relative coordinates. + // If the reference origin is outside the target resource, the URI + // fragment is document-root-relative and we must re-prepend the + // resource prefix that was sliced off + const auto new_fragment{ + saved_reference.origin.starts_with(resource_prefix) + ? new_relative + : resource_prefix.concat(new_relative)}; core::URI original{saved_reference.original}; original.fragment(core::to_string(new_fragment)); diff --git a/test/alterschema/alterschema_canonicalize_2020_12_test.cc b/test/alterschema/alterschema_canonicalize_2020_12_test.cc index 2f034115a..6837bf251 100644 --- a/test/alterschema/alterschema_canonicalize_2020_12_test.cc +++ b/test/alterschema/alterschema_canonicalize_2020_12_test.cc @@ -1365,3 +1365,57 @@ TEST(AlterSchema_canonicalize_2020_12, object_anyof_untyped_branches_1) { EXPECT_EQ(document, expected); } + +TEST(AlterSchema_canonicalize_2020_12, + ref_into_subschema_broken_by_type_array_to_any_of) { + auto document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "#/$defs/schema/items", + "$defs": { + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://www.example.com", + "items": { "type": "string" } + } + } + })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", + "$ref": "#/$defs/schema/anyOf/3/items", + "$defs": { + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://www.example.com", + "anyOf": [ + { "enum": [ null ] }, + { "enum": [ false, true ] }, + { + "type": "object", + "minProperties": 0, + "properties": {} + }, + { + "type": "array", + "items": { + "type": "string", + "minLength": 0 + }, + "minItems": 0 + }, + { + "type": "string", + "minLength": 0 + }, + { "type": "number" } + ] + } + } + })JSON"); + + EXPECT_EQ(document, expected); +} diff --git a/test/alterschema/alterschema_transformer_test.cc b/test/alterschema/alterschema_transformer_test.cc index 7daee48af..70cec0e5e 100644 --- a/test/alterschema/alterschema_transformer_test.cc +++ b/test/alterschema/alterschema_transformer_test.cc @@ -2140,3 +2140,54 @@ TEST(AlterSchema_transformer, check_exclude_keyword_array_with_non_strings) { EXPECT_EQ(result.second, 100); EXPECT_EQ(entries.size(), 0); } + +TEST(AlterSchema_transformer, rereference_fixed_through_subschema_with_id) { + sourcemeta::blaze::SchemaTransformer bundle; + EXPECT_EQ(bundle.add(), + "example_rule_definitions_to_defs_with_rereference"); + + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "#/properties/nested/definitions/foo", + "properties": { + "nested": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://www.example.com", + "definitions": { + "foo": { "type": "string" } + } + } + } + })JSON"); + + TestTransformTraces entries; + const auto result = bundle.apply(document, sourcemeta::core::schema_walker, + sourcemeta::core::schema_resolver, + transformer_callback_trace(entries)); + + EXPECT_TRUE(result.first); + EXPECT_EQ(result.second, 100); + EXPECT_EQ(entries.size(), 1); + + EXPECT_EQ(std::get<0>(entries.at(0)), + sourcemeta::core::Pointer({"properties", "nested"})); + EXPECT_EQ(std::get<1>(entries.at(0)), + "example_rule_definitions_to_defs_with_rereference"); + EXPECT_TRUE(std::get<4>(entries.at(0))); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "#/properties/nested/$defs/foo", + "properties": { + "nested": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://www.example.com", + "$defs": { + "foo": { "type": "string" } + } + } + } + })JSON"); + + EXPECT_EQ(document, expected); +} From 88f906737ba89f1780faa8ead8f873911badc2a9 Mon Sep 17 00:00:00 2001 From: Juan Cruz Viotti Date: Fri, 10 Apr 2026 14:39:43 -0400 Subject: [PATCH 2/5] More Signed-off-by: Juan Cruz Viotti --- .../canonicalizer/type_array_to_any_of.h | 12 +++-- .../alterschema_canonicalize_2020_12_test.cc | 46 +++++++++++++++++++ 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/src/alterschema/canonicalizer/type_array_to_any_of.h b/src/alterschema/canonicalizer/type_array_to_any_of.h index b89a63e53..07978e660 100644 --- a/src/alterschema/canonicalizer/type_array_to_any_of.h +++ b/src/alterschema/canonicalizer/type_array_to_any_of.h @@ -70,6 +70,8 @@ class TypeArrayToAnyOf final : public SchemaTransformRule { schema.erase(keyword); } + static const std::string anyof_keyword{"anyOf"}; + static const std::string allof_keyword{"allOf"}; if (schema.defines("anyOf")) { auto first_branch{sourcemeta::core::JSON::make_object()}; first_branch.assign("anyOf", schema.at("anyOf")); @@ -78,19 +80,23 @@ class TypeArrayToAnyOf final : public SchemaTransformRule { schema.erase("anyOf"); if (schema.defines("allOf")) { + const auto allof_index{schema.at("allOf").size() + 1}; schema.at("allOf").push_back(std::move(first_branch)); schema.at("allOf").push_back(std::move(second_branch)); schema.erase("type"); + this->disjunctors_prefix_ = {allof_keyword, allof_index, anyof_keyword}; } else { auto allof_wrapper{sourcemeta::core::JSON::make_array()}; allof_wrapper.push_back(std::move(first_branch)); allof_wrapper.push_back(std::move(second_branch)); schema.at("type").into(std::move(allof_wrapper)); schema.rename("type", "allOf"); + this->disjunctors_prefix_ = {allof_keyword, 1, anyof_keyword}; } } else { schema.at("type").into(std::move(disjunctors)); schema.rename("type", "anyOf"); + this->disjunctors_prefix_ = {anyof_keyword}; } } @@ -106,10 +112,9 @@ class TypeArrayToAnyOf final : public SchemaTransformRule { current); } - static const std::string anyof_keyword{"anyOf"}; const Pointer old_prefix{current.concat({keyword})}; - const Pointer new_prefix{ - current.concat({anyof_keyword, match->second, keyword})}; + const Pointer new_prefix{current.concat(this->disjunctors_prefix_) + .concat({match->second, keyword})}; return target.rebase(old_prefix, new_prefix); } @@ -117,4 +122,5 @@ class TypeArrayToAnyOf final : public SchemaTransformRule { mutable std::unordered_map keyword_instances_; mutable std::unordered_map keyword_branch_index_; + mutable Pointer disjunctors_prefix_; }; diff --git a/test/alterschema/alterschema_canonicalize_2020_12_test.cc b/test/alterschema/alterschema_canonicalize_2020_12_test.cc index 6837bf251..6df1554d7 100644 --- a/test/alterschema/alterschema_canonicalize_2020_12_test.cc +++ b/test/alterschema/alterschema_canonicalize_2020_12_test.cc @@ -1419,3 +1419,49 @@ TEST(AlterSchema_canonicalize_2020_12, EXPECT_EQ(document, expected); } + +TEST(AlterSchema_canonicalize_2020_12, ref_into_subschema_with_existing_anyof) { + auto document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "#/$defs/schema/items", + "$defs": { + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://www.example.com", + "type": [ "array", "string" ], + "anyOf": [ { "minLength": 1 } ], + "items": { "type": "integer" } + } + } + })JSON"); + + CANONICALIZE(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_TRUE(document.defines("$ref")); + EXPECT_TRUE(document.defines("$defs")); +} + +TEST(AlterSchema_canonicalize_2020_12, + ref_into_subschema_with_existing_allof_and_anyof) { + auto document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "#/$defs/schema/items", + "$defs": { + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://www.example.com", + "type": [ "array", "string" ], + "allOf": [ { "maxLength": 100 } ], + "anyOf": [ { "minLength": 1 } ], + "items": { "type": "integer" } + } + } + })JSON"); + + CANONICALIZE(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_TRUE(document.defines("$ref")); + EXPECT_TRUE(document.defines("$defs")); +} From 65bb522670c582738a0552c4b726a15b2b143344 Mon Sep 17 00:00:00 2001 From: Juan Cruz Viotti Date: Fri, 10 Apr 2026 14:45:48 -0400 Subject: [PATCH 3/5] Better Signed-off-by: Juan Cruz Viotti --- src/alterschema/transformer.cc | 25 ++- .../alterschema_canonicalize_2020_12_test.cc | 166 +++++++++++++++++- 2 files changed, 179 insertions(+), 12 deletions(-) diff --git a/src/alterschema/transformer.cc b/src/alterschema/transformer.cc index 5d056de97..381c8d74e 100644 --- a/src/alterschema/transformer.cc +++ b/src/alterschema/transformer.cc @@ -305,8 +305,6 @@ auto SchemaTransformer::apply(core::JSON &schema, continue; } - const auto resource_prefix{saved_reference.target_pointer.slice( - 0, saved_reference.target_relative_pointer)}; const auto new_relative{rule->rereference( saved_reference.destination, saved_reference.origin, saved_reference.target_pointer.slice( @@ -314,13 +312,24 @@ auto SchemaTransformer::apply(core::JSON &schema, entry_pointer.slice( new_location.value().get().relative_pointer))}; // Rereference operates in resource-relative coordinates. - // If the reference origin is outside the target resource, the URI - // fragment is document-root-relative and we must re-prepend the - // resource prefix that was sliced off + // The original URI fragment may be document-root-relative + // (e.g. #/$defs/schema/items) or resource-relative (e.g. + // https://example.com#/items or #/items inside a resource). + // We detect which by comparing the original fragment to the + // full document path. If they match, prepend the resource + // prefix that was sliced off before calling rereference + const core::URI original_uri{saved_reference.original}; + const auto original_fragment{original_uri.fragment()}; + const auto is_document_root_relative{ + original_fragment.has_value() && + original_fragment.value() == + core::to_string(saved_reference.target_pointer)}; const auto new_fragment{ - saved_reference.origin.starts_with(resource_prefix) - ? new_relative - : resource_prefix.concat(new_relative)}; + is_document_root_relative + ? saved_reference.target_pointer + .slice(0, saved_reference.target_relative_pointer) + .concat(new_relative) + : new_relative}; core::URI original{saved_reference.original}; original.fragment(core::to_string(new_fragment)); diff --git a/test/alterschema/alterschema_canonicalize_2020_12_test.cc b/test/alterschema/alterschema_canonicalize_2020_12_test.cc index 6df1554d7..76003783e 100644 --- a/test/alterschema/alterschema_canonicalize_2020_12_test.cc +++ b/test/alterschema/alterschema_canonicalize_2020_12_test.cc @@ -1438,8 +1438,60 @@ TEST(AlterSchema_canonicalize_2020_12, ref_into_subschema_with_existing_anyof) { CANONICALIZE(document, result, traces); EXPECT_TRUE(result.first); - EXPECT_TRUE(document.defines("$ref")); - EXPECT_TRUE(document.defines("$defs")); + + const auto expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "#/$defs/schema/anyOf/0/items", + "$defs": { + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://www.example.com", + "allOf": [ + { + "anyOf": [ + { + "anyOf": [ + { "enum": [ null ] }, + { "enum": [ false, true ] }, + { + "type": "object", + "minProperties": 0, + "properties": {} + }, + { + "type": "array", + "minItems": 0, + "items": true + }, + { + "type": "string", + "minLength": 1 + }, + { "type": "number" } + ] + } + ] + } + ], + "anyOf": [ + { + "type": "array", + "minItems": 0, + "items": { + "type": "integer", + "multipleOf": 1 + } + }, + { + "type": "string", + "minLength": 0 + } + ] + } + } + })JSON"); + + EXPECT_EQ(document, expected); } TEST(AlterSchema_canonicalize_2020_12, @@ -1462,6 +1514,112 @@ TEST(AlterSchema_canonicalize_2020_12, CANONICALIZE(document, result, traces); EXPECT_TRUE(result.first); - EXPECT_TRUE(document.defines("$ref")); - EXPECT_TRUE(document.defines("$defs")); + + const auto expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "#/$defs/schema/anyOf/0/items", + "$defs": { + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://www.example.com", + "allOf": [ + { + "anyOf": [ + { + "anyOf": [ + { "enum": [ null ] }, + { "enum": [ false, true ] }, + { + "type": "object", + "minProperties": 0, + "properties": {} + }, + { + "type": "array", + "minItems": 0, + "items": true + }, + { + "type": "string", + "minLength": 1 + }, + { "type": "number" } + ] + } + ] + } + ], + "anyOf": [ + { + "type": "array", + "minItems": 0, + "items": { + "type": "integer", + "multipleOf": 1 + } + }, + { + "type": "string", + "minLength": 0 + } + ], + "maxLength": 100 + } + } + })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", + "$ref": "https://www.example.com#/items", + "$defs": { + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://www.example.com", + "items": { "type": "string" } + } + } + })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", + "$ref": "https://www.example.com#/anyOf/3/items", + "$defs": { + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://www.example.com", + "anyOf": [ + { "enum": [ null ] }, + { "enum": [ false, true ] }, + { + "type": "object", + "minProperties": 0, + "properties": {} + }, + { + "type": "array", + "minItems": 0, + "items": { + "type": "string", + "minLength": 0 + } + }, + { + "type": "string", + "minLength": 0 + }, + { "type": "number" } + ] + } + } + })JSON"); + + EXPECT_EQ(document, expected); } From 80fcce479d46da1e4f3613dd0025c0514e57a0b3 Mon Sep 17 00:00:00 2001 From: Juan Cruz Viotti Date: Fri, 10 Apr 2026 14:48:16 -0400 Subject: [PATCH 4/5] More Signed-off-by: Juan Cruz Viotti --- .../canonicalizer/type_array_to_any_of.h | 5 +- .../alterschema_canonicalize_2020_12_test.cc | 73 +++++++++++++++++++ 2 files changed, 76 insertions(+), 2 deletions(-) diff --git a/src/alterschema/canonicalizer/type_array_to_any_of.h b/src/alterschema/canonicalizer/type_array_to_any_of.h index 07978e660..4881a9f8d 100644 --- a/src/alterschema/canonicalizer/type_array_to_any_of.h +++ b/src/alterschema/canonicalizer/type_array_to_any_of.h @@ -104,8 +104,9 @@ class TypeArrayToAnyOf final : public SchemaTransformRule { const Pointer &origin, const Pointer &target, const Pointer ¤t) const -> Pointer override { - assert(!target.empty() && target.at(0).is_property()); - const auto &keyword{target.at(0).to_property()}; + const auto relative{target.resolve_from(current)}; + assert(!relative.empty() && relative.at(0).is_property()); + const auto &keyword{relative.at(0).to_property()}; const auto match{this->keyword_branch_index_.find(keyword)}; if (match == this->keyword_branch_index_.end()) { return SchemaTransformRule::rereference(reference, origin, target, diff --git a/test/alterschema/alterschema_canonicalize_2020_12_test.cc b/test/alterschema/alterschema_canonicalize_2020_12_test.cc index 76003783e..833713c2e 100644 --- a/test/alterschema/alterschema_canonicalize_2020_12_test.cc +++ b/test/alterschema/alterschema_canonicalize_2020_12_test.cc @@ -1571,6 +1571,79 @@ TEST(AlterSchema_canonicalize_2020_12, EXPECT_EQ(document, expected); } +TEST(AlterSchema_canonicalize_2020_12, + ref_into_nested_subschema_within_resource) { + auto document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "#/$defs/resource/properties/nested/items", + "$defs": { + "resource": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://www.example.com", + "properties": { + "nested": { + "type": [ "array", "string" ], + "items": { "type": "integer" } + } + } + } + } + })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", + "$ref": "#/$defs/resource/anyOf/2/properties/nested/anyOf/0/items", + "$defs": { + "resource": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://www.example.com", + "anyOf": [ + { "enum": [ null ] }, + { "enum": [ false, true ] }, + { + "type": "object", + "minProperties": 0, + "properties": { + "nested": { + "anyOf": [ + { + "type": "array", + "minItems": 0, + "items": { + "type": "integer", + "multipleOf": 1 + } + }, + { + "type": "string", + "minLength": 0 + } + ] + } + } + }, + { + "type": "array", + "minItems": 0, + "items": true + }, + { + "type": "string", + "minLength": 0 + }, + { "type": "number" } + ] + } + } + })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", From 7774e6b3bc2f00b9f5dc6f8c0947bfca84f5c1e3 Mon Sep 17 00:00:00 2001 From: Juan Cruz Viotti Date: Fri, 10 Apr 2026 14:52:45 -0400 Subject: [PATCH 5/5] Simple Signed-off-by: Juan Cruz Viotti --- src/alterschema/transformer.cc | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/src/alterschema/transformer.cc b/src/alterschema/transformer.cc index 381c8d74e..e3b12e7ab 100644 --- a/src/alterschema/transformer.cc +++ b/src/alterschema/transformer.cc @@ -209,6 +209,7 @@ auto SchemaTransformer::apply(core::JSON &schema, core::Pointer origin; core::JSON::String original; core::JSON::String destination; + core::JSON::String fragment; core::Pointer target_pointer; std::size_t target_relative_pointer; }; @@ -266,8 +267,9 @@ auto SchemaTransformer::apply(core::JSON &schema, potentially_broken_references.push_back( {core::to_pointer(reference.first.second), core::JSON::String{reference.second.original}, - reference.second.destination, core::to_pointer(target.pointer), - target.relative_pointer}); + reference.second.destination, + core::JSON::String{reference.second.fragment.value()}, + core::to_pointer(target.pointer), target.relative_pointer}); } rule->transform(current, outcome); @@ -311,21 +313,9 @@ auto SchemaTransformer::apply(core::JSON &schema, saved_reference.target_relative_pointer), entry_pointer.slice( new_location.value().get().relative_pointer))}; - // Rereference operates in resource-relative coordinates. - // The original URI fragment may be document-root-relative - // (e.g. #/$defs/schema/items) or resource-relative (e.g. - // https://example.com#/items or #/items inside a resource). - // We detect which by comparing the original fragment to the - // full document path. If they match, prepend the resource - // prefix that was sliced off before calling rereference - const core::URI original_uri{saved_reference.original}; - const auto original_fragment{original_uri.fragment()}; - const auto is_document_root_relative{ - original_fragment.has_value() && - original_fragment.value() == - core::to_string(saved_reference.target_pointer)}; const auto new_fragment{ - is_document_root_relative + saved_reference.fragment == + core::to_string(saved_reference.target_pointer) ? saved_reference.target_pointer .slice(0, saved_reference.target_relative_pointer) .concat(new_relative)