Skip to content

Commit

Permalink
De-duplicate anyOf/allOf
Browse files Browse the repository at this point in the history
Signed-off-by: Juan Cruz Viotti <jv@jviotti.com>
  • Loading branch information
jviotti committed Sep 9, 2024
1 parent 6da241b commit 2ed56f7
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 2 deletions.
2 changes: 2 additions & 0 deletions src/linter/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ noa_library(NAMESPACE sourcemeta PROJECT alterschema NAME linter
redundant/content_media_type_without_encoding.h
redundant/content_schema_default.h
redundant/content_schema_without_media_type.h
redundant/duplicate_allof_branches.h
redundant/duplicate_anyof_branches.h
redundant/else_without_if.h
redundant/items_array_default.h
redundant/items_schema_default.h
Expand Down
8 changes: 6 additions & 2 deletions src/linter/linter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
#include <cassert> // assert

// For built-in rules
#include <algorithm> // std::any_of
#include <iterator> // std::cbegin, std::cend
#include <algorithm>
#include <iterator>
namespace sourcemeta::alterschema {
template <typename T>
auto contains_any(const T &container, const T &values) -> bool {
Expand All @@ -29,6 +29,8 @@ auto contains_any(const T &container, const T &values) -> bool {
#include "redundant/content_media_type_without_encoding.h"
#include "redundant/content_schema_default.h"
#include "redundant/content_schema_without_media_type.h"
#include "redundant/duplicate_allof_branches.h"
#include "redundant/duplicate_anyof_branches.h"
#include "redundant/else_without_if.h"
#include "redundant/items_array_default.h"
#include "redundant/items_schema_default.h"
Expand Down Expand Up @@ -62,6 +64,8 @@ auto add(Bundle &bundle, const LinterCategory category) -> void {
bundle.add<ContentMediaTypeWithoutEncoding>();
bundle.add<ContentSchemaDefault>();
bundle.add<ContentSchemaWithoutMediaType>();
bundle.add<DuplicateAllOfBranches>();
bundle.add<DuplicateAnyOfBranches>();
bundle.add<ElseWithoutIf>();
bundle.add<ItemsArrayDefault>();
bundle.add<ItemsSchemaDefault>();
Expand Down
33 changes: 33 additions & 0 deletions src/linter/redundant/duplicate_allof_branches.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
class DuplicateAllOfBranches final : public Rule {
public:
DuplicateAllOfBranches()
: Rule{"duplicate_allof_branches",
"Setting duplicate subschemas in `allOf` is redundant, as it "
"produces "
"unnecessary additional validation that is guaranteed to not "
"affect the validation result"} {};

[[nodiscard]] auto
condition(const sourcemeta::jsontoolkit::JSON &schema, const std::string &,
const std::set<std::string> &vocabularies,
const sourcemeta::jsontoolkit::Pointer &) const -> bool override {
return contains_any(
vocabularies,
{"https://json-schema.org/draft/2020-12/vocab/applicator",
"https://json-schema.org/draft/2019-09/vocab/applicator",
"http://json-schema.org/draft-07/schema#",
"http://json-schema.org/draft-06/schema#",
"http://json-schema.org/draft-04/schema#"}) &&
schema.is_object() && schema.defines("allOf") &&
schema.at("allOf").is_array() && !schema.at("allOf").unique();
}

auto transform(Transformer &transformer) const -> void override {
auto collection = transformer.schema().at("allOf");
std::sort(collection.as_array().begin(), collection.as_array().end());
auto last =
std::unique(collection.as_array().begin(), collection.as_array().end());
collection.erase(last, collection.as_array().end());
transformer.replace({"allOf"}, std::move(collection));
}
};
33 changes: 33 additions & 0 deletions src/linter/redundant/duplicate_anyof_branches.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
class DuplicateAnyOfBranches final : public Rule {
public:
DuplicateAnyOfBranches()
: Rule{"duplicate_anyof_branches",
"Setting duplicate subschemas in `anyOf` is redundant, as it "
"produces "
"unnecessary additional validation that is guaranteed to not "
"affect the validation result"} {};

[[nodiscard]] auto
condition(const sourcemeta::jsontoolkit::JSON &schema, const std::string &,
const std::set<std::string> &vocabularies,
const sourcemeta::jsontoolkit::Pointer &) const -> bool override {
return contains_any(
vocabularies,
{"https://json-schema.org/draft/2020-12/vocab/applicator",
"https://json-schema.org/draft/2019-09/vocab/applicator",
"http://json-schema.org/draft-07/schema#",
"http://json-schema.org/draft-06/schema#",
"http://json-schema.org/draft-04/schema#"}) &&
schema.is_object() && schema.defines("anyOf") &&
schema.at("anyOf").is_array() && !schema.at("anyOf").unique();
}

auto transform(Transformer &transformer) const -> void override {
auto collection = transformer.schema().at("anyOf");
std::sort(collection.as_array().begin(), collection.as_array().end());
auto last =
std::unique(collection.as_array().begin(), collection.as_array().end());
collection.erase(last, collection.as_array().end());
transformer.replace({"anyOf"}, std::move(collection));
}
};
18 changes: 18 additions & 0 deletions test/linter/2019_09_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -700,3 +700,21 @@ TEST(Lint_2019_09, exclusive_minimum_number_and_minimum_3) {

EXPECT_EQ(document, expected);
}

TEST(Lint_2019_09, duplicate_allof_branches_1) {
sourcemeta::jsontoolkit::JSON document =
sourcemeta::jsontoolkit::parse(R"JSON({
"$schema": "https://json-schema.org/draft/2019-09/schema",
"allOf": [ { "type": "string" }, { "type": "integer" }, { "type": "string" } ]
})JSON");

LINT_AND_FIX(document);

const sourcemeta::jsontoolkit::JSON expected =
sourcemeta::jsontoolkit::parse(R"JSON({
"$schema": "https://json-schema.org/draft/2019-09/schema",
"allOf": [ { "type": "string" }, { "type": "integer" } ]
})JSON");

EXPECT_EQ(document, expected);
}

0 comments on commit 2ed56f7

Please sign in to comment.