From b3a208db341ec683999af3061643f64bce80b80f Mon Sep 17 00:00:00 2001 From: Juan Cruz Viotti Date: Mon, 6 Jan 2025 12:59:32 -0400 Subject: [PATCH] Throw an exception when re-identifying and `$ref` overrides `($)id` (#1421) Signed-off-by: Juan Cruz Viotti --- src/jsonschema/jsonschema.cc | 28 +++++++++++++------ .../jsonschema_identify_2019_09_test.cc | 21 ++++++++++++++ .../jsonschema_identify_2020_12_test.cc | 21 ++++++++++++++ .../jsonschema_identify_draft3_test.cc | 13 +++++++++ .../jsonschema_identify_draft4_test.cc | 13 +++++++++ .../jsonschema_identify_draft6_test.cc | 13 +++++++++ .../jsonschema_identify_draft7_test.cc | 13 +++++++++ 7 files changed, 113 insertions(+), 9 deletions(-) diff --git a/src/jsonschema/jsonschema.cc b/src/jsonschema/jsonschema.cc index 9b5ba030f..b85451de2 100644 --- a/src/jsonschema/jsonschema.cc +++ b/src/jsonschema/jsonschema.cc @@ -100,6 +100,17 @@ 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) @@ -125,15 +136,7 @@ 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") && - (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#")) { + if (schema.defines("$ref") && ref_overrides_sibling(base_dialect)) { return std::nullopt; } @@ -167,6 +170,13 @@ auto sourcemeta::jsontoolkit::reidentify(JSON &schema, -> void { assert(is_schema(schema)); assert(schema.is_object()); + + 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"); + } + schema.assign(id_keyword(base_dialect), JSON{new_identifier}); assert(identify(schema, base_dialect).has_value()); } diff --git a/test/jsonschema/jsonschema_identify_2019_09_test.cc b/test/jsonschema/jsonschema_identify_2019_09_test.cc index 339c105cf..b6dfc4f47 100644 --- a/test/jsonschema/jsonschema_identify_2019_09_test.cc +++ b/test/jsonschema/jsonschema_identify_2019_09_test.cc @@ -237,3 +237,24 @@ TEST(JSONSchema_identify_2019_09, reidentify_replace_base_dialect_shortcut) { EXPECT_EQ(document, expected); } + +TEST(JSONSchema_identify_2019_09, reidentify_set_with_top_level_ref) { + sourcemeta::jsontoolkit::JSON document = + sourcemeta::jsontoolkit::parse(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$ref": "https://example.com/schema" + })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": "https://json-schema.org/draft/2019-09/schema", + "$ref": "https://example.com/schema" + })JSON"); + + EXPECT_EQ(document, expected); +} diff --git a/test/jsonschema/jsonschema_identify_2020_12_test.cc b/test/jsonschema/jsonschema_identify_2020_12_test.cc index c481def35..fdc0e417f 100644 --- a/test/jsonschema/jsonschema_identify_2020_12_test.cc +++ b/test/jsonschema/jsonschema_identify_2020_12_test.cc @@ -237,3 +237,24 @@ TEST(JSONSchema_identify_2020_12, reidentify_replace_base_dialect_shortcut) { EXPECT_EQ(document, expected); } + +TEST(JSONSchema_identify_2020_12, reidentify_set_with_top_level_ref) { + sourcemeta::jsontoolkit::JSON document = + sourcemeta::jsontoolkit::parse(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "https://example.com/schema" + })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": "https://json-schema.org/draft/2020-12/schema", + "$ref": "https://example.com/schema" + })JSON"); + + EXPECT_EQ(document, expected); +} diff --git a/test/jsonschema/jsonschema_identify_draft3_test.cc b/test/jsonschema/jsonschema_identify_draft3_test.cc index f3d3057e0..c59a90bc8 100644 --- a/test/jsonschema/jsonschema_identify_draft3_test.cc +++ b/test/jsonschema/jsonschema_identify_draft3_test.cc @@ -236,3 +236,16 @@ TEST(JSONSchema_identify_draft3, reidentify_replace_base_dialect_shortcut) { EXPECT_EQ(document, expected); } + +TEST(JSONSchema_identify_draft3, reidentify_set_with_top_level_ref) { + sourcemeta::jsontoolkit::JSON document = + sourcemeta::jsontoolkit::parse(R"JSON({ + "$schema": "http://json-schema.org/draft-03/schema#", + "$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); +} diff --git a/test/jsonschema/jsonschema_identify_draft4_test.cc b/test/jsonschema/jsonschema_identify_draft4_test.cc index 7bb57c344..4a3c04d85 100644 --- a/test/jsonschema/jsonschema_identify_draft4_test.cc +++ b/test/jsonschema/jsonschema_identify_draft4_test.cc @@ -236,3 +236,16 @@ TEST(JSONSchema_identify_draft4, reidentify_replace_base_dialect_shortcut) { EXPECT_EQ(document, expected); } + +TEST(JSONSchema_identify_draft4, reidentify_set_with_top_level_ref) { + sourcemeta::jsontoolkit::JSON document = + sourcemeta::jsontoolkit::parse(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "$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); +} diff --git a/test/jsonschema/jsonschema_identify_draft6_test.cc b/test/jsonschema/jsonschema_identify_draft6_test.cc index ed965b7cd..8df61be7e 100644 --- a/test/jsonschema/jsonschema_identify_draft6_test.cc +++ b/test/jsonschema/jsonschema_identify_draft6_test.cc @@ -236,3 +236,16 @@ TEST(JSONSchema_identify_draft6, reidentify_replace_base_dialect_shortcut) { EXPECT_EQ(document, expected); } + +TEST(JSONSchema_identify_draft6, reidentify_set_with_top_level_ref) { + sourcemeta::jsontoolkit::JSON document = + sourcemeta::jsontoolkit::parse(R"JSON({ + "$schema": "http://json-schema.org/draft-06/schema#", + "$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); +} diff --git a/test/jsonschema/jsonschema_identify_draft7_test.cc b/test/jsonschema/jsonschema_identify_draft7_test.cc index 8bfb25c3b..e3b4ea041 100644 --- a/test/jsonschema/jsonschema_identify_draft7_test.cc +++ b/test/jsonschema/jsonschema_identify_draft7_test.cc @@ -236,3 +236,16 @@ TEST(JSONSchema_identify_draft7, reidentify_replace_base_dialect_shortcut) { EXPECT_EQ(document, expected); } + +TEST(JSONSchema_identify_draft7, reidentify_set_with_top_level_ref) { + sourcemeta::jsontoolkit::JSON document = + sourcemeta::jsontoolkit::parse(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "$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); +}