Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/core/jsonschema/bundle.cc
Original file line number Diff line number Diff line change
Expand Up @@ -454,10 +454,14 @@ auto bundle(JSON &schema, const SchemaWalker &walker,
if (ref_overrides_adjacent_keywords(schema_base_dialect.value()) &&
schema.is_object() && schema.defines("$ref")) {
if (schema.size() == 1) {
const auto is_draft3{schema_base_dialect.value() ==
SchemaBaseDialect::JSON_Schema_Draft_3 ||
schema_base_dialect.value() ==
SchemaBaseDialect::JSON_Schema_Draft_3_Hyper};
auto branches{JSON::make_array()};
branches.push_back(schema);
schema.at("$ref").into(std::move(branches));
schema.rename("$ref", "allOf");
schema.rename("$ref", is_draft3 ? "extends" : "allOf");
} else {
throw SchemaError(
"Cannot bundle a JSON Schema Draft 7 or older with a top-level "
Expand Down
2 changes: 1 addition & 1 deletion src/core/jsonschema/helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ inline auto definitions_keyword(const SchemaBaseDialect base_dialect)
case SchemaBaseDialect::JSON_Schema_Draft_6_Hyper:
case SchemaBaseDialect::JSON_Schema_Draft_4:
case SchemaBaseDialect::JSON_Schema_Draft_4_Hyper:
return "definitions";
case SchemaBaseDialect::JSON_Schema_Draft_3:
case SchemaBaseDialect::JSON_Schema_Draft_3_Hyper:
return "definitions";
case SchemaBaseDialect::JSON_Schema_Draft_2_Hyper:
case SchemaBaseDialect::JSON_Schema_Draft_1_Hyper:
case SchemaBaseDialect::JSON_Schema_Draft_0_Hyper:
Expand Down
4 changes: 4 additions & 0 deletions src/core/jsonschema/known_walker.cc
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,10 @@ auto handle_definitions(const Vocabularies &vocabularies)
LocationMembers, "$ref")
CHECK_VOCABULARY_WITH_DEPENDENCIES(Known::JSON_Schema_Draft_4_Hyper, {},
LocationMembers, "$ref")
CHECK_VOCABULARY_WITH_DEPENDENCIES(Known::JSON_Schema_Draft_3, {},
LocationMembers, "$ref")
CHECK_VOCABULARY_WITH_DEPENDENCIES(Known::JSON_Schema_Draft_3_Hyper, {},
LocationMembers, "$ref")
return UNKNOWN_RESULT;
}

Expand Down
293 changes: 292 additions & 1 deletion test/jsonschema/jsonschema_bundle_draft3_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,32 @@ static auto test_resolver(std::string_view identifier)
"id": "https://www.sourcemeta.com/test-1",
"type": "string"
})JSON");
} else if (identifier == "https://www.sourcemeta.com/test-2") {
return sourcemeta::core::parse_json(R"JSON({
"$schema": "http://json-schema.org/draft-03/schema#",
"id": "https://www.sourcemeta.com/test-2",
"extends": { "$ref": "test-3" }
})JSON");
} else if (identifier == "https://www.sourcemeta.com/test-3") {
return sourcemeta::core::parse_json(R"JSON({
"$schema": "http://json-schema.org/draft-03/schema#",
"id": "https://www.sourcemeta.com/test-3",
"extends": { "$ref": "test-1" }
})JSON");
} else if (identifier == "https://www.sourcemeta.com/test-4") {
return sourcemeta::core::parse_json(R"JSON({
"$schema": "http://json-schema.org/draft-03/schema#",
"id": "https://www.sourcemeta.com/test-4",
"type": "boolean"
})JSON");
} else if (identifier == "https://www.sourcemeta.com/recursive") {
return sourcemeta::core::parse_json(R"JSON({
"$schema": "http://json-schema.org/draft-03/schema#",
"id": "https://www.sourcemeta.com/recursive",
"properties": {
"foo": { "$ref": "#" }
}
})JSON");
} else {
return sourcemeta::core::schema_resolver(identifier);
}
Expand Down Expand Up @@ -58,7 +84,272 @@ TEST(JSONSchema_bundle_draft3, simple_bundling) {
}
})JSON");

sourcemeta::core::bundle(document, sourcemeta::core::schema_walker,
test_resolver);

const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({
"id": "https://example.com",
"$schema": "http://json-schema.org/draft-03/schema#",
"properties": {
"test": { "$ref": "https://www.sourcemeta.com/test-1" }
},
"definitions": {
"https://www.sourcemeta.com/test-1": {
"$schema": "http://json-schema.org/draft-03/schema#",
"id": "https://www.sourcemeta.com/test-1",
"type": "string"
}
}
})JSON");

EXPECT_EQ(document, expected);
}

TEST(JSONSchema_bundle_draft3, simple_with_id) {
sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
"id": "https://example.com",
"$schema": "http://json-schema.org/draft-03/schema#",
"properties": {
"foo": { "$ref": "https://www.sourcemeta.com/test-1" },
"bar": {
"id": "https://www.sourcemeta.com",
"extends": { "$ref": "test-2" }
}
}
})JSON");

sourcemeta::core::bundle(document, sourcemeta::core::schema_walker,
test_resolver);

const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({
"id": "https://example.com",
"$schema": "http://json-schema.org/draft-03/schema#",
"properties": {
"foo": { "$ref": "https://www.sourcemeta.com/test-1" },
"bar": {
"id": "https://www.sourcemeta.com",
"extends": { "$ref": "test-2" }
}
},
"definitions": {
"https://www.sourcemeta.com/test-1": {
"$schema": "http://json-schema.org/draft-03/schema#",
"id": "https://www.sourcemeta.com/test-1",
"type": "string"
},
"https://www.sourcemeta.com/test-2": {
"$schema": "http://json-schema.org/draft-03/schema#",
"id": "https://www.sourcemeta.com/test-2",
"extends": { "$ref": "test-3" }
},
"https://www.sourcemeta.com/test-3": {
"$schema": "http://json-schema.org/draft-03/schema#",
"id": "https://www.sourcemeta.com/test-3",
"extends": { "$ref": "test-1" }
}
}
})JSON");

EXPECT_EQ(document, expected);
}

TEST(JSONSchema_bundle_draft3, schema_not_found) {
sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
"id": "https://example.com",
"$schema": "http://json-schema.org/draft-03/schema#",
"properties": {
"foo": { "$ref": "https://www.sourcemeta.com/xxx" }
}
})JSON");

EXPECT_THROW(sourcemeta::core::bundle(
document, sourcemeta::core::schema_walker, test_resolver),
sourcemeta::core::SchemaError);
sourcemeta::core::SchemaResolutionError);
}

TEST(JSONSchema_bundle_draft3, idempotency) {
sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
"id": "https://example.com",
"$schema": "http://json-schema.org/draft-03/schema#",
"properties": {
"test": { "$ref": "https://www.sourcemeta.com/test-2" }
}
})JSON");

sourcemeta::core::bundle(document, sourcemeta::core::schema_walker,
test_resolver);
sourcemeta::core::bundle(document, sourcemeta::core::schema_walker,
test_resolver);
sourcemeta::core::bundle(document, sourcemeta::core::schema_walker,
test_resolver);

const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({
"id": "https://example.com",
"$schema": "http://json-schema.org/draft-03/schema#",
"properties": {
"test": { "$ref": "https://www.sourcemeta.com/test-2" }
},
"definitions": {
"https://www.sourcemeta.com/test-1": {
"$schema": "http://json-schema.org/draft-03/schema#",
"id": "https://www.sourcemeta.com/test-1",
"type": "string"
},
"https://www.sourcemeta.com/test-2": {
"$schema": "http://json-schema.org/draft-03/schema#",
"id": "https://www.sourcemeta.com/test-2",
"extends": { "$ref": "test-3" }
},
"https://www.sourcemeta.com/test-3": {
"$schema": "http://json-schema.org/draft-03/schema#",
"id": "https://www.sourcemeta.com/test-3",
"extends": { "$ref": "test-1" }
}
}
})JSON");

EXPECT_EQ(document, expected);
}

TEST(JSONSchema_bundle_draft3, pre_embedded) {
sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
"id": "https://example.com",
"$schema": "http://json-schema.org/draft-03/schema#",
"properties": {
"test": { "$ref": "https://www.sourcemeta.com/test-2" }
},
"definitions": {
"already-embedded": {
"$schema": "http://json-schema.org/draft-03/schema#",
"id": "https://www.sourcemeta.com/test-1",
"type": "string"
}
}
})JSON");

sourcemeta::core::bundle(document, sourcemeta::core::schema_walker,
test_resolver);

const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({
"id": "https://example.com",
"$schema": "http://json-schema.org/draft-03/schema#",
"properties": {
"test": { "$ref": "https://www.sourcemeta.com/test-2" }
},
"definitions": {
"already-embedded": {
"$schema": "http://json-schema.org/draft-03/schema#",
"id": "https://www.sourcemeta.com/test-1",
"type": "string"
},
"https://www.sourcemeta.com/test-2": {
"$schema": "http://json-schema.org/draft-03/schema#",
"id": "https://www.sourcemeta.com/test-2",
"extends": { "$ref": "test-3" }
},
"https://www.sourcemeta.com/test-3": {
"$schema": "http://json-schema.org/draft-03/schema#",
"id": "https://www.sourcemeta.com/test-3",
"extends": { "$ref": "test-1" }
}
}
})JSON");

EXPECT_EQ(document, expected);
}

TEST(JSONSchema_bundle_draft3, taken_definitions_entry) {
sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
"id": "https://example.com",
"$schema": "http://json-schema.org/draft-03/schema#",
"properties": {
"test": { "$ref": "https://www.sourcemeta.com/test-1" },
"extra": { "$ref": "https://www.sourcemeta.com/test-4" }
},
"definitions": {
"https://www.sourcemeta.com/test-1": { "type": "object" },
"https://www.sourcemeta.com/test-4": { "type": "object" },
"https://www.sourcemeta.com/test-4/x": { "type": "object" },
"https://www.sourcemeta.com/test-4/x/x": { "type": "object" }
}
})JSON");

sourcemeta::core::bundle(document, sourcemeta::core::schema_walker,
test_resolver);

const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({
"id": "https://example.com",
"$schema": "http://json-schema.org/draft-03/schema#",
"properties": {
"test": { "$ref": "https://www.sourcemeta.com/test-1" },
"extra": { "$ref": "https://www.sourcemeta.com/test-4" }
},
"definitions": {
"https://www.sourcemeta.com/test-1": { "type": "object" },
"https://www.sourcemeta.com/test-1/x": {
"$schema": "http://json-schema.org/draft-03/schema#",
"id": "https://www.sourcemeta.com/test-1",
"type": "string"
},
"https://www.sourcemeta.com/test-4": { "type": "object" },
"https://www.sourcemeta.com/test-4/x": { "type": "object" },
"https://www.sourcemeta.com/test-4/x/x": { "type": "object" },
"https://www.sourcemeta.com/test-4/x/x/x": {
"$schema": "http://json-schema.org/draft-03/schema#",
"id": "https://www.sourcemeta.com/test-4",
"type": "boolean"
}
}
})JSON");

EXPECT_EQ(document, expected);
}

TEST(JSONSchema_bundle_draft3, recursive) {
sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
"$schema": "http://json-schema.org/draft-03/schema#",
"extends": { "$ref": "https://www.sourcemeta.com/recursive" }
})JSON");

sourcemeta::core::bundle(document, sourcemeta::core::schema_walker,
test_resolver);

const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({
"$schema": "http://json-schema.org/draft-03/schema#",
"extends": { "$ref": "https://www.sourcemeta.com/recursive" },
"definitions": {
"https://www.sourcemeta.com/recursive": {
"$schema": "http://json-schema.org/draft-03/schema#",
"id": "https://www.sourcemeta.com/recursive",
"properties": {
"foo": { "$ref": "#" }
}
}
}
})JSON");

EXPECT_EQ(document, expected);
}

TEST(JSONSchema_bundle_draft3, standalone_ref_with_default_dialect) {
sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({
"$ref": "https://www.sourcemeta.com/test-1"
})JSON");

sourcemeta::core::bundle(document, sourcemeta::core::schema_walker,
test_resolver,
"http://json-schema.org/draft-03/schema#");

const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({
"extends": [ { "$ref": "https://www.sourcemeta.com/test-1" } ],
"definitions": {
"https://www.sourcemeta.com/test-1": {
"$schema": "http://json-schema.org/draft-03/schema#",
"id": "https://www.sourcemeta.com/test-1",
"type": "string"
}
}
})JSON");

EXPECT_EQ(document, expected);
}
Loading
Loading