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
76 changes: 31 additions & 45 deletions src/extension/alterschema/canonicalizer/type_union_implicit.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ class TypeUnionImplicit final : public SchemaTransformRule {
const sourcemeta::core::Vocabularies &vocabularies,
const sourcemeta::core::SchemaFrame &,
const sourcemeta::core::SchemaFrame::Location &,
const sourcemeta::core::SchemaWalker &,
const sourcemeta::core::SchemaWalker &walker,
const sourcemeta::core::SchemaResolver &) const
-> sourcemeta::core::SchemaTransformRule::Result override {
using namespace sourcemeta::core;
ONLY_CONTINUE_IF(schema.is_object());
ONLY_CONTINUE_IF(vocabularies.contains_any(
{Vocabularies::Known::JSON_Schema_2020_12_Validation,
Expand All @@ -26,50 +27,35 @@ class TypeUnionImplicit final : public SchemaTransformRule {
Vocabularies::Known::JSON_Schema_Draft_1,
Vocabularies::Known::JSON_Schema_Draft_0}));
ONLY_CONTINUE_IF(!schema.defines("type"));
ONLY_CONTINUE_IF(
!vocabularies.contains(Vocabularies::Known::JSON_Schema_2020_12_Core) ||
!schema.defines_any({"$ref", "$dynamicRef"}));
ONLY_CONTINUE_IF(!vocabularies.contains(
Vocabularies::Known::JSON_Schema_2020_12_Applicator) ||
!schema.defines_any({"anyOf", "oneOf", "allOf", "if",
"then", "else", "not"}));
ONLY_CONTINUE_IF(!vocabularies.contains(
Vocabularies::Known::JSON_Schema_2020_12_Validation) ||
!schema.defines_any({"enum", "const"}));
ONLY_CONTINUE_IF(
!vocabularies.contains(Vocabularies::Known::JSON_Schema_2019_09_Core) ||
!schema.defines_any({"$ref", "$recursiveRef"}));
ONLY_CONTINUE_IF(!vocabularies.contains(
Vocabularies::Known::JSON_Schema_2019_09_Applicator) ||
!schema.defines_any({"anyOf", "oneOf", "allOf", "if",
"then", "else", "not"}));
ONLY_CONTINUE_IF(!vocabularies.contains(
Vocabularies::Known::JSON_Schema_2019_09_Validation) ||
!schema.defines_any({"enum", "const"}));
ONLY_CONTINUE_IF(
!vocabularies.contains(Vocabularies::Known::JSON_Schema_Draft_7) ||
!schema.defines_any({"$ref", "enum", "const", "anyOf", "oneOf", "allOf",
"if", "then", "else", "not"}));
ONLY_CONTINUE_IF(
!vocabularies.contains(Vocabularies::Known::JSON_Schema_Draft_6) ||
!schema.defines_any(
{"$ref", "enum", "const", "anyOf", "oneOf", "allOf", "not"}));
ONLY_CONTINUE_IF(
!vocabularies.contains(Vocabularies::Known::JSON_Schema_Draft_4) ||
!schema.defines_any(
{"$ref", "enum", "anyOf", "oneOf", "allOf", "not"}));
ONLY_CONTINUE_IF(
!vocabularies.contains(Vocabularies::Known::JSON_Schema_Draft_3) ||
!schema.defines_any({"$ref", "enum", "disallow", "extends"}))
ONLY_CONTINUE_IF(
!vocabularies.contains(Vocabularies::Known::JSON_Schema_Draft_2) ||
!schema.defines_any({"enum", "disallow", "extends"}));
ONLY_CONTINUE_IF(
!vocabularies.contains(Vocabularies::Known::JSON_Schema_Draft_1) ||
!schema.defines_any({"enum", "disallow", "extends"}));
ONLY_CONTINUE_IF(!vocabularies.contains(
Vocabularies::Known::JSON_Schema_Draft_0_Hyper) ||
!schema.defines_any({"enum", "disallow", "extends"}));
ONLY_CONTINUE_IF(!schema.defines("enum"));
ONLY_CONTINUE_IF(!vocabularies.contains_any(
{Vocabularies::Known::JSON_Schema_2020_12_Validation,
Vocabularies::Known::JSON_Schema_2019_09_Validation,
Vocabularies::Known::JSON_Schema_Draft_7,
Vocabularies::Known::JSON_Schema_Draft_6}) ||
!schema.defines("const"));

for (const auto &entry : schema.as_object()) {
const auto &keyword_type{walker(entry.first, vocabularies).type};

// References point to other schemas that may have type constraints
ONLY_CONTINUE_IF(keyword_type != SchemaKeywordType::Reference);

// Logical in-place applicators apply without affecting the instance
// location, meaning they impose constraints on the same instance. Adding
// an implicit type union alongside these would create redundant branches
// that need complex simplification
ONLY_CONTINUE_IF(
keyword_type != SchemaKeywordType::ApplicatorValueOrElementsInPlace &&
keyword_type != SchemaKeywordType::ApplicatorMembersInPlaceSome &&
keyword_type != SchemaKeywordType::ApplicatorElementsInPlace &&
keyword_type != SchemaKeywordType::ApplicatorElementsInPlaceSome &&
keyword_type !=
SchemaKeywordType::ApplicatorElementsInPlaceSomeNegate &&
keyword_type != SchemaKeywordType::ApplicatorValueInPlaceMaybe &&
keyword_type != SchemaKeywordType::ApplicatorValueInPlaceNegate);
}

return true;
}

Expand Down
26 changes: 26 additions & 0 deletions test/alterschema/alterschema_canonicalize_2020_12_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1296,3 +1296,29 @@ TEST(AlterSchema_canonicalize_2020_12, allof_two_properties_branches_1) {

EXPECT_EQ(document, expected);
}

TEST(AlterSchema_canonicalize_2020_12,
type_union_implicit_with_content_schema) {
auto document = sourcemeta::core::parse_json(R"JSON({
"$schema": "https://json-schema.org/draft/2020-12/schema",
"contentMediaType": "application/json",
"contentEncoding": "base64",
"contentSchema": { "type": "object" }
})JSON");

CANONICALIZE(document);

const auto expected = sourcemeta::core::parse_json(R"JSON({
"$schema": "https://json-schema.org/draft/2020-12/schema",
"anyOf": [
{ "enum": [ null ] },
{ "enum": [ false, true ] },
{ "type": "object", "minProperties": 0, "properties": {} },
{ "type": "array", "minItems": 0 },
{ "type": "string", "minLength": 0 },
{ "type": "number" }
]
})JSON");

EXPECT_EQ(document, expected);
}