Skip to content

Commit

Permalink
[WIP] Extend SchemaFrame::references_to to support dynamic references
Browse files Browse the repository at this point in the history
Signed-off-by: Juan Cruz Viotti <[email protected]>
  • Loading branch information
jviotti committed Feb 12, 2025
1 parent d7462b0 commit fa90791
Show file tree
Hide file tree
Showing 3 changed files with 209 additions and 6 deletions.
92 changes: 86 additions & 6 deletions src/core/jsonschema/frame.cc
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
#include <utility> // std::pair, std::move
#include <vector> // std::vector

#include <iostream> // TODO DEBUG

enum class AnchorType : std::uint8_t { Static, Dynamic, All };

static auto find_anchors(const sourcemeta::core::JSON &schema,
Expand Down Expand Up @@ -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<const typename References::value_type>> {
std::cerr << "REFERENCES TO: ";
sourcemeta::core::stringify(pointer, std::cerr);
std::cerr << "\n";

std::vector<std::reference_wrapper<const typename References::value_type>>
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);
}
}
}

Expand Down
116 changes: 116 additions & 0 deletions test/jsonschema/jsonschema_frame_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
7 changes: 7 additions & 0 deletions test/jsonschema/jsonschema_test_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit fa90791

Please sign in to comment.