Skip to content

Commit 1c2f201

Browse files
authored
Misc canonicalizer improvements (#2200)
Signed-off-by: Juan Cruz Viotti <[email protected]>
1 parent 15f5381 commit 1c2f201

26 files changed

+2138
-573
lines changed

src/extension/alterschema/CMakeLists.txt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
sourcemeta_library(NAMESPACE sourcemeta PROJECT core NAME alterschema
22
SOURCES alterschema.cc
33
# Canonicalizer
4-
canonicalizer/boolean_true.h
54
canonicalizer/const_as_enum.h
65
canonicalizer/exclusive_maximum_integer_to_maximum.h
76
canonicalizer/exclusive_minimum_integer_to_minimum.h
7+
canonicalizer/items_implicit.h
88
canonicalizer/max_contains_covered_by_max_items.h
99
canonicalizer/min_items_given_min_contains.h
1010
canonicalizer/min_items_implicit.h
@@ -20,6 +20,10 @@ sourcemeta_library(NAMESPACE sourcemeta PROJECT core NAME alterschema
2020
canonicalizer/type_union_implicit.h
2121

2222
# Common
23+
common/allof_false_simplify.h
24+
common/anyof_false_simplify.h
25+
common/anyof_remove_false_schemas.h
26+
common/anyof_true_simplify.h
2327
common/const_with_type.h
2428
common/orphan_definitions.h
2529
common/content_media_type_without_encoding.h
@@ -33,6 +37,7 @@ sourcemeta_library(NAMESPACE sourcemeta PROJECT core NAME alterschema
3337
common/duplicate_anyof_branches.h
3438
common/duplicate_enum_values.h
3539
common/duplicate_required_values.h
40+
common/empty_object_as_true.h
3641
common/else_empty.h
3742
common/else_without_if.h
3843
common/enum_with_type.h
@@ -50,12 +55,15 @@ sourcemeta_library(NAMESPACE sourcemeta PROJECT core NAME alterschema
5055
common/non_applicable_enum_validation_keywords.h
5156
common/non_applicable_type_specific_keywords.h
5257
common/not_false.h
58+
common/oneof_false_simplify.h
59+
common/oneof_to_anyof_disjoint_types.h
5360
common/required_properties_in_properties.h
5461
common/single_type_array.h
5562
common/then_empty.h
5663
common/then_without_if.h
5764
common/unknown_keywords_prefix.h
5865
common/unknown_local_ref.h
66+
common/unsatisfiable_drop_validation.h
5967
common/unnecessary_allof_ref_wrapper_draft.h
6068
common/unnecessary_allof_ref_wrapper_modern.h
6169
common/unnecessary_allof_wrapper.h

src/extension/alterschema/alterschema.cc

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ inline auto APPLIES_TO_POINTERS(std::vector<Pointer> &&keywords)
2929
}
3030

3131
// Canonicalizer
32-
#include "canonicalizer/boolean_true.h"
3332
#include "canonicalizer/const_as_enum.h"
3433
#include "canonicalizer/exclusive_maximum_integer_to_maximum.h"
3534
#include "canonicalizer/exclusive_minimum_integer_to_minimum.h"
35+
#include "canonicalizer/items_implicit.h"
3636
#include "canonicalizer/max_contains_covered_by_max_items.h"
3737
#include "canonicalizer/min_items_given_min_contains.h"
3838
#include "canonicalizer/min_items_implicit.h"
@@ -48,6 +48,10 @@ inline auto APPLIES_TO_POINTERS(std::vector<Pointer> &&keywords)
4848
#include "canonicalizer/type_union_implicit.h"
4949

5050
// Common
51+
#include "common/allof_false_simplify.h"
52+
#include "common/anyof_false_simplify.h"
53+
#include "common/anyof_remove_false_schemas.h"
54+
#include "common/anyof_true_simplify.h"
5155
#include "common/const_with_type.h"
5256
#include "common/content_media_type_without_encoding.h"
5357
#include "common/content_schema_without_media_type.h"
@@ -62,6 +66,7 @@ inline auto APPLIES_TO_POINTERS(std::vector<Pointer> &&keywords)
6266
#include "common/duplicate_required_values.h"
6367
#include "common/else_empty.h"
6468
#include "common/else_without_if.h"
69+
#include "common/empty_object_as_true.h"
6570
#include "common/enum_with_type.h"
6671
#include "common/equal_numeric_bounds_to_enum.h"
6772
#include "common/exclusive_maximum_number_and_maximum.h"
@@ -77,6 +82,8 @@ inline auto APPLIES_TO_POINTERS(std::vector<Pointer> &&keywords)
7782
#include "common/non_applicable_enum_validation_keywords.h"
7883
#include "common/non_applicable_type_specific_keywords.h"
7984
#include "common/not_false.h"
85+
#include "common/oneof_false_simplify.h"
86+
#include "common/oneof_to_anyof_disjoint_types.h"
8087
#include "common/orphan_definitions.h"
8188
#include "common/required_properties_in_properties.h"
8289
#include "common/single_type_array.h"
@@ -87,6 +94,7 @@ inline auto APPLIES_TO_POINTERS(std::vector<Pointer> &&keywords)
8794
#include "common/unnecessary_allof_ref_wrapper_draft.h"
8895
#include "common/unnecessary_allof_ref_wrapper_modern.h"
8996
#include "common/unnecessary_allof_wrapper.h"
97+
#include "common/unsatisfiable_drop_validation.h"
9098
#include "common/unsatisfiable_in_place_applicator_type.h"
9199

92100
// Linter
@@ -138,9 +146,16 @@ auto add(SchemaTransformer &bundle, const AlterSchemaMode mode) -> void {
138146
bundle.add<ContentSchemaWithoutMediaType>();
139147
bundle.add<DraftOfficialDialectWithoutEmptyFragment>();
140148
bundle.add<NonApplicableTypeSpecificKeywords>();
149+
bundle.add<AnyOfRemoveFalseSchemas>();
150+
bundle.add<AnyOfTrueSimplify>();
141151
bundle.add<DuplicateAllOfBranches>();
142152
bundle.add<DuplicateAnyOfBranches>();
143153
bundle.add<UnsatisfiableInPlaceApplicatorType>();
154+
bundle.add<AllOfFalseSimplify>();
155+
bundle.add<AnyOfFalseSimplify>();
156+
bundle.add<OneOfFalseSimplify>();
157+
bundle.add<OneOfToAnyOfDisjointTypes>();
158+
bundle.add<UnsatisfiableDropValidation>();
144159
bundle.add<ElseWithoutIf>();
145160
bundle.add<IfWithoutThenElse>();
146161
bundle.add<IgnoredMetaschema>();
@@ -172,7 +187,6 @@ auto add(SchemaTransformer &bundle, const AlterSchemaMode mode) -> void {
172187
bundle.add<OrphanDefinitions>();
173188

174189
if (mode == AlterSchemaMode::Canonicalizer) {
175-
bundle.add<BooleanTrue>();
176190
bundle.add<ConstAsEnum>();
177191
bundle.add<EqualNumericBoundsToConst>();
178192
bundle.add<ExclusiveMaximumIntegerToMaximum>();
@@ -188,6 +202,7 @@ auto add(SchemaTransformer &bundle, const AlterSchemaMode mode) -> void {
188202
bundle.add<MinPropertiesImplicit>();
189203
bundle.add<MultipleOfImplicit>();
190204
bundle.add<PropertiesImplicit>();
205+
bundle.add<ItemsImplicit>();
191206
}
192207

193208
if (mode == AlterSchemaMode::Linter) {
@@ -224,6 +239,7 @@ auto add(SchemaTransformer &bundle, const AlterSchemaMode mode) -> void {
224239
bundle.add<UnnecessaryAllOfRefWrapperDraft>();
225240
bundle.add<UnnecessaryAllOfWrapper>();
226241
bundle.add<DropAllOfEmptySchemas>();
242+
bundle.add<EmptyObjectAsTrue>();
227243
}
228244

229245
} // namespace sourcemeta::core

src/extension/alterschema/canonicalizer/boolean_true.h

Lines changed: 0 additions & 23 deletions
This file was deleted.
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
class ItemsImplicit final : public SchemaTransformRule {
2+
public:
3+
ItemsImplicit()
4+
: SchemaTransformRule{"items_implicit",
5+
"Every array has an implicit `items` "
6+
"that consists of the boolean schema `true`"} {};
7+
8+
[[nodiscard]] auto
9+
condition(const sourcemeta::core::JSON &schema,
10+
const sourcemeta::core::JSON &,
11+
const sourcemeta::core::Vocabularies &vocabularies,
12+
const sourcemeta::core::SchemaFrame &,
13+
const sourcemeta::core::SchemaFrame::Location &,
14+
const sourcemeta::core::SchemaWalker &,
15+
const sourcemeta::core::SchemaResolver &) const
16+
-> sourcemeta::core::SchemaTransformRule::Result override {
17+
ONLY_CONTINUE_IF(
18+
((vocabularies.contains(
19+
Vocabularies::Known::JSON_Schema_2020_12_Validation) &&
20+
vocabularies.contains(
21+
Vocabularies::Known::JSON_Schema_2020_12_Applicator)) ||
22+
(vocabularies.contains(
23+
Vocabularies::Known::JSON_Schema_2019_09_Validation) &&
24+
vocabularies.contains(
25+
Vocabularies::Known::JSON_Schema_2019_09_Applicator)) ||
26+
vocabularies.contains_any(
27+
{Vocabularies::Known::JSON_Schema_Draft_7,
28+
Vocabularies::Known::JSON_Schema_Draft_6})) &&
29+
schema.is_object() && schema.defines("type") &&
30+
schema.at("type").is_string() &&
31+
schema.at("type").to_string() == "array" && !schema.defines("items"));
32+
return true;
33+
}
34+
35+
auto transform(JSON &schema, const Result &) const -> void override {
36+
schema.assign("items", JSON{true});
37+
}
38+
};
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
class AllOfFalseSimplify final : public SchemaTransformRule {
2+
public:
3+
AllOfFalseSimplify()
4+
: SchemaTransformRule{"allof_false_simplify",
5+
"When `allOf` contains a `false` branch, the "
6+
"schema is unsatisfiable"} {};
7+
8+
[[nodiscard]] auto
9+
condition(const sourcemeta::core::JSON &schema,
10+
const sourcemeta::core::JSON &,
11+
const sourcemeta::core::Vocabularies &vocabularies,
12+
const sourcemeta::core::SchemaFrame &frame,
13+
const sourcemeta::core::SchemaFrame::Location &location,
14+
const sourcemeta::core::SchemaWalker &,
15+
const sourcemeta::core::SchemaResolver &) const
16+
-> sourcemeta::core::SchemaTransformRule::Result override {
17+
static const JSON::String KEYWORD{"allOf"};
18+
ONLY_CONTINUE_IF(vocabularies.contains_any(
19+
{Vocabularies::Known::JSON_Schema_2020_12_Applicator,
20+
Vocabularies::Known::JSON_Schema_2019_09_Applicator,
21+
Vocabularies::Known::JSON_Schema_Draft_7,
22+
Vocabularies::Known::JSON_Schema_Draft_6}) &&
23+
schema.is_object() && schema.defines(KEYWORD) &&
24+
!schema.defines("not") && schema.at(KEYWORD).is_array());
25+
26+
const auto &allof{schema.at(KEYWORD)};
27+
for (std::size_t index = 0; index < allof.size(); ++index) {
28+
const auto &entry{allof.at(index)};
29+
if (entry.is_boolean() && !entry.to_boolean()) {
30+
ONLY_CONTINUE_IF(!frame.has_references_through(
31+
location.pointer, WeakPointer::Token{std::cref(KEYWORD)}));
32+
return APPLIES_TO_POINTERS({Pointer{KEYWORD, index}});
33+
}
34+
}
35+
36+
return false;
37+
}
38+
39+
auto transform(JSON &schema, const Result &) const -> void override {
40+
schema.at("allOf").into(JSON{true});
41+
schema.rename("allOf", "not");
42+
}
43+
};
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
class AnyOfFalseSimplify final : public SchemaTransformRule {
2+
public:
3+
AnyOfFalseSimplify()
4+
: SchemaTransformRule{"anyof_false_simplify",
5+
"An `anyOf` of a single `false` branch is "
6+
"unsatisfiable"} {};
7+
8+
[[nodiscard]] auto
9+
condition(const sourcemeta::core::JSON &schema,
10+
const sourcemeta::core::JSON &,
11+
const sourcemeta::core::Vocabularies &vocabularies,
12+
const sourcemeta::core::SchemaFrame &frame,
13+
const sourcemeta::core::SchemaFrame::Location &location,
14+
const sourcemeta::core::SchemaWalker &,
15+
const sourcemeta::core::SchemaResolver &) const
16+
-> sourcemeta::core::SchemaTransformRule::Result override {
17+
static const JSON::String KEYWORD{"anyOf"};
18+
ONLY_CONTINUE_IF(vocabularies.contains_any(
19+
{Vocabularies::Known::JSON_Schema_2020_12_Applicator,
20+
Vocabularies::Known::JSON_Schema_2019_09_Applicator,
21+
Vocabularies::Known::JSON_Schema_Draft_7,
22+
Vocabularies::Known::JSON_Schema_Draft_6}) &&
23+
schema.is_object() && schema.defines(KEYWORD) &&
24+
!schema.defines("not") && schema.at(KEYWORD).is_array() &&
25+
schema.at(KEYWORD).size() == 1);
26+
27+
const auto &entry{schema.at(KEYWORD).front()};
28+
ONLY_CONTINUE_IF(entry.is_boolean() && !entry.to_boolean());
29+
ONLY_CONTINUE_IF(!frame.has_references_through(
30+
location.pointer, WeakPointer::Token{std::cref(KEYWORD)}));
31+
return APPLIES_TO_POINTERS({Pointer{KEYWORD, 0}});
32+
}
33+
34+
auto transform(JSON &schema, const Result &) const -> void override {
35+
schema.at("anyOf").into(JSON{true});
36+
schema.rename("anyOf", "not");
37+
}
38+
};
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
class AnyOfRemoveFalseSchemas final : public SchemaTransformRule {
2+
public:
3+
AnyOfRemoveFalseSchemas()
4+
: SchemaTransformRule{
5+
"anyof_remove_false_schemas",
6+
"The boolean schema `false` is guaranteed to never match in "
7+
"`anyOf`, as it is sufficient for any other branch to match"} {};
8+
9+
[[nodiscard]] auto
10+
condition(const sourcemeta::core::JSON &schema,
11+
const sourcemeta::core::JSON &,
12+
const sourcemeta::core::Vocabularies &vocabularies,
13+
const sourcemeta::core::SchemaFrame &frame,
14+
const sourcemeta::core::SchemaFrame::Location &location,
15+
const sourcemeta::core::SchemaWalker &,
16+
const sourcemeta::core::SchemaResolver &) const
17+
-> sourcemeta::core::SchemaTransformRule::Result override {
18+
static const JSON::String KEYWORD{"anyOf"};
19+
ONLY_CONTINUE_IF(vocabularies.contains_any(
20+
{Vocabularies::Known::JSON_Schema_2020_12_Applicator,
21+
Vocabularies::Known::JSON_Schema_2019_09_Applicator,
22+
Vocabularies::Known::JSON_Schema_Draft_7,
23+
Vocabularies::Known::JSON_Schema_Draft_6}) &&
24+
schema.is_object() && schema.defines(KEYWORD) &&
25+
schema.at(KEYWORD).is_array() &&
26+
schema.at(KEYWORD).contains(JSON{false}));
27+
ONLY_CONTINUE_IF(!frame.has_references_through(
28+
location.pointer, WeakPointer::Token{std::cref(KEYWORD)}));
29+
30+
std::vector<Pointer> false_locations;
31+
bool has_non_false{false};
32+
const auto &anyof{schema.at(KEYWORD)};
33+
for (std::size_t index = 0; index < anyof.size(); ++index) {
34+
const auto &entry{anyof.at(index)};
35+
if (entry.is_boolean() && !entry.to_boolean()) {
36+
false_locations.push_back(Pointer{KEYWORD, index});
37+
} else {
38+
has_non_false = true;
39+
}
40+
}
41+
42+
ONLY_CONTINUE_IF(has_non_false);
43+
return APPLIES_TO_POINTERS(std::move(false_locations));
44+
}
45+
46+
auto transform(JSON &schema, const Result &result) const -> void override {
47+
static const JSON::String KEYWORD{"anyOf"};
48+
49+
std::unordered_set<std::size_t> indices_to_remove;
50+
for (const auto &location : result.locations) {
51+
indices_to_remove.insert(location.at(1).to_index());
52+
}
53+
54+
auto new_anyof{JSON::make_array()};
55+
const auto &anyof{schema.at(KEYWORD)};
56+
for (std::size_t index = 0; index < anyof.size(); ++index) {
57+
if (!indices_to_remove.contains(index)) {
58+
new_anyof.push_back(anyof.at(index));
59+
}
60+
}
61+
62+
schema.assign(KEYWORD, std::move(new_anyof));
63+
}
64+
};
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
class AnyOfTrueSimplify final : public SchemaTransformRule {
2+
public:
3+
AnyOfTrueSimplify()
4+
: SchemaTransformRule{
5+
"anyof_true_simplify",
6+
"An `anyOf` with a `true` or `{}` branch always succeeds"} {};
7+
8+
[[nodiscard]] auto
9+
condition(const sourcemeta::core::JSON &schema,
10+
const sourcemeta::core::JSON &,
11+
const sourcemeta::core::Vocabularies &vocabularies,
12+
const sourcemeta::core::SchemaFrame &frame,
13+
const sourcemeta::core::SchemaFrame::Location &location,
14+
const sourcemeta::core::SchemaWalker &,
15+
const sourcemeta::core::SchemaResolver &) const
16+
-> sourcemeta::core::SchemaTransformRule::Result override {
17+
static const JSON::String KEYWORD{"anyOf"};
18+
ONLY_CONTINUE_IF(vocabularies.contains_any(
19+
{Vocabularies::Known::JSON_Schema_2020_12_Applicator,
20+
Vocabularies::Known::JSON_Schema_2019_09_Applicator,
21+
Vocabularies::Known::JSON_Schema_Draft_7,
22+
Vocabularies::Known::JSON_Schema_Draft_6,
23+
Vocabularies::Known::JSON_Schema_Draft_4}) &&
24+
schema.is_object() && schema.defines(KEYWORD) &&
25+
schema.at(KEYWORD).is_array());
26+
27+
const auto &anyof{schema.at(KEYWORD)};
28+
for (std::size_t index = 0; index < anyof.size(); ++index) {
29+
const auto &entry{anyof.at(index)};
30+
if ((entry.is_boolean() && entry.to_boolean()) ||
31+
(entry.is_object() && entry.empty())) {
32+
ONLY_CONTINUE_IF(!frame.has_references_through(
33+
location.pointer, WeakPointer::Token{std::cref(KEYWORD)}));
34+
return APPLIES_TO_POINTERS({Pointer{KEYWORD, index}});
35+
}
36+
}
37+
38+
return false;
39+
}
40+
41+
auto transform(JSON &schema, const Result &) const -> void override {
42+
schema.erase("anyOf");
43+
}
44+
};

0 commit comments

Comments
 (0)