Skip to content
Open
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
10 changes: 9 additions & 1 deletion src/extension/alterschema/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
sourcemeta_library(NAMESPACE sourcemeta PROJECT core NAME alterschema
SOURCES alterschema.cc
# Canonicalizer
canonicalizer/boolean_true.h
canonicalizer/const_as_enum.h
canonicalizer/exclusive_maximum_integer_to_maximum.h
canonicalizer/exclusive_minimum_integer_to_minimum.h
canonicalizer/items_implicit.h
canonicalizer/max_contains_covered_by_max_items.h
canonicalizer/min_items_given_min_contains.h
canonicalizer/min_items_implicit.h
Expand All @@ -20,6 +20,10 @@ sourcemeta_library(NAMESPACE sourcemeta PROJECT core NAME alterschema
canonicalizer/type_union_implicit.h

# Common
common/allof_false_simplify.h
common/anyof_false_simplify.h
common/anyof_remove_false_schemas.h
common/anyof_true_simplify.h
common/const_with_type.h
common/orphan_definitions.h
common/content_media_type_without_encoding.h
Expand All @@ -33,6 +37,7 @@ sourcemeta_library(NAMESPACE sourcemeta PROJECT core NAME alterschema
common/duplicate_anyof_branches.h
common/duplicate_enum_values.h
common/duplicate_required_values.h
common/empty_object_as_true.h
common/else_empty.h
common/else_without_if.h
common/enum_with_type.h
Expand All @@ -50,12 +55,15 @@ sourcemeta_library(NAMESPACE sourcemeta PROJECT core NAME alterschema
common/non_applicable_enum_validation_keywords.h
common/non_applicable_type_specific_keywords.h
common/not_false.h
common/oneof_false_simplify.h
common/oneof_to_anyof_disjoint_types.h
common/required_properties_in_properties.h
common/single_type_array.h
common/then_empty.h
common/then_without_if.h
common/unknown_keywords_prefix.h
common/unknown_local_ref.h
common/unsatisfiable_drop_validation.h
common/unnecessary_allof_ref_wrapper_draft.h
common/unnecessary_allof_ref_wrapper_modern.h
common/unnecessary_allof_wrapper.h
Expand Down
20 changes: 18 additions & 2 deletions src/extension/alterschema/alterschema.cc
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ inline auto APPLIES_TO_POINTERS(std::vector<Pointer> &&keywords)
}

// Canonicalizer
#include "canonicalizer/boolean_true.h"
#include "canonicalizer/const_as_enum.h"
#include "canonicalizer/exclusive_maximum_integer_to_maximum.h"
#include "canonicalizer/exclusive_minimum_integer_to_minimum.h"
#include "canonicalizer/items_implicit.h"
#include "canonicalizer/max_contains_covered_by_max_items.h"
#include "canonicalizer/min_items_given_min_contains.h"
#include "canonicalizer/min_items_implicit.h"
Expand All @@ -48,6 +48,10 @@ inline auto APPLIES_TO_POINTERS(std::vector<Pointer> &&keywords)
#include "canonicalizer/type_union_implicit.h"

// Common
#include "common/allof_false_simplify.h"
#include "common/anyof_false_simplify.h"
#include "common/anyof_remove_false_schemas.h"
#include "common/anyof_true_simplify.h"
#include "common/const_with_type.h"
#include "common/content_media_type_without_encoding.h"
#include "common/content_schema_without_media_type.h"
Expand All @@ -62,6 +66,7 @@ inline auto APPLIES_TO_POINTERS(std::vector<Pointer> &&keywords)
#include "common/duplicate_required_values.h"
#include "common/else_empty.h"
#include "common/else_without_if.h"
#include "common/empty_object_as_true.h"
#include "common/enum_with_type.h"
#include "common/equal_numeric_bounds_to_enum.h"
#include "common/exclusive_maximum_number_and_maximum.h"
Expand All @@ -77,6 +82,8 @@ inline auto APPLIES_TO_POINTERS(std::vector<Pointer> &&keywords)
#include "common/non_applicable_enum_validation_keywords.h"
#include "common/non_applicable_type_specific_keywords.h"
#include "common/not_false.h"
#include "common/oneof_false_simplify.h"
#include "common/oneof_to_anyof_disjoint_types.h"
#include "common/orphan_definitions.h"
#include "common/required_properties_in_properties.h"
#include "common/single_type_array.h"
Expand All @@ -87,6 +94,7 @@ inline auto APPLIES_TO_POINTERS(std::vector<Pointer> &&keywords)
#include "common/unnecessary_allof_ref_wrapper_draft.h"
#include "common/unnecessary_allof_ref_wrapper_modern.h"
#include "common/unnecessary_allof_wrapper.h"
#include "common/unsatisfiable_drop_validation.h"
#include "common/unsatisfiable_in_place_applicator_type.h"

// Linter
Expand Down Expand Up @@ -139,9 +147,16 @@ auto add(SchemaTransformer &bundle, const AlterSchemaMode mode) -> void {
bundle.add<ContentSchemaWithoutMediaType>();
bundle.add<DraftOfficialDialectWithoutEmptyFragment>();
bundle.add<NonApplicableTypeSpecificKeywords>();
bundle.add<AnyOfRemoveFalseSchemas>();
bundle.add<AnyOfTrueSimplify>();
bundle.add<DuplicateAllOfBranches>();
bundle.add<DuplicateAnyOfBranches>();
bundle.add<UnsatisfiableInPlaceApplicatorType>();
bundle.add<AllOfFalseSimplify>();
bundle.add<AnyOfFalseSimplify>();
bundle.add<OneOfFalseSimplify>();
bundle.add<OneOfToAnyOfDisjointTypes>();
bundle.add<UnsatisfiableDropValidation>();
bundle.add<ElseWithoutIf>();
bundle.add<IfWithoutThenElse>();
bundle.add<IgnoredMetaschema>();
Expand Down Expand Up @@ -173,7 +188,6 @@ auto add(SchemaTransformer &bundle, const AlterSchemaMode mode) -> void {
bundle.add<OrphanDefinitions>();

if (mode == AlterSchemaMode::Canonicalizer) {
bundle.add<BooleanTrue>();
bundle.add<ConstAsEnum>();
bundle.add<EqualNumericBoundsToConst>();
bundle.add<ExclusiveMaximumIntegerToMaximum>();
Expand All @@ -189,6 +203,7 @@ auto add(SchemaTransformer &bundle, const AlterSchemaMode mode) -> void {
bundle.add<MinPropertiesImplicit>();
bundle.add<MultipleOfImplicit>();
bundle.add<PropertiesImplicit>();
bundle.add<ItemsImplicit>();
}

if (mode == AlterSchemaMode::Linter) {
Expand Down Expand Up @@ -226,6 +241,7 @@ auto add(SchemaTransformer &bundle, const AlterSchemaMode mode) -> void {
bundle.add<UnnecessaryAllOfRefWrapperDraft>();
bundle.add<UnnecessaryAllOfWrapper>();
bundle.add<DropAllOfEmptySchemas>();
bundle.add<EmptyObjectAsTrue>();
}

} // namespace sourcemeta::core
23 changes: 0 additions & 23 deletions src/extension/alterschema/canonicalizer/boolean_true.h

This file was deleted.

38 changes: 38 additions & 0 deletions src/extension/alterschema/canonicalizer/items_implicit.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
class ItemsImplicit final : public SchemaTransformRule {
public:
ItemsImplicit()
: SchemaTransformRule{"items_implicit",
"Every array has an implicit `items` "
"that consists of the boolean schema `true`"} {};

[[nodiscard]] auto
condition(const sourcemeta::core::JSON &schema,
const sourcemeta::core::JSON &,
const sourcemeta::core::Vocabularies &vocabularies,
const sourcemeta::core::SchemaFrame &,
const sourcemeta::core::SchemaFrame::Location &,
const sourcemeta::core::SchemaWalker &,
const sourcemeta::core::SchemaResolver &) const
-> sourcemeta::core::SchemaTransformRule::Result override {
ONLY_CONTINUE_IF(
((vocabularies.contains(
Vocabularies::Known::JSON_Schema_2020_12_Validation) &&
vocabularies.contains(
Vocabularies::Known::JSON_Schema_2020_12_Applicator)) ||
(vocabularies.contains(
Vocabularies::Known::JSON_Schema_2019_09_Validation) &&
vocabularies.contains(
Vocabularies::Known::JSON_Schema_2019_09_Applicator)) ||
vocabularies.contains_any(
{Vocabularies::Known::JSON_Schema_Draft_7,
Vocabularies::Known::JSON_Schema_Draft_6})) &&
schema.is_object() && schema.defines("type") &&
schema.at("type").is_string() &&
schema.at("type").to_string() == "array" && !schema.defines("items"));
return true;
}

auto transform(JSON &schema, const Result &) const -> void override {
schema.assign("items", JSON{true});
}
};
43 changes: 43 additions & 0 deletions src/extension/alterschema/common/allof_false_simplify.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
class AllOfFalseSimplify final : public SchemaTransformRule {
public:
AllOfFalseSimplify()
: SchemaTransformRule{"allof_false_simplify",
"When `allOf` contains a `false` branch, the "
"schema is unsatisfiable"} {};

[[nodiscard]] auto
condition(const sourcemeta::core::JSON &schema,
const sourcemeta::core::JSON &,
const sourcemeta::core::Vocabularies &vocabularies,
const sourcemeta::core::SchemaFrame &frame,
const sourcemeta::core::SchemaFrame::Location &location,
const sourcemeta::core::SchemaWalker &,
const sourcemeta::core::SchemaResolver &) const
-> sourcemeta::core::SchemaTransformRule::Result override {
static const JSON::String KEYWORD{"allOf"};
ONLY_CONTINUE_IF(vocabularies.contains_any(
{Vocabularies::Known::JSON_Schema_2020_12_Applicator,
Vocabularies::Known::JSON_Schema_2019_09_Applicator,
Vocabularies::Known::JSON_Schema_Draft_7,
Vocabularies::Known::JSON_Schema_Draft_6}) &&
schema.is_object() && schema.defines(KEYWORD) &&
!schema.defines("not") && schema.at(KEYWORD).is_array());

const auto &allof{schema.at(KEYWORD)};
for (std::size_t index = 0; index < allof.size(); ++index) {
const auto &entry{allof.at(index)};
if (entry.is_boolean() && !entry.to_boolean()) {
ONLY_CONTINUE_IF(!frame.has_references_through(
location.pointer, WeakPointer::Token{std::cref(KEYWORD)}));
return APPLIES_TO_POINTERS({Pointer{KEYWORD, index}});
}
}

return false;
}

auto transform(JSON &schema, const Result &) const -> void override {
schema.at("allOf").into(JSON{true});
schema.rename("allOf", "not");
}
};
38 changes: 38 additions & 0 deletions src/extension/alterschema/common/anyof_false_simplify.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
class AnyOfFalseSimplify final : public SchemaTransformRule {
public:
AnyOfFalseSimplify()
: SchemaTransformRule{"anyof_false_simplify",
"An `anyOf` of a single `false` branch is "
"unsatisfiable"} {};

[[nodiscard]] auto
condition(const sourcemeta::core::JSON &schema,
const sourcemeta::core::JSON &,
const sourcemeta::core::Vocabularies &vocabularies,
const sourcemeta::core::SchemaFrame &frame,
const sourcemeta::core::SchemaFrame::Location &location,
const sourcemeta::core::SchemaWalker &,
const sourcemeta::core::SchemaResolver &) const
-> sourcemeta::core::SchemaTransformRule::Result override {
static const JSON::String KEYWORD{"anyOf"};
ONLY_CONTINUE_IF(vocabularies.contains_any(
{Vocabularies::Known::JSON_Schema_2020_12_Applicator,
Vocabularies::Known::JSON_Schema_2019_09_Applicator,
Vocabularies::Known::JSON_Schema_Draft_7,
Vocabularies::Known::JSON_Schema_Draft_6}) &&
schema.is_object() && schema.defines(KEYWORD) &&
!schema.defines("not") && schema.at(KEYWORD).is_array() &&
schema.at(KEYWORD).size() == 1);

const auto &entry{schema.at(KEYWORD).front()};
ONLY_CONTINUE_IF(entry.is_boolean() && !entry.to_boolean());
ONLY_CONTINUE_IF(!frame.has_references_through(
location.pointer, WeakPointer::Token{std::cref(KEYWORD)}));
return APPLIES_TO_POINTERS({Pointer{KEYWORD, 0}});
}

auto transform(JSON &schema, const Result &) const -> void override {
schema.at("anyOf").into(JSON{true});
schema.rename("anyOf", "not");
}
};
64 changes: 64 additions & 0 deletions src/extension/alterschema/common/anyof_remove_false_schemas.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
class AnyOfRemoveFalseSchemas final : public SchemaTransformRule {
public:
AnyOfRemoveFalseSchemas()
: SchemaTransformRule{
"anyof_remove_false_schemas",
"The boolean schema `false` is guaranteed to never match in "
"`anyOf`, as it is sufficient for any other branch to match"} {};

[[nodiscard]] auto
condition(const sourcemeta::core::JSON &schema,
const sourcemeta::core::JSON &,
const sourcemeta::core::Vocabularies &vocabularies,
const sourcemeta::core::SchemaFrame &frame,
const sourcemeta::core::SchemaFrame::Location &location,
const sourcemeta::core::SchemaWalker &,
const sourcemeta::core::SchemaResolver &) const
-> sourcemeta::core::SchemaTransformRule::Result override {
static const JSON::String KEYWORD{"anyOf"};
ONLY_CONTINUE_IF(vocabularies.contains_any(
{Vocabularies::Known::JSON_Schema_2020_12_Applicator,
Vocabularies::Known::JSON_Schema_2019_09_Applicator,
Vocabularies::Known::JSON_Schema_Draft_7,
Vocabularies::Known::JSON_Schema_Draft_6}) &&
schema.is_object() && schema.defines(KEYWORD) &&
schema.at(KEYWORD).is_array() &&
schema.at(KEYWORD).contains(JSON{false}));
ONLY_CONTINUE_IF(!frame.has_references_through(
location.pointer, WeakPointer::Token{std::cref(KEYWORD)}));

std::vector<Pointer> false_locations;
bool has_non_false{false};
const auto &anyof{schema.at(KEYWORD)};
for (std::size_t index = 0; index < anyof.size(); ++index) {
const auto &entry{anyof.at(index)};
if (entry.is_boolean() && !entry.to_boolean()) {
false_locations.push_back(Pointer{KEYWORD, index});
} else {
has_non_false = true;
}
}

ONLY_CONTINUE_IF(has_non_false);
return APPLIES_TO_POINTERS(std::move(false_locations));
}

auto transform(JSON &schema, const Result &result) const -> void override {
static const JSON::String KEYWORD{"anyOf"};

std::unordered_set<std::size_t> indices_to_remove;
for (const auto &location : result.locations) {
indices_to_remove.insert(location.at(1).to_index());
}

auto new_anyof{JSON::make_array()};
const auto &anyof{schema.at(KEYWORD)};
for (std::size_t index = 0; index < anyof.size(); ++index) {
if (!indices_to_remove.contains(index)) {
new_anyof.push_back(anyof.at(index));
}
}

schema.assign(KEYWORD, std::move(new_anyof));
}
};
44 changes: 44 additions & 0 deletions src/extension/alterschema/common/anyof_true_simplify.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
class AnyOfTrueSimplify final : public SchemaTransformRule {
public:
AnyOfTrueSimplify()
: SchemaTransformRule{
"anyof_true_simplify",
"An `anyOf` with a `true` or `{}` branch always succeeds"} {};

[[nodiscard]] auto
condition(const sourcemeta::core::JSON &schema,
const sourcemeta::core::JSON &,
const sourcemeta::core::Vocabularies &vocabularies,
const sourcemeta::core::SchemaFrame &frame,
const sourcemeta::core::SchemaFrame::Location &location,
const sourcemeta::core::SchemaWalker &,
const sourcemeta::core::SchemaResolver &) const
-> sourcemeta::core::SchemaTransformRule::Result override {
static const JSON::String KEYWORD{"anyOf"};
ONLY_CONTINUE_IF(vocabularies.contains_any(
{Vocabularies::Known::JSON_Schema_2020_12_Applicator,
Vocabularies::Known::JSON_Schema_2019_09_Applicator,
Vocabularies::Known::JSON_Schema_Draft_7,
Vocabularies::Known::JSON_Schema_Draft_6,
Vocabularies::Known::JSON_Schema_Draft_4}) &&
schema.is_object() && schema.defines(KEYWORD) &&
schema.at(KEYWORD).is_array());

const auto &anyof{schema.at(KEYWORD)};
for (std::size_t index = 0; index < anyof.size(); ++index) {
const auto &entry{anyof.at(index)};
if ((entry.is_boolean() && entry.to_boolean()) ||
(entry.is_object() && entry.empty())) {
ONLY_CONTINUE_IF(!frame.has_references_through(
location.pointer, WeakPointer::Token{std::cref(KEYWORD)}));
return APPLIES_TO_POINTERS({Pointer{KEYWORD, index}});
}
}

return false;
}

auto transform(JSON &schema, const Result &) const -> void override {
schema.erase("anyOf");
}
};
Loading