From 9ae3921842faa40aeedf9c1e4047c61aabdf969f Mon Sep 17 00:00:00 2001 From: Juan Cruz Viotti Date: Fri, 30 Jan 2026 16:55:44 -0400 Subject: [PATCH] [WIP] Optimise wrapping to subschemas with no references See: https://github.com/sourcemeta/jsonschema/issues/629 Signed-off-by: Juan Cruz Viotti --- .../include/sourcemeta/core/jsonschema.h | 15 +- src/core/jsonschema/jsonschema.cc | 40 +- test/jsonschema/jsonschema_wrap_test.cc | 421 ++++++------------ 3 files changed, 171 insertions(+), 305 deletions(-) diff --git a/src/core/jsonschema/include/sourcemeta/core/jsonschema.h b/src/core/jsonschema/include/sourcemeta/core/jsonschema.h index a18eeadab..c27287f39 100644 --- a/src/core/jsonschema/include/sourcemeta/core/jsonschema.h +++ b/src/core/jsonschema/include/sourcemeta/core/jsonschema.h @@ -422,16 +422,25 @@ auto wrap(std::string_view identifier) -> JSON; /// "items": { "type": "string" } /// })JSON"); /// +/// sourcemeta::core::SchemaFrame frame{ +/// sourcemeta::core::SchemaFrame::Mode::References}; +/// frame.analyse(document, sourcemeta::core::schema_walker, +/// sourcemeta::core::schema_resolver); +/// +/// const auto location{frame.traverse( +/// sourcemeta::core::Pointer{"items"}, +/// sourcemeta::core::SchemaFrame::LocationType::Subschema)}; +/// /// const sourcemeta::core::JSON result = -/// sourcemeta::core::wrap(document, { "items" }, +/// sourcemeta::core::wrap(document, frame, location.value().get(), /// sourcemeta::core::schema_resolver); /// /// sourcemeta::core::prettify(result, std::cerr); /// std::cerr << "\n"; /// ``` SOURCEMETA_CORE_JSONSCHEMA_EXPORT -auto wrap(const JSON &schema, const Pointer &pointer, - const SchemaResolver &resolver, std::string_view default_dialect = "") +auto wrap(const JSON &schema, const SchemaFrame &frame, + const SchemaFrame::Location &location, const SchemaResolver &resolver) -> JSON; /// @ingroup jsonschema diff --git a/src/core/jsonschema/jsonschema.cc b/src/core/jsonschema/jsonschema.cc index aad330940..5a688328e 100644 --- a/src/core/jsonschema/jsonschema.cc +++ b/src/core/jsonschema/jsonschema.cc @@ -590,24 +590,34 @@ auto sourcemeta::core::wrap(const std::string_view identifier) return result; } -auto sourcemeta::core::wrap(const sourcemeta::core::JSON &schema, - const sourcemeta::core::Pointer &pointer, - const sourcemeta::core::SchemaResolver &resolver, - std::string_view default_dialect) +auto sourcemeta::core::wrap( + const sourcemeta::core::JSON &schema, + const sourcemeta::core::SchemaFrame &frame, + const sourcemeta::core::SchemaFrame::Location &location, + const sourcemeta::core::SchemaResolver &resolver) -> sourcemeta::core::JSON { + assert(location.type != SchemaFrame::LocationType::Pointer); + const auto &pointer{location.pointer}; assert(try_get(schema, pointer)); if (pointer.empty()) { return schema; } - auto copy = schema; - const auto effective_dialect{dialect(copy, default_dialect)}; - if (!effective_dialect.empty()) { - copy.assign("$schema", JSON{effective_dialect}); - } else { - throw SchemaUnknownBaseDialectError(); + const auto has_internal_references{ + std::any_of(frame.references().cbegin(), frame.references().cend(), + [&pointer](const auto &reference) { + return reference.first.second.starts_with(pointer); + })}; + + if (!has_internal_references) { + auto subschema{get(schema, pointer)}; + subschema.assign("$schema", JSON{JSON::String{location.dialect}}); + return subschema; } + auto copy = schema; + copy.assign("$schema", JSON{JSON::String{location.dialect}}); + auto result{JSON::make_object()}; // JSON Schema 2020-12 is the first dialect that truly supports // cross-dialect references In practice, others do, but we can @@ -622,13 +632,13 @@ auto sourcemeta::core::wrap(const sourcemeta::core::JSON &schema, // other schemas whose top-level identifiers are relative URIs don't // get affected. Otherwise, we would cause unintended base resolution. constexpr std::string_view WRAPPER_IDENTIFIER{"__sourcemeta-core-wrap__"}; - const auto maybe_id{identify(copy, resolver, default_dialect)}; + const auto maybe_id{identify(copy, resolver, location.dialect)}; const auto id{maybe_id.empty() ? WRAPPER_IDENTIFIER : maybe_id}; URI uri{id}; try { - reidentify(copy, id, resolver, default_dialect); + reidentify(copy, id, resolver, location.dialect); // Otherwise we will get an error with the `WRAPPER_IDENTIFIER`, which will // be confusing to end users @@ -647,9 +657,13 @@ auto sourcemeta::core::wrap(const sourcemeta::core::JSON &schema, uri.fragment(to_string(pointer)); result.assign_assume_new("$ref", JSON{uri.recompose()}); } else { + static const JSON::String DEFS{"$defs"}; + static const JSON::String SCHEMA{"schema"}; result.assign_assume_new( "$ref", - JSON{to_uri(Pointer{"$defs", "schema"}.concat(pointer)).recompose()}); + JSON{to_uri(WeakPointer{std::cref(DEFS), std::cref(SCHEMA)}.concat( + pointer)) + .recompose()}); } return result; diff --git a/test/jsonschema/jsonschema_wrap_test.cc b/test/jsonschema/jsonschema_wrap_test.cc index a9b5fdbcc..35bc2afac 100644 --- a/test/jsonschema/jsonschema_wrap_test.cc +++ b/test/jsonschema/jsonschema_wrap_test.cc @@ -3,6 +3,21 @@ #include #include +static auto wrap_schema(const sourcemeta::core::JSON &schema, + const sourcemeta::core::Pointer &pointer, + std::string_view default_dialect = "") + -> sourcemeta::core::JSON { + sourcemeta::core::SchemaFrame frame{ + sourcemeta::core::SchemaFrame::Mode::References}; + frame.analyse(schema, sourcemeta::core::schema_walker, + sourcemeta::core::schema_resolver, default_dialect); + const auto location{ + frame.traverse(sourcemeta::core::to_weak_pointer(pointer))}; + assert(location.has_value()); + return sourcemeta::core::wrap(schema, frame, location.value().get(), + sourcemeta::core::schema_resolver); +} + TEST(JSONSchema_wrap, identifier_without_fragment) { const auto identifier{"https://www.example.com"}; const auto result{sourcemeta::core::wrap(identifier)}; @@ -49,21 +64,11 @@ TEST(JSONSchema_wrap, schema_without_identifier) { } })JSON")}; - const auto result{sourcemeta::core::wrap(schema, {"items"}, - sourcemeta::core::schema_resolver)}; + const auto result{wrap_schema(schema, {"items"})}; const auto expected{sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2020-12/schema", - "$ref": "__sourcemeta-core-wrap__#/items", - "$defs": { - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "__sourcemeta-core-wrap__", - "items": { - "type": "string" - } - } - } + "type": "string" })JSON")}; EXPECT_EQ(result, expected); @@ -77,8 +82,7 @@ TEST(JSONSchema_wrap, schema_without_identifier_and_relative_uri) { } })JSON")}; - const auto result{sourcemeta::core::wrap(schema, {"items"}, - sourcemeta::core::schema_resolver)}; + const auto result{wrap_schema(schema, {"items"})}; // We don't want the relative reference to be resolved against // an absolute base @@ -106,22 +110,12 @@ TEST(JSONSchema_wrap, schema_without_identifier_with_default_dialect) { } })JSON")}; - const auto result{sourcemeta::core::wrap( - schema, {"items"}, sourcemeta::core::schema_resolver, - "https://json-schema.org/draft/2020-12/schema")}; + const auto result{wrap_schema( + schema, {"items"}, "https://json-schema.org/draft/2020-12/schema")}; const auto expected{sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2020-12/schema", - "$ref": "__sourcemeta-core-wrap__#/items", - "$defs": { - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "__sourcemeta-core-wrap__", - "items": { - "type": "string" - } - } - } + "type": "string" })JSON")}; EXPECT_EQ(result, expected); @@ -136,22 +130,12 @@ TEST(JSONSchema_wrap, } })JSON")}; - const auto result{sourcemeta::core::wrap( - schema, {"items"}, sourcemeta::core::schema_resolver, - "https://json-schema.org/draft/2019-09/schema")}; + const auto result{wrap_schema( + schema, {"items"}, "https://json-schema.org/draft/2019-09/schema")}; const auto expected{sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2020-12/schema", - "$ref": "__sourcemeta-core-wrap__#/items", - "$defs": { - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "__sourcemeta-core-wrap__", - "items": { - "type": "string" - } - } - } + "type": "string" })JSON")}; EXPECT_EQ(result, expected); @@ -165,8 +149,7 @@ TEST(JSONSchema_wrap, schema_without_identifier_empty_pointer) { } })JSON")}; - const auto result{ - sourcemeta::core::wrap(schema, {}, sourcemeta::core::schema_resolver)}; + const auto result{wrap_schema(schema, {})}; const auto expected{sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2020-12/schema", @@ -185,8 +168,7 @@ TEST(JSONSchema_wrap, schema_without_identifier_without_dialect) { } })JSON")}; - EXPECT_THROW(sourcemeta::core::wrap(schema, {"items"}, - sourcemeta::core::schema_resolver), + EXPECT_THROW(wrap_schema(schema, {"items"}), sourcemeta::core::SchemaUnknownBaseDialectError); } @@ -199,21 +181,11 @@ TEST(JSONSchema_wrap, schema_with_identifier) { } })JSON")}; - const auto result{sourcemeta::core::wrap(schema, {"items"}, - sourcemeta::core::schema_resolver)}; + const auto result{wrap_schema(schema, {"items"})}; const auto expected{sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2020-12/schema", - "$ref": "https://sourcemeta.com/1#/items", - "$defs": { - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://sourcemeta.com/1", - "items": { - "type": "string" - } - } - } + "type": "string" })JSON")}; EXPECT_EQ(result, expected); @@ -228,21 +200,11 @@ TEST(JSONSchema_wrap, schema_with_identifier_trailing_empty_fragment) { } })JSON")}; - const auto result{sourcemeta::core::wrap(schema, {"items"}, - sourcemeta::core::schema_resolver)}; + const auto result{wrap_schema(schema, {"items"})}; const auto expected{sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2020-12/schema", - "$ref": "https://sourcemeta.com/1#/items", - "$defs": { - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://sourcemeta.com/1#", - "items": { - "type": "string" - } - } - } + "type": "string" })JSON")}; EXPECT_EQ(result, expected); @@ -257,22 +219,12 @@ TEST(JSONSchema_wrap, schema_with_identifier_different_default_dialect) { } })JSON")}; - const auto result{sourcemeta::core::wrap( - schema, {"items"}, sourcemeta::core::schema_resolver, - "https://json-schema.org/draft/2019-09/schema")}; + const auto result{wrap_schema( + schema, {"items"}, "https://json-schema.org/draft/2019-09/schema")}; const auto expected{sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2020-12/schema", - "$ref": "https://sourcemeta.com/1#/items", - "$defs": { - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://sourcemeta.com/1", - "items": { - "type": "string" - } - } - } + "type": "string" })JSON")}; EXPECT_EQ(result, expected); @@ -286,22 +238,12 @@ TEST(JSONSchema_wrap, schema_with_identifier_default_dialect) { } })JSON")}; - const auto result{sourcemeta::core::wrap( - schema, {"items"}, sourcemeta::core::schema_resolver, - "https://json-schema.org/draft/2019-09/schema")}; + const auto result{wrap_schema( + schema, {"items"}, "https://json-schema.org/draft/2019-09/schema")}; const auto expected{sourcemeta::core::parse_json(R"JSON({ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$ref": "https://sourcemeta.com/1#/items", - "$defs": { - "schema": { - "$schema": "https://json-schema.org/draft/2019-09/schema", - "$id": "https://sourcemeta.com/1", - "items": { - "type": "string" - } - } - } + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "string" })JSON")}; EXPECT_EQ(result, expected); @@ -309,16 +251,17 @@ TEST(JSONSchema_wrap, schema_with_identifier_default_dialect) { TEST(JSONSchema_wrap, schema_with_identifier_empty_pointer) { const auto schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://sourcemeta.com/1", "items": { "type": "string" } })JSON")}; - const auto result{ - sourcemeta::core::wrap(schema, {}, sourcemeta::core::schema_resolver)}; + const auto result{wrap_schema(schema, {})}; const auto expected{sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://sourcemeta.com/1", "items": { "type": "string" @@ -336,48 +279,17 @@ TEST(JSONSchema_wrap, schema_with_identifier_no_dialect) { } })JSON")}; - EXPECT_THROW(sourcemeta::core::wrap(schema, {"items"}, - sourcemeta::core::schema_resolver), + EXPECT_THROW(wrap_schema(schema, {"items"}), sourcemeta::core::SchemaUnknownBaseDialectError); } -TEST(JSONSchema_wrap, schema_with_identifier_with_fragment) { - const auto schema{sourcemeta::core::parse_json(R"JSON({ - "$schema": "http://json-schema.org/draft-04/schema#", - "id": "https://sourcemeta.com/1#foo", - "items": { - "type": "string" - } - })JSON")}; - - const auto result{sourcemeta::core::wrap(schema, {"items"}, - sourcemeta::core::schema_resolver)}; - - const auto expected{sourcemeta::core::parse_json(R"JSON({ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$ref": "#/$defs/schema/items", - "$defs": { - "schema": { - "$schema": "http://json-schema.org/draft-04/schema#", - "id": "https://sourcemeta.com/1#foo", - "items": { - "type": "string" - } - } - } - })JSON")}; - - EXPECT_EQ(result, expected); -} - TEST(JSONSchema_wrap, draft4_standalone_ref_with_default_dialect) { const auto schema{sourcemeta::core::parse_json(R"JSON({ "$ref": "https://example.com" })JSON")}; const auto result{ - sourcemeta::core::wrap(schema, {}, sourcemeta::core::schema_resolver, - "http://json-schema.org/draft-04/schema#")}; + wrap_schema(schema, {}, "http://json-schema.org/draft-04/schema#")}; const auto expected{sourcemeta::core::parse_json(R"JSON({ "$ref": "https://example.com" @@ -396,8 +308,7 @@ TEST(JSONSchema_wrap, draft4_top_level_ref_with_id_empty) { } })JSON")}; - const auto result{ - sourcemeta::core::wrap(schema, {}, sourcemeta::core::schema_resolver)}; + const auto result{wrap_schema(schema, {})}; const auto expected{sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-04/schema#", @@ -411,21 +322,6 @@ TEST(JSONSchema_wrap, draft4_top_level_ref_with_id_empty) { EXPECT_EQ(result, expected); } -TEST(JSONSchema_wrap, draft4_top_level_ref_with_id_definitions_foo) { - const auto schema{sourcemeta::core::parse_json(R"JSON({ - "$schema": "http://json-schema.org/draft-04/schema#", - "id": "https://example.com", - "$ref": "#/definitions/foo", - "definitions": { - "foo": {} - } - })JSON")}; - - EXPECT_THROW(sourcemeta::core::wrap(schema, {"definitions", "foo"}, - sourcemeta::core::schema_resolver), - sourcemeta::core::SchemaError); -} - TEST(JSONSchema_wrap, draft4_top_level_ref_without_id_empty) { const auto schema{sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-04/schema#", @@ -435,8 +331,7 @@ TEST(JSONSchema_wrap, draft4_top_level_ref_without_id_empty) { } })JSON")}; - const auto result{ - sourcemeta::core::wrap(schema, {}, sourcemeta::core::schema_resolver)}; + const auto result{wrap_schema(schema, {})}; const auto expected{sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-04/schema#", @@ -449,28 +344,13 @@ TEST(JSONSchema_wrap, draft4_top_level_ref_without_id_empty) { EXPECT_EQ(result, expected); } -TEST(JSONSchema_wrap, draft4_top_level_ref_without_id_definitions_foo) { - const auto schema{sourcemeta::core::parse_json(R"JSON({ - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/foo", - "definitions": { - "foo": {} - } - })JSON")}; - - EXPECT_THROW(sourcemeta::core::wrap(schema, {"definitions", "foo"}, - sourcemeta::core::schema_resolver), - sourcemeta::core::SchemaError); -} - TEST(JSONSchema_wrap, draft6_standalone_ref_with_default_dialect) { const auto schema{sourcemeta::core::parse_json(R"JSON({ "$ref": "https://example.com" })JSON")}; const auto result{ - sourcemeta::core::wrap(schema, {}, sourcemeta::core::schema_resolver, - "http://json-schema.org/draft-06/schema#")}; + wrap_schema(schema, {}, "http://json-schema.org/draft-06/schema#")}; const auto expected{sourcemeta::core::parse_json(R"JSON({ "$ref": "https://example.com" @@ -489,8 +369,7 @@ TEST(JSONSchema_wrap, draft6_top_level_ref_with_id_empty) { } })JSON")}; - const auto result{ - sourcemeta::core::wrap(schema, {}, sourcemeta::core::schema_resolver)}; + const auto result{wrap_schema(schema, {})}; const auto expected{sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-06/schema#", @@ -504,21 +383,6 @@ TEST(JSONSchema_wrap, draft6_top_level_ref_with_id_empty) { EXPECT_EQ(result, expected); } -TEST(JSONSchema_wrap, draft6_top_level_ref_with_id_definitions_foo) { - const auto schema{sourcemeta::core::parse_json(R"JSON({ - "$schema": "http://json-schema.org/draft-06/schema#", - "$id": "https://example.com", - "$ref": "#/definitions/foo", - "definitions": { - "foo": {} - } - })JSON")}; - - EXPECT_THROW(sourcemeta::core::wrap(schema, {"definitions", "foo"}, - sourcemeta::core::schema_resolver), - sourcemeta::core::SchemaError); -} - TEST(JSONSchema_wrap, draft6_top_level_ref_without_id_empty) { const auto schema{sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-06/schema#", @@ -528,8 +392,7 @@ TEST(JSONSchema_wrap, draft6_top_level_ref_without_id_empty) { } })JSON")}; - const auto result{ - sourcemeta::core::wrap(schema, {}, sourcemeta::core::schema_resolver)}; + const auto result{wrap_schema(schema, {})}; const auto expected{sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-06/schema#", @@ -542,28 +405,13 @@ TEST(JSONSchema_wrap, draft6_top_level_ref_without_id_empty) { EXPECT_EQ(result, expected); } -TEST(JSONSchema_wrap, draft6_top_level_ref_without_id_definitions_foo) { - const auto schema{sourcemeta::core::parse_json(R"JSON({ - "$schema": "http://json-schema.org/draft-06/schema#", - "$ref": "#/definitions/foo", - "definitions": { - "foo": {} - } - })JSON")}; - - EXPECT_THROW(sourcemeta::core::wrap(schema, {"definitions", "foo"}, - sourcemeta::core::schema_resolver), - sourcemeta::core::SchemaError); -} - TEST(JSONSchema_wrap, draft7_standalone_ref_with_default_dialect) { const auto schema{sourcemeta::core::parse_json(R"JSON({ "$ref": "https://example.com" })JSON")}; const auto result{ - sourcemeta::core::wrap(schema, {}, sourcemeta::core::schema_resolver, - "http://json-schema.org/draft-07/schema#")}; + wrap_schema(schema, {}, "http://json-schema.org/draft-07/schema#")}; const auto expected{sourcemeta::core::parse_json(R"JSON({ "$ref": "https://example.com" @@ -582,8 +430,7 @@ TEST(JSONSchema_wrap, draft7_top_level_ref_with_id_empty) { } })JSON")}; - const auto result{ - sourcemeta::core::wrap(schema, {}, sourcemeta::core::schema_resolver)}; + const auto result{wrap_schema(schema, {})}; const auto expected{sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-07/schema#", @@ -597,21 +444,6 @@ TEST(JSONSchema_wrap, draft7_top_level_ref_with_id_empty) { EXPECT_EQ(result, expected); } -TEST(JSONSchema_wrap, draft7_top_level_ref_with_id_definitions_foo) { - const auto schema{sourcemeta::core::parse_json(R"JSON({ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://example.com", - "$ref": "#/definitions/foo", - "definitions": { - "foo": {} - } - })JSON")}; - - EXPECT_THROW(sourcemeta::core::wrap(schema, {"definitions", "foo"}, - sourcemeta::core::schema_resolver), - sourcemeta::core::SchemaError); -} - TEST(JSONSchema_wrap, draft7_top_level_ref_without_id_empty) { const auto schema{sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-07/schema#", @@ -621,8 +453,7 @@ TEST(JSONSchema_wrap, draft7_top_level_ref_without_id_empty) { } })JSON")}; - const auto result{ - sourcemeta::core::wrap(schema, {}, sourcemeta::core::schema_resolver)}; + const auto result{wrap_schema(schema, {})}; const auto expected{sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-07/schema#", @@ -635,20 +466,6 @@ TEST(JSONSchema_wrap, draft7_top_level_ref_without_id_empty) { EXPECT_EQ(result, expected); } -TEST(JSONSchema_wrap, draft7_top_level_ref_without_id_definitions_foo) { - const auto schema{sourcemeta::core::parse_json(R"JSON({ - "$schema": "http://json-schema.org/draft-07/schema#", - "$ref": "#/definitions/foo", - "definitions": { - "foo": {} - } - })JSON")}; - - EXPECT_THROW(sourcemeta::core::wrap(schema, {"definitions", "foo"}, - sourcemeta::core::schema_resolver), - sourcemeta::core::SchemaError); -} - TEST(JSONSchema_wrap, 2019_09_top_level_ref_with_id_empty) { const auto schema{sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2019-09/schema", @@ -659,8 +476,7 @@ TEST(JSONSchema_wrap, 2019_09_top_level_ref_with_id_empty) { } })JSON")}; - const auto result{ - sourcemeta::core::wrap(schema, {}, sourcemeta::core::schema_resolver)}; + const auto result{wrap_schema(schema, {})}; const auto expected{sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2019-09/schema", @@ -684,22 +500,10 @@ TEST(JSONSchema_wrap, 2019_09_top_level_ref_with_id_defs_foo) { } })JSON")}; - const auto result{sourcemeta::core::wrap(schema, {"$defs", "foo"}, - sourcemeta::core::schema_resolver)}; + const auto result{wrap_schema(schema, {"$defs", "foo"})}; const auto expected{sourcemeta::core::parse_json(R"JSON({ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$ref": "https://example.com#/$defs/foo", - "$defs": { - "schema": { - "$schema": "https://json-schema.org/draft/2019-09/schema", - "$id": "https://example.com", - "$ref": "#/$defs/foo", - "$defs": { - "foo": {} - } - } - } + "$schema": "https://json-schema.org/draft/2019-09/schema" })JSON")}; EXPECT_EQ(result, expected); @@ -714,8 +518,7 @@ TEST(JSONSchema_wrap, 2019_09_top_level_ref_without_id_empty) { } })JSON")}; - const auto result{ - sourcemeta::core::wrap(schema, {}, sourcemeta::core::schema_resolver)}; + const auto result{wrap_schema(schema, {})}; const auto expected{sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2019-09/schema", @@ -737,22 +540,10 @@ TEST(JSONSchema_wrap, 2019_09_top_level_ref_without_id_defs_foo) { } })JSON")}; - const auto result{sourcemeta::core::wrap(schema, {"$defs", "foo"}, - sourcemeta::core::schema_resolver)}; + const auto result{wrap_schema(schema, {"$defs", "foo"})}; const auto expected{sourcemeta::core::parse_json(R"JSON({ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$ref": "__sourcemeta-core-wrap__#/$defs/foo", - "$defs": { - "schema": { - "$schema": "https://json-schema.org/draft/2019-09/schema", - "$id": "__sourcemeta-core-wrap__", - "$ref": "#/$defs/foo", - "$defs": { - "foo": {} - } - } - } + "$schema": "https://json-schema.org/draft/2019-09/schema" })JSON")}; EXPECT_EQ(result, expected); @@ -768,8 +559,7 @@ TEST(JSONSchema_wrap, 2020_12_top_level_ref_with_id_empty) { } })JSON")}; - const auto result{ - sourcemeta::core::wrap(schema, {}, sourcemeta::core::schema_resolver)}; + const auto result{wrap_schema(schema, {})}; const auto expected{sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2020-12/schema", @@ -793,22 +583,10 @@ TEST(JSONSchema_wrap, 2020_12_top_level_ref_with_id_defs_foo) { } })JSON")}; - const auto result{sourcemeta::core::wrap(schema, {"$defs", "foo"}, - sourcemeta::core::schema_resolver)}; + const auto result{wrap_schema(schema, {"$defs", "foo"})}; const auto expected{sourcemeta::core::parse_json(R"JSON({ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$ref": "https://example.com#/$defs/foo", - "$defs": { - "schema": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://example.com", - "$ref": "#/$defs/foo", - "$defs": { - "foo": {} - } - } - } + "$schema": "https://json-schema.org/draft/2020-12/schema" })JSON")}; EXPECT_EQ(result, expected); @@ -823,8 +601,7 @@ TEST(JSONSchema_wrap, 2020_12_top_level_ref_without_id_empty) { } })JSON")}; - const auto result{ - sourcemeta::core::wrap(schema, {}, sourcemeta::core::schema_resolver)}; + const auto result{wrap_schema(schema, {})}; const auto expected{sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2020-12/schema", @@ -846,19 +623,85 @@ TEST(JSONSchema_wrap, 2020_12_top_level_ref_without_id_defs_foo) { } })JSON")}; - const auto result{sourcemeta::core::wrap(schema, {"$defs", "foo"}, - sourcemeta::core::schema_resolver)}; + const auto result{wrap_schema(schema, {"$defs", "foo"})}; + + const auto expected{sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema" + })JSON")}; + + EXPECT_EQ(result, expected); +} + +TEST(JSONSchema_wrap, subschema_with_direct_ref) { + const auto schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://example.com", + "$defs": { + "string": { "type": "string" } + }, + "properties": { + "foo": { "$ref": "#/$defs/string" } + } + })JSON")}; + + const auto result{wrap_schema(schema, {"properties", "foo"})}; const auto expected{sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2020-12/schema", - "$ref": "__sourcemeta-core-wrap__#/$defs/foo", + "$ref": "https://example.com#/properties/foo", "$defs": { "schema": { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "__sourcemeta-core-wrap__", - "$ref": "#/$defs/foo", + "$id": "https://example.com", + "$defs": { + "string": { "type": "string" } + }, + "properties": { + "foo": { "$ref": "#/$defs/string" } + } + } + } + })JSON")}; + + EXPECT_EQ(result, expected); +} + +TEST(JSONSchema_wrap, subschema_with_nested_ref) { + const auto schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://example.com", + "$defs": { + "string": { "type": "string" } + }, + "properties": { + "foo": { + "type": "object", + "properties": { + "bar": { "$ref": "#/$defs/string" } + } + } + } + })JSON")}; + + const auto result{wrap_schema(schema, {"properties", "foo"})}; + + const auto expected{sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "https://example.com#/properties/foo", + "$defs": { + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://example.com", "$defs": { - "foo": {} + "string": { "type": "string" } + }, + "properties": { + "foo": { + "type": "object", + "properties": { + "bar": { "$ref": "#/$defs/string" } + } + } } } }