Skip to content

Commit

Permalink
Make reidentify() work even with top-level $ref
Browse files Browse the repository at this point in the history
Signed-off-by: Juan Cruz Viotti <jv@jviotti.com>
  • Loading branch information
jviotti committed Jan 6, 2025
1 parent b3a208d commit ef5b451
Show file tree
Hide file tree
Showing 5 changed files with 181 additions and 33 deletions.
61 changes: 44 additions & 17 deletions src/jsonschema/jsonschema.cc
Original file line number Diff line number Diff line change
Expand Up @@ -100,17 +100,6 @@ auto sourcemeta::jsontoolkit::identify(
return identify(schema, maybe_base_dialect.value(), default_id);
}

static auto ref_overrides_sibling(const std::string &base_dialect) -> bool {
return (base_dialect == "http://json-schema.org/draft-07/schema#" ||
base_dialect == "http://json-schema.org/draft-07/hyper-schema#" ||
base_dialect == "http://json-schema.org/draft-06/schema#" ||
base_dialect == "http://json-schema.org/draft-06/hyper-schema#" ||
base_dialect == "http://json-schema.org/draft-04/schema#" ||
base_dialect == "http://json-schema.org/draft-04/hyper-schema#" ||
base_dialect == "http://json-schema.org/draft-03/schema#" ||
base_dialect == "http://json-schema.org/draft-03/hyper-schema#");
}

auto sourcemeta::jsontoolkit::identify(
const JSON &schema, const std::string &base_dialect,
const std::optional<std::string> &default_id)
Expand All @@ -136,7 +125,15 @@ auto sourcemeta::jsontoolkit::identify(
// don't check for base dialects lower than that.
// See
// https://json-schema.org/draft-07/draft-handrews-json-schema-01#rfc.section.8.3
if (schema.defines("$ref") && ref_overrides_sibling(base_dialect)) {
if (schema.defines("$ref") &&
(base_dialect == "http://json-schema.org/draft-07/schema#" ||
base_dialect == "http://json-schema.org/draft-07/hyper-schema#" ||
base_dialect == "http://json-schema.org/draft-06/schema#" ||
base_dialect == "http://json-schema.org/draft-06/hyper-schema#" ||
base_dialect == "http://json-schema.org/draft-04/schema#" ||
base_dialect == "http://json-schema.org/draft-04/hyper-schema#" ||
base_dialect == "http://json-schema.org/draft-03/schema#" ||
base_dialect == "http://json-schema.org/draft-03/hyper-schema#")) {
return std::nullopt;
}

Expand Down Expand Up @@ -170,14 +167,44 @@ auto sourcemeta::jsontoolkit::reidentify(JSON &schema,
-> void {
assert(is_schema(schema));
assert(schema.is_object());
schema.assign(id_keyword(base_dialect), JSON{new_identifier});

if (ref_overrides_sibling(base_dialect) && schema.defines("$ref")) {
throw SchemaError(
"Cannot set an identifier on a schema that declares a "
"top-level static reference in this dialect of JSON Schema");
if (schema.defines("$ref")) {
// Workaround top-level `$ref` with `allOf`
if (base_dialect == "http://json-schema.org/draft-07/schema#" ||
base_dialect == "http://json-schema.org/draft-07/hyper-schema#" ||
base_dialect == "http://json-schema.org/draft-06/schema#" ||
base_dialect == "http://json-schema.org/draft-06/hyper-schema#" ||
base_dialect == "http://json-schema.org/draft-04/schema#" ||
base_dialect == "http://json-schema.org/draft-04/hyper-schema#") {
// Note that if the schema already has an `allOf`, we can safely
// remove it, as it was by definition ignored by `$ref` already
if (schema.defines("allOf")) {
schema.erase("allOf");
}

schema.assign("allOf", JSON::make_array());
auto conjunction{JSON::make_object()};
conjunction.assign("$ref", schema.at("$ref"));
schema.at("allOf").push_back(std::move(conjunction));
schema.erase("$ref");
}

// Workaround top-level `$ref` with `extends`
if (base_dialect == "http://json-schema.org/draft-03/schema#" ||
base_dialect == "http://json-schema.org/draft-03/hyper-schema#") {
// Note that if the schema already has an `extends`, we can safely
// remove it, as it was by definition ignored by `$ref` already
if (schema.defines("extends")) {
schema.erase("extends");
}

schema.assign("extends", JSON::make_object());
schema.at("extends").assign("$ref", schema.at("$ref"));
schema.erase("$ref");
}
}

schema.assign(id_keyword(base_dialect), JSON{new_identifier});
assert(identify(schema, base_dialect).has_value());
}

Expand Down
39 changes: 35 additions & 4 deletions test/jsonschema/jsonschema_identify_draft3_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -244,8 +244,39 @@ TEST(JSONSchema_identify_draft3, reidentify_set_with_top_level_ref) {
"$ref": "https://example.com/schema"
})JSON");

EXPECT_THROW(sourcemeta::jsontoolkit::reidentify(
document, "https://example.com/my-new-id",
sourcemeta::jsontoolkit::official_resolver),
sourcemeta::jsontoolkit::SchemaError);
sourcemeta::jsontoolkit::reidentify(
document, "https://example.com/my-new-id",
sourcemeta::jsontoolkit::official_resolver);

const sourcemeta::jsontoolkit::JSON expected =
sourcemeta::jsontoolkit::parse(R"JSON({
"id": "https://example.com/my-new-id",
"$schema": "http://json-schema.org/draft-03/schema#",
"extends": { "$ref": "https://example.com/schema" }
})JSON");

EXPECT_EQ(document, expected);
}

TEST(JSONSchema_identify_draft3,
reidentify_set_with_top_level_ref_and_extends) {
sourcemeta::jsontoolkit::JSON document =
sourcemeta::jsontoolkit::parse(R"JSON({
"$schema": "http://json-schema.org/draft-03/schema#",
"$ref": "https://example.com/schema",
"extends": { "type": "string" }
})JSON");

sourcemeta::jsontoolkit::reidentify(
document, "https://example.com/my-new-id",
sourcemeta::jsontoolkit::official_resolver);

const sourcemeta::jsontoolkit::JSON expected =
sourcemeta::jsontoolkit::parse(R"JSON({
"id": "https://example.com/my-new-id",
"$schema": "http://json-schema.org/draft-03/schema#",
"extends": { "$ref": "https://example.com/schema" }
})JSON");

EXPECT_EQ(document, expected);
}
38 changes: 34 additions & 4 deletions test/jsonschema/jsonschema_identify_draft4_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -244,8 +244,38 @@ TEST(JSONSchema_identify_draft4, reidentify_set_with_top_level_ref) {
"$ref": "https://example.com/schema"
})JSON");

EXPECT_THROW(sourcemeta::jsontoolkit::reidentify(
document, "https://example.com/my-new-id",
sourcemeta::jsontoolkit::official_resolver),
sourcemeta::jsontoolkit::SchemaError);
sourcemeta::jsontoolkit::reidentify(
document, "https://example.com/my-new-id",
sourcemeta::jsontoolkit::official_resolver);

const sourcemeta::jsontoolkit::JSON expected =
sourcemeta::jsontoolkit::parse(R"JSON({
"id": "https://example.com/my-new-id",
"$schema": "http://json-schema.org/draft-04/schema#",
"allOf": [ { "$ref": "https://example.com/schema" } ]
})JSON");

EXPECT_EQ(document, expected);
}

TEST(JSONSchema_identify_draft4, reidentify_set_with_top_level_ref_and_allof) {
sourcemeta::jsontoolkit::JSON document =
sourcemeta::jsontoolkit::parse(R"JSON({
"$schema": "http://json-schema.org/draft-04/schema#",
"$ref": "https://example.com/schema",
"allOf": [ { "type": "string" } ]
})JSON");

sourcemeta::jsontoolkit::reidentify(
document, "https://example.com/my-new-id",
sourcemeta::jsontoolkit::official_resolver);

const sourcemeta::jsontoolkit::JSON expected =
sourcemeta::jsontoolkit::parse(R"JSON({
"id": "https://example.com/my-new-id",
"$schema": "http://json-schema.org/draft-04/schema#",
"allOf": [ { "$ref": "https://example.com/schema" } ]
})JSON");

EXPECT_EQ(document, expected);
}
38 changes: 34 additions & 4 deletions test/jsonschema/jsonschema_identify_draft6_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -244,8 +244,38 @@ TEST(JSONSchema_identify_draft6, reidentify_set_with_top_level_ref) {
"$ref": "https://example.com/schema"
})JSON");

EXPECT_THROW(sourcemeta::jsontoolkit::reidentify(
document, "https://example.com/my-new-id",
sourcemeta::jsontoolkit::official_resolver),
sourcemeta::jsontoolkit::SchemaError);
sourcemeta::jsontoolkit::reidentify(
document, "https://example.com/my-new-id",
sourcemeta::jsontoolkit::official_resolver);

const sourcemeta::jsontoolkit::JSON expected =
sourcemeta::jsontoolkit::parse(R"JSON({
"$id": "https://example.com/my-new-id",
"$schema": "http://json-schema.org/draft-06/schema#",
"allOf": [ { "$ref": "https://example.com/schema" } ]
})JSON");

EXPECT_EQ(document, expected);
}

TEST(JSONSchema_identify_draft6, reidentify_set_with_top_level_ref_and_allof) {
sourcemeta::jsontoolkit::JSON document =
sourcemeta::jsontoolkit::parse(R"JSON({
"$schema": "http://json-schema.org/draft-06/schema#",
"$ref": "https://example.com/schema",
"allOf": [ { "type": "string" } ]
})JSON");

sourcemeta::jsontoolkit::reidentify(
document, "https://example.com/my-new-id",
sourcemeta::jsontoolkit::official_resolver);

const sourcemeta::jsontoolkit::JSON expected =
sourcemeta::jsontoolkit::parse(R"JSON({
"$id": "https://example.com/my-new-id",
"$schema": "http://json-schema.org/draft-06/schema#",
"allOf": [ { "$ref": "https://example.com/schema" } ]
})JSON");

EXPECT_EQ(document, expected);
}
38 changes: 34 additions & 4 deletions test/jsonschema/jsonschema_identify_draft7_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -244,8 +244,38 @@ TEST(JSONSchema_identify_draft7, reidentify_set_with_top_level_ref) {
"$ref": "https://example.com/schema"
})JSON");

EXPECT_THROW(sourcemeta::jsontoolkit::reidentify(
document, "https://example.com/my-new-id",
sourcemeta::jsontoolkit::official_resolver),
sourcemeta::jsontoolkit::SchemaError);
sourcemeta::jsontoolkit::reidentify(
document, "https://example.com/my-new-id",
sourcemeta::jsontoolkit::official_resolver);

const sourcemeta::jsontoolkit::JSON expected =
sourcemeta::jsontoolkit::parse(R"JSON({
"$id": "https://example.com/my-new-id",
"$schema": "http://json-schema.org/draft-07/schema#",
"allOf": [ { "$ref": "https://example.com/schema" } ]
})JSON");

EXPECT_EQ(document, expected);
}

TEST(JSONSchema_identify_draft7, reidentify_set_with_top_level_ref_and_allof) {
sourcemeta::jsontoolkit::JSON document =
sourcemeta::jsontoolkit::parse(R"JSON({
"$schema": "http://json-schema.org/draft-07/schema#",
"$ref": "https://example.com/schema",
"allOf": [ { "type": "string" } ]
})JSON");

sourcemeta::jsontoolkit::reidentify(
document, "https://example.com/my-new-id",
sourcemeta::jsontoolkit::official_resolver);

const sourcemeta::jsontoolkit::JSON expected =
sourcemeta::jsontoolkit::parse(R"JSON({
"$id": "https://example.com/my-new-id",
"$schema": "http://json-schema.org/draft-07/schema#",
"allOf": [ { "$ref": "https://example.com/schema" } ]
})JSON");

EXPECT_EQ(document, expected);
}

0 comments on commit ef5b451

Please sign in to comment.