From 53e33fde117d0fadc4ca0fafd171585424c804d9 Mon Sep 17 00:00:00 2001 From: Vaibhav mittal Date: Wed, 8 Apr 2026 13:53:54 +0000 Subject: [PATCH 1/2] feat(linter): add format_type_mismatch rule Signed-off-by: Vaibhav mittal --- src/extension/alterschema/CMakeLists.txt | 1 + src/extension/alterschema/alterschema.cc | 2 + .../alterschema/linter/format_type_mismatch.h | 34 +++ .../alterschema_lint_2019_09_test.cc | 196 +++++++++++++++++ .../alterschema_lint_2020_12_test.cc | 196 +++++++++++++++++ .../alterschema_lint_draft3_test.cc | 193 +++++++++++++++++ .../alterschema_lint_draft4_test.cc | 191 +++++++++++++++++ .../alterschema_lint_draft6_test.cc | 200 ++++++++++++++++++ .../alterschema_lint_draft7_test.cc | 200 ++++++++++++++++++ 9 files changed, 1213 insertions(+) create mode 100644 src/extension/alterschema/linter/format_type_mismatch.h diff --git a/src/extension/alterschema/CMakeLists.txt b/src/extension/alterschema/CMakeLists.txt index d17efb350..76ac6ea44 100644 --- a/src/extension/alterschema/CMakeLists.txt +++ b/src/extension/alterschema/CMakeLists.txt @@ -85,6 +85,7 @@ sourcemeta_library(NAMESPACE sourcemeta PROJECT core NAME alterschema linter/enum_to_const.h linter/equal_numeric_bounds_to_const.h linter/forbid_empty_enum.h + linter/format_type_mismatch.h linter/incoherent_min_max_contains.h linter/invalid_external_ref.h linter/items_array_default.h diff --git a/src/extension/alterschema/alterschema.cc b/src/extension/alterschema/alterschema.cc index d20eff011..cd24c259b 100644 --- a/src/extension/alterschema/alterschema.cc +++ b/src/extension/alterschema/alterschema.cc @@ -114,6 +114,7 @@ inline auto APPLIES_TO_POINTERS(std::vector &&keywords) #include "linter/enum_to_const.h" #include "linter/equal_numeric_bounds_to_const.h" #include "linter/forbid_empty_enum.h" +#include "linter/format_type_mismatch.h" #include "linter/incoherent_min_max_contains.h" #include "linter/invalid_external_ref.h" #include "linter/items_array_default.h" @@ -236,6 +237,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/extension/alterschema/linter/format_type_mismatch.h b/src/extension/alterschema/linter/format_type_mismatch.h new file mode 100644 index 000000000..6aa46b15a --- /dev/null +++ b/src/extension/alterschema/linter/format_type_mismatch.h @@ -0,0 +1,34 @@ +class FormatTypeMismatch final : public SchemaTransformRule { +public: + using mutates = std::false_type; + using reframe_after_transform = std::false_type; + FormatTypeMismatch() + : SchemaTransformRule{ + "format_type_mismatch", + "The `format` keyword validates string instances but `type` " + "is not `string`"} {}; + + [[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_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, + Vocabularies::Known::JSON_Schema_Draft_4, + Vocabularies::Known::JSON_Schema_Draft_3}) && + schema.is_object() && schema.defines("type") && + schema.at("type").is_string() && + schema.at("type").to_string() != "string" && + schema.defines("format") && schema.at("format").is_string()); + return APPLIES_TO_KEYWORDS("format", "type"); + } +}; diff --git a/test/alterschema/alterschema_lint_2019_09_test.cc b/test/alterschema/alterschema_lint_2019_09_test.cc index e3ef2ef9c..3fa10d1cb 100644 --- a/test/alterschema/alterschema_lint_2019_09_test.cc +++ b/test/alterschema/alterschema_lint_2019_09_test.cc @@ -5092,3 +5092,199 @@ TEST(AlterSchema_lint_2019_09, EXPECT_EQ(document, expected); } + +TEST(AlterSchema_lint_2019_09, format_type_mismatch_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": [ 1 ], + "type": "integer", + "format": "email" + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_FALSE(result.first); + EXPECT_EQ(traces.size(), 2); + EXPECT_LINT_TRACE(traces, 0, "", "non_applicable_type_specific_keywords", + "Avoid keywords that don't apply to the type or " + "types that the current subschema expects", + true); + EXPECT_LINT_TRACE(traces, 1, "", "format_type_mismatch", + "The `format` keyword validates string instances but " + "`type` is not `string`", + false); +} + +TEST(AlterSchema_lint_2019_09, format_type_mismatch_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@bar.com" ], + "type": "string", + "format": "email" + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 0); +} + +TEST(AlterSchema_lint_2019_09, format_type_mismatch_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@bar.com" ], + "format": "email" + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 0); +} + +TEST(AlterSchema_lint_2019_09, format_type_mismatch_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": [ {} ], + "properties": { + "foo": { + "type": "integer", + "format": "email" + } + } + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_FALSE(result.first); + EXPECT_EQ(traces.size(), 2); + EXPECT_LINT_TRACE(traces, 0, "/properties/foo", + "non_applicable_type_specific_keywords", + "Avoid keywords that don't apply to the type or " + "types that the current subschema expects", + true); + EXPECT_LINT_TRACE(traces, 1, "/properties/foo", "format_type_mismatch", + "The `format` keyword validates string instances but " + "`type` is not `string`", + false); +} + +TEST(AlterSchema_lint_2019_09, format_type_mismatch_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": [ 1 ], + "type": "integer" + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 0); +} + +TEST(AlterSchema_lint_2019_09, format_type_mismatch_6) { + 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": [ [] ], + "type": "array", + "items": { + "type": "number", + "format": "date-time" + } + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_FALSE(result.first); + EXPECT_EQ(traces.size(), 2); + EXPECT_LINT_TRACE(traces, 0, "/items", + "non_applicable_type_specific_keywords", + "Avoid keywords that don't apply to the type or " + "types that the current subschema expects", + true); + EXPECT_LINT_TRACE(traces, 1, "/items", "format_type_mismatch", + "The `format` keyword validates string instances but " + "`type` is not `string`", + false); +} + +TEST(AlterSchema_lint_2019_09, format_type_mismatch_7) { + 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": [ {} ], + "$ref": "#/$defs/foo", + "$defs": { + "foo": { + "type": "integer", + "format": "uri" + } + } + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_FALSE(result.first); + EXPECT_EQ(traces.size(), 1); + EXPECT_LINT_TRACE(traces, 0, "/$defs/foo", "format_type_mismatch", + "The `format` keyword validates string instances but " + "`type` is not `string`", + false); +} + +TEST(AlterSchema_lint_2019_09, format_type_mismatch_8) { + 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": [ {} ], + "$ref": "#/$defs/A/properties/bar", + "$defs": { + "A": { + "type": "integer", + "format": "uri", + "properties": { + "bar": { "type": "string" } + } + } + } + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_FALSE(result.first); + EXPECT_EQ(traces.size(), 1); + EXPECT_LINT_TRACE(traces, 0, "/$defs/A", "format_type_mismatch", + "The `format` keyword validates string instances but " + "`type` is not `string`", + false); +} + +TEST(AlterSchema_lint_2019_09, format_type_mismatch_9) { + 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": [ 1 ], + "type": [ "integer", "string" ], + "format": "email" + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 0); +} diff --git a/test/alterschema/alterschema_lint_2020_12_test.cc b/test/alterschema/alterschema_lint_2020_12_test.cc index b303726c3..539683314 100644 --- a/test/alterschema/alterschema_lint_2020_12_test.cc +++ b/test/alterschema/alterschema_lint_2020_12_test.cc @@ -10654,3 +10654,199 @@ TEST(AlterSchema_lint_2020_12, EXPECT_EQ(document, expected); } + +TEST(AlterSchema_lint_2020_12, format_type_mismatch_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": [ 1 ], + "type": "integer", + "format": "email" + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_FALSE(result.first); + EXPECT_EQ(traces.size(), 2); + EXPECT_LINT_TRACE(traces, 0, "", "non_applicable_type_specific_keywords", + "Avoid keywords that don't apply to the type or " + "types that the current subschema expects", + true); + EXPECT_LINT_TRACE(traces, 1, "", "format_type_mismatch", + "The `format` keyword validates string instances but " + "`type` is not `string`", + false); +} + +TEST(AlterSchema_lint_2020_12, format_type_mismatch_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@bar.com" ], + "type": "string", + "format": "email" + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 0); +} + +TEST(AlterSchema_lint_2020_12, format_type_mismatch_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@bar.com" ], + "format": "email" + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 0); +} + +TEST(AlterSchema_lint_2020_12, format_type_mismatch_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": [ {} ], + "properties": { + "foo": { + "type": "integer", + "format": "email" + } + } + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_FALSE(result.first); + EXPECT_EQ(traces.size(), 2); + EXPECT_LINT_TRACE(traces, 0, "/properties/foo", + "non_applicable_type_specific_keywords", + "Avoid keywords that don't apply to the type or " + "types that the current subschema expects", + true); + EXPECT_LINT_TRACE(traces, 1, "/properties/foo", "format_type_mismatch", + "The `format` keyword validates string instances but " + "`type` is not `string`", + false); +} + +TEST(AlterSchema_lint_2020_12, format_type_mismatch_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": [ 1 ], + "type": "integer" + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 0); +} + +TEST(AlterSchema_lint_2020_12, format_type_mismatch_6) { + 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": [ [] ], + "type": "array", + "items": { + "type": "number", + "format": "date-time" + } + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_FALSE(result.first); + EXPECT_EQ(traces.size(), 2); + EXPECT_LINT_TRACE(traces, 0, "/items", + "non_applicable_type_specific_keywords", + "Avoid keywords that don't apply to the type or " + "types that the current subschema expects", + true); + EXPECT_LINT_TRACE(traces, 1, "/items", "format_type_mismatch", + "The `format` keyword validates string instances but " + "`type` is not `string`", + false); +} + +TEST(AlterSchema_lint_2020_12, format_type_mismatch_7) { + 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": [ {} ], + "$ref": "#/$defs/foo", + "$defs": { + "foo": { + "type": "integer", + "format": "uri" + } + } + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_FALSE(result.first); + EXPECT_EQ(traces.size(), 1); + EXPECT_LINT_TRACE(traces, 0, "/$defs/foo", "format_type_mismatch", + "The `format` keyword validates string instances but " + "`type` is not `string`", + false); +} + +TEST(AlterSchema_lint_2020_12, format_type_mismatch_8) { + 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": [ {} ], + "$ref": "#/$defs/A/properties/bar", + "$defs": { + "A": { + "type": "integer", + "format": "uri", + "properties": { + "bar": { "type": "string" } + } + } + } + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_FALSE(result.first); + EXPECT_EQ(traces.size(), 1); + EXPECT_LINT_TRACE(traces, 0, "/$defs/A", "format_type_mismatch", + "The `format` keyword validates string instances but " + "`type` is not `string`", + false); +} + +TEST(AlterSchema_lint_2020_12, format_type_mismatch_9) { + 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": [ 1 ], + "type": [ "integer", "string" ], + "format": "email" + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 0); +} diff --git a/test/alterschema/alterschema_lint_draft3_test.cc b/test/alterschema/alterschema_lint_draft3_test.cc index 02dd03541..6fd34f2c2 100644 --- a/test/alterschema/alterschema_lint_draft3_test.cc +++ b/test/alterschema/alterschema_lint_draft3_test.cc @@ -1328,3 +1328,196 @@ TEST(AlterSchema_lint_draft3, draft_official_dialect_with_https_1) { EXPECT_EQ(document, expected); } + +TEST(AlterSchema_lint_draft3, format_type_mismatch_1) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-03/schema#", + "title": "Test", + "description": "A test schema", + "type": "integer", + "format": "email" + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_FALSE(result.first); + EXPECT_EQ(traces.size(), 2); + EXPECT_LINT_TRACE(traces, 0, "", "non_applicable_type_specific_keywords", + "Avoid keywords that don't apply to the type or " + "types that the current subschema expects", + true); + EXPECT_LINT_TRACE(traces, 1, "", "format_type_mismatch", + "The `format` keyword validates string instances but " + "`type` is not `string`", + false); +} + +TEST(AlterSchema_lint_draft3, format_type_mismatch_2) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-03/schema#", + "title": "Test", + "description": "A test schema", + "type": "string", + "format": "email" + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 0); +} + +TEST(AlterSchema_lint_draft3, format_type_mismatch_3) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-03/schema#", + "title": "Test", + "description": "A test schema", + "format": "email" + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 0); +} + +TEST(AlterSchema_lint_draft3, format_type_mismatch_4) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-03/schema#", + "title": "Test", + "description": "A test schema", + "properties": { + "foo": { + "type": "integer", + "format": "email" + } + } + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_FALSE(result.first); + EXPECT_EQ(traces.size(), 2); + EXPECT_LINT_TRACE(traces, 0, "/properties/foo", + "non_applicable_type_specific_keywords", + "Avoid keywords that don't apply to the type or " + "types that the current subschema expects", + true); + EXPECT_LINT_TRACE(traces, 1, "/properties/foo", "format_type_mismatch", + "The `format` keyword validates string instances but " + "`type` is not `string`", + false); +} + +TEST(AlterSchema_lint_draft3, format_type_mismatch_5) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-03/schema#", + "title": "Test", + "description": "A test schema", + "type": "integer" + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 0); +} + +TEST(AlterSchema_lint_draft3, format_type_mismatch_6) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-03/schema#", + "title": "Test", + "description": "A test schema", + "type": "array", + "items": { + "type": "number", + "format": "date-time" + } + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_FALSE(result.first); + EXPECT_EQ(traces.size(), 2); + EXPECT_LINT_TRACE(traces, 0, "/items", + "non_applicable_type_specific_keywords", + "Avoid keywords that don't apply to the type or " + "types that the current subschema expects", + true); + EXPECT_LINT_TRACE(traces, 1, "/items", "format_type_mismatch", + "The `format` keyword validates string instances but " + "`type` is not `string`", + false); +} + +TEST(AlterSchema_lint_draft3, format_type_mismatch_7) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-03/schema#", + "title": "Test", + "description": "A test schema", + "additionalProperties": { + "type": "integer", + "format": "uri" + } + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_FALSE(result.first); + EXPECT_EQ(traces.size(), 2); + EXPECT_LINT_TRACE(traces, 0, "/additionalProperties", + "non_applicable_type_specific_keywords", + "Avoid keywords that don't apply to the type or " + "types that the current subschema expects", + true); + EXPECT_LINT_TRACE(traces, 1, "/additionalProperties", + "format_type_mismatch", + "The `format` keyword validates string instances but " + "`type` is not `string`", + false); +} + +TEST(AlterSchema_lint_draft3, format_type_mismatch_8) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-03/schema#", + "title": "Test", + "description": "A test schema", + "additionalProperties": { + "type": "integer", + "format": "uri", + "properties": { + "bar": { "type": "string" } + } + } + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_FALSE(result.first); + EXPECT_EQ(traces.size(), 2); + EXPECT_LINT_TRACE(traces, 0, "/additionalProperties", + "non_applicable_type_specific_keywords", + "Avoid keywords that don't apply to the type or " + "types that the current subschema expects", + true); + EXPECT_LINT_TRACE(traces, 1, "/additionalProperties", + "format_type_mismatch", + "The `format` keyword validates string instances but " + "`type` is not `string`", + false); +} + +TEST(AlterSchema_lint_draft3, format_type_mismatch_9) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-03/schema#", + "title": "Test", + "description": "A test schema", + "type": [ "integer", "string" ], + "format": "email" + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 0); +} diff --git a/test/alterschema/alterschema_lint_draft4_test.cc b/test/alterschema/alterschema_lint_draft4_test.cc index 8c333cb3d..ff13b77ad 100644 --- a/test/alterschema/alterschema_lint_draft4_test.cc +++ b/test/alterschema/alterschema_lint_draft4_test.cc @@ -2788,3 +2788,194 @@ TEST(AlterSchema_lint_draft4, draft_official_dialect_with_https_1) { EXPECT_EQ(document, expected); } + +TEST(AlterSchema_lint_draft4, format_type_mismatch_1) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Test", + "description": "A test schema", + "type": "integer", + "format": "email" + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_FALSE(result.first); + EXPECT_EQ(traces.size(), 2); + EXPECT_LINT_TRACE(traces, 0, "", "non_applicable_type_specific_keywords", + "Avoid keywords that don't apply to the type or " + "types that the current subschema expects", + true); + EXPECT_LINT_TRACE(traces, 1, "", "format_type_mismatch", + "The `format` keyword validates string instances but " + "`type` is not `string`", + false); +} + +TEST(AlterSchema_lint_draft4, format_type_mismatch_2) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Test", + "description": "A test schema", + "type": "string", + "format": "email" + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 0); +} + +TEST(AlterSchema_lint_draft4, format_type_mismatch_3) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Test", + "description": "A test schema", + "format": "email" + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 0); +} + +TEST(AlterSchema_lint_draft4, format_type_mismatch_4) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Test", + "description": "A test schema", + "properties": { + "foo": { + "type": "integer", + "format": "email" + } + } + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_FALSE(result.first); + EXPECT_EQ(traces.size(), 2); + EXPECT_LINT_TRACE(traces, 0, "/properties/foo", + "non_applicable_type_specific_keywords", + "Avoid keywords that don't apply to the type or " + "types that the current subschema expects", + true); + EXPECT_LINT_TRACE(traces, 1, "/properties/foo", "format_type_mismatch", + "The `format` keyword validates string instances but " + "`type` is not `string`", + false); +} + +TEST(AlterSchema_lint_draft4, format_type_mismatch_5) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Test", + "description": "A test schema", + "type": "integer" + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 0); +} + +TEST(AlterSchema_lint_draft4, format_type_mismatch_6) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Test", + "description": "A test schema", + "type": "array", + "items": { + "type": "number", + "format": "date-time" + } + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_FALSE(result.first); + EXPECT_EQ(traces.size(), 2); + EXPECT_LINT_TRACE(traces, 0, "/items", + "non_applicable_type_specific_keywords", + "Avoid keywords that don't apply to the type or " + "types that the current subschema expects", + true); + EXPECT_LINT_TRACE(traces, 1, "/items", "format_type_mismatch", + "The `format` keyword validates string instances but " + "`type` is not `string`", + false); +} + +TEST(AlterSchema_lint_draft4, format_type_mismatch_7) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Test", + "description": "A test schema", + "properties": { + "x": { "$ref": "#/definitions/foo" } + }, + "definitions": { + "foo": { + "type": "integer", + "format": "uri" + } + } + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_FALSE(result.first); + EXPECT_EQ(traces.size(), 1); + EXPECT_LINT_TRACE(traces, 0, "/definitions/foo", "format_type_mismatch", + "The `format` keyword validates string instances but " + "`type` is not `string`", + false); +} + +TEST(AlterSchema_lint_draft4, format_type_mismatch_8) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Test", + "description": "A test schema", + "properties": { + "x": { "$ref": "#/definitions/A/properties/bar" } + }, + "definitions": { + "A": { + "type": "integer", + "format": "uri", + "properties": { + "bar": { "type": "string" } + } + } + } + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_FALSE(result.first); + EXPECT_EQ(traces.size(), 1); + EXPECT_LINT_TRACE(traces, 0, "/definitions/A", "format_type_mismatch", + "The `format` keyword validates string instances but " + "`type` is not `string`", + false); +} + +TEST(AlterSchema_lint_draft4, format_type_mismatch_9) { + const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Test", + "description": "A test schema", + "type": [ "integer", "string" ], + "format": "email" + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 0); +} diff --git a/test/alterschema/alterschema_lint_draft6_test.cc b/test/alterschema/alterschema_lint_draft6_test.cc index 601772ca3..e1ffaf97d 100644 --- a/test/alterschema/alterschema_lint_draft6_test.cc +++ b/test/alterschema/alterschema_lint_draft6_test.cc @@ -3165,3 +3165,203 @@ TEST(AlterSchema_lint_draft6, draft_official_dialect_with_https_1) { EXPECT_EQ(document, expected); } + +TEST(AlterSchema_lint_draft6, format_type_mismatch_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": [ 1 ], + "type": "integer", + "format": "email" + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_FALSE(result.first); + EXPECT_EQ(traces.size(), 2); + EXPECT_LINT_TRACE(traces, 0, "", "non_applicable_type_specific_keywords", + "Avoid keywords that don't apply to the type or " + "types that the current subschema expects", + true); + EXPECT_LINT_TRACE(traces, 1, "", "format_type_mismatch", + "The `format` keyword validates string instances but " + "`type` is not `string`", + false); +} + +TEST(AlterSchema_lint_draft6, format_type_mismatch_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@bar.com" ], + "type": "string", + "format": "email" + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 0); +} + +TEST(AlterSchema_lint_draft6, format_type_mismatch_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@bar.com" ], + "format": "email" + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 0); +} + +TEST(AlterSchema_lint_draft6, format_type_mismatch_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": [ {} ], + "properties": { + "foo": { + "type": "integer", + "format": "email" + } + } + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_FALSE(result.first); + EXPECT_EQ(traces.size(), 2); + EXPECT_LINT_TRACE(traces, 0, "/properties/foo", + "non_applicable_type_specific_keywords", + "Avoid keywords that don't apply to the type or " + "types that the current subschema expects", + true); + EXPECT_LINT_TRACE(traces, 1, "/properties/foo", "format_type_mismatch", + "The `format` keyword validates string instances but " + "`type` is not `string`", + false); +} + +TEST(AlterSchema_lint_draft6, format_type_mismatch_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": [ 1 ], + "type": "integer" + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 0); +} + +TEST(AlterSchema_lint_draft6, format_type_mismatch_6) { + 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": [ [] ], + "type": "array", + "items": { + "type": "number", + "format": "date-time" + } + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_FALSE(result.first); + EXPECT_EQ(traces.size(), 2); + EXPECT_LINT_TRACE(traces, 0, "/items", + "non_applicable_type_specific_keywords", + "Avoid keywords that don't apply to the type or " + "types that the current subschema expects", + true); + EXPECT_LINT_TRACE(traces, 1, "/items", "format_type_mismatch", + "The `format` keyword validates string instances but " + "`type` is not `string`", + false); +} + +TEST(AlterSchema_lint_draft6, format_type_mismatch_7) { + 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": [ {} ], + "properties": { + "x": { "$ref": "#/definitions/foo" } + }, + "definitions": { + "foo": { + "type": "integer", + "format": "uri" + } + } + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_FALSE(result.first); + EXPECT_EQ(traces.size(), 1); + EXPECT_LINT_TRACE(traces, 0, "/definitions/foo", "format_type_mismatch", + "The `format` keyword validates string instances but " + "`type` is not `string`", + false); +} + +TEST(AlterSchema_lint_draft6, format_type_mismatch_8) { + 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": [ {} ], + "properties": { + "x": { "$ref": "#/definitions/A/properties/bar" } + }, + "definitions": { + "A": { + "type": "integer", + "format": "uri", + "properties": { + "bar": { "type": "string" } + } + } + } + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_FALSE(result.first); + EXPECT_EQ(traces.size(), 1); + EXPECT_LINT_TRACE(traces, 0, "/definitions/A", "format_type_mismatch", + "The `format` keyword validates string instances but " + "`type` is not `string`", + false); +} + +TEST(AlterSchema_lint_draft6, format_type_mismatch_9) { + 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": [ 1 ], + "type": [ "integer", "string" ], + "format": "email" + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 0); +} diff --git a/test/alterschema/alterschema_lint_draft7_test.cc b/test/alterschema/alterschema_lint_draft7_test.cc index d16640583..6c9690219 100644 --- a/test/alterschema/alterschema_lint_draft7_test.cc +++ b/test/alterschema/alterschema_lint_draft7_test.cc @@ -3841,3 +3841,203 @@ TEST(AlterSchema_lint_draft7, EXPECT_EQ(document, expected); } + +TEST(AlterSchema_lint_draft7, format_type_mismatch_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": [ 1 ], + "type": "integer", + "format": "email" + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_FALSE(result.first); + EXPECT_EQ(traces.size(), 2); + EXPECT_LINT_TRACE(traces, 0, "", "non_applicable_type_specific_keywords", + "Avoid keywords that don't apply to the type or " + "types that the current subschema expects", + true); + EXPECT_LINT_TRACE(traces, 1, "", "format_type_mismatch", + "The `format` keyword validates string instances but " + "`type` is not `string`", + false); +} + +TEST(AlterSchema_lint_draft7, format_type_mismatch_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@bar.com" ], + "type": "string", + "format": "email" + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 0); +} + +TEST(AlterSchema_lint_draft7, format_type_mismatch_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@bar.com" ], + "format": "email" + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 0); +} + +TEST(AlterSchema_lint_draft7, format_type_mismatch_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": [ {} ], + "properties": { + "foo": { + "type": "integer", + "format": "email" + } + } + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_FALSE(result.first); + EXPECT_EQ(traces.size(), 2); + EXPECT_LINT_TRACE(traces, 0, "/properties/foo", + "non_applicable_type_specific_keywords", + "Avoid keywords that don't apply to the type or " + "types that the current subschema expects", + true); + EXPECT_LINT_TRACE(traces, 1, "/properties/foo", "format_type_mismatch", + "The `format` keyword validates string instances but " + "`type` is not `string`", + false); +} + +TEST(AlterSchema_lint_draft7, format_type_mismatch_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": [ 1 ], + "type": "integer" + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 0); +} + +TEST(AlterSchema_lint_draft7, format_type_mismatch_6) { + 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": [ [] ], + "type": "array", + "items": { + "type": "number", + "format": "date-time" + } + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_FALSE(result.first); + EXPECT_EQ(traces.size(), 2); + EXPECT_LINT_TRACE(traces, 0, "/items", + "non_applicable_type_specific_keywords", + "Avoid keywords that don't apply to the type or " + "types that the current subschema expects", + true); + EXPECT_LINT_TRACE(traces, 1, "/items", "format_type_mismatch", + "The `format` keyword validates string instances but " + "`type` is not `string`", + false); +} + +TEST(AlterSchema_lint_draft7, format_type_mismatch_7) { + 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": [ {} ], + "properties": { + "x": { "$ref": "#/definitions/foo" } + }, + "definitions": { + "foo": { + "type": "integer", + "format": "uri" + } + } + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_FALSE(result.first); + EXPECT_EQ(traces.size(), 1); + EXPECT_LINT_TRACE(traces, 0, "/definitions/foo", "format_type_mismatch", + "The `format` keyword validates string instances but " + "`type` is not `string`", + false); +} + +TEST(AlterSchema_lint_draft7, format_type_mismatch_8) { + 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": [ {} ], + "properties": { + "x": { "$ref": "#/definitions/A/properties/bar" } + }, + "definitions": { + "A": { + "type": "integer", + "format": "uri", + "properties": { + "bar": { "type": "string" } + } + } + } + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_FALSE(result.first); + EXPECT_EQ(traces.size(), 1); + EXPECT_LINT_TRACE(traces, 0, "/definitions/A", "format_type_mismatch", + "The `format` keyword validates string instances but " + "`type` is not `string`", + false); +} + +TEST(AlterSchema_lint_draft7, format_type_mismatch_9) { + 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": [ 1 ], + "type": [ "integer", "string" ], + "format": "email" + })JSON"); + + LINT_WITHOUT_FIX(document, result, traces); + + EXPECT_TRUE(result.first); + EXPECT_EQ(traces.size(), 0); +} From 41ca4c4a05be5a17e79015d56641d4391ad3b4ed Mon Sep 17 00:00:00 2001 From: Vaibhav mittal Date: Wed, 8 Apr 2026 14:24:51 +0000 Subject: [PATCH 2/2] fix(linter): resolve CI failures for format_type_mismatch - apply clang-format to all modified files - fix test expectations to align with existing rule interactions - ensure full CI pipeline passes locally Signed-off-by: Vaibhav mittal --- .../alterschema/linter/format_type_mismatch.h | 24 +++++++++---------- .../alterschema_lint_draft3_test.cc | 6 ++--- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/extension/alterschema/linter/format_type_mismatch.h b/src/extension/alterschema/linter/format_type_mismatch.h index 6aa46b15a..b6ec7f3bd 100644 --- a/src/extension/alterschema/linter/format_type_mismatch.h +++ b/src/extension/alterschema/linter/format_type_mismatch.h @@ -17,18 +17,18 @@ class FormatTypeMismatch final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result override { - 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, - Vocabularies::Known::JSON_Schema_Draft_4, - Vocabularies::Known::JSON_Schema_Draft_3}) && - schema.is_object() && schema.defines("type") && - schema.at("type").is_string() && - schema.at("type").to_string() != "string" && - schema.defines("format") && schema.at("format").is_string()); + 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, + Vocabularies::Known::JSON_Schema_Draft_4, + Vocabularies::Known::JSON_Schema_Draft_3}) && + schema.is_object() && schema.defines("type") && + schema.at("type").is_string() && + schema.at("type").to_string() != "string" && + schema.defines("format") && + schema.at("format").is_string()); return APPLIES_TO_KEYWORDS("format", "type"); } }; diff --git a/test/alterschema/alterschema_lint_draft3_test.cc b/test/alterschema/alterschema_lint_draft3_test.cc index 6fd34f2c2..94ebb2454 100644 --- a/test/alterschema/alterschema_lint_draft3_test.cc +++ b/test/alterschema/alterschema_lint_draft3_test.cc @@ -1470,8 +1470,7 @@ TEST(AlterSchema_lint_draft3, format_type_mismatch_7) { "Avoid keywords that don't apply to the type or " "types that the current subschema expects", true); - EXPECT_LINT_TRACE(traces, 1, "/additionalProperties", - "format_type_mismatch", + EXPECT_LINT_TRACE(traces, 1, "/additionalProperties", "format_type_mismatch", "The `format` keyword validates string instances but " "`type` is not `string`", false); @@ -1500,8 +1499,7 @@ TEST(AlterSchema_lint_draft3, format_type_mismatch_8) { "Avoid keywords that don't apply to the type or " "types that the current subschema expects", true); - EXPECT_LINT_TRACE(traces, 1, "/additionalProperties", - "format_type_mismatch", + EXPECT_LINT_TRACE(traces, 1, "/additionalProperties", "format_type_mismatch", "The `format` keyword validates string instances but " "`type` is not `string`", false);