diff --git a/DEPENDENCIES b/DEPENDENCIES index ed4c0fa..0e8ffee 100644 --- a/DEPENDENCIES +++ b/DEPENDENCIES @@ -1,6 +1,6 @@ vendorpull https://github.com/sourcemeta/vendorpull dea311b5bfb53b6926a4140267959ae334d3ecf4 noa https://github.com/sourcemeta/noa 924f5cc8549af7f12227869dcbab4259029ac650 -jsontoolkit https://github.com/sourcemeta/jsontoolkit 30bdbfd3dc94a3329e3c76224f360525c9645bc4 +jsontoolkit https://github.com/sourcemeta/jsontoolkit 0073e1e712ecd6634163d7c7c83afeb6c5cfbe82 blaze https://github.com/sourcemeta/blaze bde17029b8fc8daf38a496d4d27354a4d24b3530 hydra https://github.com/sourcemeta/hydra 1f958f97d578ece9984a09065a53d317976ae2da bootstrap https://github.com/twbs/bootstrap v5.3.3 diff --git a/test/e2e/common/get.hurl b/test/e2e/common/get.hurl index 27dfa3e..d66ab3a 100644 --- a/test/e2e/common/get.hurl +++ b/test/e2e/common/get.hurl @@ -54,6 +54,15 @@ HTTP 200 ] } +# With absolute references +GET {{base}}/example/schemas/absolute-refs.json +HTTP 200 +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "{{base}}/example/schemas/absolute-refs.json", + "$ref": "string.json" +} + # Folder with period GET {{base}}/example/v2.0/schema.json HTTP 200 diff --git a/test/sandbox/manifest-ce.txt b/test/sandbox/manifest-ce.txt index 5998934..3f0945a 100644 --- a/test/sandbox/manifest-ce.txt +++ b/test/sandbox/manifest-ce.txt @@ -8,6 +8,7 @@ ./schemas/example/extension ./schemas/example/extension/with.schema.json ./schemas/example/schemas +./schemas/example/schemas/absolute-refs.json ./schemas/example/schemas/camelcase.json ./schemas/example/schemas/no-id-draft7-ref.json ./schemas/example/schemas/no-id.json diff --git a/test/sandbox/manifest-ee.txt b/test/sandbox/manifest-ee.txt index 3258957..eccda2e 100644 --- a/test/sandbox/manifest-ee.txt +++ b/test/sandbox/manifest-ee.txt @@ -30,6 +30,7 @@ ./schemas/example/extension ./schemas/example/extension/with.schema.json ./schemas/example/schemas +./schemas/example/schemas/absolute-refs.json ./schemas/example/schemas/camelcase.json ./schemas/example/schemas/no-id-draft7-ref.json ./schemas/example/schemas/no-id.json diff --git a/test/sandbox/schemas/example/folder/absolute-refs.json b/test/sandbox/schemas/example/folder/absolute-refs.json new file mode 100644 index 0000000..1173cee --- /dev/null +++ b/test/sandbox/schemas/example/folder/absolute-refs.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://example.com/schemas/absolute-refs.json", + "$ref": "https://example.com/schemas/string.json" +} diff --git a/vendor/jsontoolkit/src/jsonschema/CMakeLists.txt b/vendor/jsontoolkit/src/jsonschema/CMakeLists.txt index 8de677a..068c9aa 100644 --- a/vendor/jsontoolkit/src/jsonschema/CMakeLists.txt +++ b/vendor/jsontoolkit/src/jsonschema/CMakeLists.txt @@ -7,7 +7,8 @@ noa_library(NAMESPACE sourcemeta PROJECT jsontoolkit NAME jsonschema PRIVATE_HEADERS anchor.h bundle.h resolver.h walker.h reference.h frame.h error.h unevaluated.h keywords.h SOURCES jsonschema.cc default_walker.cc frame.cc - anchor.cc resolver.cc walker.cc bundle.cc unevaluated.cc + anchor.cc resolver.cc walker.cc bundle.cc + unevaluated.cc relativize.cc "${CMAKE_CURRENT_BINARY_DIR}/official_resolver.cc") if(JSONTOOLKIT_INSTALL) diff --git a/vendor/jsontoolkit/src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema.h b/vendor/jsontoolkit/src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema.h index 3f18f16..a3fd5f3 100644 --- a/vendor/jsontoolkit/src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema.h +++ b/vendor/jsontoolkit/src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema.h @@ -322,6 +322,42 @@ SOURCEMETA_JSONTOOLKIT_JSONSCHEMA_EXPORT auto schema_format_compare(const JSON::String &left, const JSON::String &right) -> bool; +/// @ingroup jsonschema +/// +/// Try to turn every possible absolute reference in a schema into a relative +/// one. For example: +/// +/// ```cpp +/// #include +/// #include +/// #include +/// +/// sourcemeta::jsontoolkit::JSON document = +/// sourcemeta::jsontoolkit::parse(R"JSON({ +/// "$id": "https://www.example.com/schema", +/// "$schema": "https://json-schema.org/draft/2020-12/schema", +/// "$ref": "https://www.example.com/another", +/// })JSON"); +/// +/// sourcemeta::jsontoolkit::relativize(schema, +/// sourcemeta::jsontoolkit::default_dialect, +/// sourcemeta::jsontoolkit::official_resolver); +/// +/// const sourcemeta::jsontoolkit::JSON expected = +/// sourcemeta::jsontoolkit::parse(R"JSON({ +/// "$id": "https://www.example.com/schema", +/// "$schema": "https://json-schema.org/draft/2020-12/schema", +/// "$ref": "another", +/// })JSON"); +/// +/// assert(document == expected); +/// ``` +SOURCEMETA_JSONTOOLKIT_JSONSCHEMA_EXPORT +auto relativize( + JSON &schema, const SchemaWalker &walker, const SchemaResolver &resolver, + const std::optional &default_dialect = std::nullopt, + const std::optional &default_id = std::nullopt) -> void; + } // namespace sourcemeta::jsontoolkit #endif diff --git a/vendor/jsontoolkit/src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema_resolver.h b/vendor/jsontoolkit/src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema_resolver.h index 37060e8..d8d3c0a 100644 --- a/vendor/jsontoolkit/src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema_resolver.h +++ b/vendor/jsontoolkit/src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema_resolver.h @@ -145,6 +145,7 @@ class SOURCEMETA_JSONTOOLKIT_JSONSCHEMA_EXPORT FlatFileSchemaResolver { struct Entry { std::filesystem::path path; std::optional default_dialect; + std::string original_identifier; }; private: diff --git a/vendor/jsontoolkit/src/jsonschema/relativize.cc b/vendor/jsontoolkit/src/jsonschema/relativize.cc new file mode 100644 index 0000000..664c398 --- /dev/null +++ b/vendor/jsontoolkit/src/jsonschema/relativize.cc @@ -0,0 +1,43 @@ +#include + +namespace sourcemeta::jsontoolkit { + +auto relativize(JSON &schema, const SchemaWalker &walker, + const SchemaResolver &resolver, + const std::optional &default_dialect, + const std::optional &default_id) -> void { + Frame frame; + frame.analyse(schema, walker, resolver, default_dialect, default_id); + + for (const auto &entry : frame.locations()) { + if (entry.second.type != Frame::LocationType::Resource && + entry.second.type != Frame::LocationType::Subschema) { + continue; + } + + auto &subschema{get(schema, entry.second.pointer)}; + assert(is_schema(subschema)); + if (!subschema.is_object()) { + continue; + } + + const auto base{URI{entry.second.base}.canonicalize()}; + for (const auto &property : subschema.as_object()) { + if (walker(property.first, frame.vocabularies(entry.second, resolver)) + .type != KeywordType::Reference || + !property.second.is_string()) { + continue; + } + + URI reference{property.second.to_string()}; + reference.canonicalize(); + reference.relative_to(base); + + if (reference.is_relative()) { + subschema.assign(property.first, JSON{reference.recompose()}); + } + } + } +} + +} // namespace sourcemeta::jsontoolkit diff --git a/vendor/jsontoolkit/src/jsonschema/resolver.cc b/vendor/jsontoolkit/src/jsonschema/resolver.cc index 61c405d..dc5575d 100644 --- a/vendor/jsontoolkit/src/jsonschema/resolver.cc +++ b/vendor/jsontoolkit/src/jsonschema/resolver.cc @@ -95,8 +95,9 @@ auto FlatFileSchemaResolver::add( throw SchemaError(error.str()); } - const auto result{this->schemas.emplace(identifier.value(), - Entry{canonical, default_dialect})}; + const auto result{this->schemas.emplace( + identifier.value(), + Entry{canonical, default_dialect, identifier.value()})}; if (!result.second && result.first->second.path != canonical) { std::ostringstream error; error << "Cannot register the same identifier twice: " @@ -128,6 +129,14 @@ auto FlatFileSchemaResolver::operator()(std::string_view identifier) const schema.assign("$schema", JSON{result->second.default_dialect.value()}); } + sourcemeta::jsontoolkit::reidentify(schema, + result->second.original_identifier, + *this, result->second.default_dialect); + // Because we allow re-identification, we can get into issues unless we + // always try to relativize references + sourcemeta::jsontoolkit::relativize(schema, default_schema_walker, *this, + result->second.default_dialect, + result->second.original_identifier); sourcemeta::jsontoolkit::reidentify(schema, result->first, *this, result->second.default_dialect);