diff --git a/src/core/jsonschema/include/sourcemeta/core/jsonschema.h b/src/core/jsonschema/include/sourcemeta/core/jsonschema.h index a18eeadab..54005110b 100644 --- a/src/core/jsonschema/include/sourcemeta/core/jsonschema.h +++ b/src/core/jsonschema/include/sourcemeta/core/jsonschema.h @@ -422,17 +422,27 @@ 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::WeakPointer{"items"}, +/// sourcemeta::core::SchemaFrame::LocationType::Subschema)}; +/// +/// sourcemeta::core::WeakPointer base; /// const sourcemeta::core::JSON result = -/// sourcemeta::core::wrap(document, { "items" }, -/// sourcemeta::core::schema_resolver); +/// sourcemeta::core::wrap(document, frame, location.value().get(), +/// sourcemeta::core::schema_resolver, base); /// /// 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 = "") - -> JSON; +auto wrap(const JSON &schema, const SchemaFrame &frame, + const SchemaFrame::Location &location, const SchemaResolver &resolver, + WeakPointer &base) -> JSON; /// @ingroup jsonschema /// diff --git a/src/core/jsonschema/jsonschema.cc b/src/core/jsonschema/jsonschema.cc index aad330940..78814050b 100644 --- a/src/core/jsonschema/jsonschema.cc +++ b/src/core/jsonschema/jsonschema.cc @@ -590,24 +590,41 @@ 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) - -> sourcemeta::core::JSON { - assert(try_get(schema, pointer)); +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::WeakPointer &base) -> sourcemeta::core::JSON { + assert(frame.mode() == SchemaFrame::Mode::References); + assert(location.type != SchemaFrame::LocationType::Pointer); + + const auto &pointer{location.pointer}; if (pointer.empty()) { - return schema; + auto copy = schema; + if (copy.is_object()) { + copy.assign("$schema", JSON{location.dialect}); + } + + return copy; } - auto copy = schema; - const auto effective_dialect{dialect(copy, default_dialect)}; - if (!effective_dialect.empty()) { - copy.assign("$schema", JSON{effective_dialect}); - } else { - throw SchemaUnknownBaseDialectError(); + assert(try_get(schema, pointer)); + 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{location.dialect}); + return subschema; } + auto copy = schema; + copy.assign("$schema", JSON{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 +639,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,11 +664,17 @@ 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()}); } + static const JSON::String REF{"$ref"}; + base.push_back(REF); return result; } diff --git a/test/jsonschema/jsonschema_wrap_test.cc b/test/jsonschema/jsonschema_wrap_test.cc index a9b5fdbcc..9832c461f 100644 --- a/test/jsonschema/jsonschema_wrap_test.cc +++ b/test/jsonschema/jsonschema_wrap_test.cc @@ -3,6 +3,25 @@ #include #include +#include // std::pair + +static auto wrap_schema(const sourcemeta::core::JSON &schema, + const sourcemeta::core::Pointer &pointer, + std::string_view default_dialect = "") + -> std::pair { + 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()); + sourcemeta::core::WeakPointer base; + auto result{sourcemeta::core::wrap(schema, frame, location.value().get(), + sourcemeta::core::schema_resolver, base)}; + return {std::move(result), sourcemeta::core::to_pointer(base)}; +} + TEST(JSONSchema_wrap, identifier_without_fragment) { const auto identifier{"https://www.example.com"}; const auto result{sourcemeta::core::wrap(identifier)}; @@ -49,24 +68,15 @@ TEST(JSONSchema_wrap, schema_without_identifier) { } })JSON")}; - const auto result{sourcemeta::core::wrap(schema, {"items"}, - sourcemeta::core::schema_resolver)}; + const auto [result, base]{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); + EXPECT_TRUE(base.empty()); } TEST(JSONSchema_wrap, schema_without_identifier_and_relative_uri) { @@ -77,8 +87,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, base]{wrap_schema(schema, {"items"})}; // We don't want the relative reference to be resolved against // an absolute base @@ -97,6 +106,9 @@ TEST(JSONSchema_wrap, schema_without_identifier_and_relative_uri) { })JSON")}; EXPECT_EQ(result, expected); + EXPECT_EQ(base.size(), 1); + EXPECT_TRUE(base.at(0).is_property()); + EXPECT_EQ(base.at(0).to_property(), "$ref"); } TEST(JSONSchema_wrap, schema_without_identifier_with_default_dialect) { @@ -106,25 +118,16 @@ 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, base]{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); + EXPECT_TRUE(base.empty()); } TEST(JSONSchema_wrap, @@ -136,25 +139,16 @@ 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, base]{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); + EXPECT_TRUE(base.empty()); } TEST(JSONSchema_wrap, schema_without_identifier_empty_pointer) { @@ -165,8 +159,7 @@ TEST(JSONSchema_wrap, schema_without_identifier_empty_pointer) { } })JSON")}; - const auto result{ - sourcemeta::core::wrap(schema, {}, sourcemeta::core::schema_resolver)}; + const auto [result, base]{wrap_schema(schema, {})}; const auto expected{sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2020-12/schema", @@ -176,6 +169,7 @@ TEST(JSONSchema_wrap, schema_without_identifier_empty_pointer) { })JSON")}; EXPECT_EQ(result, expected); + EXPECT_TRUE(base.empty()); } TEST(JSONSchema_wrap, schema_without_identifier_without_dialect) { @@ -185,8 +179,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,24 +192,15 @@ TEST(JSONSchema_wrap, schema_with_identifier) { } })JSON")}; - const auto result{sourcemeta::core::wrap(schema, {"items"}, - sourcemeta::core::schema_resolver)}; + const auto [result, base]{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); + EXPECT_TRUE(base.empty()); } TEST(JSONSchema_wrap, schema_with_identifier_trailing_empty_fragment) { @@ -228,24 +212,15 @@ 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, base]{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); + EXPECT_TRUE(base.empty()); } TEST(JSONSchema_wrap, schema_with_identifier_different_default_dialect) { @@ -257,25 +232,16 @@ 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, base]{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); + EXPECT_TRUE(base.empty()); } TEST(JSONSchema_wrap, schema_with_identifier_default_dialect) { @@ -286,39 +252,31 @@ 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, base]{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); + EXPECT_TRUE(base.empty()); } 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, base]{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" @@ -326,6 +284,7 @@ TEST(JSONSchema_wrap, schema_with_identifier_empty_pointer) { })JSON")}; EXPECT_EQ(result, expected); + EXPECT_TRUE(base.empty()); } TEST(JSONSchema_wrap, schema_with_identifier_no_dialect) { @@ -336,54 +295,25 @@ 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#")}; + const auto [result, base]{ + wrap_schema(schema, {}, "http://json-schema.org/draft-04/schema#")}; const auto expected{sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", "$ref": "https://example.com" })JSON")}; EXPECT_EQ(result, expected); + EXPECT_TRUE(base.empty()); } TEST(JSONSchema_wrap, draft4_top_level_ref_with_id_empty) { @@ -396,8 +326,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, base]{wrap_schema(schema, {})}; const auto expected{sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-04/schema#", @@ -409,21 +338,7 @@ TEST(JSONSchema_wrap, draft4_top_level_ref_with_id_empty) { })JSON")}; 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); + EXPECT_TRUE(base.empty()); } TEST(JSONSchema_wrap, draft4_top_level_ref_without_id_empty) { @@ -435,8 +350,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, base]{wrap_schema(schema, {})}; const auto expected{sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-04/schema#", @@ -447,20 +361,7 @@ TEST(JSONSchema_wrap, draft4_top_level_ref_without_id_empty) { })JSON")}; 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); + EXPECT_TRUE(base.empty()); } TEST(JSONSchema_wrap, draft6_standalone_ref_with_default_dialect) { @@ -468,15 +369,16 @@ TEST(JSONSchema_wrap, draft6_standalone_ref_with_default_dialect) { "$ref": "https://example.com" })JSON")}; - const auto result{ - sourcemeta::core::wrap(schema, {}, sourcemeta::core::schema_resolver, - "http://json-schema.org/draft-06/schema#")}; + const auto [result, base]{ + wrap_schema(schema, {}, "http://json-schema.org/draft-06/schema#")}; const auto expected{sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-06/schema#", "$ref": "https://example.com" })JSON")}; EXPECT_EQ(result, expected); + EXPECT_TRUE(base.empty()); } TEST(JSONSchema_wrap, draft6_top_level_ref_with_id_empty) { @@ -489,8 +391,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, base]{wrap_schema(schema, {})}; const auto expected{sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-06/schema#", @@ -502,21 +403,7 @@ TEST(JSONSchema_wrap, draft6_top_level_ref_with_id_empty) { })JSON")}; 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); + EXPECT_TRUE(base.empty()); } TEST(JSONSchema_wrap, draft6_top_level_ref_without_id_empty) { @@ -528,8 +415,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, base]{wrap_schema(schema, {})}; const auto expected{sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-06/schema#", @@ -540,20 +426,7 @@ TEST(JSONSchema_wrap, draft6_top_level_ref_without_id_empty) { })JSON")}; 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); + EXPECT_TRUE(base.empty()); } TEST(JSONSchema_wrap, draft7_standalone_ref_with_default_dialect) { @@ -561,15 +434,16 @@ TEST(JSONSchema_wrap, draft7_standalone_ref_with_default_dialect) { "$ref": "https://example.com" })JSON")}; - const auto result{ - sourcemeta::core::wrap(schema, {}, sourcemeta::core::schema_resolver, - "http://json-schema.org/draft-07/schema#")}; + const auto [result, base]{ + wrap_schema(schema, {}, "http://json-schema.org/draft-07/schema#")}; const auto expected{sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", "$ref": "https://example.com" })JSON")}; EXPECT_EQ(result, expected); + EXPECT_TRUE(base.empty()); } TEST(JSONSchema_wrap, draft7_top_level_ref_with_id_empty) { @@ -582,8 +456,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, base]{wrap_schema(schema, {})}; const auto expected{sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-07/schema#", @@ -595,21 +468,7 @@ TEST(JSONSchema_wrap, draft7_top_level_ref_with_id_empty) { })JSON")}; 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); + EXPECT_TRUE(base.empty()); } TEST(JSONSchema_wrap, draft7_top_level_ref_without_id_empty) { @@ -621,8 +480,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, base]{wrap_schema(schema, {})}; const auto expected{sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-07/schema#", @@ -633,20 +491,7 @@ TEST(JSONSchema_wrap, draft7_top_level_ref_without_id_empty) { })JSON")}; 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); + EXPECT_TRUE(base.empty()); } TEST(JSONSchema_wrap, 2019_09_top_level_ref_with_id_empty) { @@ -659,8 +504,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, base]{wrap_schema(schema, {})}; const auto expected{sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2019-09/schema", @@ -672,6 +516,7 @@ TEST(JSONSchema_wrap, 2019_09_top_level_ref_with_id_empty) { })JSON")}; EXPECT_EQ(result, expected); + EXPECT_TRUE(base.empty()); } TEST(JSONSchema_wrap, 2019_09_top_level_ref_with_id_defs_foo) { @@ -684,25 +529,14 @@ 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, base]{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); + EXPECT_TRUE(base.empty()); } TEST(JSONSchema_wrap, 2019_09_top_level_ref_without_id_empty) { @@ -714,8 +548,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, base]{wrap_schema(schema, {})}; const auto expected{sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2019-09/schema", @@ -726,6 +559,7 @@ TEST(JSONSchema_wrap, 2019_09_top_level_ref_without_id_empty) { })JSON")}; EXPECT_EQ(result, expected); + EXPECT_TRUE(base.empty()); } TEST(JSONSchema_wrap, 2019_09_top_level_ref_without_id_defs_foo) { @@ -737,25 +571,14 @@ 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, base]{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); + EXPECT_TRUE(base.empty()); } TEST(JSONSchema_wrap, 2020_12_top_level_ref_with_id_empty) { @@ -768,8 +591,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, base]{wrap_schema(schema, {})}; const auto expected{sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2020-12/schema", @@ -781,6 +603,7 @@ TEST(JSONSchema_wrap, 2020_12_top_level_ref_with_id_empty) { })JSON")}; EXPECT_EQ(result, expected); + EXPECT_TRUE(base.empty()); } TEST(JSONSchema_wrap, 2020_12_top_level_ref_with_id_defs_foo) { @@ -793,25 +616,14 @@ 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, base]{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); + EXPECT_TRUE(base.empty()); } TEST(JSONSchema_wrap, 2020_12_top_level_ref_without_id_empty) { @@ -823,8 +635,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, base]{wrap_schema(schema, {})}; const auto expected{sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2020-12/schema", @@ -835,6 +646,7 @@ TEST(JSONSchema_wrap, 2020_12_top_level_ref_without_id_empty) { })JSON")}; EXPECT_EQ(result, expected); + EXPECT_TRUE(base.empty()); } TEST(JSONSchema_wrap, 2020_12_top_level_ref_without_id_defs_foo) { @@ -846,23 +658,96 @@ 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, base]{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); + EXPECT_TRUE(base.empty()); +} + +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", - "$ref": "__sourcemeta-core-wrap__#/$defs/foo", + "$id": "https://example.com", + "$defs": { + "string": { "type": "string" } + }, + "properties": { + "foo": { "$ref": "#/$defs/string" } + } + })JSON")}; + + const auto [result, base]{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": "__sourcemeta-core-wrap__", - "$ref": "#/$defs/foo", + "$id": "https://example.com", + "$defs": { + "string": { "type": "string" } + }, + "properties": { + "foo": { "$ref": "#/$defs/string" } + } + } + } + })JSON")}; + + EXPECT_EQ(result, expected); + EXPECT_EQ(base.size(), 1); + EXPECT_TRUE(base.at(0).is_property()); + EXPECT_EQ(base.at(0).to_property(), "$ref"); +} + +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, base]{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" } + } + } } } } })JSON")}; EXPECT_EQ(result, expected); + EXPECT_EQ(base.size(), 1); + EXPECT_TRUE(base.at(0).is_property()); + EXPECT_EQ(base.at(0).to_property(), "$ref"); }