Skip to content

Commit

Permalink
[WIP] Implement a function to turn absolute schema references into re…
Browse files Browse the repository at this point in the history
…lative

Signed-off-by: Juan Cruz Viotti <jv@jviotti.com>
  • Loading branch information
jviotti committed Jan 7, 2025
1 parent 30bdbfd commit 0676211
Show file tree
Hide file tree
Showing 5 changed files with 283 additions and 1 deletion.
3 changes: 2 additions & 1 deletion src/jsonschema/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
7 changes: 7 additions & 0 deletions src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema.h
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,13 @@ SOURCEMETA_JSONTOOLKIT_JSONSCHEMA_EXPORT
auto schema_format_compare(const JSON::String &left, const JSON::String &right)
-> bool;

// TODO: Test & document
SOURCEMETA_JSONTOOLKIT_JSONSCHEMA_EXPORT
auto relativize(
JSON &schema, const SchemaWalker &walker, const SchemaResolver &resolver,
const std::optional<std::string> &default_dialect = std::nullopt,
const std::optional<std::string> &default_id = std::nullopt) -> void;

} // namespace sourcemeta::jsontoolkit

#endif
43 changes: 43 additions & 0 deletions src/jsonschema/relativize.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#include <sourcemeta/jsontoolkit/jsonschema.h>

namespace sourcemeta::jsontoolkit {

auto relativize(JSON &schema, const SchemaWalker &walker,
const SchemaResolver &resolver,
const std::optional<std::string> &default_dialect,
const std::optional<std::string> &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
1 change: 1 addition & 0 deletions test/jsonschema/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ add_executable(sourcemeta_jsontoolkit_jsonschema_unit
jsonschema_error_test.cc
jsonschema_keyword_iterator_test.cc
jsonschema_official_resolver_test.cc
jsonschema_relativize_test.cc
jsonschema_map_resolver_test.cc
jsonschema_flat_file_resolver_test.cc
jsonschema_format_test.cc)
Expand Down
230 changes: 230 additions & 0 deletions test/jsonschema/jsonschema_relativize_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
#include <gtest/gtest.h>

#include <sourcemeta/jsontoolkit/json.h>
#include <sourcemeta/jsontoolkit/jsonschema.h>

// TODO: Test with URNs

TEST(JSONSchema_relativize, draft4_1) {
auto schema = sourcemeta::jsontoolkit::parse(R"JSON({
"id": "http://asyncapi.com/definitions/1.0.0/asyncapi.json",
"$schema": "http://json-schema.org/draft-04/schema",
"title": "AsyncAPI 1.0 schema.",
"type": "object",
"required": [
"asyncapi",
"info",
"topics"
],
"additionalProperties": false,
"patternProperties": {
"^x-": {
"$ref": "http://asyncapi.com/definitions/1.0.0/vendorExtension.json"
}
}
})JSON");

sourcemeta::jsontoolkit::relativize(
schema, sourcemeta::jsontoolkit::default_schema_walker,
sourcemeta::jsontoolkit::official_resolver);

const auto expected = sourcemeta::jsontoolkit::parse(R"JSON({
"id": "http://asyncapi.com/definitions/1.0.0/asyncapi.json",
"$schema": "http://json-schema.org/draft-04/schema",
"title": "AsyncAPI 1.0 schema.",
"type": "object",
"required": [
"asyncapi",
"info",
"topics"
],
"additionalProperties": false,
"patternProperties": {
"^x-": {
"$ref": "vendorExtension.json"
}
}
})JSON");

EXPECT_EQ(schema, expected);
}

TEST(JSONSchema_relativize, draft4_2) {
auto schema = sourcemeta::jsontoolkit::parse(R"JSON({
"id": "http://example.com",
"$schema": "http://json-schema.org/draft-04/schema",
"properties": {
"foo": {
"$ref": "http://asyncapi.com/definitions/1.0.0/vendorExtension.json"
}
}
})JSON");

sourcemeta::jsontoolkit::relativize(
schema, sourcemeta::jsontoolkit::default_schema_walker,
sourcemeta::jsontoolkit::official_resolver);

const auto expected = sourcemeta::jsontoolkit::parse(R"JSON({
"id": "http://example.com",
"$schema": "http://json-schema.org/draft-04/schema",
"properties": {
"foo": {
"$ref": "http://asyncapi.com/definitions/1.0.0/vendorExtension.json"
}
}
})JSON");

EXPECT_EQ(schema, expected);
}

TEST(JSONSchema_relativize, draft4_3) {
auto schema = sourcemeta::jsontoolkit::parse(R"JSON({
"id": "http://example.com",
"$schema": "http://json-schema.org/draft-04/schema",
"properties": {
"foo": {
"$ref": "http://example.com/nested"
},
"bar": {
"id": "http://sourcemeta.com",
"properties": {
"bar": {
"$ref": "http://example.com/nested"
},
"baz": {
"$ref": "http://sourcemeta.com/nested"
}
}
}
}
})JSON");

sourcemeta::jsontoolkit::relativize(
schema, sourcemeta::jsontoolkit::default_schema_walker,
sourcemeta::jsontoolkit::official_resolver);

const auto expected = sourcemeta::jsontoolkit::parse(R"JSON({
"id": "http://example.com",
"$schema": "http://json-schema.org/draft-04/schema",
"properties": {
"foo": {
"$ref": "nested"
},
"bar": {
"id": "http://sourcemeta.com",
"properties": {
"bar": {
"$ref": "http://example.com/nested"
},
"baz": {
"$ref": "nested"
}
}
}
}
})JSON");

EXPECT_EQ(schema, expected);
}

TEST(JSONSchema_relativize, draft4_4) {
auto schema = sourcemeta::jsontoolkit::parse(R"JSON({
"$schema": "http://json-schema.org/draft-04/schema",
"properties": {
"foo": {
"$ref": "http://asyncapi.com/definitions/1.0.0/vendorExtension.json"
}
}
})JSON");

sourcemeta::jsontoolkit::relativize(
schema, sourcemeta::jsontoolkit::default_schema_walker,
sourcemeta::jsontoolkit::official_resolver);

const auto expected = sourcemeta::jsontoolkit::parse(R"JSON({
"$schema": "http://json-schema.org/draft-04/schema",
"properties": {
"foo": {
"$ref": "http://asyncapi.com/definitions/1.0.0/vendorExtension.json"
}
}
})JSON");

EXPECT_EQ(schema, expected);
}

TEST(JSONSchema_relativize, draft4_5) {
auto schema = sourcemeta::jsontoolkit::parse(R"JSON({
"$schema": "http://json-schema.org/draft-04/schema",
"properties": {
"foo": {
"$ref": "http://example.com/nested"
},
"bar": {
"id": "http://sourcemeta.com",
"properties": {
"bar": {
"$ref": "http://example.com/nested"
},
"baz": {
"$ref": "http://sourcemeta.com/nested"
}
}
}
}
})JSON");

sourcemeta::jsontoolkit::relativize(
schema, sourcemeta::jsontoolkit::default_schema_walker,
sourcemeta::jsontoolkit::official_resolver);

const auto expected = sourcemeta::jsontoolkit::parse(R"JSON({
"$schema": "http://json-schema.org/draft-04/schema",
"properties": {
"foo": {
"$ref": "http://example.com/nested"
},
"bar": {
"id": "http://sourcemeta.com",
"properties": {
"bar": {
"$ref": "http://example.com/nested"
},
"baz": {
"$ref": "nested"
}
}
}
}
})JSON");

EXPECT_EQ(schema, expected);
}

TEST(JSONSchema_relativize, 2020_12_1) {
auto schema = sourcemeta::jsontoolkit::parse(R"JSON({
"$id": "http://example.com",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"foo": {
"$dynamicRef": "http://example.com/foo#bar"
}
}
})JSON");

sourcemeta::jsontoolkit::relativize(
schema, sourcemeta::jsontoolkit::default_schema_walker,
sourcemeta::jsontoolkit::official_resolver);

const auto expected = sourcemeta::jsontoolkit::parse(R"JSON({
"$id": "http://example.com",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"foo": {
"$dynamicRef": "foo#bar"
}
}
})JSON");

EXPECT_EQ(schema, expected);
}

0 comments on commit 0676211

Please sign in to comment.