Skip to content

Commit

Permalink
Frame $dynamicAnchor for 2020-12 (#454)
Browse files Browse the repository at this point in the history
Signed-off-by: Juan Cruz Viotti <[email protected]>
  • Loading branch information
jviotti authored Jan 19, 2024
1 parent 2ceffc5 commit 86a2da1
Show file tree
Hide file tree
Showing 3 changed files with 224 additions and 32 deletions.
27 changes: 23 additions & 4 deletions src/jsonschema/reference.cc
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ static auto fragment_string(const sourcemeta::jsontoolkit::URI uri)
return std::nullopt;
}

// TODO: Revise this function, try to simplify it, and avoid redundant
// operations (like resolving schemas) by adding relevant overloads
// for the functions it consumes.
auto sourcemeta::jsontoolkit::frame(
const sourcemeta::jsontoolkit::JSON &schema,
sourcemeta::jsontoolkit::ReferenceFrame &frame,
Expand Down Expand Up @@ -178,10 +181,17 @@ auto sourcemeta::jsontoolkit::frame(
const auto relative_anchor_uri{anchor_uri.recompose()};

if (bases.empty()) {
if (type == sourcemeta::jsontoolkit::AnchorType::Static) {
if (type == sourcemeta::jsontoolkit::AnchorType::Static ||
type == sourcemeta::jsontoolkit::AnchorType::All) {
frame.store(ReferenceType::Static, relative_anchor_uri, root_id, "",
pointer, effective_dialects.front());
}

if (type == sourcemeta::jsontoolkit::AnchorType::Dynamic ||
type == sourcemeta::jsontoolkit::AnchorType::All) {
frame.store(ReferenceType::Dynamic, relative_anchor_uri, root_id, "",
pointer, effective_dialects.front());
}
} else {
bool is_first = true;
for (const auto &base_string : bases) {
Expand All @@ -192,9 +202,18 @@ auto sourcemeta::jsontoolkit::frame(
continue;
}

if (type == sourcemeta::jsontoolkit::AnchorType::Static) {
frame.store(ReferenceType::Static, absolute_anchor_uri, root_id,
base_string, pointer, effective_dialects.front());
if (type == sourcemeta::jsontoolkit::AnchorType::Static ||
type == sourcemeta::jsontoolkit::AnchorType::All) {
frame.store(sourcemeta::jsontoolkit::ReferenceType::Static,
absolute_anchor_uri, root_id, base_string, pointer,
effective_dialects.front());
}

if (type == sourcemeta::jsontoolkit::AnchorType::Dynamic ||
type == sourcemeta::jsontoolkit::AnchorType::All) {
frame.store(sourcemeta::jsontoolkit::ReferenceType::Dynamic,
absolute_anchor_uri, root_id, base_string, pointer,
effective_dialects.front());
}

is_first = false;
Expand Down
169 changes: 169 additions & 0 deletions test/jsonschema/jsonschema_frame_2020_12_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
EXPECT_FRAME_STATIC(frame, reference, root_id, expected_pointer, \
"https://json-schema.org/draft/2020-12/schema");

#define EXPECT_FRAME_DYNAMIC_2020_12(frame, reference, root_id, \
expected_pointer) \
EXPECT_FRAME_DYNAMIC(frame, reference, root_id, expected_pointer, \
"https://json-schema.org/draft/2020-12/schema");

TEST(JSONSchema_frame_2020_12, empty_schema) {
const sourcemeta::jsontoolkit::JSON document =
sourcemeta::jsontoolkit::parse(R"JSON({
Expand Down Expand Up @@ -717,3 +722,167 @@ TEST(JSONSchema_frame_2020_12, same_dynamic_and_refs_in_same_object) {
"https://www.sourcemeta.com/schema",
"/properties/bar");
}

TEST(JSONSchema_frame_2020_12, dynamic_anchor_with_id) {
const sourcemeta::jsontoolkit::JSON document =
sourcemeta::jsontoolkit::parse(R"JSON({
"$id": "https://www.sourcemeta.com/schema",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$dynamicAnchor": "foo",
"properties": {
"foo": {
"$dynamicAnchor": "test"
},
"bar": {
"$id": "bar",
"$dynamicAnchor": "test",
"$anchor": "test"
}
}
})JSON");

sourcemeta::jsontoolkit::ReferenceFrame frame;
sourcemeta::jsontoolkit::ReferenceMap references;
sourcemeta::jsontoolkit::frame(document, frame, references,
sourcemeta::jsontoolkit::default_schema_walker,
sourcemeta::jsontoolkit::official_resolver)
.wait();

EXPECT_EQ(frame.size(), 19);

// Dynamic anchors

EXPECT_FRAME_DYNAMIC_2020_12(frame, "https://www.sourcemeta.com/schema#foo",
"https://www.sourcemeta.com/schema", "");
EXPECT_FRAME_DYNAMIC_2020_12(frame, "https://www.sourcemeta.com/schema#test",
"https://www.sourcemeta.com/schema",
"/properties/foo");
EXPECT_FRAME_DYNAMIC_2020_12(frame, "https://www.sourcemeta.com/bar#test",
"https://www.sourcemeta.com/schema",
"/properties/bar");

// Static anchors

EXPECT_FRAME_STATIC_2020_12(frame, "https://www.sourcemeta.com/bar#test",
"https://www.sourcemeta.com/schema",
"/properties/bar");

// Static identifiers

EXPECT_FRAME_STATIC_2020_12(frame, "https://www.sourcemeta.com/schema",
"https://www.sourcemeta.com/schema", "");
EXPECT_FRAME_STATIC_2020_12(frame, "https://www.sourcemeta.com/bar",
"https://www.sourcemeta.com/schema",
"/properties/bar");

// Static pointers

EXPECT_FRAME_STATIC_2020_12(frame, "https://www.sourcemeta.com/schema#/$id",
"https://www.sourcemeta.com/schema", "/$id");
EXPECT_FRAME_STATIC_2020_12(frame,
"https://www.sourcemeta.com/schema#/$schema",
"https://www.sourcemeta.com/schema", "/$schema");
EXPECT_FRAME_STATIC_2020_12(
frame, "https://www.sourcemeta.com/schema#/$dynamicAnchor",
"https://www.sourcemeta.com/schema", "/$dynamicAnchor");
EXPECT_FRAME_STATIC_2020_12(
frame, "https://www.sourcemeta.com/schema#/properties",
"https://www.sourcemeta.com/schema", "/properties");
EXPECT_FRAME_STATIC_2020_12(
frame, "https://www.sourcemeta.com/schema#/properties/foo",
"https://www.sourcemeta.com/schema", "/properties/foo");
EXPECT_FRAME_STATIC_2020_12(
frame, "https://www.sourcemeta.com/schema#/properties/foo/$dynamicAnchor",
"https://www.sourcemeta.com/schema", "/properties/foo/$dynamicAnchor");
EXPECT_FRAME_STATIC_2020_12(
frame, "https://www.sourcemeta.com/schema#/properties/bar",
"https://www.sourcemeta.com/schema", "/properties/bar");
EXPECT_FRAME_STATIC_2020_12(
frame, "https://www.sourcemeta.com/schema#/properties/bar/$id",
"https://www.sourcemeta.com/schema", "/properties/bar/$id");
EXPECT_FRAME_STATIC_2020_12(
frame, "https://www.sourcemeta.com/schema#/properties/bar/$dynamicAnchor",
"https://www.sourcemeta.com/schema", "/properties/bar/$dynamicAnchor");
EXPECT_FRAME_STATIC_2020_12(
frame, "https://www.sourcemeta.com/schema#/properties/bar/$anchor",
"https://www.sourcemeta.com/schema", "/properties/bar/$anchor");
EXPECT_FRAME_STATIC_2020_12(frame, "https://www.sourcemeta.com/bar#/$id",
"https://www.sourcemeta.com/schema",
"/properties/bar/$id");
EXPECT_FRAME_STATIC_2020_12(
frame, "https://www.sourcemeta.com/bar#/$dynamicAnchor",
"https://www.sourcemeta.com/schema", "/properties/bar/$dynamicAnchor");
EXPECT_FRAME_STATIC_2020_12(frame, "https://www.sourcemeta.com/bar#/$anchor",
"https://www.sourcemeta.com/schema",
"/properties/bar/$anchor");

// References

EXPECT_TRUE(references.empty());
}

TEST(JSONSchema_frame_2020_12, dynamic_anchor_without_id) {
const sourcemeta::jsontoolkit::JSON document =
sourcemeta::jsontoolkit::parse(R"JSON({
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"foo": {
"$dynamicAnchor": "test"
}
}
})JSON");

sourcemeta::jsontoolkit::ReferenceFrame frame;
sourcemeta::jsontoolkit::ReferenceMap references;
sourcemeta::jsontoolkit::frame(document, frame, references,
sourcemeta::jsontoolkit::default_schema_walker,
sourcemeta::jsontoolkit::official_resolver)
.wait();

EXPECT_EQ(frame.size(), 6);

// Dynamic anchors

EXPECT_ANONYMOUS_FRAME_DYNAMIC(
frame, "#test", "/properties/foo",
"https://json-schema.org/draft/2020-12/schema");

// Static frames

EXPECT_ANONYMOUS_FRAME_STATIC(frame, "", "",
"https://json-schema.org/draft/2020-12/schema");
EXPECT_ANONYMOUS_FRAME_STATIC(frame, "#/$schema", "/$schema",
"https://json-schema.org/draft/2020-12/schema");
EXPECT_ANONYMOUS_FRAME_STATIC(frame, "#/properties", "/properties",
"https://json-schema.org/draft/2020-12/schema");
EXPECT_ANONYMOUS_FRAME_STATIC(frame, "#/properties/foo", "/properties/foo",
"https://json-schema.org/draft/2020-12/schema");
EXPECT_ANONYMOUS_FRAME_STATIC(frame, "#/properties/foo/$dynamicAnchor",
"/properties/foo/$dynamicAnchor",
"https://json-schema.org/draft/2020-12/schema");

// References

EXPECT_TRUE(references.empty());
}

TEST(JSONSchema_frame_2020_12, dynamic_anchor_same_on_schema_resource) {
const sourcemeta::jsontoolkit::JSON document =
sourcemeta::jsontoolkit::parse(R"JSON({
"$id": "https://www.sourcemeta.com/schema",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$dynamicAnchor": "foo",
"items": {
"$dynamicAnchor": "foo"
}
})JSON");

sourcemeta::jsontoolkit::ReferenceFrame frame;
sourcemeta::jsontoolkit::ReferenceMap references;
EXPECT_THROW(sourcemeta::jsontoolkit::frame(
document, frame, references,
sourcemeta::jsontoolkit::default_schema_walker,
sourcemeta::jsontoolkit::official_resolver)
.wait(),
sourcemeta::jsontoolkit::SchemaError);
}
60 changes: 32 additions & 28 deletions test/jsonschema/jsonschema_test_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,40 +4,44 @@
#define TO_POINTER(pointer_string) \
sourcemeta::jsontoolkit::to_pointer((pointer_string))

#define EXPECT_FRAME(frame, expected_type, reference, root_id, \
expected_pointer, expected_dialect) \
EXPECT_TRUE((frame).defines((expected_type), (reference))); \
EXPECT_TRUE((frame).root((expected_type), (reference)).has_value()); \
EXPECT_EQ((frame).root((expected_type), (reference)).value(), (root_id)); \
EXPECT_EQ((frame).pointer((expected_type), (reference)), \
TO_POINTER(expected_pointer)); \
EXPECT_EQ((frame).dialect((expected_type), (reference)), (expected_dialect));

#define EXPECT_FRAME_STATIC(frame, reference, root_id, expected_pointer, \
expected_dialect) \
EXPECT_TRUE((frame).defines(sourcemeta::jsontoolkit::ReferenceType::Static, \
(reference))); \
EXPECT_TRUE( \
(frame) \
.root(sourcemeta::jsontoolkit::ReferenceType::Static, (reference)) \
.has_value()); \
EXPECT_EQ( \
(frame) \
.root(sourcemeta::jsontoolkit::ReferenceType::Static, (reference)) \
.value(), \
(root_id)); \
EXPECT_EQ((frame).pointer(sourcemeta::jsontoolkit::ReferenceType::Static, \
(reference)), \
EXPECT_FRAME(frame, sourcemeta::jsontoolkit::ReferenceType::Static, \
reference, root_id, expected_pointer, expected_dialect)

#define EXPECT_FRAME_DYNAMIC(frame, reference, root_id, expected_pointer, \
expected_dialect) \
EXPECT_FRAME(frame, sourcemeta::jsontoolkit::ReferenceType::Dynamic, \
reference, root_id, expected_pointer, expected_dialect)

#define EXPECT_ANONYMOUS_FRAME(frame, expected_type, reference, \
expected_pointer, expected_dialect) \
EXPECT_TRUE((frame).defines((expected_type), (reference))); \
EXPECT_FALSE((frame).root((expected_type), (reference)).has_value()); \
EXPECT_EQ((frame).pointer((expected_type), (reference)), \
TO_POINTER(expected_pointer)); \
EXPECT_EQ((frame).dialect(sourcemeta::jsontoolkit::ReferenceType::Static, \
(reference)), \
(expected_dialect));
EXPECT_EQ((frame).dialect((expected_type), (reference)), (expected_dialect));

#define EXPECT_ANONYMOUS_FRAME_STATIC(frame, reference, expected_pointer, \
expected_dialect) \
EXPECT_TRUE((frame).defines(sourcemeta::jsontoolkit::ReferenceType::Static, \
(reference))); \
EXPECT_FALSE( \
(frame) \
.root(sourcemeta::jsontoolkit::ReferenceType::Static, (reference)) \
.has_value()); \
EXPECT_EQ((frame).pointer(sourcemeta::jsontoolkit::ReferenceType::Static, \
(reference)), \
TO_POINTER(expected_pointer)); \
EXPECT_EQ((frame).dialect(sourcemeta::jsontoolkit::ReferenceType::Static, \
(reference)), \
(expected_dialect));
EXPECT_ANONYMOUS_FRAME(frame, \
sourcemeta::jsontoolkit::ReferenceType::Static, \
reference, expected_pointer, expected_dialect)

#define EXPECT_ANONYMOUS_FRAME_DYNAMIC(frame, reference, expected_pointer, \
expected_dialect) \
EXPECT_ANONYMOUS_FRAME(frame, \
sourcemeta::jsontoolkit::ReferenceType::Dynamic, \
reference, expected_pointer, expected_dialect)

#define EXPECT_REFERENCE(references, expected_type, expected_pointer, \
expected_uri, expected_base, expected_fragment) \
Expand Down

0 comments on commit 86a2da1

Please sign in to comment.