diff --git a/src/linter/include/sourcemeta/blaze/linter.h b/src/linter/include/sourcemeta/blaze/linter.h index c8d4a3b6..88543038 100644 --- a/src/linter/include/sourcemeta/blaze/linter.h +++ b/src/linter/include/sourcemeta/blaze/linter.h @@ -8,6 +8,9 @@ #include #include +#include // std::size_t +#include // std::vector + /// @defgroup linter Linter /// @brief A set of JSON Schema linter extensions powered by Blaze /// @@ -47,6 +50,7 @@ class SOURCEMETA_BLAZE_LINTER_EXPORT ValidExamples final #pragma warning(disable : 4251) #endif const Compiler compiler_; + mutable std::vector invalid_indices_; #if defined(_MSC_VER) #pragma warning(default : 4251) #endif diff --git a/src/linter/valid_examples.cc b/src/linter/valid_examples.cc index 68b0bc65..ed2d4053 100644 --- a/src/linter/valid_examples.cc +++ b/src/linter/valid_examples.cc @@ -5,10 +5,13 @@ #include +#include // std::find #include // std::size_t #include // std::ref, std::cref +#include // std::ranges::reverse_view #include // std::ostringstream #include // std::move +#include // std::vector namespace sourcemeta::blaze { @@ -71,28 +74,46 @@ auto ValidExamples::condition( Evaluator evaluator; std::size_t cursor{0}; + this->invalid_indices_.clear(); + std::ostringstream collected_messages; + for (const auto &example : schema.at("examples").as_array()) { const std::string ref{"$ref"}; SimpleOutput output{example, {std::cref(ref)}}; const auto result{ evaluator.validate(schema_template, example, std::ref(output))}; if (!result) { - std::ostringstream message; - message << "Invalid example instance at index " << cursor << "\n"; - output.stacktrace(message, " "); - return {{{"examples", cursor}}, std::move(message).str()}; + this->invalid_indices_.push_back(cursor); + collected_messages << "Invalid example instance at index " << cursor + << "\n"; + output.stacktrace(collected_messages, " "); } cursor += 1; } + if (!this->invalid_indices_.empty()) { + std::size_t first_invalid = this->invalid_indices_.front(); + return {{{"examples", first_invalid}}, collected_messages.str()}; + } + return false; } auto ValidExamples::transform( sourcemeta::core::JSON &schema, const sourcemeta::core::SchemaTransformRule::Result &) const -> void { - schema.erase("examples"); -} + auto &examples = schema.at("examples"); + for (const auto index : std::ranges::reverse_view(this->invalid_indices_)) { + if (index < examples.size()) { + examples.erase(examples.as_array().cbegin() + + static_cast(index)); + } + } + + if (examples.size() == 0) { + schema.erase("examples"); + } +} } // namespace sourcemeta::blaze diff --git a/test/linter/linter_valid_examples_test.cc b/test/linter/linter_valid_examples_test.cc index d1ba60a9..2a740b7a 100644 --- a/test/linter/linter_valid_examples_test.cc +++ b/test/linter/linter_valid_examples_test.cc @@ -286,7 +286,8 @@ TEST(Linter, valid_examples_3) { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "foo": { - "type": "string" + "type": "string", + "examples": [ "foo", "baz" ] } } })JSON")}; @@ -320,7 +321,8 @@ TEST(Linter, valid_examples_4) { "$schema": "https://json-schema.org/draft/2019-09/schema", "properties": { "foo": { - "type": "string" + "type": "string", + "examples": [ "foo", "baz" ] } } })JSON")}; @@ -354,7 +356,8 @@ TEST(Linter, valid_examples_5) { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { "foo": { - "type": "string" + "type": "string", + "examples": [ "foo", "baz" ] } } })JSON")}; @@ -388,7 +391,8 @@ TEST(Linter, valid_examples_6) { "$schema": "http://json-schema.org/draft-06/schema#", "properties": { "foo": { - "type": "string" + "type": "string", + "examples": [ "foo", "baz" ] } } })JSON")}; @@ -707,3 +711,54 @@ TEST(Linter, valid_examples_15) { EXPECT_EQ(schema, expected); } + +TEST(Linter, valid_examples_mixed_valid_invalid) { + sourcemeta::core::SchemaTransformer bundle; + bundle.add( + sourcemeta::blaze::default_schema_compiler); + + auto schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "string", + "examples": [ "valid1", 123, "valid2", true, "valid3" ] + })JSON")}; + + const auto result = bundle.apply( + schema, sourcemeta::core::schema_official_walker, + sourcemeta::core::schema_official_resolver, transformer_callback_error); + + EXPECT_TRUE(result); + + const auto expected{sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "string", + "examples": [ "valid1", "valid2", "valid3" ] + })JSON")}; + + EXPECT_EQ(schema, expected); +} + +TEST(Linter, valid_examples_all_invalid_removes_property) { + sourcemeta::core::SchemaTransformer bundle; + bundle.add( + sourcemeta::blaze::default_schema_compiler); + + auto schema{sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "string", + "examples": [ 123, true, 456 ] + })JSON")}; + + const auto result = bundle.apply( + schema, sourcemeta::core::schema_official_walker, + sourcemeta::core::schema_official_resolver, transformer_callback_error); + + EXPECT_TRUE(result); + + const auto expected{sourcemeta::core::parse_json(R"JSON({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "string" + })JSON")}; + + EXPECT_EQ(schema, expected); +}