diff --git a/src/core/jsonschema/frame.cc b/src/core/jsonschema/frame.cc index 5c9a98adc..db09cbf81 100644 --- a/src/core/jsonschema/frame.cc +++ b/src/core/jsonschema/frame.cc @@ -10,6 +10,8 @@ #include // std::pair, std::move #include // std::vector +#include // TODO DEBUG + enum class AnchorType : std::uint8_t { Static, Dynamic, All }; static auto find_anchors(const sourcemeta::core::JSON &schema, @@ -941,15 +943,93 @@ auto SchemaFrame::instance_locations(const Location &location) const -> const // to brute force whether it points to the desired entry or not auto SchemaFrame::references_to(const Pointer &pointer) const -> std::vector< std::reference_wrapper> { + std::cerr << "REFERENCES TO: "; + sourcemeta::core::stringify(pointer, std::cerr); + std::cerr << "\n"; + std::vector> result; for (const auto &reference : this->references_) { - // TODO: Handle dynamic references by attempting to find all possible - // targets - const auto match{this->locations_.find( - {SchemaReferenceType::Static, reference.second.destination})}; - if (match != this->locations_.cend() && match->second.pointer == pointer) { - result.emplace_back(reference); + assert(!reference.first.second.empty()); + assert(reference.first.second.back().is_property()); + + std::cerr << " CONSIDERING REFERENCE TO: " << reference.second.destination + << "\n"; + + if (reference.first.first == SchemaReferenceType::Static) { + const auto match{this->locations_.find( + {reference.first.first, reference.second.destination})}; + if (match != this->locations_.cend() && + match->second.pointer == pointer) { + std::cerr << " @@@ (STATIC) PUSHING: "; + sourcemeta::core::stringify(reference.first.second, std::cerr); + std::cerr << "\n"; + result.emplace_back(reference); + } + } else if (reference.second.fragment.has_value()) { + const auto &anchor{reference.second.fragment.value()}; + std::cerr << " DYNAMIC SEARCH FOR ANCHOR: " << anchor << "\n"; + + for (const auto &location : this->locations_) { + if (location.second.type != LocationType::Anchor) { + continue; + } + + if (location.first.first != SchemaReferenceType::Dynamic) { + continue; + } + + if (location.second.pointer != pointer) { + continue; + } + + std::cerr << " CONSIDERING ANCHOR: " << location.first.second + << "\n"; + + if (URI{location.first.second}.fragment().value_or("") != anchor) { + continue; + } + + std::cerr << " POINTER: "; + sourcemeta::core::stringify(location.second.pointer, std::cerr); + std::cerr << "\n"; + + result.emplace_back(reference); + } + } else if (reference.first.second.back().to_property() == "$recursiveRef") { + std::cerr << " RECURSIVE SEARCH\n"; + + for (const auto &location : this->locations_) { + if (location.second.type != LocationType::Anchor) { + continue; + } + + if (location.first.first != SchemaReferenceType::Dynamic) { + continue; + } + + if (location.second.pointer != pointer) { + continue; + } + + std::cerr << " CONSIDERING ANCHOR: " << location.first.second + << "\n"; + + result.emplace_back(reference); + } + } else { + sourcemeta::core::stringify(reference.first.second, std::cerr); + std::cerr << "\n"; + + const auto match{this->locations_.find( + {reference.first.first, reference.second.destination})}; + if (match != this->locations_.cend() && + match->second.pointer == pointer) { + std::cerr << " @@@ (STATIC) PUSHING: "; + sourcemeta::core::stringify(reference.first.second, std::cerr); + std::cerr << "\n"; + result.emplace_back(reference); + } } } diff --git a/test/jsonschema/jsonschema_frame_test.cc b/test/jsonschema/jsonschema_frame_test.cc index ea16b84b2..cd9cbd7a6 100644 --- a/test/jsonschema/jsonschema_frame_test.cc +++ b/test/jsonschema/jsonschema_frame_test.cc @@ -795,3 +795,119 @@ TEST(JSONSchema_frame, mode_locations) { EXPECT_EQ(frame.references().size(), 0); } + +TEST(JSONSchema_frame, references_to_1) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$id": "https://example.com", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo": { + "$id": "foo", + "$dynamicAnchor": "test" + }, + "bar": { + "$id": "bar", + "$dynamicAnchor": "test" + }, + "baz": { + "$id": "baz", + "$anchor": "test" + } + }, + "$defs": { + "bookending": { + "$dynamicAnchor": "test" + }, + "static": { + "$ref": "#test" + }, + "dynamic": { + "$dynamicRef": "#test" + }, + "dynamic-non-anchor": { + "$dynamicRef": "baz" + } + } + })JSON"); + + sourcemeta::core::SchemaFrame frame{ + sourcemeta::core::SchemaFrame::Mode::References}; + frame.analyse(document, sourcemeta::core::schema_official_walker, + sourcemeta::core::schema_official_resolver); + + const auto foo{frame.references_to({"properties", "foo"})}; + EXPECT_EQ(foo.size(), 1); + EXPECT_REFERENCE_TO(foo, 0, Dynamic, "/$defs/dynamic/$dynamicRef"); + + const auto bar{frame.references_to({"properties", "bar"})}; + EXPECT_EQ(bar.size(), 1); + EXPECT_REFERENCE_TO(bar, 0, Dynamic, "/$defs/dynamic/$dynamicRef"); + + const auto baz{frame.references_to({"properties", "baz"})}; + EXPECT_EQ(baz.size(), 1); + EXPECT_REFERENCE_TO(baz, 0, Static, "/$defs/dynamic-non-anchor/$dynamicRef"); + + const auto bookending{frame.references_to({"$defs", "bookending"})}; + EXPECT_EQ(bookending.size(), 2); + EXPECT_REFERENCE_TO(bookending, 0, Static, "/$defs/static/$ref"); + EXPECT_REFERENCE_TO(bookending, 1, Dynamic, "/$defs/dynamic/$dynamicRef"); +} + +TEST(JSONSchema_frame, references_to_2) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$id": "https://example.com", + "$schema": "https://json-schema.org/draft/2019-09/schema", + "properties": { + "foo": { + "$id": "foo", + "$recursiveAnchor": true + }, + "bar": { + "$id": "bar", + "$recursiveAnchor": true + }, + "baz": { + "$id": "baz", + "$anchor": "test" + }, + "qux": { + "$id": "qux", + "$recursiveAnchor": false + } + }, + "$defs": { + "bookending": { + "$recursiveAnchor": true + }, + "static": { + "$ref": "#test" + }, + "dynamic": { + "$recursiveRef": "#" + } + } + })JSON"); + + sourcemeta::core::SchemaFrame frame{ + sourcemeta::core::SchemaFrame::Mode::References}; + frame.analyse(document, sourcemeta::core::schema_official_walker, + sourcemeta::core::schema_official_resolver); + + const auto foo{frame.references_to({"properties", "foo"})}; + EXPECT_EQ(foo.size(), 1); + EXPECT_REFERENCE_TO(foo, 0, Dynamic, "/$defs/dynamic/$recursiveRef"); + + const auto bar{frame.references_to({"properties", "bar"})}; + EXPECT_EQ(bar.size(), 1); + EXPECT_REFERENCE_TO(bar, 0, Dynamic, "/$defs/dynamic/$recursiveRef"); + + const auto baz{frame.references_to({"properties", "baz"})}; + EXPECT_EQ(baz.size(), 0); + + const auto qux{frame.references_to({"properties", "qux"})}; + EXPECT_EQ(qux.size(), 0); + + const auto bookending{frame.references_to({"$defs", "bookending"})}; + EXPECT_EQ(bookending.size(), 1); + EXPECT_REFERENCE_TO(bookending, 0, Dynamic, "/$defs/dynamic/$recursiveRef"); +} diff --git a/test/jsonschema/jsonschema_test_utils.h b/test/jsonschema/jsonschema_test_utils.h index 3b24c24e5..e5a8e06ef 100644 --- a/test/jsonschema/jsonschema_test_utils.h +++ b/test/jsonschema/jsonschema_test_utils.h @@ -521,4 +521,11 @@ expected_instance_location, expected_relative_instance_location); \ EXPECT_TRUE(entries.at(index).orphan); +#define EXPECT_REFERENCE_TO(result, index, type, origin) \ + EXPECT_EQ((result).at((index)).get().first.first, \ + sourcemeta::core::SchemaReferenceType::type); \ + EXPECT_EQ( \ + sourcemeta::core::to_string((result).at((index)).get().first.second), \ + (origin)); + #endif