diff --git a/src/alterschema/CMakeLists.txt b/src/alterschema/CMakeLists.txt index 37c5b5a15..19dac645c 100644 --- a/src/alterschema/CMakeLists.txt +++ b/src/alterschema/CMakeLists.txt @@ -96,6 +96,7 @@ sourcemeta_library(NAMESPACE sourcemeta PROJECT blaze NAME alterschema common/exclusive_minimum_number_and_minimum.h common/if_without_then_else.h common/ignored_metaschema.h + common/incoherent_exclusive_limits.h common/max_contains_without_contains.h common/maximum_real_for_integer.h common/min_contains_without_contains.h diff --git a/src/alterschema/alterschema.cc b/src/alterschema/alterschema.cc index 4e511019c..1aba9e790 100644 --- a/src/alterschema/alterschema.cc +++ b/src/alterschema/alterschema.cc @@ -203,6 +203,7 @@ auto WALK_UP_IN_PLACE_APPLICATORS(const JSON &root, const SchemaFrame &frame, #include "common/flatten_nested_extends.h" #include "common/if_without_then_else.h" #include "common/ignored_metaschema.h" +#include "common/incoherent_exclusive_limits.h" #include "common/max_contains_without_contains.h" #include "common/maximum_real_for_integer.h" #include "common/min_contains_without_contains.h" @@ -342,6 +343,7 @@ auto add(SchemaTransformer &bundle, const AlterSchemaMode mode) -> void { if (mode == AlterSchemaMode::Canonicalizer) { bundle.add(); bundle.add(); + bundle.add(); bundle.add(); bundle.add(); bundle.add(); @@ -469,6 +471,7 @@ auto add(SchemaTransformer &bundle, const AlterSchemaMode mode) -> void { bundle.add(); bundle.add(); bundle.add(); + bundle.add(); bundle.add(); bundle.add(); bundle.add(); diff --git a/src/alterschema/common/incoherent_exclusive_limits.h b/src/alterschema/common/incoherent_exclusive_limits.h new file mode 100644 index 000000000..fb0964f7c --- /dev/null +++ b/src/alterschema/common/incoherent_exclusive_limits.h @@ -0,0 +1,119 @@ +class IncoherentExclusiveLimits final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + IncoherentExclusiveLimits() + : SchemaTransformRule{ + "incoherent_exclusive_limits", + "`exclusiveMinimum` greater than or equal to " + "`exclusiveMaximum` makes the schema unsatisfiable"} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, const Vocabularies &vocabularies, + const SchemaFrame &, const SchemaFrame::Location &, + const SchemaWalker &, const SchemaResolver &, const bool) const + -> SchemaTransformRule::Result override { + using Known = Vocabularies::Known; + ONLY_CONTINUE_IF(schema.is_object()); + + const bool is_2020_12 = + vocabularies.contains(Known::JSON_Schema_2020_12_Validation) && + vocabularies.contains(Known::JSON_Schema_2020_12_Applicator); + const bool is_2019_09 = + vocabularies.contains(Known::JSON_Schema_2019_09_Validation) && + vocabularies.contains(Known::JSON_Schema_2019_09_Applicator); + const bool is_draft7 = vocabularies.contains(Known::JSON_Schema_Draft_7); + const bool is_draft6 = vocabularies.contains(Known::JSON_Schema_Draft_6); + + ONLY_CONTINUE_IF(is_2020_12 || is_2019_09 || is_draft7 || is_draft6); + + const auto *exclusive_minimum{schema.try_at("exclusiveMinimum")}; + ONLY_CONTINUE_IF(exclusive_minimum && exclusive_minimum->is_number()); + const auto *exclusive_maximum{schema.try_at("exclusiveMaximum")}; + ONLY_CONTINUE_IF(exclusive_maximum && exclusive_maximum->is_number() && + *exclusive_minimum >= *exclusive_maximum); + return APPLIES_TO_KEYWORDS("exclusiveMinimum", "exclusiveMaximum"); + } + + auto transform(JSON &schema, const Result &) const -> void override { + bool needs_not = true; + if (schema.defines("type")) { + const auto &type_node = schema.at("type"); + if (type_node.is_string()) { + const auto &type_str{type_node.to_string()}; + if (type_str == "number" || type_str == "integer") { + schema.assign("not", JSON{true}); + schema.erase("type"); + schema.erase("exclusiveMinimum"); + schema.erase("exclusiveMaximum"); + return; + } else { + needs_not = false; + } + } else if (type_node.is_array()) { + auto remaining_types = JSON::make_array(); + for (const auto &element : type_node.as_array()) { + if (element.is_string()) { + const auto &element_str{element.to_string()}; + if (element_str != "number" && element_str != "integer") { + remaining_types.push_back(element); + } + } else { + remaining_types.push_back(element); + } + } + if (remaining_types.empty()) { + schema.assign("not", JSON{true}); + schema.erase("type"); + schema.erase("exclusiveMinimum"); + schema.erase("exclusiveMaximum"); + return; + } else if (remaining_types.size() == 1) { + schema.assign("type", remaining_types.front()); + needs_not = false; + } else { + schema.assign("type", std::move(remaining_types)); + needs_not = false; + } + } + } + + if (needs_not) { + auto inner = JSON::make_object(); + inner.assign("type", JSON{"number"}); + + const std::string target_key = schema.defines("exclusiveMinimum") + ? "exclusiveMinimum" + : "exclusiveMaximum"; + + if (!schema.defines("not") && !schema.defines("allOf")) { + schema.try_assign_before("not", inner, target_key); + } else { + auto new_entry = JSON::make_object(); + new_entry.assign("not", std::move(inner)); + + if (schema.defines("allOf") && schema.at("allOf").is_array()) { + schema.at("allOf").push_back(std::move(new_entry)); + } else { + auto all_of = JSON::make_array(); + if (schema.defines("not")) { + auto existing_not = JSON::make_object(); + existing_not.assign("not", schema.at("not")); + all_of.push_back(std::move(existing_not)); + all_of.push_back(std::move(new_entry)); + + schema.try_assign_before("allOf", all_of, "not"); + schema.erase("not"); + } else { + all_of.push_back(std::move(new_entry)); + schema.try_assign_before("allOf", all_of, target_key); + } + } + } + } + + schema.erase("exclusiveMinimum"); + schema.erase("exclusiveMaximum"); + } +}; diff --git a/test/alterschema/alterschema_canonicalize_2019_09_test.cc b/test/alterschema/alterschema_canonicalize_2019_09_test.cc index b138d5b3b..e817de9b9 100644 --- a/test/alterschema/alterschema_canonicalize_2019_09_test.cc +++ b/test/alterschema/alterschema_canonicalize_2019_09_test.cc @@ -324,6 +324,199 @@ TEST_F(Canonicalizer201909Test, exclusive_minimum_integer_to_minimum_3) { CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); } +TEST_F(Canonicalizer201909Test, incoherent_exclusive_limits_1) { + auto document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "exclusiveMinimum": 3, + "exclusiveMaximum": 3 + })JSON"); + + const auto expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "not": { "type": "number" } + })JSON"); + + CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); +} + +TEST_F(Canonicalizer201909Test, incoherent_exclusive_limits_2) { + auto document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "exclusiveMinimum": 5, + "exclusiveMaximum": 2 + })JSON"); + + const auto expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "not": { "type": "number" } + })JSON"); + + CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); +} + +TEST_F(Canonicalizer201909Test, incoherent_exclusive_limits_3) { + auto document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "exclusiveMinimum": 5, + "exclusiveMaximum": 2, + "not": { "type": "string" } + })JSON"); + + const auto expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "allOf": [ + { + "not": { "type": "string", "minLength": 0 } + }, + { + "not": { "type": "number" } + } + ] + })JSON"); + + CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); +} + +TEST_F(Canonicalizer201909Test, incoherent_exclusive_limits_4) { + auto document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$ref": "#/$defs/test/additionalProperties", + "$defs": { + "test": { + "additionalProperties": { + "type": "string" + }, + "exclusiveMaximum": 5, + "exclusiveMinimum": 8 + } + } + })JSON"); + + const auto expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$defs": { + "test": { + "allOf": [ + { + "not": { "type": "number" } + }, + { + "anyOf": [ + { + "enum": [ null ] + }, + { + "enum": [ false, true ] + }, + { + "type": "object", + "additionalProperties": { + "type": "string", + "minLength": 0 + }, + "patternProperties": {}, + "propertyNames": true, + "minProperties": 0, + "properties": {} + }, + { + "type": "array", + "uniqueItems": false, + "minItems": 0, + "contains": true, + "minContains": 0, + "items": true + }, + { + "type": "string", + "minLength": 0 + }, + { + "type": "number" + } + ] + } + ] + } + }, + "allOf": [ + { + "$ref": "#/$defs/test/allOf/1/anyOf/2/additionalProperties" + } + ] + })JSON"); + + CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); +} + +TEST_F(Canonicalizer201909Test, incoherent_exclusive_limits_5) { + auto document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "number", + "exclusiveMinimum": 5, + "exclusiveMaximum": 2 + })JSON"); + + const auto expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "not": true + })JSON"); + + CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); +} + +TEST_F(Canonicalizer201909Test, incoherent_exclusive_limits_6) { + auto document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "integer", + "exclusiveMinimum": 5, + "exclusiveMaximum": 2 + })JSON"); + + const auto expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "not": true + })JSON"); + + CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); +} + +TEST_F(Canonicalizer201909Test, incoherent_exclusive_limits_7) { + auto document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": ["string", "number"], + "exclusiveMinimum": 5, + "exclusiveMaximum": 2 + })JSON"); + + const auto expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "type": "string", + "minLength": 0 + })JSON"); + + CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); +} + +TEST_F(Canonicalizer201909Test, incoherent_exclusive_limits_8) { + auto document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "allOf": [ { "type": "string" } ], + "exclusiveMinimum": 5, + "exclusiveMaximum": 2 + })JSON"); + + const auto expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "allOf": [ + { "type": "string", "minLength": 0 }, + { "not": { "type": "number" } } + ] + })JSON"); + + CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); +} + TEST_F(Canonicalizer201909Test, exclusive_minimum_integer_to_minimum_5) { auto document = sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2019-09/schema", diff --git a/test/alterschema/alterschema_canonicalize_2020_12_test.cc b/test/alterschema/alterschema_canonicalize_2020_12_test.cc index 3a535184f..35b7d0ff4 100644 --- a/test/alterschema/alterschema_canonicalize_2020_12_test.cc +++ b/test/alterschema/alterschema_canonicalize_2020_12_test.cc @@ -666,6 +666,199 @@ TEST_F(Canonicalizer202012Test, exclusive_minimum_integer_to_minimum_3) { CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); } +TEST_F(Canonicalizer202012Test, incoherent_exclusive_limits_1) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "exclusiveMinimum": 3, + "exclusiveMaximum": 3 + })JSON"); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "not": { "type": "number" } + })JSON"); + + CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); +} + +TEST_F(Canonicalizer202012Test, incoherent_exclusive_limits_2) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "exclusiveMinimum": 5, + "exclusiveMaximum": 2 + })JSON"); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "not": { "type": "number" } + })JSON"); + + CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); +} + +TEST_F(Canonicalizer202012Test, incoherent_exclusive_limits_3) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "exclusiveMinimum": 5, + "exclusiveMaximum": 2, + "not": { "type": "string" } + })JSON"); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ + { + "not": { "type": "string", "minLength": 0 } + }, + { + "not": { "type": "number" } + } + ] + })JSON"); + + CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); +} + +TEST_F(Canonicalizer202012Test, incoherent_exclusive_limits_4) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "#/$defs/test/additionalProperties", + "$defs": { + "test": { + "additionalProperties": { + "type": "string" + }, + "exclusiveMaximum": 5, + "exclusiveMinimum": 8 + } + } + })JSON"); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "test": { + "allOf": [ + { + "not": { "type": "number" } + }, + { + "anyOf": [ + { + "enum": [ null ] + }, + { + "enum": [ false, true ] + }, + { + "type": "object", + "additionalProperties": { + "type": "string", + "minLength": 0 + }, + "patternProperties": {}, + "propertyNames": true, + "minProperties": 0, + "properties": {} + }, + { + "type": "array", + "uniqueItems": false, + "minItems": 0, + "contains": true, + "minContains": 0, + "items": true + }, + { + "type": "string", + "minLength": 0 + }, + { + "type": "number" + } + ] + } + ] + } + }, + "allOf": [ + { + "$ref": "#/$defs/test/allOf/1/anyOf/2/additionalProperties" + } + ] + })JSON"); + + CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); +} + +TEST_F(Canonicalizer202012Test, incoherent_exclusive_limits_5) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "number", + "exclusiveMinimum": 5, + "exclusiveMaximum": 2 + })JSON"); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "not": true + })JSON"); + + CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); +} + +TEST_F(Canonicalizer202012Test, incoherent_exclusive_limits_6) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "integer", + "exclusiveMinimum": 5, + "exclusiveMaximum": 2 + })JSON"); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "not": true + })JSON"); + + CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); +} + +TEST_F(Canonicalizer202012Test, incoherent_exclusive_limits_7) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": ["string", "number"], + "exclusiveMinimum": 5, + "exclusiveMaximum": 2 + })JSON"); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "string", + "minLength": 0 + })JSON"); + + CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); +} + +TEST_F(Canonicalizer202012Test, incoherent_exclusive_limits_8) { + sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ { "type": "string" } ], + "exclusiveMinimum": 5, + "exclusiveMaximum": 2 + })JSON"); + + const sourcemeta::core::JSON expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ + { "type": "string", "minLength": 0 }, + { "not": { "type": "number" } } + ] + })JSON"); + + CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); +} + TEST_F(Canonicalizer202012Test, exclusive_minimum_integer_to_minimum_5) { sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2020-12/schema", diff --git a/test/alterschema/alterschema_canonicalize_draft6_test.cc b/test/alterschema/alterschema_canonicalize_draft6_test.cc index 4b1b3765a..593af35e5 100644 --- a/test/alterschema/alterschema_canonicalize_draft6_test.cc +++ b/test/alterschema/alterschema_canonicalize_draft6_test.cc @@ -438,6 +438,198 @@ TEST_F(CanonicalizerDraft6Test, integer_both_exclusive_bounds_fold) { CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); } +TEST_F(CanonicalizerDraft6Test, incoherent_exclusive_limits_1) { + auto document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-06/schema#", + "exclusiveMinimum": 3, + "exclusiveMaximum": 3 + })JSON"); + + const auto expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-06/schema#", + "not": { "type": "number" } + })JSON"); + + CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); +} + +TEST_F(CanonicalizerDraft6Test, incoherent_exclusive_limits_2) { + auto document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-06/schema#", + "exclusiveMinimum": 5, + "exclusiveMaximum": 2 + })JSON"); + + const auto expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-06/schema#", + "not": { "type": "number" } + })JSON"); + + CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); +} + +TEST_F(CanonicalizerDraft6Test, incoherent_exclusive_limits_3) { + auto document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-06/schema#", + "exclusiveMinimum": 5, + "exclusiveMaximum": 2, + "not": { "type": "string" } + })JSON"); + + const auto expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-06/schema#", + "allOf": [ + { + "not": { "type": "string", "minLength": 0 } + }, + { + "not": { "type": "number" } + } + ] + })JSON"); + + CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); +} + +TEST_F(CanonicalizerDraft6Test, incoherent_exclusive_limits_4) { + auto document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-06/schema#", + "properties": { + "main": { "$ref": "#/definitions/test/additionalProperties" } + }, + "definitions": { + "test": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "exclusiveMaximum": 5, + "exclusiveMinimum": 8 + } + } + })JSON"); + + const auto expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-06/schema#", + "definitions": { + "test": { + "type": "object", + "additionalProperties": { + "type": "string", + "minLength": 0 + }, + "patternProperties": {}, + "propertyNames": true, + "minProperties": 0, + "properties": {} + } + }, + "anyOf": [ + { + "enum": [ null ] + }, + { + "enum": [ false, true ] + }, + { + "type": "object", + "properties": { + "main": { + "$ref": "#/definitions/test/additionalProperties" + } + }, + "patternProperties": {}, + "propertyNames": true, + "minProperties": 0, + "additionalProperties": true + }, + { + "type": "array", + "uniqueItems": false, + "items": true, + "minItems": 0 + }, + { + "type": "string", + "minLength": 0 + }, + { + "type": "number" + } + ] + })JSON"); + + CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); +} + +TEST_F(CanonicalizerDraft6Test, incoherent_exclusive_limits_5) { + auto document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-06/schema#", + "type": "number", + "exclusiveMinimum": 5, + "exclusiveMaximum": 2 + })JSON"); + + const auto expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-06/schema#", + "not": true + })JSON"); + + CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); +} + +TEST_F(CanonicalizerDraft6Test, incoherent_exclusive_limits_6) { + auto document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-06/schema#", + "type": "integer", + "exclusiveMinimum": 5, + "exclusiveMaximum": 2 + })JSON"); + + const auto expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-06/schema#", + "not": true + })JSON"); + + CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); +} + +TEST_F(CanonicalizerDraft6Test, incoherent_exclusive_limits_7) { + auto document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-06/schema#", + "type": ["string", "number"], + "exclusiveMinimum": 5, + "exclusiveMaximum": 2 + })JSON"); + + const auto expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-06/schema#", + "type": "string", + "minLength": 0 + })JSON"); + + CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); +} + +TEST_F(CanonicalizerDraft6Test, incoherent_exclusive_limits_8) { + auto document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-06/schema#", + "allOf": [ { "type": "string" } ], + "exclusiveMinimum": 5, + "exclusiveMaximum": 2 + })JSON"); + + const auto expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-06/schema#", + "allOf": [ + { "type": "string", "minLength": 0 }, + { "not": { "type": "number" } } + ] + })JSON"); + + CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); +} + TEST_F(CanonicalizerDraft6Test, number_bare) { auto document = sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-06/schema#", diff --git a/test/alterschema/alterschema_canonicalize_draft7_test.cc b/test/alterschema/alterschema_canonicalize_draft7_test.cc index 9bb3017af..e22511303 100644 --- a/test/alterschema/alterschema_canonicalize_draft7_test.cc +++ b/test/alterschema/alterschema_canonicalize_draft7_test.cc @@ -438,6 +438,198 @@ TEST_F(CanonicalizerDraft7Test, integer_both_exclusive_bounds_fold) { CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); } +TEST_F(CanonicalizerDraft7Test, incoherent_exclusive_limits_1) { + auto document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "exclusiveMinimum": 3, + "exclusiveMaximum": 3 + })JSON"); + + const auto expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "not": { "type": "number" } + })JSON"); + + CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); +} + +TEST_F(CanonicalizerDraft7Test, incoherent_exclusive_limits_2) { + auto document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "exclusiveMinimum": 5, + "exclusiveMaximum": 2 + })JSON"); + + const auto expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "not": { "type": "number" } + })JSON"); + + CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); +} + +TEST_F(CanonicalizerDraft7Test, incoherent_exclusive_limits_3) { + auto document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "exclusiveMinimum": 5, + "exclusiveMaximum": 2, + "not": { "type": "string" } + })JSON"); + + const auto expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "allOf": [ + { + "not": { "type": "string", "minLength": 0 } + }, + { + "not": { "type": "number" } + } + ] + })JSON"); + + CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); +} + +TEST_F(CanonicalizerDraft7Test, incoherent_exclusive_limits_4) { + auto document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "main": { "$ref": "#/definitions/test/additionalProperties" } + }, + "definitions": { + "test": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "exclusiveMaximum": 5, + "exclusiveMinimum": 8 + } + } + })JSON"); + + const auto expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "test": { + "type": "object", + "additionalProperties": { + "type": "string", + "minLength": 0 + }, + "patternProperties": {}, + "propertyNames": true, + "minProperties": 0, + "properties": {} + } + }, + "anyOf": [ + { + "enum": [ null ] + }, + { + "enum": [ false, true ] + }, + { + "type": "object", + "properties": { + "main": { + "$ref": "#/definitions/test/additionalProperties" + } + }, + "patternProperties": {}, + "propertyNames": true, + "minProperties": 0, + "additionalProperties": true + }, + { + "type": "array", + "uniqueItems": false, + "items": true, + "minItems": 0 + }, + { + "type": "string", + "minLength": 0 + }, + { + "type": "number" + } + ] + })JSON"); + + CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); +} + +TEST_F(CanonicalizerDraft7Test, incoherent_exclusive_limits_5) { + auto document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "number", + "exclusiveMinimum": 5, + "exclusiveMaximum": 2 + })JSON"); + + const auto expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "not": true + })JSON"); + + CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); +} + +TEST_F(CanonicalizerDraft7Test, incoherent_exclusive_limits_6) { + auto document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "integer", + "exclusiveMinimum": 5, + "exclusiveMaximum": 2 + })JSON"); + + const auto expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "not": true + })JSON"); + + CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); +} + +TEST_F(CanonicalizerDraft7Test, incoherent_exclusive_limits_7) { + auto document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": ["string", "number"], + "exclusiveMinimum": 5, + "exclusiveMaximum": 2 + })JSON"); + + const auto expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "string", + "minLength": 0 + })JSON"); + + CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); +} + +TEST_F(CanonicalizerDraft7Test, incoherent_exclusive_limits_8) { + auto document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "allOf": [ { "type": "string" } ], + "exclusiveMinimum": 5, + "exclusiveMaximum": 2 + })JSON"); + + const auto expected = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "allOf": [ + { "type": "string", "minLength": 0 }, + { "not": { "type": "number" } } + ] + })JSON"); + + CANONICALIZE_AND_VALIDATE(document, expected, *compiled_meta_); +} + TEST_F(CanonicalizerDraft7Test, number_bare) { auto document = sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/test/alterschema/alterschema_lint_2019_09_test.cc b/test/alterschema/alterschema_lint_2019_09_test.cc index e3232b0f5..8f292224f 100644 --- a/test/alterschema/alterschema_lint_2019_09_test.cc +++ b/test/alterschema/alterschema_lint_2019_09_test.cc @@ -2417,6 +2417,92 @@ TEST(AlterSchema_lint_2019_09, incoherent_min_max_contains_9) { true); } +TEST(AlterSchema_lint_2019_09, incoherent_exclusive_limits_1) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "title": "Test", + "description": "A test schema", + "examples": [ "foo" ], + "exclusiveMinimum": 1, + "exclusiveMaximum": 5 + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 0); +} + +TEST(AlterSchema_lint_2019_09, incoherent_exclusive_limits_2) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "title": "Test", + "description": "A test schema", + "examples": [ "foo" ], + "exclusiveMinimum": 1 + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 0); +} + +TEST(AlterSchema_lint_2019_09, incoherent_exclusive_limits_3) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "title": "Test", + "description": "A test schema", + "examples": [ "foo" ], + "exclusiveMinimum": 3, + "exclusiveMaximum": 3 + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_FALSE(result.first); + EXPECT_EQ(traces.size(), 1); + EXPECT_LINT_TRACE(traces, 0, "", "incoherent_exclusive_limits", + "`exclusiveMinimum` greater than or equal to " + "`exclusiveMaximum` makes the schema unsatisfiable", + true); +} + +TEST(AlterSchema_lint_2019_09, incoherent_exclusive_limits_4) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "title": "Test", + "description": "A test schema", + "examples": [ "foo" ], + "exclusiveMinimum": 5, + "exclusiveMaximum": 2 + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_FALSE(result.first); + EXPECT_EQ(traces.size(), 1); + EXPECT_LINT_TRACE(traces, 0, "", "incoherent_exclusive_limits", + "`exclusiveMinimum` greater than or equal to " + "`exclusiveMaximum` makes the schema unsatisfiable", + true); +} + +TEST(AlterSchema_lint_2019_09, incoherent_exclusive_limits_5) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "title": "Test", + "description": "A test schema", + "examples": [ "foo" ], + "exclusiveMaximum": 5 + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 0); +} + TEST(AlterSchema_lint_2019_09, equal_numeric_bounds_to_const_1) { sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2019-09/schema", diff --git a/test/alterschema/alterschema_lint_2020_12_test.cc b/test/alterschema/alterschema_lint_2020_12_test.cc index d153ad517..cf586ba5f 100644 --- a/test/alterschema/alterschema_lint_2020_12_test.cc +++ b/test/alterschema/alterschema_lint_2020_12_test.cc @@ -2324,6 +2324,92 @@ TEST(AlterSchema_lint_2020_12, incoherent_min_max_contains_9) { true); } +TEST(AlterSchema_lint_2020_12, incoherent_exclusive_limits_1) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Test", + "description": "A test schema", + "examples": [ "foo" ], + "exclusiveMinimum": 1, + "exclusiveMaximum": 5 + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 0); +} + +TEST(AlterSchema_lint_2020_12, incoherent_exclusive_limits_2) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Test", + "description": "A test schema", + "examples": [ "foo" ], + "exclusiveMinimum": 1 + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 0); +} + +TEST(AlterSchema_lint_2020_12, incoherent_exclusive_limits_3) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Test", + "description": "A test schema", + "examples": [ "foo" ], + "exclusiveMinimum": 3, + "exclusiveMaximum": 3 + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_FALSE(result.first); + EXPECT_EQ(traces.size(), 1); + EXPECT_LINT_TRACE(traces, 0, "", "incoherent_exclusive_limits", + "`exclusiveMinimum` greater than or equal to " + "`exclusiveMaximum` makes the schema unsatisfiable", + true); +} + +TEST(AlterSchema_lint_2020_12, incoherent_exclusive_limits_4) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Test", + "description": "A test schema", + "examples": [ "foo" ], + "exclusiveMinimum": 5, + "exclusiveMaximum": 2 + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_FALSE(result.first); + EXPECT_EQ(traces.size(), 1); + EXPECT_LINT_TRACE(traces, 0, "", "incoherent_exclusive_limits", + "`exclusiveMinimum` greater than or equal to " + "`exclusiveMaximum` makes the schema unsatisfiable", + true); +} + +TEST(AlterSchema_lint_2020_12, incoherent_exclusive_limits_5) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Test", + "description": "A test schema", + "examples": [ "foo" ], + "exclusiveMaximum": 5 + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 0); +} + TEST(AlterSchema_lint_2020_12, equal_numeric_bounds_to_const_1) { sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "https://json-schema.org/draft/2020-12/schema", diff --git a/test/alterschema/alterschema_lint_draft6_test.cc b/test/alterschema/alterschema_lint_draft6_test.cc index 4a3ad83ad..95db4f47d 100644 --- a/test/alterschema/alterschema_lint_draft6_test.cc +++ b/test/alterschema/alterschema_lint_draft6_test.cc @@ -1291,6 +1291,92 @@ TEST(AlterSchema_lint_draft6, exclusive_minimum_integer_to_minimum_4) { EXPECT_EQ(document, expected); } +TEST(AlterSchema_lint_draft6, incoherent_exclusive_limits_1) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-06/schema#", + "title": "Test", + "description": "A test schema", + "examples": [ "foo" ], + "exclusiveMinimum": 1, + "exclusiveMaximum": 5 + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 0); +} + +TEST(AlterSchema_lint_draft6, incoherent_exclusive_limits_2) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-06/schema#", + "title": "Test", + "description": "A test schema", + "examples": [ "foo" ], + "exclusiveMinimum": 1 + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 0); +} + +TEST(AlterSchema_lint_draft6, incoherent_exclusive_limits_3) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-06/schema#", + "title": "Test", + "description": "A test schema", + "examples": [ "foo" ], + "exclusiveMinimum": 3, + "exclusiveMaximum": 3 + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_FALSE(result.first); + EXPECT_EQ(traces.size(), 1); + EXPECT_LINT_TRACE(traces, 0, "", "incoherent_exclusive_limits", + "`exclusiveMinimum` greater than or equal to " + "`exclusiveMaximum` makes the schema unsatisfiable", + true); +} + +TEST(AlterSchema_lint_draft6, incoherent_exclusive_limits_4) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-06/schema#", + "title": "Test", + "description": "A test schema", + "examples": [ "foo" ], + "exclusiveMinimum": 5, + "exclusiveMaximum": 2 + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_FALSE(result.first); + EXPECT_EQ(traces.size(), 1); + EXPECT_LINT_TRACE(traces, 0, "", "incoherent_exclusive_limits", + "`exclusiveMinimum` greater than or equal to " + "`exclusiveMaximum` makes the schema unsatisfiable", + true); +} + +TEST(AlterSchema_lint_draft6, incoherent_exclusive_limits_5) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-06/schema#", + "title": "Test", + "description": "A test schema", + "examples": [ "foo" ], + "exclusiveMaximum": 5 + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 0); +} + TEST(AlterSchema_lint_draft6, equal_numeric_bounds_to_const_1) { sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-06/schema#", diff --git a/test/alterschema/alterschema_lint_draft7_test.cc b/test/alterschema/alterschema_lint_draft7_test.cc index be902c1b0..155ef1695 100644 --- a/test/alterschema/alterschema_lint_draft7_test.cc +++ b/test/alterschema/alterschema_lint_draft7_test.cc @@ -3041,6 +3041,92 @@ TEST(AlterSchema_lint_draft7, conflicting_readonly_writeonly_4) { false); } +TEST(AlterSchema_lint_draft7, incoherent_exclusive_limits_1) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Test", + "description": "A test schema", + "examples": [ "foo" ], + "exclusiveMinimum": 1, + "exclusiveMaximum": 5 + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 0); +} + +TEST(AlterSchema_lint_draft7, incoherent_exclusive_limits_2) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Test", + "description": "A test schema", + "examples": [ "foo" ], + "exclusiveMinimum": 1 + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 0); +} + +TEST(AlterSchema_lint_draft7, incoherent_exclusive_limits_3) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Test", + "description": "A test schema", + "examples": [ "foo" ], + "exclusiveMinimum": 3, + "exclusiveMaximum": 3 + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_FALSE(result.first); + EXPECT_EQ(traces.size(), 1); + EXPECT_LINT_TRACE(traces, 0, "", "incoherent_exclusive_limits", + "`exclusiveMinimum` greater than or equal to " + "`exclusiveMaximum` makes the schema unsatisfiable", + true); +} + +TEST(AlterSchema_lint_draft7, incoherent_exclusive_limits_4) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Test", + "description": "A test schema", + "examples": [ "foo" ], + "exclusiveMinimum": 5, + "exclusiveMaximum": 2 + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_FALSE(result.first); + EXPECT_EQ(traces.size(), 1); + EXPECT_LINT_TRACE(traces, 0, "", "incoherent_exclusive_limits", + "`exclusiveMinimum` greater than or equal to " + "`exclusiveMaximum` makes the schema unsatisfiable", + true); +} + +TEST(AlterSchema_lint_draft7, incoherent_exclusive_limits_5) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Test", + "description": "A test schema", + "examples": [ "foo" ], + "exclusiveMaximum": 5 + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 0); +} + TEST(AlterSchema_lint_draft7, duplicate_examples_1) { sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ "$schema": "http://json-schema.org/draft-07/schema#",