diff --git a/src/jsonschema/jsonschema.cc b/src/jsonschema/jsonschema.cc index b85451de2..c46893e6f 100644 --- a/src/jsonschema/jsonschema.cc +++ b/src/jsonschema/jsonschema.cc @@ -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 &default_id) @@ -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; } @@ -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()); } diff --git a/test/jsonschema/jsonschema_identify_draft3_test.cc b/test/jsonschema/jsonschema_identify_draft3_test.cc index c59a90bc8..fcf936c88 100644 --- a/test/jsonschema/jsonschema_identify_draft3_test.cc +++ b/test/jsonschema/jsonschema_identify_draft3_test.cc @@ -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); } diff --git a/test/jsonschema/jsonschema_identify_draft4_test.cc b/test/jsonschema/jsonschema_identify_draft4_test.cc index 4a3c04d85..0604bb326 100644 --- a/test/jsonschema/jsonschema_identify_draft4_test.cc +++ b/test/jsonschema/jsonschema_identify_draft4_test.cc @@ -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); } diff --git a/test/jsonschema/jsonschema_identify_draft6_test.cc b/test/jsonschema/jsonschema_identify_draft6_test.cc index 8df61be7e..4ded14a73 100644 --- a/test/jsonschema/jsonschema_identify_draft6_test.cc +++ b/test/jsonschema/jsonschema_identify_draft6_test.cc @@ -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); } diff --git a/test/jsonschema/jsonschema_identify_draft7_test.cc b/test/jsonschema/jsonschema_identify_draft7_test.cc index e3b4ea041..d04646f28 100644 --- a/test/jsonschema/jsonschema_identify_draft7_test.cc +++ b/test/jsonschema/jsonschema_identify_draft7_test.cc @@ -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); }