Skip to content

Commit

Permalink
Collect schema frame destinations as location 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 7, 2025
1 parent a04f529 commit 8560609
Show file tree
Hide file tree
Showing 9 changed files with 119 additions and 73 deletions.
92 changes: 76 additions & 16 deletions src/core/jsonschema/frame.cc
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#include <sourcemeta/core/jsonschema.h>

#include <algorithm> // std::sort, std::all_of
#include <algorithm> // std::sort, std::all_of, std::any_of
#include <cassert> // assert
#include <functional> // std::less
#include <map> // std::map
Expand Down Expand Up @@ -172,6 +172,69 @@ static auto fragment_string(const sourcemeta::core::URI &uri)
return std::nullopt;
}

static auto has_equivalent_origin(
const sourcemeta::core::SchemaFrame::Locations &frame,
const std::vector<std::reference_wrapper<
const sourcemeta::core::SchemaFrame::LocationKey>> &destination_of,
const sourcemeta::core::SchemaFrame::Locations::value_type &entry) -> bool {
return std::any_of(destination_of.cbegin(), destination_of.cend(),
[&entry, &frame](const auto &destination) {
return destination.get() == entry.first ||
frame.at(destination.get()).pointer ==
entry.second.pointer;
});
}

static auto mark_reference_origins_from(
sourcemeta::core::SchemaFrame::Locations &frame,
const sourcemeta::core::SchemaFrame::References &references,
const sourcemeta::core::SchemaFrame::Locations::value_type &entry) -> void {
for (const auto &reference : references) {
assert(!reference.first.second.empty() &&
reference.first.second.back().is_property());
assert(reference.first.second.back().to_property() == "$schema" ||
reference.first.second.back().to_property() == "$ref" ||
reference.first.second.back().to_property() == "$recursiveRef" ||
reference.first.second.back().to_property() == "$dynamicRef");
if (reference.first.second.initial() != entry.second.pointer) {
continue;
}

auto match{
frame.find({reference.first.first, reference.second.destination})};
if (match == frame.cend()) {
continue;
}

if (match->second.type ==
sourcemeta::core::SchemaFrame::LocationType::Resource ||
match->second.type ==
sourcemeta::core::SchemaFrame::LocationType::Subschema) {
if (!has_equivalent_origin(frame, match->second.destination_of, entry)) {
match->second.destination_of.emplace_back(entry.first);
}
} else if (match->second.type ==
sourcemeta::core::SchemaFrame::LocationType::Anchor) {
for (auto &subentry : frame) {
// We only care about marking reference origins from/to resources and
// subschemas
if (subentry.second.type !=
sourcemeta::core::SchemaFrame::LocationType::Resource &&
subentry.second.type !=
sourcemeta::core::SchemaFrame::LocationType::Subschema) {
continue;
}

if (subentry.second.pointer == match->second.pointer &&
!has_equivalent_origin(frame, subentry.second.destination_of,
entry)) {
subentry.second.destination_of.emplace_back(entry.first);
}
}
}
}
}

static auto
store(sourcemeta::core::SchemaFrame::Locations &frame,
const sourcemeta::core::SchemaReferenceType type,
Expand Down Expand Up @@ -687,26 +750,23 @@ auto internal_analyse(const sourcemeta::core::JSON &schema,
}
}

for (const auto &reference : references) {
auto match{
frame.find({reference.first.first, reference.second.destination})};
if (match == frame.cend()) {
// We only care about marking reference origins from/to resources and
// subschemas

for (const auto &entry : frame) {
if (entry.second.type != SchemaFrame::LocationType::Resource) {
continue;
}

for (auto &entry : frame) {
if (entry.second.pointer != match->second.pointer ||
// Don't count the same origin twice
std::any_of(entry.second.destination_of.cbegin(),
entry.second.destination_of.cend(),
[&reference](const auto &destination) {
return destination.get() == reference.first;
})) {
continue;
}
mark_reference_origins_from(frame, references, entry);
}

entry.second.destination_of.emplace_back(reference.first);
for (const auto &entry : frame) {
if (entry.second.type != SchemaFrame::LocationType::Subschema) {
continue;
}

mark_reference_origins_from(frame, references, entry);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,9 @@ class SOURCEMETA_CORE_JSONSCHEMA_EXPORT SchemaFrame {
#pragma GCC diagnostic pop
#endif

/// Determines a location entry
using LocationKey = std::pair<SchemaReferenceType, std::string>;

/// A single frame in a JSON Schema reference frame
struct LocationsEntry {
LocationType type;
Expand All @@ -146,15 +149,13 @@ class SOURCEMETA_CORE_JSONSCHEMA_EXPORT SchemaFrame {
std::string dialect;
std::string base_dialect;
std::vector<PointerTemplate> instance_locations;
std::vector<std::reference_wrapper<const References::key_type>>
destination_of;
std::vector<std::reference_wrapper<const LocationKey>> destination_of;
};

/// A JSON Schema reference frame is a mapping of URIs to schema identifiers,
/// JSON Pointers within the schema, and subschemas dialects. We call it
/// reference frame as this mapping is essential for resolving references.
using Locations =
std::map<std::pair<SchemaReferenceType, std::string>, LocationsEntry>;
using Locations = std::map<LocationKey, LocationsEntry>;

/// Analyse a given schema
auto analyse(const JSON &schema, const SchemaWalker &walker,
Expand Down
14 changes: 7 additions & 7 deletions test/jsonschema/jsonschema_frame_2019_09_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1301,7 +1301,7 @@ TEST(JSONSchema_frame_2019_09, recursive_ref_recursive_anchor_true_anonymous) {

EXPECT_ANONYMOUS_FRAME_DYNAMIC_ANCHOR(
frame, "", "", "https://json-schema.org/draft/2019-09/schema",
"https://json-schema.org/draft/2019-09/schema", {""}, 1);
"https://json-schema.org/draft/2019-09/schema", {""}, 0);

// References

Expand Down Expand Up @@ -1362,7 +1362,7 @@ TEST(JSONSchema_frame_2019_09, recursive_ref_recursive_anchor_true) {
EXPECT_FRAME_DYNAMIC_2019_09_ANCHOR(
frame, "https://www.sourcemeta.com/schema",
"https://www.sourcemeta.com/schema", "",
"https://www.sourcemeta.com/schema", "", {""}, 1);
"https://www.sourcemeta.com/schema", "", {""}, 0);

// References

Expand Down Expand Up @@ -1408,7 +1408,7 @@ TEST(JSONSchema_frame_2019_09,
EXPECT_ANONYMOUS_FRAME_STATIC_SUBSCHEMA(
frame, "#/additionalItems", "/additionalItems",
"https://json-schema.org/draft/2019-09/schema",
"https://json-schema.org/draft/2019-09/schema", {"/~I~"}, 1);
"https://json-schema.org/draft/2019-09/schema", {"/~I~"}, 0);
EXPECT_ANONYMOUS_FRAME_STATIC_POINTER(
frame, "#/additionalItems/$id", "/additionalItems/$id",
"https://json-schema.org/draft/2019-09/schema",
Expand Down Expand Up @@ -1519,7 +1519,7 @@ TEST(JSONSchema_frame_2019_09,
EXPECT_ANONYMOUS_FRAME_DYNAMIC_ANCHOR(
frame, "https://example.com", "/additionalItems",
"https://json-schema.org/draft/2019-09/schema",
"https://json-schema.org/draft/2019-09/schema", {"/~I~"}, 1);
"https://json-schema.org/draft/2019-09/schema", {"/~I~"}, 0);

// References

Expand Down Expand Up @@ -1583,7 +1583,7 @@ TEST(JSONSchema_frame_2019_09, recursive_ref_nested_recursive_anchor_true) {
EXPECT_FRAME_DYNAMIC_2019_09_ANCHOR(
frame, "https://www.sourcemeta.com/schema",
"https://www.sourcemeta.com/schema", "/additionalItems",
"https://www.sourcemeta.com/schema", "/additionalItems", {"/~I~"}, 1);
"https://www.sourcemeta.com/schema", "/additionalItems", {"/~I~"}, 0);

// References

Expand Down Expand Up @@ -1676,7 +1676,7 @@ TEST(JSONSchema_frame_2019_09, recursive_ref_multiple_recursive_anchor_true) {
EXPECT_FRAME_DYNAMIC_2019_09_ANCHOR(
frame, "https://www.sourcemeta.com/nested",
"https://www.sourcemeta.com/schema", "/additionalItems",
"https://www.sourcemeta.com/nested", "", {"/~I~"}, 1);
"https://www.sourcemeta.com/nested", "", {"/~I~"}, 0);

// References

Expand Down Expand Up @@ -1982,7 +1982,7 @@ TEST(JSONSchema_frame_2019_09, relative_base_uri_with_ref) {
// Anchors
EXPECT_FRAME_STATIC_2019_09_ANCHOR(frame, "common#foo", "common",
"/$defs/foo", "common", "/$defs/foo", {""},
1);
0);

// JSON Pointers

Expand Down
10 changes: 5 additions & 5 deletions test/jsonschema/jsonschema_frame_2020_12_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1226,11 +1226,11 @@ TEST(JSONSchema_frame_2020_12,
EXPECT_ANONYMOUS_FRAME_DYNAMIC_ANCHOR(
frame, "#test", "/$defs/test",
"https://json-schema.org/draft/2020-12/schema",
"https://json-schema.org/draft/2020-12/schema", {""}, 1);
"https://json-schema.org/draft/2020-12/schema", {""}, 0);
EXPECT_ANONYMOUS_FRAME_STATIC_ANCHOR(
frame, "#test", "/$defs/test",
"https://json-schema.org/draft/2020-12/schema",
"https://json-schema.org/draft/2020-12/schema", {""}, 1);
"https://json-schema.org/draft/2020-12/schema", {""}, 0);

// Static frames

Expand Down Expand Up @@ -1293,11 +1293,11 @@ TEST(JSONSchema_frame_2020_12, dynamic_ref_to_single_dynamic_anchor_external) {
EXPECT_ANONYMOUS_FRAME_DYNAMIC_ANCHOR(
frame, "#test", "/$defs/test",
"https://json-schema.org/draft/2020-12/schema",
"https://json-schema.org/draft/2020-12/schema", {""}, 1);
"https://json-schema.org/draft/2020-12/schema", {""}, 0);
EXPECT_ANONYMOUS_FRAME_STATIC_ANCHOR(
frame, "#test", "/$defs/test",
"https://json-schema.org/draft/2020-12/schema",
"https://json-schema.org/draft/2020-12/schema", {""}, 1);
"https://json-schema.org/draft/2020-12/schema", {""}, 0);

// Static frames

Expand Down Expand Up @@ -1652,7 +1652,7 @@ TEST(JSONSchema_frame_2020_12, relative_base_uri_with_ref) {
// Anchors
EXPECT_FRAME_STATIC_2020_12_ANCHOR(frame, "common#foo", "common",
"/$defs/foo", "common", "/$defs/foo", {""},
1);
0);

// JSON Pointers

Expand Down
4 changes: 2 additions & 2 deletions test/jsonschema/jsonschema_frame_draft4_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -582,7 +582,7 @@ TEST(JSONSchema_frame_draft4, location_independent_identifier_anonymous) {
EXPECT_ANONYMOUS_FRAME_STATIC_ANCHOR(
frame, "#foo", "/definitions/foo",
"http://json-schema.org/draft-04/schema#",
"http://json-schema.org/draft-04/schema#", {""}, 1);
"http://json-schema.org/draft-04/schema#", {""}, 0);

// References

Expand Down Expand Up @@ -704,7 +704,7 @@ TEST(JSONSchema_frame_draft4, relative_base_uri_with_ref) {
// Anchors
EXPECT_FRAME_STATIC_DRAFT4_ANCHOR(frame, "common#foo", "common",
"/definitions/foo", "common",
"/definitions/foo", {""}, 1);
"/definitions/foo", {""}, 0);

// JSON Pointers

Expand Down
4 changes: 2 additions & 2 deletions test/jsonschema/jsonschema_frame_draft6_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -582,7 +582,7 @@ TEST(JSONSchema_frame_draft6, location_independent_identifier_anonymous) {
EXPECT_ANONYMOUS_FRAME_STATIC_ANCHOR(
frame, "#foo", "/definitions/foo",
"http://json-schema.org/draft-06/schema#",
"http://json-schema.org/draft-06/schema#", {""}, 1);
"http://json-schema.org/draft-06/schema#", {""}, 0);

// References

Expand Down Expand Up @@ -704,7 +704,7 @@ TEST(JSONSchema_frame_draft6, relative_base_uri_with_ref) {
// Anchors
EXPECT_FRAME_STATIC_DRAFT6_ANCHOR(frame, "common#foo", "common",
"/definitions/foo", "common",
"/definitions/foo", {""}, 1);
"/definitions/foo", {""}, 0);

// JSON Pointers

Expand Down
4 changes: 2 additions & 2 deletions test/jsonschema/jsonschema_frame_draft7_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -582,7 +582,7 @@ TEST(JSONSchema_frame_draft7, location_independent_identifier_anonymous) {
EXPECT_ANONYMOUS_FRAME_STATIC_ANCHOR(
frame, "#foo", "/definitions/foo",
"http://json-schema.org/draft-07/schema#",
"http://json-schema.org/draft-07/schema#", {""}, 1);
"http://json-schema.org/draft-07/schema#", {""}, 0);

// References

Expand Down Expand Up @@ -704,7 +704,7 @@ TEST(JSONSchema_frame_draft7, relative_base_uri_with_ref) {
// Anchors
EXPECT_FRAME_STATIC_DRAFT7_ANCHOR(frame, "common#foo", "common",
"/definitions/foo", "common",
"/definitions/foo", {""}, 1);
"/definitions/foo", {""}, 0);

// JSON Pointers

Expand Down
44 changes: 15 additions & 29 deletions test/jsonschema/jsonschema_frame_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -498,26 +498,17 @@ TEST(JSONSchema_frame, refs_with_id) {
frame.analyse(document, sourcemeta::core::schema_official_walker,
sourcemeta::core::schema_official_resolver);

EXPECT_FRAME_DESTINATION_OF(frame, Static,
"https://www.sourcemeta.com/schema", 0,
"/properties/foo/$ref")
EXPECT_FRAME_DESTINATION_OF(frame, Static,
"https://www.sourcemeta.com/schema#baz", 0,
"/properties/anchor/$ref")
EXPECT_FRAME_DESTINATION_OF(frame, Static,
"https://www.sourcemeta.com/schema#baz", 1,
"/properties/bar/$ref")
EXPECT_FRAME_DESTINATION_OF(
frame, Static, "https://www.sourcemeta.com/schema#/properties/baz", 0,
"/properties/anchor/$ref")
frame, Static, "https://www.sourcemeta.com/schema#/properties/baz", 0, 2,
"https://www.sourcemeta.com/schema#/properties/anchor");
EXPECT_FRAME_DESTINATION_OF(
frame, Static, "https://www.sourcemeta.com/schema#/properties/baz", 1,
"/properties/bar/$ref")
EXPECT_FRAME_DESTINATION_OF(frame, Static, "https://www.sourcemeta.com/test",
0, "/properties/qux/$ref")
frame, Static, "https://www.sourcemeta.com/schema#/properties/baz", 1, 2,
"https://www.sourcemeta.com/schema#/properties/bar");
EXPECT_FRAME_DESTINATION_OF(
frame, Static, "https://www.sourcemeta.com/schema#/properties/qux", 0,
"/properties/qux/$ref")
frame, Static, "https://www.sourcemeta.com/schema", 0, 1,
"https://www.sourcemeta.com/schema#/properties/foo");
EXPECT_FRAME_DESTINATION_OF(frame, Static, "https://www.sourcemeta.com/test",
0, 1, "https://www.sourcemeta.com/test");

EXPECT_EQ(frame.references().size(), 5);
EXPECT_STATIC_REFERENCE(
Expand Down Expand Up @@ -562,18 +553,13 @@ TEST(JSONSchema_frame, refs_with_no_id) {
frame.analyse(document, sourcemeta::core::schema_official_walker,
sourcemeta::core::schema_official_resolver);

EXPECT_FRAME_DESTINATION_OF(frame, Static, "", 0, "/properties/foo/$ref")
EXPECT_FRAME_DESTINATION_OF(frame, Static, "#baz", 0,
"/properties/anchor/$ref")
EXPECT_FRAME_DESTINATION_OF(frame, Static, "#baz", 1, "/properties/bar/$ref")
EXPECT_FRAME_DESTINATION_OF(frame, Static, "#/properties/baz", 0,
"/properties/anchor/$ref")
EXPECT_FRAME_DESTINATION_OF(frame, Static, "#/properties/baz", 1,
"/properties/bar/$ref")
EXPECT_FRAME_DESTINATION_OF(frame, Static, "https://www.example.com", 0,
"/properties/qux/$ref")
EXPECT_FRAME_DESTINATION_OF(frame, Static, "#/properties/qux", 0,
"/properties/qux/$ref")
EXPECT_FRAME_DESTINATION_OF(frame, Static, "#/properties/baz", 0, 2,
"#/properties/anchor");
EXPECT_FRAME_DESTINATION_OF(frame, Static, "#/properties/baz", 1, 2,
"#/properties/bar");
EXPECT_FRAME_DESTINATION_OF(frame, Static, "", 0, 1, "#/properties/foo");
EXPECT_FRAME_DESTINATION_OF(frame, Static, "https://www.example.com", 0, 1,
"https://www.example.com");

EXPECT_EQ(frame.references().size(), 5);
EXPECT_STATIC_REFERENCE(
Expand Down
11 changes: 5 additions & 6 deletions test/jsonschema/jsonschema_test_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -302,21 +302,20 @@
expected_fragment)

#define EXPECT_FRAME_DESTINATION_OF(frame, expected_type, expected_uri, \
expected_index, expected_origin) \
expected_index, expected_size, \
expected_origin) \
EXPECT_EQ(frame.locations() \
.at({sourcemeta::core::SchemaReferenceType::expected_type, \
expected_uri}) \
.destination_of.at(expected_index) \
.get() \
.first, \
sourcemeta::core::SchemaReferenceType::Static); \
.destination_of.size(), \
expected_size); \
EXPECT_EQ(frame.locations() \
.at({sourcemeta::core::SchemaReferenceType::expected_type, \
expected_uri}) \
.destination_of.at(expected_index) \
.get() \
.second, \
TO_POINTER(expected_origin));
expected_origin);

#define EXPECT_UNEVALUATED_STATIC(keywords, expected_pointer, \
expected_dependencies_size) \
Expand Down

0 comments on commit 8560609

Please sign in to comment.