diff --git a/DEPENDENCIES b/DEPENDENCIES index 845896293..d1cb9b983 100644 --- a/DEPENDENCIES +++ b/DEPENDENCIES @@ -1,3 +1,3 @@ vendorpull https://github.com/sourcemeta/vendorpull dea311b5bfb53b6926a4140267959ae334d3ecf4 -core https://github.com/sourcemeta/core 1e83e267f00bd5686d9546fb20877bf6e4b20dc5 +core https://github.com/sourcemeta/core 1f23fc7df56f41ceb140719bbcf94ee6f4d6f066 jsonschema-test-suite https://github.com/json-schema-org/JSON-Schema-Test-Suite 15e4505bf689de5d30c29d50782bb48fa465c93f diff --git a/src/linter/include/sourcemeta/blaze/linter.h b/src/linter/include/sourcemeta/blaze/linter.h index 3f4e1b110..c8d4a3b68 100644 --- a/src/linter/include/sourcemeta/blaze/linter.h +++ b/src/linter/include/sourcemeta/blaze/linter.h @@ -35,7 +35,9 @@ class SOURCEMETA_BLAZE_LINTER_EXPORT ValidExamples final const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result override; - auto transform(sourcemeta::core::JSON &) const -> void override; + auto transform(sourcemeta::core::JSON &, + const sourcemeta::core::SchemaTransformRule::Result &) const + -> void override; private: // Exporting symbols that depends on the standard C++ library is considered @@ -66,7 +68,9 @@ class SOURCEMETA_BLAZE_LINTER_EXPORT ValidDefault final const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result override; - auto transform(sourcemeta::core::JSON &) const -> void override; + auto transform(sourcemeta::core::JSON &, + const sourcemeta::core::SchemaTransformRule::Result &) const + -> void override; private: // Exporting symbols that depends on the standard C++ library is considered diff --git a/src/linter/valid_default.cc b/src/linter/valid_default.cc index 02709b5f9..9fdc2fac9 100644 --- a/src/linter/valid_default.cc +++ b/src/linter/valid_default.cc @@ -75,10 +75,12 @@ auto ValidDefault::condition( std::ostringstream message; output.stacktrace(message); - return message.str(); + return {{{"default"}}, std::move(message).str()}; } -auto ValidDefault::transform(sourcemeta::core::JSON &schema) const -> void { +auto ValidDefault::transform( + sourcemeta::core::JSON &schema, + const sourcemeta::core::SchemaTransformRule::Result &) const -> void { schema.erase("default"); } diff --git a/src/linter/valid_examples.cc b/src/linter/valid_examples.cc index c85bf6756..f8af456bd 100644 --- a/src/linter/valid_examples.cc +++ b/src/linter/valid_examples.cc @@ -82,7 +82,7 @@ auto ValidExamples::condition( std::ostringstream message; message << "Invalid example instance at index " << cursor << "\n"; output.stacktrace(message, " "); - return message.str(); + return {{{"examples", cursor}}, std::move(message).str()}; } cursor += 1; @@ -91,7 +91,9 @@ auto ValidExamples::condition( return false; } -auto ValidExamples::transform(sourcemeta::core::JSON &schema) const -> void { +auto ValidExamples::transform( + sourcemeta::core::JSON &schema, + const sourcemeta::core::SchemaTransformRule::Result &) const -> void { schema.erase("examples"); } diff --git a/test/linter/linter_valid_default_test.cc b/test/linter/linter_valid_default_test.cc index 53e6e1b01..045a4609e 100644 --- a/test/linter/linter_valid_default_test.cc +++ b/test/linter/linter_valid_default_test.cc @@ -5,10 +5,10 @@ #include #include -static auto transformer_callback_error(const sourcemeta::core::Pointer &, - const std::string_view, - const std::string_view, - const std::string_view) -> void { +static auto transformer_callback_error( + const sourcemeta::core::Pointer &, const std::string_view, + const std::string_view, + const sourcemeta::core::SchemaTransformRule::Result &) -> void { throw std::runtime_error("The transform callback must not be called"); } @@ -28,14 +28,14 @@ TEST(Linter, valid_default_error_message_without_id_nested) { })JSON")}; std::vector> + sourcemeta::core::SchemaTransformRule::Result>> entries; const auto result = bundle.check(schema, sourcemeta::core::schema_official_walker, sourcemeta::core::schema_official_resolver, [&entries](const auto &pointer, const auto &name, - const auto &message, const auto &description) { - entries.emplace_back(pointer, name, message, description); + const auto &message, const auto &outcome) { + entries.emplace_back(pointer, name, message, outcome); }); EXPECT_FALSE(result.first); @@ -46,12 +46,18 @@ TEST(Linter, valid_default_error_message_without_id_nested) { EXPECT_EQ(std::get<1>(entries.at(0)), "blaze/valid_default"); EXPECT_EQ(std::get<2>(entries.at(0)), "Only set a `default` value that validates against the schema"); + EXPECT_TRUE(std::get<3>(entries.at(0)).description.has_value()); EXPECT_EQ( - std::get<3>(entries.at(0)), + std::get<3>(entries.at(0)).description.value(), R"TXT(The value was expected to be of type string but it was of type integer at instance location "" at evaluate path "/type" )TXT"); + + EXPECT_EQ(std::get<3>(entries.at(0)).locations.size(), 1); + EXPECT_EQ( + sourcemeta::core::to_string(std::get<3>(entries.at(0)).locations.at(0)), + "/default"); } TEST(Linter, valid_default_error_message_without_id_flat) { @@ -66,14 +72,14 @@ TEST(Linter, valid_default_error_message_without_id_flat) { })JSON")}; std::vector> + sourcemeta::core::SchemaTransformRule::Result>> entries; const auto result = bundle.check(schema, sourcemeta::core::schema_official_walker, sourcemeta::core::schema_official_resolver, [&entries](const auto &pointer, const auto &name, - const auto &message, const auto &description) { - entries.emplace_back(pointer, name, message, description); + const auto &message, const auto &outcome) { + entries.emplace_back(pointer, name, message, outcome); }); EXPECT_FALSE(result.first); @@ -83,12 +89,18 @@ TEST(Linter, valid_default_error_message_without_id_flat) { EXPECT_EQ(std::get<1>(entries.at(0)), "blaze/valid_default"); EXPECT_EQ(std::get<2>(entries.at(0)), "Only set a `default` value that validates against the schema"); + EXPECT_TRUE(std::get<3>(entries.at(0)).description.has_value()); EXPECT_EQ( - std::get<3>(entries.at(0)), + std::get<3>(entries.at(0)).description.value(), R"TXT(The value was expected to be of type string but it was of type integer at instance location "" at evaluate path "/type" )TXT"); + + EXPECT_EQ(std::get<3>(entries.at(0)).locations.size(), 1); + EXPECT_EQ( + sourcemeta::core::to_string(std::get<3>(entries.at(0)).locations.at(0)), + "/default"); } TEST(Linter, valid_default_error_message_with_id_nested) { @@ -108,14 +120,14 @@ TEST(Linter, valid_default_error_message_with_id_nested) { })JSON")}; std::vector> + sourcemeta::core::SchemaTransformRule::Result>> entries; const auto result = bundle.check(schema, sourcemeta::core::schema_official_walker, sourcemeta::core::schema_official_resolver, [&entries](const auto &pointer, const auto &name, - const auto &message, const auto &description) { - entries.emplace_back(pointer, name, message, description); + const auto &message, const auto &outcome) { + entries.emplace_back(pointer, name, message, outcome); }); EXPECT_FALSE(result.first); @@ -126,12 +138,18 @@ TEST(Linter, valid_default_error_message_with_id_nested) { EXPECT_EQ(std::get<1>(entries.at(0)), "blaze/valid_default"); EXPECT_EQ(std::get<2>(entries.at(0)), "Only set a `default` value that validates against the schema"); + EXPECT_TRUE(std::get<3>(entries.at(0)).description.has_value()); EXPECT_EQ( - std::get<3>(entries.at(0)), + std::get<3>(entries.at(0)).description.value(), R"TXT(The value was expected to be of type string but it was of type integer at instance location "" at evaluate path "/type" )TXT"); + + EXPECT_EQ(std::get<3>(entries.at(0)).locations.size(), 1); + EXPECT_EQ( + sourcemeta::core::to_string(std::get<3>(entries.at(0)).locations.at(0)), + "/default"); } TEST(Linter, valid_default_error_message_with_id_flat) { @@ -147,14 +165,14 @@ TEST(Linter, valid_default_error_message_with_id_flat) { })JSON")}; std::vector> + sourcemeta::core::SchemaTransformRule::Result>> entries; const auto result = bundle.check(schema, sourcemeta::core::schema_official_walker, sourcemeta::core::schema_official_resolver, [&entries](const auto &pointer, const auto &name, - const auto &message, const auto &description) { - entries.emplace_back(pointer, name, message, description); + const auto &message, const auto &outcome) { + entries.emplace_back(pointer, name, message, outcome); }); EXPECT_FALSE(result.first); @@ -164,12 +182,18 @@ TEST(Linter, valid_default_error_message_with_id_flat) { EXPECT_EQ(std::get<1>(entries.at(0)), "blaze/valid_default"); EXPECT_EQ(std::get<2>(entries.at(0)), "Only set a `default` value that validates against the schema"); + EXPECT_TRUE(std::get<3>(entries.at(0)).description.has_value()); EXPECT_EQ( - std::get<3>(entries.at(0)), + std::get<3>(entries.at(0)).description.value(), R"TXT(The value was expected to be of type string but it was of type integer at instance location "" at evaluate path "/type" )TXT"); + + EXPECT_EQ(std::get<3>(entries.at(0)).locations.size(), 1); + EXPECT_EQ( + sourcemeta::core::to_string(std::get<3>(entries.at(0)).locations.at(0)), + "/default"); } TEST(Linter, valid_default_1) { diff --git a/test/linter/linter_valid_examples_test.cc b/test/linter/linter_valid_examples_test.cc index c318dfcf5..d1ba60a97 100644 --- a/test/linter/linter_valid_examples_test.cc +++ b/test/linter/linter_valid_examples_test.cc @@ -5,10 +5,10 @@ #include #include -static auto transformer_callback_error(const sourcemeta::core::Pointer &, - const std::string_view, - const std::string_view, - const std::string_view) -> void { +static auto transformer_callback_error( + const sourcemeta::core::Pointer &, const std::string_view, + const std::string_view, + const sourcemeta::core::SchemaTransformRule::Result &) -> void { throw std::runtime_error("The transform callback must not be called"); } @@ -28,14 +28,14 @@ TEST(Linter, valid_examples_error_message_without_id_nested) { })JSON")}; std::vector> + sourcemeta::core::SchemaTransformRule::Result>> entries; const auto result = bundle.check(schema, sourcemeta::core::schema_official_walker, sourcemeta::core::schema_official_resolver, [&entries](const auto &pointer, const auto &name, - const auto &message, const auto &description) { - entries.emplace_back(pointer, name, message, description); + const auto &message, const auto &outcome) { + entries.emplace_back(pointer, name, message, outcome); }); EXPECT_FALSE(result.first); @@ -47,12 +47,18 @@ TEST(Linter, valid_examples_error_message_without_id_nested) { EXPECT_EQ(std::get<2>(entries.at(0)), "Only include instances in the `examples` array that validate " "against the schema"); - EXPECT_EQ(std::get<3>(entries.at(0)), + EXPECT_TRUE(std::get<3>(entries.at(0)).description.has_value()); + EXPECT_EQ(std::get<3>(entries.at(0)).description.value(), R"TXT(Invalid example instance at index 0 The value was expected to be of type string but it was of type integer at instance location "" at evaluate path "/type" )TXT"); + + EXPECT_EQ(std::get<3>(entries.at(0)).locations.size(), 1); + EXPECT_EQ( + sourcemeta::core::to_string(std::get<3>(entries.at(0)).locations.at(0)), + "/examples/0"); } TEST(Linter, valid_examples_error_message_without_id_flat) { @@ -67,14 +73,14 @@ TEST(Linter, valid_examples_error_message_without_id_flat) { })JSON")}; std::vector> + sourcemeta::core::SchemaTransformRule::Result>> entries; const auto result = bundle.check(schema, sourcemeta::core::schema_official_walker, sourcemeta::core::schema_official_resolver, [&entries](const auto &pointer, const auto &name, - const auto &message, const auto &description) { - entries.emplace_back(pointer, name, message, description); + const auto &message, const auto &outcome) { + entries.emplace_back(pointer, name, message, outcome); }); EXPECT_FALSE(result.first); @@ -85,12 +91,18 @@ TEST(Linter, valid_examples_error_message_without_id_flat) { EXPECT_EQ(std::get<2>(entries.at(0)), "Only include instances in the `examples` array that validate " "against the schema"); - EXPECT_EQ(std::get<3>(entries.at(0)), + EXPECT_TRUE(std::get<3>(entries.at(0)).description.has_value()); + EXPECT_EQ(std::get<3>(entries.at(0)).description.value(), R"TXT(Invalid example instance at index 0 The value was expected to be of type string but it was of type integer at instance location "" at evaluate path "/type" )TXT"); + + EXPECT_EQ(std::get<3>(entries.at(0)).locations.size(), 1); + EXPECT_EQ( + sourcemeta::core::to_string(std::get<3>(entries.at(0)).locations.at(0)), + "/examples/0"); } TEST(Linter, valid_examples_error_message_with_id_nested) { @@ -110,14 +122,14 @@ TEST(Linter, valid_examples_error_message_with_id_nested) { })JSON")}; std::vector> + sourcemeta::core::SchemaTransformRule::Result>> entries; const auto result = bundle.check(schema, sourcemeta::core::schema_official_walker, sourcemeta::core::schema_official_resolver, [&entries](const auto &pointer, const auto &name, - const auto &message, const auto &description) { - entries.emplace_back(pointer, name, message, description); + const auto &message, const auto &outcome) { + entries.emplace_back(pointer, name, message, outcome); }); EXPECT_FALSE(result.first); @@ -129,12 +141,18 @@ TEST(Linter, valid_examples_error_message_with_id_nested) { EXPECT_EQ(std::get<2>(entries.at(0)), "Only include instances in the `examples` array that validate " "against the schema"); - EXPECT_EQ(std::get<3>(entries.at(0)), + EXPECT_TRUE(std::get<3>(entries.at(0)).description.has_value()); + EXPECT_EQ(std::get<3>(entries.at(0)).description.value(), R"TXT(Invalid example instance at index 0 The value was expected to be of type string but it was of type integer at instance location "" at evaluate path "/type" )TXT"); + + EXPECT_EQ(std::get<3>(entries.at(0)).locations.size(), 1); + EXPECT_EQ( + sourcemeta::core::to_string(std::get<3>(entries.at(0)).locations.at(0)), + "/examples/0"); } TEST(Linter, valid_examples_error_message_with_id_flat) { @@ -150,14 +168,14 @@ TEST(Linter, valid_examples_error_message_with_id_flat) { })JSON")}; std::vector> + sourcemeta::core::SchemaTransformRule::Result>> entries; const auto result = bundle.check(schema, sourcemeta::core::schema_official_walker, sourcemeta::core::schema_official_resolver, [&entries](const auto &pointer, const auto &name, - const auto &message, const auto &description) { - entries.emplace_back(pointer, name, message, description); + const auto &message, const auto &outcome) { + entries.emplace_back(pointer, name, message, outcome); }); EXPECT_FALSE(result.first); @@ -168,12 +186,18 @@ TEST(Linter, valid_examples_error_message_with_id_flat) { EXPECT_EQ(std::get<2>(entries.at(0)), "Only include instances in the `examples` array that validate " "against the schema"); - EXPECT_EQ(std::get<3>(entries.at(0)), + EXPECT_TRUE(std::get<3>(entries.at(0)).description.has_value()); + EXPECT_EQ(std::get<3>(entries.at(0)).description.value(), R"TXT(Invalid example instance at index 0 The value was expected to be of type string but it was of type integer at instance location "" at evaluate path "/type" )TXT"); + + EXPECT_EQ(std::get<3>(entries.at(0)).locations.size(), 1); + EXPECT_EQ( + sourcemeta::core::to_string(std::get<3>(entries.at(0)).locations.at(0)), + "/examples/0"); } TEST(Linter, valid_examples_1) { diff --git a/vendor/core/CMakeLists.txt b/vendor/core/CMakeLists.txt index cbfc1a33b..c95fb387e 100644 --- a/vendor/core/CMakeLists.txt +++ b/vendor/core/CMakeLists.txt @@ -4,6 +4,7 @@ list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") # Options option(SOURCEMETA_CORE_LANG_IO "Build the Sourcemeta Core language I/O library" ON) +option(SOURCEMETA_CORE_LANG_PARALLEL "Build the Sourcemeta Core language parallel library" ON) option(SOURCEMETA_CORE_TIME "Build the Sourcemeta Core time library" ON) option(SOURCEMETA_CORE_UUID "Build the Sourcemeta Core UUID library" ON) option(SOURCEMETA_CORE_GZIP "Build the Sourcemeta Core GZIP library" ON) @@ -57,6 +58,11 @@ if(SOURCEMETA_CORE_LANG_IO) add_subdirectory(src/lang/io) endif() +if(SOURCEMETA_CORE_LANG_PARALLEL) + find_package(Threads REQUIRED) + add_subdirectory(src/lang/parallel) +endif() + if(SOURCEMETA_CORE_TIME) add_subdirectory(src/core/time) endif() @@ -155,6 +161,10 @@ if(SOURCEMETA_CORE_TESTS) add_subdirectory(test/io) endif() + if(SOURCEMETA_CORE_LANG_PARALLEL) + add_subdirectory(test/parallel) + endif() + if(SOURCEMETA_CORE_TIME) add_subdirectory(test/time) endif() diff --git a/vendor/core/config.cmake.in b/vendor/core/config.cmake.in index 6ae951da5..b84d621a2 100644 --- a/vendor/core/config.cmake.in +++ b/vendor/core/config.cmake.in @@ -5,6 +5,7 @@ list(APPEND SOURCEMETA_CORE_COMPONENTS ${Core_FIND_COMPONENTS}) list(APPEND SOURCEMETA_CORE_COMPONENTS ${core_FIND_COMPONENTS}) if(NOT SOURCEMETA_CORE_COMPONENTS) list(APPEND SOURCEMETA_CORE_COMPONENTS io) + list(APPEND SOURCEMETA_CORE_COMPONENTS parallel) list(APPEND SOURCEMETA_CORE_COMPONENTS time) list(APPEND SOURCEMETA_CORE_COMPONENTS uuid) list(APPEND SOURCEMETA_CORE_COMPONENTS gzip) @@ -26,6 +27,9 @@ include(CMakeFindDependencyMacro) foreach(component ${SOURCEMETA_CORE_COMPONENTS}) if(component STREQUAL "io") include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_io.cmake") + elseif(component STREQUAL "parallel") + find_dependency(Threads) + include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_parallel.cmake") elseif(component STREQUAL "time") include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_time.cmake") elseif(component STREQUAL "uuid") diff --git a/vendor/core/src/core/jsonschema/frame.cc b/vendor/core/src/core/jsonschema/frame.cc index e7d16c444..4d5d4d294 100644 --- a/vendor/core/src/core/jsonschema/frame.cc +++ b/vendor/core/src/core/jsonschema/frame.cc @@ -1065,6 +1065,19 @@ auto SchemaFrame::traverse(const JSON::String &uri) const return std::nullopt; } +auto SchemaFrame::uri(const Pointer &pointer) const + -> std::optional> { + // TODO: This is potentially very slow. Traversing by pointer shouldn't + // require an O(N) operation + for (const auto &entry : this->locations_) { + if (entry.second.pointer == pointer) { + return entry.first.second; + } + } + + return std::nullopt; +} + auto SchemaFrame::dereference(const Location &location, const Pointer &relative_schema_location) const -> std::pair std::optional>; + /// Turn an absolute pointer into a location URI + [[nodiscard]] auto uri(const Pointer &pointer) const + -> std::optional>; + /// Try to dereference a reference location into its destination location [[nodiscard]] auto dereference(const Location &location, diff --git a/vendor/core/src/core/jsonschema/include/sourcemeta/core/jsonschema_transform.h b/vendor/core/src/core/jsonschema/include/sourcemeta/core/jsonschema_transform.h index dac576acb..8d9da8ab9 100644 --- a/vendor/core/src/core/jsonschema/include/sourcemeta/core/jsonschema_transform.h +++ b/vendor/core/src/core/jsonschema/include/sourcemeta/core/jsonschema_transform.h @@ -11,8 +11,9 @@ #include #include // assert -#include // std::derived_from +#include // std::derived_from, std::same_as #include // std::function +#include // std::make_move_iterator, std::begin, std::end #include // std::map #include // std::make_unique, std::unique_ptr #include // std::optional, std::nullopt @@ -20,7 +21,6 @@ #include // std::string #include // std::string_view #include // std::move, std::forward, std::pair -#include // std::variant #include // std::vector namespace sourcemeta::core { @@ -83,7 +83,32 @@ class SOURCEMETA_CORE_JSONSCHEMA_EXPORT SchemaTransformRule { [[nodiscard]] auto message() const -> const std::string &; /// The result of evaluating a rule - using Result = std::variant; + struct Result { + Result(const bool applies_) : applies{applies_} {} + Result(Pointer pointer) : applies{true}, locations{std::move(pointer)} { + assert(this->locations.size() == 1); + } + + template + requires std::same_as + Result(T &&container) : applies{true} { + auto &&input = std::forward(container); + if constexpr (requires { input.size(); }) { + locations.reserve(input.size()); + } + + locations.assign(std::make_move_iterator(std::begin(input)), + std::make_move_iterator(std::end(input))); + } + + Result(std::vector &&locations_, JSON::String &&description_) + : applies{true}, locations{std::move(locations_)}, + description{std::move(description_)} {} + + bool applies; + std::vector locations; + std::optional description; + }; /// Apply the rule to a schema auto apply(JSON &schema, const JSON &root, const Vocabularies &vocabularies, @@ -115,7 +140,7 @@ class SOURCEMETA_CORE_JSONSCHEMA_EXPORT SchemaTransformRule { /// The rule transformation. If this virtual method is not overriden, /// then the rule condition is considered to not be fixable. - virtual auto transform(JSON &schema) const -> void; + virtual auto transform(JSON &schema, const Result &result) const -> void; // Exporting symbols that depends on the standard C++ library is considered // safe. @@ -224,10 +249,10 @@ class SOURCEMETA_CORE_JSONSCHEMA_EXPORT SchemaTransformer { /// - The JSON Pointer to the given subschema /// - The name of the rule /// - The message of the rule - /// - The longer description of the rule (if any) - using Callback = - std::function; + /// - The rule evaluation result + using Callback = std::function; /// Apply the bundle of rules to a schema auto apply(JSON &schema, const SchemaWalker &walker, diff --git a/vendor/core/src/core/jsonschema/transformer.cc b/vendor/core/src/core/jsonschema/transformer.cc index 20e98aab8..94e096350 100644 --- a/vendor/core/src/core/jsonschema/transformer.cc +++ b/vendor/core/src/core/jsonschema/transformer.cc @@ -9,18 +9,6 @@ namespace { -auto is_true(const sourcemeta::core::SchemaTransformRule::Result &result) - -> bool { - switch (result.index()) { - case 0: - assert(std::holds_alternative(result)); - return *std::get_if(&result); - default: - assert(std::holds_alternative(result)); - return true; - } -} - auto calculate_health_percentage(const std::size_t subschemas, const std::size_t failed_subschemas) -> std::uint8_t { @@ -51,7 +39,7 @@ auto SchemaTransformRule::message() const -> const std::string & { return this->message_; } -auto SchemaTransformRule::transform(JSON &) const -> void { +auto SchemaTransformRule::transform(JSON &, const Result &) const -> void { throw SchemaAbortError("This rule cannot be automatically transformed"); } @@ -71,20 +59,21 @@ auto SchemaTransformRule::apply(JSON &schema, const JSON &root, -> std::pair { auto outcome{this->condition(schema, root, vocabularies, frame, location, walker, resolver)}; - if (!is_true(outcome)) { + if (!outcome.applies) { return {true, std::move(outcome)}; } try { - this->transform(schema); + this->transform(schema, outcome); } catch (const SchemaAbortError &) { return {false, std::move(outcome)}; } // The condition must always be false after applying the // transformation in order to avoid infinite loops - if (is_true(this->condition(schema, root, vocabularies, frame, location, - walker, resolver))) { + if (this->condition(schema, root, vocabularies, frame, location, walker, + resolver) + .applies) { // TODO: Throw a better custom error that also highlights the schema // location std::ostringstream error; @@ -133,21 +122,9 @@ auto SchemaTransformer::check( for (const auto &[name, rule] : this->rules) { const auto outcome{rule->check(current, schema, current_vocabularies, walker, resolver, frame, entry.second)}; - switch (outcome.index()) { - case 0: - assert(std::holds_alternative(outcome)); - if (*std::get_if(&outcome)) { - subresult = false; - callback(entry.second.pointer, name, rule->message(), ""); - } - - break; - default: - assert(std::holds_alternative(outcome)); - subresult = false; - callback(entry.second.pointer, name, rule->message(), - *std::get_if(&outcome)); - break; + if (outcome.applies) { + subresult = false; + callback(entry.second.pointer, name, rule->message(), outcome); } } @@ -191,13 +168,11 @@ auto SchemaTransformer::apply( entry.second)}; // This means the rule is fixable if (subresult.first) { - applied = is_true(subresult.second) || applied; + applied = subresult.second.applies || applied; } else { result = false; callback(entry.second.pointer, name, rule->message(), - subresult.second.index() == 0 - ? "" - : *std::get_if(&subresult.second)); + subresult.second); } if (!applied) { diff --git a/vendor/core/src/core/yaml/include/sourcemeta/core/yaml.h b/vendor/core/src/core/yaml/include/sourcemeta/core/yaml.h index bddc67a46..0e899ea16 100644 --- a/vendor/core/src/core/yaml/include/sourcemeta/core/yaml.h +++ b/vendor/core/src/core/yaml/include/sourcemeta/core/yaml.h @@ -104,7 +104,8 @@ auto read_yaml(const std::filesystem::path &path) -> JSON; /// std::cerr << "\n"; /// ``` SOURCEMETA_CORE_YAML_EXPORT -auto read_yaml_or_json(const std::filesystem::path &path) -> JSON; +auto read_yaml_or_json(const std::filesystem::path &path, + const JSON::ParseCallback &callback = nullptr) -> JSON; } // namespace sourcemeta::core diff --git a/vendor/core/src/core/yaml/yaml.cc b/vendor/core/src/core/yaml/yaml.cc index ddc6808ef..c467df99c 100644 --- a/vendor/core/src/core/yaml/yaml.cc +++ b/vendor/core/src/core/yaml/yaml.cc @@ -146,10 +146,12 @@ auto read_yaml(const std::filesystem::path &path) -> JSON { return parse_yaml(buffer.str()); } -auto read_yaml_or_json(const std::filesystem::path &path) -> JSON { +auto read_yaml_or_json(const std::filesystem::path &path, + const JSON::ParseCallback &callback) -> JSON { return path.extension() == ".yaml" || path.extension() == ".yml" + // TODO: We should be able to pass a parse callback to YAML ? read_yaml(path) - : read_json(path); + : read_json(path, callback); } } // namespace sourcemeta::core diff --git a/vendor/core/src/extension/alterschema/CMakeLists.txt b/vendor/core/src/extension/alterschema/CMakeLists.txt index 8a02c1870..705100142 100644 --- a/vendor/core/src/extension/alterschema/CMakeLists.txt +++ b/vendor/core/src/extension/alterschema/CMakeLists.txt @@ -66,7 +66,11 @@ sourcemeta_library(NAMESPACE sourcemeta PROJECT core NAME alterschema linter/property_names_type_default.h linter/property_names_default.h linter/draft_ref_siblings.h - linter/definitions_to_defs.h) + linter/definitions_to_defs.h + linter/unknown_keywords_prefix.h + + # Strict + strict/required_properties_in_properties.h) if(SOURCEMETA_CORE_INSTALL) sourcemeta_library_install(NAMESPACE sourcemeta PROJECT core NAME alterschema) diff --git a/vendor/core/src/extension/alterschema/alterschema.cc b/vendor/core/src/extension/alterschema/alterschema.cc index 01dad594f..ae0c39ea2 100644 --- a/vendor/core/src/extension/alterschema/alterschema.cc +++ b/vendor/core/src/extension/alterschema/alterschema.cc @@ -1,7 +1,5 @@ #include -#include // assert - // For built-in rules #include #include @@ -16,6 +14,24 @@ contains_any(const Vocabularies &container, }); } +template +auto APPLIES_TO_KEYWORDS(Args &&...args) -> SchemaTransformRule::Result { + std::vector result; + result.reserve(sizeof...(args)); + (result.push_back(Pointer{std::forward(args)}), ...); + return result; +} + +inline auto APPLIES_TO_POINTERS(std::vector &&keywords) + -> SchemaTransformRule::Result { + return {std::move(keywords)}; +} + +#define ONLY_CONTINUE_IF(condition) \ + if (!(condition)) { \ + return false; \ + } + // Canonicalizer #include "canonicalizer/boolean_true.h" #include "canonicalizer/const_as_enum.h" @@ -80,18 +96,22 @@ contains_any(const Vocabularies &container, #include "linter/then_without_if.h" #include "linter/unevaluated_items_default.h" #include "linter/unevaluated_properties_default.h" +#include "linter/unknown_keywords_prefix.h" #include "linter/unnecessary_allof_wrapper_draft.h" #include "linter/unnecessary_allof_wrapper_modern.h" #include "linter/unnecessary_allof_wrapper_properties.h" #include "linter/unsatisfiable_max_contains.h" #include "linter/unsatisfiable_min_properties.h" + +// Strict +#include "strict/required_properties_in_properties.h" + +#undef ONLY_CONTINUE_IF } // namespace sourcemeta::core namespace sourcemeta::core { -auto add(SchemaTransformer &bundle, const AlterSchemaMode mode) - - -> void { +auto add(SchemaTransformer &bundle, const AlterSchemaMode mode) -> void { // Common rules that apply to all modes bundle.add(); bundle.add(); @@ -125,51 +145,52 @@ auto add(SchemaTransformer &bundle, const AlterSchemaMode mode) bundle.add(); bundle.add(); bundle.add(); + bundle.add(); + + if (mode == AlterSchemaMode::StaticAnalysis) { + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + } + + if (mode == AlterSchemaMode::Readability || + mode == AlterSchemaMode::ReadabilityStrict) { + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + } - switch (mode) { - case AlterSchemaMode::StaticAnalysis: - bundle.add(); - bundle.add(); - bundle.add(); - bundle.add(); - bundle.add(); - bundle.add(); - bundle.add(); - bundle.add(); - bundle.add(); - bundle.add(); - bundle.add(); - bundle.add(); - bundle.add(); - bundle.add(); - bundle.add(); - bundle.add(); - bundle.add(); - break; - case AlterSchemaMode::Readability: - bundle.add(); - bundle.add(); - bundle.add(); - bundle.add(); - bundle.add(); - bundle.add(); - bundle.add(); - bundle.add(); - bundle.add(); - bundle.add(); - bundle.add(); - bundle.add(); - bundle.add(); - bundle.add(); - bundle.add(); - bundle.add(); - bundle.add(); - bundle.add(); - break; - default: - // We should never get here - assert(false); - break; + if (mode == AlterSchemaMode::ReadabilityStrict) { + bundle.add(); } } diff --git a/vendor/core/src/extension/alterschema/canonicalizer/boolean_true.h b/vendor/core/src/extension/alterschema/canonicalizer/boolean_true.h index 76d67573d..18cdb035c 100644 --- a/vendor/core/src/extension/alterschema/canonicalizer/boolean_true.h +++ b/vendor/core/src/extension/alterschema/canonicalizer/boolean_true.h @@ -17,7 +17,7 @@ class BooleanTrue final : public SchemaTransformRule { return schema.is_boolean() && schema.to_boolean(); } - auto transform(JSON &schema) const -> void override { + auto transform(JSON &schema, const Result &) const -> void override { schema.into(sourcemeta::core::JSON::make_object()); } }; diff --git a/vendor/core/src/extension/alterschema/canonicalizer/const_as_enum.h b/vendor/core/src/extension/alterschema/canonicalizer/const_as_enum.h index 39009b848..838d80f93 100644 --- a/vendor/core/src/extension/alterschema/canonicalizer/const_as_enum.h +++ b/vendor/core/src/extension/alterschema/canonicalizer/const_as_enum.h @@ -14,17 +14,18 @@ class ConstAsEnum final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result override { - return contains_any( - vocabularies, - {"https://json-schema.org/draft/2020-12/vocab/validation", - "https://json-schema.org/draft/2019-09/vocab/validation", - "http://json-schema.org/draft-07/schema#", - "http://json-schema.org/draft-06/schema#"}) && - schema.is_object() && schema.defines("const") && - !schema.defines("enum"); + ONLY_CONTINUE_IF( + contains_any(vocabularies, + {"https://json-schema.org/draft/2020-12/vocab/validation", + "https://json-schema.org/draft/2019-09/vocab/validation", + "http://json-schema.org/draft-07/schema#", + "http://json-schema.org/draft-06/schema#"}) && + schema.is_object() && schema.defines("const") && + !schema.defines("enum")); + return APPLIES_TO_KEYWORDS("const"); } - auto transform(JSON &schema) const -> void override { + auto transform(JSON &schema, const Result &) const -> void override { auto values = sourcemeta::core::JSON::make_array(); values.push_back(schema.at("const")); schema.at("const").into(std::move(values)); diff --git a/vendor/core/src/extension/alterschema/canonicalizer/exclusive_maximum_integer_to_maximum.h b/vendor/core/src/extension/alterschema/canonicalizer/exclusive_maximum_integer_to_maximum.h index 07f000bd6..bf0f3d190 100644 --- a/vendor/core/src/extension/alterschema/canonicalizer/exclusive_maximum_integer_to_maximum.h +++ b/vendor/core/src/extension/alterschema/canonicalizer/exclusive_maximum_integer_to_maximum.h @@ -15,21 +15,22 @@ class ExclusiveMaximumIntegerToMaximum final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result override { - return contains_any( - vocabularies, - {"https://json-schema.org/draft/2020-12/vocab/validation", - "https://json-schema.org/draft/2019-09/vocab/validation", - "http://json-schema.org/draft-07/schema#", - "http://json-schema.org/draft-06/schema#"}) && - schema.is_object() && schema.defines("type") && - schema.at("type").is_string() && - schema.at("type").to_string() == "integer" && - schema.defines("exclusiveMaximum") && - schema.at("exclusiveMaximum").is_number() && - !schema.defines("maximum"); + ONLY_CONTINUE_IF( + contains_any(vocabularies, + {"https://json-schema.org/draft/2020-12/vocab/validation", + "https://json-schema.org/draft/2019-09/vocab/validation", + "http://json-schema.org/draft-07/schema#", + "http://json-schema.org/draft-06/schema#"}) && + schema.is_object() && schema.defines("type") && + schema.at("type").is_string() && + schema.at("type").to_string() == "integer" && + schema.defines("exclusiveMaximum") && + schema.at("exclusiveMaximum").is_number() && + !schema.defines("maximum")); + return APPLIES_TO_KEYWORDS("exclusiveMaximum", "type"); } - auto transform(JSON &schema) const -> void override { + auto transform(JSON &schema, const Result &) const -> void override { if (schema.at("exclusiveMaximum").is_integer()) { auto new_maximum = schema.at("exclusiveMaximum"); new_maximum += sourcemeta::core::JSON{-1}; diff --git a/vendor/core/src/extension/alterschema/canonicalizer/exclusive_minimum_integer_to_minimum.h b/vendor/core/src/extension/alterschema/canonicalizer/exclusive_minimum_integer_to_minimum.h index 266367a4e..c7ca45237 100644 --- a/vendor/core/src/extension/alterschema/canonicalizer/exclusive_minimum_integer_to_minimum.h +++ b/vendor/core/src/extension/alterschema/canonicalizer/exclusive_minimum_integer_to_minimum.h @@ -15,21 +15,22 @@ class ExclusiveMinimumIntegerToMinimum final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result override { - return contains_any( - vocabularies, - {"https://json-schema.org/draft/2020-12/vocab/validation", - "https://json-schema.org/draft/2019-09/vocab/validation", - "http://json-schema.org/draft-07/schema#", - "http://json-schema.org/draft-06/schema#"}) && - schema.is_object() && schema.defines("type") && - schema.at("type").is_string() && - schema.at("type").to_string() == "integer" && - schema.defines("exclusiveMinimum") && - schema.at("exclusiveMinimum").is_number() && - !schema.defines("minimum"); + ONLY_CONTINUE_IF( + contains_any(vocabularies, + {"https://json-schema.org/draft/2020-12/vocab/validation", + "https://json-schema.org/draft/2019-09/vocab/validation", + "http://json-schema.org/draft-07/schema#", + "http://json-schema.org/draft-06/schema#"}) && + schema.is_object() && schema.defines("type") && + schema.at("type").is_string() && + schema.at("type").to_string() == "integer" && + schema.defines("exclusiveMinimum") && + schema.at("exclusiveMinimum").is_number() && + !schema.defines("minimum")); + return APPLIES_TO_KEYWORDS("exclusiveMinimum", "type"); } - auto transform(JSON &schema) const -> void override { + auto transform(JSON &schema, const Result &) const -> void override { if (schema.at("exclusiveMinimum").is_integer()) { auto new_minimum = schema.at("exclusiveMinimum"); new_minimum += sourcemeta::core::JSON{1}; diff --git a/vendor/core/src/extension/alterschema/canonicalizer/max_contains_covered_by_max_items.h b/vendor/core/src/extension/alterschema/canonicalizer/max_contains_covered_by_max_items.h index 55a3aacb5..3be21d50f 100644 --- a/vendor/core/src/extension/alterschema/canonicalizer/max_contains_covered_by_max_items.h +++ b/vendor/core/src/extension/alterschema/canonicalizer/max_contains_covered_by_max_items.h @@ -16,18 +16,20 @@ class MaxContainsCoveredByMaxItems final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result override { - return contains_any( - vocabularies, - {"https://json-schema.org/draft/2020-12/vocab/validation", - "https://json-schema.org/draft/2019-09/vocab/validation"}) && - schema.is_object() && schema.defines("maxContains") && - schema.at("maxContains").is_integer() && - schema.defines("maxItems") && schema.at("maxItems").is_integer() && - schema.at("maxContains").to_integer() > - schema.at("maxItems").to_integer(); + ONLY_CONTINUE_IF( + contains_any( + vocabularies, + {"https://json-schema.org/draft/2020-12/vocab/validation", + "https://json-schema.org/draft/2019-09/vocab/validation"}) && + schema.is_object() && schema.defines("maxContains") && + schema.at("maxContains").is_integer() && schema.defines("maxItems") && + schema.at("maxItems").is_integer() && + schema.at("maxContains").to_integer() > + schema.at("maxItems").to_integer()); + return APPLIES_TO_KEYWORDS("maxContains", "maxItems"); } - auto transform(JSON &schema) const -> void override { + auto transform(JSON &schema, const Result &) const -> void override { schema.assign("maxContains", schema.at("maxItems")); } }; diff --git a/vendor/core/src/extension/alterschema/canonicalizer/min_items_given_min_contains.h b/vendor/core/src/extension/alterschema/canonicalizer/min_items_given_min_contains.h index d1342237f..b5ce478c2 100644 --- a/vendor/core/src/extension/alterschema/canonicalizer/min_items_given_min_contains.h +++ b/vendor/core/src/extension/alterschema/canonicalizer/min_items_given_min_contains.h @@ -15,17 +15,19 @@ class MinItemsGivenMinContains final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result override { - return contains_any( - vocabularies, - {"https://json-schema.org/draft/2020-12/vocab/validation", - "https://json-schema.org/draft/2019-09/vocab/validation"}) && - schema.is_object() && schema.defines("type") && - schema.at("type").is_string() && - schema.at("type").to_string() == "array" && - !schema.defines("minItems"); + ONLY_CONTINUE_IF( + contains_any( + vocabularies, + {"https://json-schema.org/draft/2020-12/vocab/validation", + "https://json-schema.org/draft/2019-09/vocab/validation"}) && + schema.is_object() && schema.defines("type") && + schema.at("type").is_string() && + schema.at("type").to_string() == "array" && + !schema.defines("minItems")); + return true; } - auto transform(JSON &schema) const -> void override { + auto transform(JSON &schema, const Result &) const -> void override { if (schema.defines("contains") && schema.defines("minContains") && schema.at("minContains").is_integer()) { schema.assign("minItems", sourcemeta::core::JSON{ diff --git a/vendor/core/src/extension/alterschema/canonicalizer/min_items_implicit.h b/vendor/core/src/extension/alterschema/canonicalizer/min_items_implicit.h index 1795e4e14..1af8710c2 100644 --- a/vendor/core/src/extension/alterschema/canonicalizer/min_items_implicit.h +++ b/vendor/core/src/extension/alterschema/canonicalizer/min_items_implicit.h @@ -13,20 +13,22 @@ class MinItemsImplicit final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result override { - return contains_any(vocabularies, - {"http://json-schema.org/draft-07/schema#", - "http://json-schema.org/draft-06/schema#", - "http://json-schema.org/draft-04/schema#", - "http://json-schema.org/draft-03/schema#", - "http://json-schema.org/draft-02/schema#", - "http://json-schema.org/draft-01/schema#"}) && - schema.is_object() && schema.defines("type") && - schema.at("type").is_string() && - schema.at("type").to_string() == "array" && - !schema.defines("minItems"); + ONLY_CONTINUE_IF( + contains_any(vocabularies, + {"http://json-schema.org/draft-07/schema#", + "http://json-schema.org/draft-06/schema#", + "http://json-schema.org/draft-04/schema#", + "http://json-schema.org/draft-03/schema#", + "http://json-schema.org/draft-02/schema#", + "http://json-schema.org/draft-01/schema#"}) && + schema.is_object() && schema.defines("type") && + schema.at("type").is_string() && + schema.at("type").to_string() == "array" && + !schema.defines("minItems")); + return true; } - auto transform(JSON &schema) const -> void override { + auto transform(JSON &schema, const Result &) const -> void override { schema.assign("minItems", sourcemeta::core::JSON{0}); } }; diff --git a/vendor/core/src/extension/alterschema/canonicalizer/min_length_implicit.h b/vendor/core/src/extension/alterschema/canonicalizer/min_length_implicit.h index 43f97dc2a..9db8c7f96 100644 --- a/vendor/core/src/extension/alterschema/canonicalizer/min_length_implicit.h +++ b/vendor/core/src/extension/alterschema/canonicalizer/min_length_implicit.h @@ -14,23 +14,24 @@ class MinLengthImplicit final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result override { - return contains_any( - vocabularies, - {"https://json-schema.org/draft/2020-12/vocab/validation", - "https://json-schema.org/draft/2019-09/vocab/validation", - "http://json-schema.org/draft-07/schema#", - "http://json-schema.org/draft-06/schema#", - "http://json-schema.org/draft-04/schema#", - "http://json-schema.org/draft-03/schema#", - "http://json-schema.org/draft-02/schema#", - "http://json-schema.org/draft-01/schema#"}) && - schema.is_object() && schema.defines("type") && - schema.at("type").is_string() && - schema.at("type").to_string() == "string" && - !schema.defines("minLength"); + ONLY_CONTINUE_IF( + contains_any(vocabularies, + {"https://json-schema.org/draft/2020-12/vocab/validation", + "https://json-schema.org/draft/2019-09/vocab/validation", + "http://json-schema.org/draft-07/schema#", + "http://json-schema.org/draft-06/schema#", + "http://json-schema.org/draft-04/schema#", + "http://json-schema.org/draft-03/schema#", + "http://json-schema.org/draft-02/schema#", + "http://json-schema.org/draft-01/schema#"}) && + schema.is_object() && schema.defines("type") && + schema.at("type").is_string() && + schema.at("type").to_string() == "string" && + !schema.defines("minLength")); + return true; } - auto transform(JSON &schema) const -> void override { + auto transform(JSON &schema, const Result &) const -> void override { schema.assign("minLength", sourcemeta::core::JSON{0}); } }; diff --git a/vendor/core/src/extension/alterschema/canonicalizer/min_properties_covered_by_required.h b/vendor/core/src/extension/alterschema/canonicalizer/min_properties_covered_by_required.h index 414487cf0..4c86fce4f 100644 --- a/vendor/core/src/extension/alterschema/canonicalizer/min_properties_covered_by_required.h +++ b/vendor/core/src/extension/alterschema/canonicalizer/min_properties_covered_by_required.h @@ -15,23 +15,23 @@ class MinPropertiesCoveredByRequired final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result override { - return contains_any( - vocabularies, - {"https://json-schema.org/draft/2020-12/vocab/validation", - "https://json-schema.org/draft/2019-09/vocab/validation", - "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("minProperties") && - schema.at("minProperties").is_integer() && - schema.defines("required") && schema.at("required").is_array() && - schema.at("required").unique() && - std::cmp_greater(schema.at("required").size(), - static_cast( - schema.at("minProperties").to_integer())); + ONLY_CONTINUE_IF( + contains_any(vocabularies, + {"https://json-schema.org/draft/2020-12/vocab/validation", + "https://json-schema.org/draft/2019-09/vocab/validation", + "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("minProperties") && + schema.at("minProperties").is_integer() && schema.defines("required") && + schema.at("required").is_array() && schema.at("required").unique() && + std::cmp_greater(schema.at("required").size(), + static_cast( + schema.at("minProperties").to_integer()))); + return APPLIES_TO_KEYWORDS("minProperties", "required"); } - auto transform(JSON &schema) const -> void override { + auto transform(JSON &schema, const Result &) const -> void override { schema.assign("minProperties", sourcemeta::core::JSON{schema.at("required").size()}); } diff --git a/vendor/core/src/extension/alterschema/canonicalizer/min_properties_implicit.h b/vendor/core/src/extension/alterschema/canonicalizer/min_properties_implicit.h index 52adab95c..9a0fbe240 100644 --- a/vendor/core/src/extension/alterschema/canonicalizer/min_properties_implicit.h +++ b/vendor/core/src/extension/alterschema/canonicalizer/min_properties_implicit.h @@ -15,20 +15,21 @@ class MinPropertiesImplicit final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result override { - return contains_any( - vocabularies, - {"https://json-schema.org/draft/2020-12/vocab/validation", - "https://json-schema.org/draft/2019-09/vocab/validation", - "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("type") && - schema.at("type").is_string() && - schema.at("type").to_string() == "object" && - !schema.defines("minProperties"); + ONLY_CONTINUE_IF( + contains_any(vocabularies, + {"https://json-schema.org/draft/2020-12/vocab/validation", + "https://json-schema.org/draft/2019-09/vocab/validation", + "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("type") && + schema.at("type").is_string() && + schema.at("type").to_string() == "object" && + !schema.defines("minProperties")); + return true; } - auto transform(JSON &schema) const -> void override { + auto transform(JSON &schema, const Result &) const -> void override { if (schema.defines("required") && schema.at("required").is_array()) { schema.assign("minProperties", sourcemeta::core::JSON{schema.at("required").size()}); diff --git a/vendor/core/src/extension/alterschema/canonicalizer/multiple_of_implicit.h b/vendor/core/src/extension/alterschema/canonicalizer/multiple_of_implicit.h index 2fe6e10be..511bde31c 100644 --- a/vendor/core/src/extension/alterschema/canonicalizer/multiple_of_implicit.h +++ b/vendor/core/src/extension/alterschema/canonicalizer/multiple_of_implicit.h @@ -13,21 +13,22 @@ class MultipleOfImplicit final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result override { - return contains_any( - vocabularies, - {"https://json-schema.org/draft/2020-12/vocab/validation", - "https://json-schema.org/draft/2019-09/vocab/validation", - "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("type") && - schema.at("type").is_string() && - (schema.at("type").to_string() == "integer" || - schema.at("type").to_string() == "number") && - !schema.defines("multipleOf"); + ONLY_CONTINUE_IF( + contains_any(vocabularies, + {"https://json-schema.org/draft/2020-12/vocab/validation", + "https://json-schema.org/draft/2019-09/vocab/validation", + "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("type") && + schema.at("type").is_string() && + (schema.at("type").to_string() == "integer" || + schema.at("type").to_string() == "number") && + !schema.defines("multipleOf")); + return true; } - auto transform(JSON &schema) const -> void override { + auto transform(JSON &schema, const Result &) const -> void override { schema.assign("multipleOf", sourcemeta::core::JSON{1}); } }; diff --git a/vendor/core/src/extension/alterschema/canonicalizer/properties_implicit.h b/vendor/core/src/extension/alterschema/canonicalizer/properties_implicit.h index fbc22f2bd..9e034af6c 100644 --- a/vendor/core/src/extension/alterschema/canonicalizer/properties_implicit.h +++ b/vendor/core/src/extension/alterschema/canonicalizer/properties_implicit.h @@ -14,28 +14,30 @@ class PropertiesImplicit final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result override { - return ((vocabularies.contains( - "https://json-schema.org/draft/2020-12/vocab/validation") && - vocabularies.contains( - "https://json-schema.org/draft/2020-12/vocab/applicator")) || - (vocabularies.contains( - "https://json-schema.org/draft/2019-09/vocab/validation") && - vocabularies.contains( - "https://json-schema.org/draft/2019-09/vocab/applicator")) || - contains_any(vocabularies, - {"http://json-schema.org/draft-07/schema#", - "http://json-schema.org/draft-06/schema#", - "http://json-schema.org/draft-04/schema#", - "http://json-schema.org/draft-03/schema#", - "http://json-schema.org/draft-02/schema#", - "http://json-schema.org/draft-01/schema#"})) && - schema.is_object() && schema.defines("type") && - schema.at("type").is_string() && - schema.at("type").to_string() == "object" && - !schema.defines("properties"); + ONLY_CONTINUE_IF( + ((vocabularies.contains( + "https://json-schema.org/draft/2020-12/vocab/validation") && + vocabularies.contains( + "https://json-schema.org/draft/2020-12/vocab/applicator")) || + (vocabularies.contains( + "https://json-schema.org/draft/2019-09/vocab/validation") && + vocabularies.contains( + "https://json-schema.org/draft/2019-09/vocab/applicator")) || + contains_any(vocabularies, + {"http://json-schema.org/draft-07/schema#", + "http://json-schema.org/draft-06/schema#", + "http://json-schema.org/draft-04/schema#", + "http://json-schema.org/draft-03/schema#", + "http://json-schema.org/draft-02/schema#", + "http://json-schema.org/draft-01/schema#"})) && + schema.is_object() && schema.defines("type") && + schema.at("type").is_string() && + schema.at("type").to_string() == "object" && + !schema.defines("properties")); + return true; } - auto transform(JSON &schema) const -> void override { + auto transform(JSON &schema, const Result &) const -> void override { schema.assign("properties", sourcemeta::core::JSON::make_object()); } }; diff --git a/vendor/core/src/extension/alterschema/canonicalizer/type_array_to_any_of_2020_12.h b/vendor/core/src/extension/alterschema/canonicalizer/type_array_to_any_of_2020_12.h index feffb1390..43f0541c0 100644 --- a/vendor/core/src/extension/alterschema/canonicalizer/type_array_to_any_of_2020_12.h +++ b/vendor/core/src/extension/alterschema/canonicalizer/type_array_to_any_of_2020_12.h @@ -22,29 +22,30 @@ class TypeArrayToAnyOf_2020_12 final : public SchemaTransformRule { // a type union declaration alongside of an `anyOf` where one branch defines // `$id` or `$anchor`. We will end up duplicating identifiers (leading to // invalid schemas) and there is no silver bullet to avoid these cases. - const auto has_identifiers{std::any_of( - frame.locations().cbegin(), frame.locations().cend(), - [](const auto &entry) { + const auto has_identifiers{ + std::ranges::any_of(frame.locations(), [](const auto &entry) { return entry.second.type == sourcemeta::core::SchemaFrame::LocationType::Resource || entry.second.type == sourcemeta::core::SchemaFrame::LocationType::Anchor; })}; - return contains_any( - vocabularies, - {"https://json-schema.org/draft/2020-12/vocab/validation", - "https://json-schema.org/draft/2020-12/vocab/applicator"}) && - !has_identifiers && schema.is_object() && schema.defines("type") && - schema.at("type").is_array() && - // Non type-specific applicators can leads to invalid schemas - !schema.defines("$defs") && !schema.defines("$ref") && - !schema.defines("if") && !schema.defines("then") && - !schema.defines("else") && !schema.defines("allOf") && - !schema.defines("oneOf") && !schema.defines("anyOf"); + ONLY_CONTINUE_IF( + contains_any( + vocabularies, + {"https://json-schema.org/draft/2020-12/vocab/validation", + "https://json-schema.org/draft/2020-12/vocab/applicator"}) && + !has_identifiers && schema.is_object() && schema.defines("type") && + schema.at("type").is_array() && + // Non type-specific applicators can leads to invalid schemas + !schema.defines("$defs") && !schema.defines("$ref") && + !schema.defines("if") && !schema.defines("then") && + !schema.defines("else") && !schema.defines("allOf") && + !schema.defines("oneOf") && !schema.defines("anyOf")); + return APPLIES_TO_KEYWORDS("type"); } - auto transform(JSON &schema) const -> void override { + auto transform(JSON &schema, const Result &) const -> void override { const std::set keep{"$schema", "$id", "$anchor", "$dynamicAnchor", "$vocabulary"}; auto disjunctors{sourcemeta::core::JSON::make_array()}; diff --git a/vendor/core/src/extension/alterschema/canonicalizer/type_boolean_as_enum.h b/vendor/core/src/extension/alterschema/canonicalizer/type_boolean_as_enum.h index 5b182abdd..2c072991e 100644 --- a/vendor/core/src/extension/alterschema/canonicalizer/type_boolean_as_enum.h +++ b/vendor/core/src/extension/alterschema/canonicalizer/type_boolean_as_enum.h @@ -15,23 +15,24 @@ class TypeBooleanAsEnum final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result override { - return contains_any( - vocabularies, - {"https://json-schema.org/draft/2020-12/vocab/validation", - "https://json-schema.org/draft/2019-09/vocab/validation", - "http://json-schema.org/draft-07/schema#", - "http://json-schema.org/draft-06/schema#", - "http://json-schema.org/draft-04/schema#", - "http://json-schema.org/draft-03/schema#", - "http://json-schema.org/draft-02/schema#", - "http://json-schema.org/draft-01/schema#"}) && - schema.is_object() && schema.defines("type") && - schema.at("type").is_string() && - schema.at("type").to_string() == "boolean" && - !schema.defines("enum") && !schema.defines("const"); + ONLY_CONTINUE_IF( + contains_any(vocabularies, + {"https://json-schema.org/draft/2020-12/vocab/validation", + "https://json-schema.org/draft/2019-09/vocab/validation", + "http://json-schema.org/draft-07/schema#", + "http://json-schema.org/draft-06/schema#", + "http://json-schema.org/draft-04/schema#", + "http://json-schema.org/draft-03/schema#", + "http://json-schema.org/draft-02/schema#", + "http://json-schema.org/draft-01/schema#"}) && + schema.is_object() && schema.defines("type") && + schema.at("type").is_string() && + schema.at("type").to_string() == "boolean" && !schema.defines("enum") && + !schema.defines("const")); + return APPLIES_TO_KEYWORDS("type"); } - auto transform(JSON &schema) const -> void override { + auto transform(JSON &schema, const Result &) const -> void override { auto choices = sourcemeta::core::JSON::make_array(); choices.push_back(sourcemeta::core::JSON{false}); choices.push_back(sourcemeta::core::JSON{true}); diff --git a/vendor/core/src/extension/alterschema/canonicalizer/type_null_as_enum.h b/vendor/core/src/extension/alterschema/canonicalizer/type_null_as_enum.h index c88dff57d..3e59fee3c 100644 --- a/vendor/core/src/extension/alterschema/canonicalizer/type_null_as_enum.h +++ b/vendor/core/src/extension/alterschema/canonicalizer/type_null_as_enum.h @@ -15,23 +15,24 @@ class TypeNullAsEnum final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result override { - return contains_any( - vocabularies, - {"https://json-schema.org/draft/2020-12/vocab/validation", - "https://json-schema.org/draft/2019-09/vocab/validation", - "http://json-schema.org/draft-07/schema#", - "http://json-schema.org/draft-06/schema#", - "http://json-schema.org/draft-04/schema#", - "http://json-schema.org/draft-03/schema#", - "http://json-schema.org/draft-02/schema#", - "http://json-schema.org/draft-01/schema#"}) && - schema.is_object() && schema.defines("type") && - schema.at("type").is_string() && - schema.at("type").to_string() == "null" && !schema.defines("enum") && - !schema.defines("const"); + ONLY_CONTINUE_IF( + contains_any(vocabularies, + {"https://json-schema.org/draft/2020-12/vocab/validation", + "https://json-schema.org/draft/2019-09/vocab/validation", + "http://json-schema.org/draft-07/schema#", + "http://json-schema.org/draft-06/schema#", + "http://json-schema.org/draft-04/schema#", + "http://json-schema.org/draft-03/schema#", + "http://json-schema.org/draft-02/schema#", + "http://json-schema.org/draft-01/schema#"}) && + schema.is_object() && schema.defines("type") && + schema.at("type").is_string() && + schema.at("type").to_string() == "null" && !schema.defines("enum") && + !schema.defines("const")); + return APPLIES_TO_KEYWORDS("type"); } - auto transform(JSON &schema) const -> void override { + auto transform(JSON &schema, const Result &) const -> void override { auto choices = sourcemeta::core::JSON::make_array(); choices.push_back(sourcemeta::core::JSON{nullptr}); schema.at("type").into(std::move(choices)); diff --git a/vendor/core/src/extension/alterschema/canonicalizer/type_union_implicit.h b/vendor/core/src/extension/alterschema/canonicalizer/type_union_implicit.h index 3ea6b24dc..d8d1fc2a5 100644 --- a/vendor/core/src/extension/alterschema/canonicalizer/type_union_implicit.h +++ b/vendor/core/src/extension/alterschema/canonicalizer/type_union_implicit.h @@ -14,110 +14,70 @@ class TypeUnionImplicit final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result override { - if (!schema.is_object()) { - return false; - } - - if (contains_any(vocabularies, - {"https://json-schema.org/draft/2020-12/vocab/validation", - "https://json-schema.org/draft/2019-09/vocab/validation", - "http://json-schema.org/draft-07/schema#", - "http://json-schema.org/draft-06/schema#", - "http://json-schema.org/draft-04/schema#", - "http://json-schema.org/draft-03/schema#", - "http://json-schema.org/draft-02/schema#", - "http://json-schema.org/draft-01/schema#", - "http://json-schema.org/draft-00/schema#"})) { - if (schema.defines("type")) { - return false; - } - - // Don't apply if we don't have the necessary vocabularies - } else { - return false; - } - - if (vocabularies.contains( - "https://json-schema.org/draft/2020-12/vocab/core") && - schema.defines_any({"$ref", "$dynamicRef"})) { - return false; - } - - if (vocabularies.contains( - "https://json-schema.org/draft/2020-12/vocab/applicator") && - schema.defines_any( - {"anyOf", "oneOf", "allOf", "if", "then", "else", "not"})) { - return false; - } - - if (vocabularies.contains( - "https://json-schema.org/draft/2020-12/vocab/validation") && - schema.defines_any({"enum", "const"})) { - return false; - } - - if (vocabularies.contains( - "https://json-schema.org/draft/2019-09/vocab/core") && - schema.defines_any({"$ref", "$recursiveRef"})) { - return false; - } - - if (vocabularies.contains( - "https://json-schema.org/draft/2019-09/vocab/applicator") && - schema.defines_any( - {"anyOf", "oneOf", "allOf", "if", "then", "else", "not"})) { - return false; - } - - if (vocabularies.contains( - "https://json-schema.org/draft/2019-09/vocab/validation") && - schema.defines_any({"enum", "const"})) { - return false; - } - - if (vocabularies.contains("http://json-schema.org/draft-07/schema#") && - schema.defines_any({"$ref", "enum", "const", "anyOf", "oneOf", "allOf", - "if", "then", "else", "not"})) { - return false; - } - - if (vocabularies.contains("http://json-schema.org/draft-06/schema#") && - schema.defines_any( - {"$ref", "enum", "const", "anyOf", "oneOf", "allOf", "not"})) { - return false; - } - - if (vocabularies.contains("http://json-schema.org/draft-04/schema#") && - schema.defines_any( - {"$ref", "enum", "anyOf", "oneOf", "allOf", "not"})) { - return false; - } - - if (vocabularies.contains("http://json-schema.org/draft-03/schema#") && - schema.defines_any({"$ref", "enum", "disallow", "extends"})) { - return false; - } - - if (vocabularies.contains("http://json-schema.org/draft-02/schema#") && - schema.defines_any({"enum", "disallow", "extends"})) { - return false; - } - - if (vocabularies.contains("http://json-schema.org/draft-01/schema#") && - schema.defines_any({"enum", "disallow", "extends"})) { - return false; - } - - if (vocabularies.contains( - "http://json-schema.org/draft-00/hyper-schema#") && - schema.defines_any({"enum", "disallow", "extends"})) { - return false; - } - + ONLY_CONTINUE_IF(schema.is_object()); + ONLY_CONTINUE_IF(contains_any( + vocabularies, {"https://json-schema.org/draft/2020-12/vocab/validation", + "https://json-schema.org/draft/2019-09/vocab/validation", + "http://json-schema.org/draft-07/schema#", + "http://json-schema.org/draft-06/schema#", + "http://json-schema.org/draft-04/schema#", + "http://json-schema.org/draft-03/schema#", + "http://json-schema.org/draft-02/schema#", + "http://json-schema.org/draft-01/schema#", + "http://json-schema.org/draft-00/schema#"})); + ONLY_CONTINUE_IF(!schema.defines("type")); + ONLY_CONTINUE_IF(!vocabularies.contains( + "https://json-schema.org/draft/2020-12/vocab/core") || + !schema.defines_any({"$ref", "$dynamicRef"})); + ONLY_CONTINUE_IF( + !vocabularies.contains( + "https://json-schema.org/draft/2020-12/vocab/applicator") || + !schema.defines_any( + {"anyOf", "oneOf", "allOf", "if", "then", "else", "not"})); + ONLY_CONTINUE_IF( + !vocabularies.contains( + "https://json-schema.org/draft/2020-12/vocab/validation") || + !schema.defines_any({"enum", "const"})); + ONLY_CONTINUE_IF(!vocabularies.contains( + "https://json-schema.org/draft/2019-09/vocab/core") || + !schema.defines_any({"$ref", "$recursiveRef"})); + ONLY_CONTINUE_IF( + !vocabularies.contains( + "https://json-schema.org/draft/2019-09/vocab/applicator") || + !schema.defines_any( + {"anyOf", "oneOf", "allOf", "if", "then", "else", "not"})); + ONLY_CONTINUE_IF( + !vocabularies.contains( + "https://json-schema.org/draft/2019-09/vocab/validation") || + !schema.defines_any({"enum", "const"})); + ONLY_CONTINUE_IF( + !vocabularies.contains("http://json-schema.org/draft-07/schema#") || + !schema.defines_any({"$ref", "enum", "const", "anyOf", "oneOf", "allOf", + "if", "then", "else", "not"})); + ONLY_CONTINUE_IF( + !vocabularies.contains("http://json-schema.org/draft-06/schema#") || + !schema.defines_any( + {"$ref", "enum", "const", "anyOf", "oneOf", "allOf", "not"})); + ONLY_CONTINUE_IF( + !vocabularies.contains("http://json-schema.org/draft-04/schema#") || + !schema.defines_any( + {"$ref", "enum", "anyOf", "oneOf", "allOf", "not"})); + ONLY_CONTINUE_IF( + !vocabularies.contains("http://json-schema.org/draft-03/schema#") || + !schema.defines_any({"$ref", "enum", "disallow", "extends"})) + ONLY_CONTINUE_IF( + !vocabularies.contains("http://json-schema.org/draft-02/schema#") || + !schema.defines_any({"enum", "disallow", "extends"})); + ONLY_CONTINUE_IF( + !vocabularies.contains("http://json-schema.org/draft-01/schema#") || + !schema.defines_any({"enum", "disallow", "extends"})); + ONLY_CONTINUE_IF(!vocabularies.contains( + "http://json-schema.org/draft-00/hyper-schema#") || + !schema.defines_any({"enum", "disallow", "extends"})); return true; } - auto transform(JSON &schema) const -> void override { + auto transform(JSON &schema, const Result &) const -> void override { auto types{sourcemeta::core::JSON::make_array()}; // All possible JSON Schema types diff --git a/vendor/core/src/extension/alterschema/include/sourcemeta/core/alterschema.h b/vendor/core/src/extension/alterschema/include/sourcemeta/core/alterschema.h index 07fb2a706..24679f53a 100644 --- a/vendor/core/src/extension/alterschema/include/sourcemeta/core/alterschema.h +++ b/vendor/core/src/extension/alterschema/include/sourcemeta/core/alterschema.h @@ -27,10 +27,16 @@ enum class AlterSchemaMode : std::uint8_t { /// performance Readability, - /// Rules that surface implicit constraints and simplifies keywords that are - /// syntax sugar to other keywords, potentially decreasing human readability - /// in favor of explicitness - StaticAnalysis + /// Rules that simplify the given schema for both human readability and + /// performance while also including opinionated rules that enforce tighter + /// conventions to help with + /// correctness + ReadabilityStrict, + + /// Rules that surface implicit constraints and simplifies keywords that + /// are syntax sugar to other keywords, potentially decreasing human + /// readability in favor of explicitness + StaticAnalysis, }; /// @ingroup alterschema diff --git a/vendor/core/src/extension/alterschema/linter/additional_items_with_schema_items.h b/vendor/core/src/extension/alterschema/linter/additional_items_with_schema_items.h index 595a0da75..62b835737 100644 --- a/vendor/core/src/extension/alterschema/linter/additional_items_with_schema_items.h +++ b/vendor/core/src/extension/alterschema/linter/additional_items_with_schema_items.h @@ -14,18 +14,19 @@ class AdditionalItemsWithSchemaItems final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result override { - return contains_any( - vocabularies, - {"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#", - "http://json-schema.org/draft-03/schema#"}) && - schema.is_object() && schema.defines("items") && - schema.defines("additionalItems") && is_schema(schema.at("items")); + ONLY_CONTINUE_IF( + contains_any(vocabularies, + {"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#", + "http://json-schema.org/draft-03/schema#"}) && + schema.is_object() && schema.defines("items") && + schema.defines("additionalItems") && is_schema(schema.at("items"))); + return APPLIES_TO_KEYWORDS("additionalItems", "items"); } - auto transform(JSON &schema) const -> void override { + auto transform(JSON &schema, const Result &) const -> void override { schema.erase("additionalItems"); } }; diff --git a/vendor/core/src/extension/alterschema/linter/additional_properties_default.h b/vendor/core/src/extension/alterschema/linter/additional_properties_default.h index b7fcf047a..2dbbce8a6 100644 --- a/vendor/core/src/extension/alterschema/linter/additional_properties_default.h +++ b/vendor/core/src/extension/alterschema/linter/additional_properties_default.h @@ -15,26 +15,27 @@ class AdditionalPropertiesDefault final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result 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#", - "http://json-schema.org/draft-03/schema#", - "http://json-schema.org/draft-02/schema#", - "http://json-schema.org/draft-02/hyper-schema#", - "http://json-schema.org/draft-01/schema#", - "http://json-schema.org/draft-01/hyper-schema#"}) && - schema.is_object() && schema.defines("additionalProperties") && - ((schema.at("additionalProperties").is_boolean() && - schema.at("additionalProperties").to_boolean()) || - (schema.at("additionalProperties").is_object() && - schema.at("additionalProperties").empty())); + ONLY_CONTINUE_IF( + 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#", + "http://json-schema.org/draft-03/schema#", + "http://json-schema.org/draft-02/schema#", + "http://json-schema.org/draft-02/hyper-schema#", + "http://json-schema.org/draft-01/schema#", + "http://json-schema.org/draft-01/hyper-schema#"}) && + schema.is_object() && schema.defines("additionalProperties") && + ((schema.at("additionalProperties").is_boolean() && + schema.at("additionalProperties").to_boolean()) || + (schema.at("additionalProperties").is_object() && + schema.at("additionalProperties").empty()))); + return APPLIES_TO_KEYWORDS("additionalProperties"); } - auto transform(JSON &schema) const -> void override { + auto transform(JSON &schema, const Result &) const -> void override { schema.erase("additionalProperties"); } }; diff --git a/vendor/core/src/extension/alterschema/linter/const_with_type.h b/vendor/core/src/extension/alterschema/linter/const_with_type.h index 8f06b6553..698b6c0d3 100644 --- a/vendor/core/src/extension/alterschema/linter/const_with_type.h +++ b/vendor/core/src/extension/alterschema/linter/const_with_type.h @@ -15,18 +15,14 @@ class ConstWithType final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result override { - if (!contains_any(vocabularies, - {"https://json-schema.org/draft/2020-12/vocab/validation", - "https://json-schema.org/draft/2019-09/vocab/validation", - "http://json-schema.org/draft-07/schema#", - "http://json-schema.org/draft-06/schema#"})) { - return false; - } - - if (!schema.is_object() || !schema.defines("type") || - !schema.defines("const")) { - return false; - } + ONLY_CONTINUE_IF( + contains_any(vocabularies, + {"https://json-schema.org/draft/2020-12/vocab/validation", + "https://json-schema.org/draft/2019-09/vocab/validation", + "http://json-schema.org/draft-07/schema#", + "http://json-schema.org/draft-06/schema#"}) && + schema.is_object() && schema.defines("type") && + schema.defines("const")); std::set current_types; if (schema.at("type").is_string()) { @@ -44,8 +40,11 @@ class ConstWithType final : public SchemaTransformRule { } } - return current_types.contains(schema.at("const").type()); + ONLY_CONTINUE_IF(current_types.contains(schema.at("const").type())); + return APPLIES_TO_KEYWORDS("const", "type"); } - auto transform(JSON &schema) const -> void override { schema.erase("type"); } + auto transform(JSON &schema, const Result &) const -> void override { + schema.erase("type"); + } }; diff --git a/vendor/core/src/extension/alterschema/linter/content_media_type_without_encoding.h b/vendor/core/src/extension/alterschema/linter/content_media_type_without_encoding.h index 01af83cb9..46efc1d7c 100644 --- a/vendor/core/src/extension/alterschema/linter/content_media_type_without_encoding.h +++ b/vendor/core/src/extension/alterschema/linter/content_media_type_without_encoding.h @@ -15,15 +15,17 @@ class ContentMediaTypeWithoutEncoding final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result override { - return contains_any(vocabularies, - {"https://json-schema.org/draft/2020-12/vocab/content", - "https://json-schema.org/draft/2019-09/vocab/content", - "http://json-schema.org/draft-07/schema#"}) && - schema.is_object() && schema.defines("contentMediaType") && - !schema.defines("contentEncoding"); + ONLY_CONTINUE_IF( + contains_any(vocabularies, + {"https://json-schema.org/draft/2020-12/vocab/content", + "https://json-schema.org/draft/2019-09/vocab/content", + "http://json-schema.org/draft-07/schema#"}) && + schema.is_object() && schema.defines("contentMediaType") && + !schema.defines("contentEncoding")); + return APPLIES_TO_KEYWORDS("contentMediaType"); } - auto transform(JSON &schema) const -> void override { + auto transform(JSON &schema, const Result &) const -> void override { schema.erase("contentMediaType"); } }; diff --git a/vendor/core/src/extension/alterschema/linter/content_schema_default.h b/vendor/core/src/extension/alterschema/linter/content_schema_default.h index 3da2703de..86ec9c705 100644 --- a/vendor/core/src/extension/alterschema/linter/content_schema_default.h +++ b/vendor/core/src/extension/alterschema/linter/content_schema_default.h @@ -15,18 +15,19 @@ class ContentSchemaDefault final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result override { - return contains_any( - vocabularies, - {"https://json-schema.org/draft/2020-12/vocab/content", - "https://json-schema.org/draft/2019-09/vocab/content"}) && - schema.is_object() && schema.defines("contentSchema") && - ((schema.at("contentSchema").is_boolean() && - schema.at("contentSchema").to_boolean()) || - (schema.at("contentSchema").is_object() && - schema.at("contentSchema").empty())); + ONLY_CONTINUE_IF( + contains_any(vocabularies, + {"https://json-schema.org/draft/2020-12/vocab/content", + "https://json-schema.org/draft/2019-09/vocab/content"}) && + schema.is_object() && schema.defines("contentSchema") && + ((schema.at("contentSchema").is_boolean() && + schema.at("contentSchema").to_boolean()) || + (schema.at("contentSchema").is_object() && + schema.at("contentSchema").empty()))); + return APPLIES_TO_KEYWORDS("contentSchema"); } - auto transform(JSON &schema) const -> void override { + auto transform(JSON &schema, const Result &) const -> void override { schema.erase("contentSchema"); } }; diff --git a/vendor/core/src/extension/alterschema/linter/content_schema_without_media_type.h b/vendor/core/src/extension/alterschema/linter/content_schema_without_media_type.h index 7c709752b..33b0d7ca7 100644 --- a/vendor/core/src/extension/alterschema/linter/content_schema_without_media_type.h +++ b/vendor/core/src/extension/alterschema/linter/content_schema_without_media_type.h @@ -15,15 +15,16 @@ class ContentSchemaWithoutMediaType final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result override { - return contains_any( - vocabularies, - {"https://json-schema.org/draft/2020-12/vocab/content", - "https://json-schema.org/draft/2019-09/vocab/content"}) && - schema.is_object() && schema.defines("contentSchema") && - !schema.defines("contentMediaType"); + ONLY_CONTINUE_IF( + contains_any(vocabularies, + {"https://json-schema.org/draft/2020-12/vocab/content", + "https://json-schema.org/draft/2019-09/vocab/content"}) && + schema.is_object() && schema.defines("contentSchema") && + !schema.defines("contentMediaType")); + return APPLIES_TO_KEYWORDS("contentSchema"); } - auto transform(JSON &schema) const -> void override { + auto transform(JSON &schema, const Result &) const -> void override { schema.erase("contentSchema"); } }; diff --git a/vendor/core/src/extension/alterschema/linter/definitions_to_defs.h b/vendor/core/src/extension/alterschema/linter/definitions_to_defs.h index 62ca99d5b..6cddc2a9e 100644 --- a/vendor/core/src/extension/alterschema/linter/definitions_to_defs.h +++ b/vendor/core/src/extension/alterschema/linter/definitions_to_defs.h @@ -14,14 +14,16 @@ class DefinitionsToDefs final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result override { - return contains_any(vocabularies, - {"https://json-schema.org/draft/2020-12/vocab/core", - "https://json-schema.org/draft/2019-09/vocab/core"}) && - schema.is_object() && schema.defines("definitions") && - !schema.defines("$defs"); + ONLY_CONTINUE_IF( + contains_any(vocabularies, + {"https://json-schema.org/draft/2020-12/vocab/core", + "https://json-schema.org/draft/2019-09/vocab/core"}) && + schema.is_object() && schema.defines("definitions") && + !schema.defines("$defs")); + return APPLIES_TO_KEYWORDS("definitions"); } - auto transform(JSON &schema) const -> void override { + auto transform(JSON &schema, const Result &) const -> void override { schema.rename("definitions", "$defs"); } diff --git a/vendor/core/src/extension/alterschema/linter/dependencies_default.h b/vendor/core/src/extension/alterschema/linter/dependencies_default.h index d9b0a5c13..21302056f 100644 --- a/vendor/core/src/extension/alterschema/linter/dependencies_default.h +++ b/vendor/core/src/extension/alterschema/linter/dependencies_default.h @@ -15,17 +15,19 @@ class DependenciesDefault final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result override { - return contains_any(vocabularies, - {"http://json-schema.org/draft-07/schema#", - "http://json-schema.org/draft-06/schema#", - "http://json-schema.org/draft-04/schema#", - "http://json-schema.org/draft-03/schema#"}) && - schema.is_object() && schema.defines("dependencies") && - schema.at("dependencies").is_object() && - schema.at("dependencies").empty(); + ONLY_CONTINUE_IF( + contains_any(vocabularies, + {"http://json-schema.org/draft-07/schema#", + "http://json-schema.org/draft-06/schema#", + "http://json-schema.org/draft-04/schema#", + "http://json-schema.org/draft-03/schema#"}) && + schema.is_object() && schema.defines("dependencies") && + schema.at("dependencies").is_object() && + schema.at("dependencies").empty()); + return APPLIES_TO_KEYWORDS("dependencies"); } - auto transform(JSON &schema) const -> void override { + auto transform(JSON &schema, const Result &) const -> void override { schema.erase("dependencies"); } }; diff --git a/vendor/core/src/extension/alterschema/linter/dependencies_property_tautology.h b/vendor/core/src/extension/alterschema/linter/dependencies_property_tautology.h index 2d5750626..36f6ba4a6 100644 --- a/vendor/core/src/extension/alterschema/linter/dependencies_property_tautology.h +++ b/vendor/core/src/extension/alterschema/linter/dependencies_property_tautology.h @@ -16,30 +16,30 @@ class DependenciesPropertyTautology final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result override { - return contains_any(vocabularies, - {"http://json-schema.org/draft-07/schema#", - "http://json-schema.org/draft-06/schema#", - "http://json-schema.org/draft-04/schema#", - "http://json-schema.org/draft-03/schema#"}) && - schema.is_object() && schema.defines("dependencies") && - schema.at("dependencies").is_object() && - schema.defines("required") && schema.at("required").is_array() && - std::any_of(schema.at("required").as_array().cbegin(), - schema.at("required").as_array().cend(), - [&schema](const auto &element) { - return element.is_string() && - schema.at("dependencies") - .defines(element.to_string()) && - (schema.at("dependencies") - .at(element.to_string()) - .is_array() || - schema.at("dependencies") - .at(element.to_string()) - .is_string()); - }); + ONLY_CONTINUE_IF( + contains_any(vocabularies, + {"http://json-schema.org/draft-07/schema#", + "http://json-schema.org/draft-06/schema#", + "http://json-schema.org/draft-04/schema#", + "http://json-schema.org/draft-03/schema#"}) && + schema.is_object() && schema.defines("dependencies") && + schema.at("dependencies").is_object() && schema.defines("required") && + schema.at("required").is_array()); + ONLY_CONTINUE_IF(std::ranges::any_of( + schema.at("required").as_array(), [&schema](const auto &element) { + return element.is_string() && + schema.at("dependencies").defines(element.to_string()) && + (schema.at("dependencies") + .at(element.to_string()) + .is_array() || + schema.at("dependencies") + .at(element.to_string()) + .is_string()); + })); + return APPLIES_TO_KEYWORDS("dependencies", "required"); } - auto transform(JSON &schema) const -> void override { + auto transform(JSON &schema, const Result &) const -> void override { auto requirements{schema.at("required")}; while (true) { bool match{false}; diff --git a/vendor/core/src/extension/alterschema/linter/dependent_required_default.h b/vendor/core/src/extension/alterschema/linter/dependent_required_default.h index 73ad0bea2..3810dad16 100644 --- a/vendor/core/src/extension/alterschema/linter/dependent_required_default.h +++ b/vendor/core/src/extension/alterschema/linter/dependent_required_default.h @@ -15,16 +15,18 @@ class DependentRequiredDefault final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result override { - return contains_any( - vocabularies, - {"https://json-schema.org/draft/2020-12/vocab/validation", - "https://json-schema.org/draft/2019-09/vocab/validation"}) && - schema.is_object() && schema.defines("dependentRequired") && - schema.at("dependentRequired").is_object() && - schema.at("dependentRequired").empty(); + ONLY_CONTINUE_IF( + contains_any( + vocabularies, + {"https://json-schema.org/draft/2020-12/vocab/validation", + "https://json-schema.org/draft/2019-09/vocab/validation"}) && + schema.is_object() && schema.defines("dependentRequired") && + schema.at("dependentRequired").is_object() && + schema.at("dependentRequired").empty()); + return APPLIES_TO_KEYWORDS("dependentRequired"); } - auto transform(JSON &schema) const -> void override { + auto transform(JSON &schema, const Result &) const -> void override { schema.erase("dependentRequired"); } }; diff --git a/vendor/core/src/extension/alterschema/linter/dependent_required_tautology.h b/vendor/core/src/extension/alterschema/linter/dependent_required_tautology.h index d5b044107..6876334ab 100644 --- a/vendor/core/src/extension/alterschema/linter/dependent_required_tautology.h +++ b/vendor/core/src/extension/alterschema/linter/dependent_required_tautology.h @@ -16,23 +16,25 @@ class DependentRequiredTautology final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result override { - return contains_any( - vocabularies, - {"https://json-schema.org/draft/2020-12/vocab/validation", - "https://json-schema.org/draft/2019-09/vocab/validation"}) && - schema.is_object() && schema.defines("dependentRequired") && - schema.at("dependentRequired").is_object() && - schema.defines("required") && schema.at("required").is_array() && - std::any_of(schema.at("required").as_array().cbegin(), - schema.at("required").as_array().cend(), - [&schema](const auto &element) { - return element.is_string() && - schema.at("dependentRequired") - .defines(element.to_string()); - }); + ONLY_CONTINUE_IF( + contains_any( + vocabularies, + {"https://json-schema.org/draft/2020-12/vocab/validation", + "https://json-schema.org/draft/2019-09/vocab/validation"}) && + schema.is_object() && schema.defines("dependentRequired") && + schema.at("dependentRequired").is_object() && + schema.defines("required") && schema.at("required").is_array()); + ONLY_CONTINUE_IF(std::any_of( + schema.at("required").as_array().cbegin(), + schema.at("required").as_array().cend(), + [&schema](const auto &element) { + return element.is_string() && + schema.at("dependentRequired").defines(element.to_string()); + })); + return APPLIES_TO_KEYWORDS("dependentRequired", "required"); } - auto transform(JSON &schema) const -> void override { + auto transform(JSON &schema, const Result &) const -> void override { auto requirements{schema.at("required")}; while (true) { bool match{false}; diff --git a/vendor/core/src/extension/alterschema/linter/draft_official_dialect_without_empty_fragment.h b/vendor/core/src/extension/alterschema/linter/draft_official_dialect_without_empty_fragment.h index 9df6ad563..060752eca 100644 --- a/vendor/core/src/extension/alterschema/linter/draft_official_dialect_without_empty_fragment.h +++ b/vendor/core/src/extension/alterschema/linter/draft_official_dialect_without_empty_fragment.h @@ -14,31 +14,31 @@ class DraftOfficialDialectWithoutEmptyFragment final const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result override { - if (!schema.is_object() || !schema.defines("$schema") || - !schema.at("$schema").is_string()) { - return false; - } - - const auto &schema_value = schema.at("$schema").to_string(); - return (schema_value == "http://json-schema.org/draft-07/schema" || - schema_value == "http://json-schema.org/draft-07/hyper-schema" || - schema_value == "http://json-schema.org/draft-06/schema" || - schema_value == "http://json-schema.org/draft-06/hyper-schema" || - schema_value == "http://json-schema.org/draft-04/schema" || - schema_value == "http://json-schema.org/draft-04/hyper-schema" || - schema_value == "http://json-schema.org/draft-03/schema" || - schema_value == "http://json-schema.org/draft-03/hyper-schema" || - schema_value == "http://json-schema.org/draft-02/schema" || - schema_value == "http://json-schema.org/draft-02/hyper-schema" || - schema_value == "http://json-schema.org/draft-01/schema" || - schema_value == "http://json-schema.org/draft-01/hyper-schema" || - schema_value == "http://json-schema.org/draft-00/schema" || - schema_value == "http://json-schema.org/draft-00/hyper-schema"); + ONLY_CONTINUE_IF(schema.is_object() && schema.defines("$schema") && + schema.at("$schema").is_string()); + const auto &dialect{schema.at("$schema").to_string()}; + ONLY_CONTINUE_IF( + dialect == "http://json-schema.org/draft-07/schema" || + dialect == "http://json-schema.org/draft-07/hyper-schema" || + dialect == "http://json-schema.org/draft-06/schema" || + dialect == "http://json-schema.org/draft-06/hyper-schema" || + dialect == "http://json-schema.org/draft-04/schema" || + dialect == "http://json-schema.org/draft-04/hyper-schema" || + dialect == "http://json-schema.org/draft-03/schema" || + dialect == "http://json-schema.org/draft-03/hyper-schema" || + dialect == "http://json-schema.org/draft-02/schema" || + dialect == "http://json-schema.org/draft-02/hyper-schema" || + dialect == "http://json-schema.org/draft-01/schema" || + dialect == "http://json-schema.org/draft-01/hyper-schema" || + dialect == "http://json-schema.org/draft-00/schema" || + dialect == "http://json-schema.org/draft-00/hyper-schema"); + return APPLIES_TO_KEYWORDS("$schema"); } - auto transform(sourcemeta::core::JSON &schema) const -> void override { - auto schema_value = schema.at("$schema").to_string(); - schema_value += "#"; - schema.at("$schema").into(sourcemeta::core::JSON{schema_value}); + auto transform(sourcemeta::core::JSON &schema, const Result &) const + -> void override { + auto dialect{std::move(schema.at("$schema")).to_string()}; + dialect += "#"; + schema.at("$schema").into(sourcemeta::core::JSON{dialect}); } }; diff --git a/vendor/core/src/extension/alterschema/linter/draft_ref_siblings.h b/vendor/core/src/extension/alterschema/linter/draft_ref_siblings.h index 574bb3b61..80489a8ea 100644 --- a/vendor/core/src/extension/alterschema/linter/draft_ref_siblings.h +++ b/vendor/core/src/extension/alterschema/linter/draft_ref_siblings.h @@ -3,7 +3,7 @@ class DraftRefSiblings final : public SchemaTransformRule { DraftRefSiblings() : SchemaTransformRule{"draft_ref_siblings", "In Draft 7 and older dialects, keywords sibling " - "to $ref are never evaluated"} {} + "to `$ref` are never evaluated"} {} [[nodiscard]] auto condition(const sourcemeta::core::JSON &schema, @@ -14,48 +14,34 @@ class DraftRefSiblings final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &walker, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result override { - if (!contains_any(vocabularies, - {"http://json-schema.org/draft-07/schema#", - "http://json-schema.org/draft-06/schema#", - "http://json-schema.org/draft-04/schema#", - "http://json-schema.org/draft-03/schema#", - "http://json-schema.org/draft-02/schema#", - "http://json-schema.org/draft-01/schema#", - "http://json-schema.org/draft-00/schema#"})) { - return false; - } - - if (!schema.is_object() || !schema.defines("$ref")) { - return false; - } - - // Clear and populate the preserve_keywords set - this->preserve_keywords.clear(); - bool has_removable_siblings = false; - - // Loop over the object properties in schema + ONLY_CONTINUE_IF(contains_any(vocabularies, + {"http://json-schema.org/draft-07/schema#", + "http://json-schema.org/draft-06/schema#", + "http://json-schema.org/draft-04/schema#", + "http://json-schema.org/draft-03/schema#", + "http://json-schema.org/draft-02/schema#", + "http://json-schema.org/draft-01/schema#", + "http://json-schema.org/draft-00/schema#"})); + ONLY_CONTINUE_IF(schema.is_object() && schema.defines("$ref")); + + std::vector locations; for (const auto &entry : schema.as_object()) { - const auto &key = entry.first; - - // Run the walker and only add to preserve_keywords the keywords whose - // type is Other or Reference - const auto metadata = walker(key, vocabularies); + const auto metadata{walker(entry.first, vocabularies)}; if (metadata.type == sourcemeta::core::SchemaKeywordType::Other || metadata.type == sourcemeta::core::SchemaKeywordType::Reference) { - this->preserve_keywords.insert(key); + continue; } else { - has_removable_siblings = true; + locations.push_back(Pointer{entry.first}); } } - return has_removable_siblings; + ONLY_CONTINUE_IF(!locations.empty()); + return APPLIES_TO_POINTERS(std::move(locations)); } - auto transform(JSON &schema) const -> void override { - schema.clear_except(this->preserve_keywords.cbegin(), - this->preserve_keywords.cend()); + auto transform(JSON &schema, const Result &result) const -> void override { + for (const auto &location : result.locations) { + schema.erase(location.at(0).to_property()); + } } - -private: - mutable std::unordered_set preserve_keywords; }; diff --git a/vendor/core/src/extension/alterschema/linter/duplicate_allof_branches.h b/vendor/core/src/extension/alterschema/linter/duplicate_allof_branches.h index 64596783a..a479b2489 100644 --- a/vendor/core/src/extension/alterschema/linter/duplicate_allof_branches.h +++ b/vendor/core/src/extension/alterschema/linter/duplicate_allof_branches.h @@ -17,18 +17,20 @@ class DuplicateAllOfBranches final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result 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(); + ONLY_CONTINUE_IF( + 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()); + // TODO: Highlight which specific entries in `allOf` are duplicated + return APPLIES_TO_KEYWORDS("allOf"); } - auto transform(JSON &schema) const -> void override { + auto transform(JSON &schema, const Result &) const -> void override { auto collection = schema.at("allOf"); std::sort(collection.as_array().begin(), collection.as_array().end()); auto last = diff --git a/vendor/core/src/extension/alterschema/linter/duplicate_anyof_branches.h b/vendor/core/src/extension/alterschema/linter/duplicate_anyof_branches.h index 9341a36f7..7f10e4c91 100644 --- a/vendor/core/src/extension/alterschema/linter/duplicate_anyof_branches.h +++ b/vendor/core/src/extension/alterschema/linter/duplicate_anyof_branches.h @@ -17,18 +17,20 @@ class DuplicateAnyOfBranches final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result 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(); + ONLY_CONTINUE_IF( + 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()); + // TODO: Highlight which specific entries in `anyOf` are duplicated + return APPLIES_TO_KEYWORDS("anyOf"); } - auto transform(JSON &schema) const -> void override { + auto transform(JSON &schema, const Result &) const -> void override { auto collection = schema.at("anyOf"); std::sort(collection.as_array().begin(), collection.as_array().end()); auto last = diff --git a/vendor/core/src/extension/alterschema/linter/duplicate_enum_values.h b/vendor/core/src/extension/alterschema/linter/duplicate_enum_values.h index 565074996..ba6840509 100644 --- a/vendor/core/src/extension/alterschema/linter/duplicate_enum_values.h +++ b/vendor/core/src/extension/alterschema/linter/duplicate_enum_values.h @@ -14,21 +14,23 @@ class DuplicateEnumValues final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result override { - return contains_any( - vocabularies, - {"https://json-schema.org/draft/2020-12/vocab/validation", - "https://json-schema.org/draft/2019-09/vocab/validation", - "http://json-schema.org/draft-07/schema#", - "http://json-schema.org/draft-06/schema#", - "http://json-schema.org/draft-04/schema#", - "http://json-schema.org/draft-03/schema#", - "http://json-schema.org/draft-02/schema#", - "http://json-schema.org/draft-01/schema#"}) && - schema.is_object() && schema.defines("enum") && - schema.at("enum").is_array() && !schema.at("enum").unique(); + ONLY_CONTINUE_IF( + contains_any(vocabularies, + {"https://json-schema.org/draft/2020-12/vocab/validation", + "https://json-schema.org/draft/2019-09/vocab/validation", + "http://json-schema.org/draft-07/schema#", + "http://json-schema.org/draft-06/schema#", + "http://json-schema.org/draft-04/schema#", + "http://json-schema.org/draft-03/schema#", + "http://json-schema.org/draft-02/schema#", + "http://json-schema.org/draft-01/schema#"}) && + schema.is_object() && schema.defines("enum") && + schema.at("enum").is_array() && !schema.at("enum").unique()); + // TODO: Highlight which specific entries in `enum` are duplicated + return APPLIES_TO_KEYWORDS("enum"); } - auto transform(JSON &schema) const -> void override { + auto transform(JSON &schema, const Result &) const -> void override { // We want to be super careful to maintain the current ordering // as we delete the duplicates auto &enumeration{schema.at("enum")}; diff --git a/vendor/core/src/extension/alterschema/linter/duplicate_required_values.h b/vendor/core/src/extension/alterschema/linter/duplicate_required_values.h index 5d9d3e150..54972c3d2 100644 --- a/vendor/core/src/extension/alterschema/linter/duplicate_required_values.h +++ b/vendor/core/src/extension/alterschema/linter/duplicate_required_values.h @@ -15,18 +15,20 @@ class DuplicateRequiredValues final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result override { - return contains_any( - vocabularies, - {"https://json-schema.org/draft/2020-12/vocab/validation", - "https://json-schema.org/draft/2019-09/vocab/validation", - "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("required") && - schema.at("required").is_array() && !schema.at("required").unique(); + ONLY_CONTINUE_IF( + contains_any(vocabularies, + {"https://json-schema.org/draft/2020-12/vocab/validation", + "https://json-schema.org/draft/2019-09/vocab/validation", + "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("required") && + schema.at("required").is_array() && !schema.at("required").unique()); + // TODO: Highlight which specific entries in `required` are duplicated + return APPLIES_TO_KEYWORDS("required"); } - auto transform(JSON &schema) const -> void override { + auto transform(JSON &schema, const Result &) const -> void override { auto collection = schema.at("required"); std::sort(collection.as_array().begin(), collection.as_array().end()); auto last = diff --git a/vendor/core/src/extension/alterschema/linter/else_empty.h b/vendor/core/src/extension/alterschema/linter/else_empty.h index a610872ea..fa10dcd6d 100644 --- a/vendor/core/src/extension/alterschema/linter/else_empty.h +++ b/vendor/core/src/extension/alterschema/linter/else_empty.h @@ -10,17 +10,20 @@ class ElseEmpty final : public SchemaTransformRule { const SchemaFrame &, const SchemaFrame::Location &, const SchemaWalker &, const SchemaResolver &) const -> SchemaTransformRule::Result 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#"}) && - schema.is_object() && schema.defines("else") && - is_schema(schema.at("else")) && is_empty_schema(schema.at("else")) && - (schema.at("else").is_object() || - (!schema.defines("if") || - !(schema.at("if").is_boolean() && schema.at("if").to_boolean()))); + ONLY_CONTINUE_IF( + 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#"}) && + schema.is_object() && schema.defines("else") && + is_schema(schema.at("else")) && is_empty_schema(schema.at("else")) && + (schema.at("else").is_object() || + (!schema.defines("if") || + !(schema.at("if").is_boolean() && schema.at("if").to_boolean())))); + return APPLIES_TO_KEYWORDS("else"); } - auto transform(JSON &schema) const -> void override { schema.erase("else"); } + auto transform(JSON &schema, const Result &) const -> void override { + schema.erase("else"); + } }; diff --git a/vendor/core/src/extension/alterschema/linter/else_without_if.h b/vendor/core/src/extension/alterschema/linter/else_without_if.h index 745b16426..424eed410 100644 --- a/vendor/core/src/extension/alterschema/linter/else_without_if.h +++ b/vendor/core/src/extension/alterschema/linter/else_without_if.h @@ -14,14 +14,16 @@ class ElseWithoutIf final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result 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#"}) && - schema.is_object() && schema.defines("else") && - !schema.defines("if"); + ONLY_CONTINUE_IF( + 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#"}) && + schema.is_object() && schema.defines("else") && !schema.defines("if")); + return APPLIES_TO_KEYWORDS("else"); } - auto transform(JSON &schema) const -> void override { schema.erase("else"); } + auto transform(JSON &schema, const Result &) const -> void override { + schema.erase("else"); + } }; diff --git a/vendor/core/src/extension/alterschema/linter/enum_to_const.h b/vendor/core/src/extension/alterschema/linter/enum_to_const.h index 8120dc51c..0e0d9a4fd 100644 --- a/vendor/core/src/extension/alterschema/linter/enum_to_const.h +++ b/vendor/core/src/extension/alterschema/linter/enum_to_const.h @@ -14,18 +14,19 @@ class EnumToConst final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result override { - return contains_any( - vocabularies, - {"https://json-schema.org/draft/2020-12/vocab/validation", - "https://json-schema.org/draft/2019-09/vocab/validation", - "http://json-schema.org/draft-07/schema#", - "http://json-schema.org/draft-06/schema#"}) && - schema.is_object() && !schema.defines("const") && - schema.defines("enum") && schema.at("enum").is_array() && - schema.at("enum").size() == 1; + ONLY_CONTINUE_IF( + contains_any(vocabularies, + {"https://json-schema.org/draft/2020-12/vocab/validation", + "https://json-schema.org/draft/2019-09/vocab/validation", + "http://json-schema.org/draft-07/schema#", + "http://json-schema.org/draft-06/schema#"}) && + schema.is_object() && !schema.defines("const") && + schema.defines("enum") && schema.at("enum").is_array() && + schema.at("enum").size() == 1); + return APPLIES_TO_KEYWORDS("enum"); } - auto transform(JSON &schema) const -> void override { + auto transform(JSON &schema, const Result &) const -> void override { auto front{schema.at("enum").front()}; schema.at("enum").into(front); schema.rename("enum", "const"); diff --git a/vendor/core/src/extension/alterschema/linter/enum_with_type.h b/vendor/core/src/extension/alterschema/linter/enum_with_type.h index b56e49c4d..ddc627189 100644 --- a/vendor/core/src/extension/alterschema/linter/enum_with_type.h +++ b/vendor/core/src/extension/alterschema/linter/enum_with_type.h @@ -15,22 +15,17 @@ class EnumWithType final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result override { - if (!contains_any(vocabularies, - {"https://json-schema.org/draft/2020-12/vocab/validation", + ONLY_CONTINUE_IF(contains_any( + vocabularies, {"https://json-schema.org/draft/2020-12/vocab/validation", "https://json-schema.org/draft/2019-09/vocab/validation", "http://json-schema.org/draft-07/schema#", "http://json-schema.org/draft-06/schema#", "http://json-schema.org/draft-04/schema#", "http://json-schema.org/draft-03/schema#", "http://json-schema.org/draft-02/schema#", - "http://json-schema.org/draft-01/schema#"})) { - return false; - } - - if (!schema.is_object() || !schema.defines("type") || - !schema.defines("enum") || !schema.at("enum").is_array()) { - return false; - } + "http://json-schema.org/draft-01/schema#"})); + ONLY_CONTINUE_IF(schema.is_object() && schema.defines("type") && + schema.defines("enum") && schema.at("enum").is_array()); std::set current_types; if (schema.at("type").is_string()) { @@ -48,12 +43,15 @@ class EnumWithType final : public SchemaTransformRule { } } - return std::all_of(schema.at("enum").as_array().cbegin(), - schema.at("enum").as_array().cend(), - [¤t_types](const auto &item) { - return current_types.contains(item.type()); - }); + ONLY_CONTINUE_IF(std::ranges::all_of( + schema.at("enum").as_array(), [¤t_types](const auto &item) { + return current_types.contains(item.type()); + })); + + return APPLIES_TO_KEYWORDS("enum", "type"); } - auto transform(JSON &schema) const -> void override { schema.erase("type"); } + auto transform(JSON &schema, const Result &) const -> void override { + schema.erase("type"); + } }; diff --git a/vendor/core/src/extension/alterschema/linter/equal_numeric_bounds_to_const.h b/vendor/core/src/extension/alterschema/linter/equal_numeric_bounds_to_const.h index d9ba77c1d..7368a08e8 100644 --- a/vendor/core/src/extension/alterschema/linter/equal_numeric_bounds_to_const.h +++ b/vendor/core/src/extension/alterschema/linter/equal_numeric_bounds_to_const.h @@ -15,24 +15,26 @@ class EqualNumericBoundsToConst final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result override { - return contains_any( - vocabularies, - { - "https://json-schema.org/draft/2020-12/vocab/validation", - "https://json-schema.org/draft/2019-09/vocab/validation", - "http://json-schema.org/draft-07/schema#", - "http://json-schema.org/draft-06/schema#", - }) && - schema.is_object() && schema.defines("type") && - schema.at("type").is_string() && - (schema.at("type").to_string() == "integer" || - schema.at("type").to_string() == "number") && - schema.defines("minimum") && schema.at("minimum").is_number() && - schema.defines("maximum") && schema.at("maximum").is_number() && - schema.at("minimum") == schema.at("maximum"); + ONLY_CONTINUE_IF( + contains_any( + vocabularies, + { + "https://json-schema.org/draft/2020-12/vocab/validation", + "https://json-schema.org/draft/2019-09/vocab/validation", + "http://json-schema.org/draft-07/schema#", + "http://json-schema.org/draft-06/schema#", + }) && + schema.is_object() && schema.defines("type") && + schema.at("type").is_string() && + (schema.at("type").to_string() == "integer" || + schema.at("type").to_string() == "number") && + schema.defines("minimum") && schema.at("minimum").is_number() && + schema.defines("maximum") && schema.at("maximum").is_number() && + schema.at("minimum") == schema.at("maximum")); + return APPLIES_TO_KEYWORDS("minimum", "maximum"); } - auto transform(JSON &schema) const -> void override { + auto transform(JSON &schema, const Result &) const -> void override { schema.rename("minimum", "const"); schema.erase("type"); schema.erase("maximum"); diff --git a/vendor/core/src/extension/alterschema/linter/equal_numeric_bounds_to_enum.h b/vendor/core/src/extension/alterschema/linter/equal_numeric_bounds_to_enum.h index 3cdb48c65..247133ee4 100644 --- a/vendor/core/src/extension/alterschema/linter/equal_numeric_bounds_to_enum.h +++ b/vendor/core/src/extension/alterschema/linter/equal_numeric_bounds_to_enum.h @@ -15,21 +15,23 @@ class EqualNumericBoundsToEnum final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result override { - return contains_any(vocabularies, - {"http://json-schema.org/draft-04/schema#", - "http://json-schema.org/draft-03/schema#", - "http://json-schema.org/draft-02/schema#", - "http://json-schema.org/draft-01/schema#"}) && - schema.is_object() && schema.defines("type") && - schema.at("type").is_string() && - (schema.at("type").to_string() == "integer" || - schema.at("type").to_string() == "number") && - schema.defines("minimum") && schema.at("minimum").is_number() && - schema.defines("maximum") && schema.at("maximum").is_number() && - schema.at("minimum") == schema.at("maximum"); + ONLY_CONTINUE_IF( + contains_any(vocabularies, + {"http://json-schema.org/draft-04/schema#", + "http://json-schema.org/draft-03/schema#", + "http://json-schema.org/draft-02/schema#", + "http://json-schema.org/draft-01/schema#"}) && + schema.is_object() && schema.defines("type") && + schema.at("type").is_string() && + (schema.at("type").to_string() == "integer" || + schema.at("type").to_string() == "number") && + schema.defines("minimum") && schema.at("minimum").is_number() && + schema.defines("maximum") && schema.at("maximum").is_number() && + schema.at("minimum") == schema.at("maximum")); + return APPLIES_TO_KEYWORDS("minimum", "maximum"); } - auto transform(JSON &schema) const -> void override { + auto transform(JSON &schema, const Result &) const -> void override { sourcemeta::core::JSON values = sourcemeta::core::JSON::make_array(); values.push_back(schema.at("minimum")); schema.assign("enum", std::move(values)); diff --git a/vendor/core/src/extension/alterschema/linter/exclusive_maximum_number_and_maximum.h b/vendor/core/src/extension/alterschema/linter/exclusive_maximum_number_and_maximum.h index 28803aa4a..a1d81923a 100644 --- a/vendor/core/src/extension/alterschema/linter/exclusive_maximum_number_and_maximum.h +++ b/vendor/core/src/extension/alterschema/linter/exclusive_maximum_number_and_maximum.h @@ -15,19 +15,20 @@ class ExclusiveMaximumNumberAndMaximum final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result override { - return contains_any( - vocabularies, - {"https://json-schema.org/draft/2020-12/vocab/validation", - "https://json-schema.org/draft/2019-09/vocab/validation", - "http://json-schema.org/draft-07/schema#", - "http://json-schema.org/draft-06/schema#"}) && - schema.is_object() && schema.defines("maximum") && - schema.defines("exclusiveMaximum") && - schema.at("maximum").is_number() && - schema.at("exclusiveMaximum").is_number(); + ONLY_CONTINUE_IF( + contains_any(vocabularies, + {"https://json-schema.org/draft/2020-12/vocab/validation", + "https://json-schema.org/draft/2019-09/vocab/validation", + "http://json-schema.org/draft-07/schema#", + "http://json-schema.org/draft-06/schema#"}) && + schema.is_object() && schema.defines("maximum") && + schema.defines("exclusiveMaximum") && + schema.at("maximum").is_number() && + schema.at("exclusiveMaximum").is_number()); + return APPLIES_TO_KEYWORDS("exclusiveMaximum", "maximum"); } - auto transform(JSON &schema) const -> void override { + auto transform(JSON &schema, const Result &) const -> void override { if (schema.at("maximum") < schema.at("exclusiveMaximum")) { schema.erase("exclusiveMaximum"); } else { diff --git a/vendor/core/src/extension/alterschema/linter/exclusive_minimum_number_and_minimum.h b/vendor/core/src/extension/alterschema/linter/exclusive_minimum_number_and_minimum.h index 2515c488a..f44abed3f 100644 --- a/vendor/core/src/extension/alterschema/linter/exclusive_minimum_number_and_minimum.h +++ b/vendor/core/src/extension/alterschema/linter/exclusive_minimum_number_and_minimum.h @@ -15,19 +15,20 @@ class ExclusiveMinimumNumberAndMinimum final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result override { - return contains_any( - vocabularies, - {"https://json-schema.org/draft/2020-12/vocab/validation", - "https://json-schema.org/draft/2019-09/vocab/validation", - "http://json-schema.org/draft-07/schema#", - "http://json-schema.org/draft-06/schema#"}) && - schema.is_object() && schema.defines("minimum") && - schema.defines("exclusiveMinimum") && - schema.at("minimum").is_number() && - schema.at("exclusiveMinimum").is_number(); + ONLY_CONTINUE_IF( + contains_any(vocabularies, + {"https://json-schema.org/draft/2020-12/vocab/validation", + "https://json-schema.org/draft/2019-09/vocab/validation", + "http://json-schema.org/draft-07/schema#", + "http://json-schema.org/draft-06/schema#"}) && + schema.is_object() && schema.defines("minimum") && + schema.defines("exclusiveMinimum") && + schema.at("minimum").is_number() && + schema.at("exclusiveMinimum").is_number()); + return APPLIES_TO_KEYWORDS("exclusiveMinimum", "minimum"); } - auto transform(JSON &schema) const -> void override { + auto transform(JSON &schema, const Result &) const -> void override { if (schema.at("exclusiveMinimum") < schema.at("minimum")) { schema.erase("exclusiveMinimum"); } else { diff --git a/vendor/core/src/extension/alterschema/linter/if_without_then_else.h b/vendor/core/src/extension/alterschema/linter/if_without_then_else.h index ab99b5040..419764385 100644 --- a/vendor/core/src/extension/alterschema/linter/if_without_then_else.h +++ b/vendor/core/src/extension/alterschema/linter/if_without_then_else.h @@ -15,14 +15,17 @@ class IfWithoutThenElse final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result 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#"}) && - schema.is_object() && schema.defines("if") && - !schema.defines("then") && !schema.defines("else"); + ONLY_CONTINUE_IF( + 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#"}) && + schema.is_object() && schema.defines("if") && !schema.defines("then") && + !schema.defines("else")); + return APPLIES_TO_KEYWORDS("if"); } - auto transform(JSON &schema) const -> void override { schema.erase("if"); } + auto transform(JSON &schema, const Result &) const -> void override { + schema.erase("if"); + } }; diff --git a/vendor/core/src/extension/alterschema/linter/items_array_default.h b/vendor/core/src/extension/alterschema/linter/items_array_default.h index a6dbaac3f..f192cd3a4 100644 --- a/vendor/core/src/extension/alterschema/linter/items_array_default.h +++ b/vendor/core/src/extension/alterschema/linter/items_array_default.h @@ -14,20 +14,23 @@ class ItemsArrayDefault final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result override { - return contains_any( - vocabularies, - {"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#", - "http://json-schema.org/draft-03/schema#", - "http://json-schema.org/draft-02/schema#", - "http://json-schema.org/draft-02/hyper-schema#", - "http://json-schema.org/draft-01/schema#", - "http://json-schema.org/draft-01/hyper-schema#"}) && - schema.is_object() && schema.defines("items") && - schema.at("items").is_array() && schema.at("items").empty(); + ONLY_CONTINUE_IF( + contains_any(vocabularies, + {"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#", + "http://json-schema.org/draft-03/schema#", + "http://json-schema.org/draft-02/schema#", + "http://json-schema.org/draft-02/hyper-schema#", + "http://json-schema.org/draft-01/schema#", + "http://json-schema.org/draft-01/hyper-schema#"}) && + schema.is_object() && schema.defines("items") && + schema.at("items").is_array() && schema.at("items").empty()); + return APPLIES_TO_KEYWORDS("items"); } - auto transform(JSON &schema) const -> void override { schema.erase("items"); } + auto transform(JSON &schema, const Result &) const -> void override { + schema.erase("items"); + } }; diff --git a/vendor/core/src/extension/alterschema/linter/items_schema_default.h b/vendor/core/src/extension/alterschema/linter/items_schema_default.h index 040499e91..5db31cb0f 100644 --- a/vendor/core/src/extension/alterschema/linter/items_schema_default.h +++ b/vendor/core/src/extension/alterschema/linter/items_schema_default.h @@ -14,23 +14,25 @@ class ItemsSchemaDefault final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result 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#", - "http://json-schema.org/draft-03/schema#", - "http://json-schema.org/draft-02/schema#", - "http://json-schema.org/draft-02/hyper-schema#", - "http://json-schema.org/draft-01/schema#", - "http://json-schema.org/draft-01/hyper-schema#"}) && - schema.is_object() && schema.defines("items") && - ((schema.at("items").is_boolean() && - schema.at("items").to_boolean()) || - (schema.at("items").is_object() && schema.at("items").empty())); + ONLY_CONTINUE_IF( + 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#", + "http://json-schema.org/draft-03/schema#", + "http://json-schema.org/draft-02/schema#", + "http://json-schema.org/draft-02/hyper-schema#", + "http://json-schema.org/draft-01/schema#", + "http://json-schema.org/draft-01/hyper-schema#"}) && + schema.is_object() && schema.defines("items") && + ((schema.at("items").is_boolean() && schema.at("items").to_boolean()) || + (schema.at("items").is_object() && schema.at("items").empty()))); + return APPLIES_TO_KEYWORDS("items"); } - auto transform(JSON &schema) const -> void override { schema.erase("items"); } + auto transform(JSON &schema, const Result &) const -> void override { + schema.erase("items"); + } }; diff --git a/vendor/core/src/extension/alterschema/linter/max_contains_without_contains.h b/vendor/core/src/extension/alterschema/linter/max_contains_without_contains.h index 8228ed1ee..745668427 100644 --- a/vendor/core/src/extension/alterschema/linter/max_contains_without_contains.h +++ b/vendor/core/src/extension/alterschema/linter/max_contains_without_contains.h @@ -15,15 +15,17 @@ class MaxContainsWithoutContains final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result override { - return contains_any( - vocabularies, - {"https://json-schema.org/draft/2020-12/vocab/validation", - "https://json-schema.org/draft/2019-09/vocab/validation"}) && - schema.is_object() && schema.defines("maxContains") && - !schema.defines("contains"); + ONLY_CONTINUE_IF( + contains_any( + vocabularies, + {"https://json-schema.org/draft/2020-12/vocab/validation", + "https://json-schema.org/draft/2019-09/vocab/validation"}) && + schema.is_object() && schema.defines("maxContains") && + !schema.defines("contains")); + return APPLIES_TO_KEYWORDS("maxContains"); } - auto transform(JSON &schema) const -> void override { + auto transform(JSON &schema, const Result &) const -> void override { schema.erase("maxContains"); } }; diff --git a/vendor/core/src/extension/alterschema/linter/maximum_real_for_integer.h b/vendor/core/src/extension/alterschema/linter/maximum_real_for_integer.h index 18cbd4c33..ac13b2d7f 100644 --- a/vendor/core/src/extension/alterschema/linter/maximum_real_for_integer.h +++ b/vendor/core/src/extension/alterschema/linter/maximum_real_for_integer.h @@ -15,23 +15,24 @@ class MaximumRealForInteger final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result override { - return contains_any( - vocabularies, - {"https://json-schema.org/draft/2020-12/vocab/validation", - "https://json-schema.org/draft/2019-09/vocab/validation", - "http://json-schema.org/draft-07/schema#", - "http://json-schema.org/draft-06/schema#", - "http://json-schema.org/draft-04/schema#", - "http://json-schema.org/draft-03/schema#", - "http://json-schema.org/draft-02/schema#", - "http://json-schema.org/draft-01/schema#"}) && - schema.is_object() && schema.defines("type") && - schema.at("type").is_string() && - schema.at("type").to_string() == "integer" && - schema.defines("maximum") && schema.at("maximum").is_real(); + ONLY_CONTINUE_IF( + contains_any(vocabularies, + {"https://json-schema.org/draft/2020-12/vocab/validation", + "https://json-schema.org/draft/2019-09/vocab/validation", + "http://json-schema.org/draft-07/schema#", + "http://json-schema.org/draft-06/schema#", + "http://json-schema.org/draft-04/schema#", + "http://json-schema.org/draft-03/schema#", + "http://json-schema.org/draft-02/schema#", + "http://json-schema.org/draft-01/schema#"}) && + schema.is_object() && schema.defines("type") && + schema.at("type").is_string() && + schema.at("type").to_string() == "integer" && + schema.defines("maximum") && schema.at("maximum").is_real()); + return APPLIES_TO_KEYWORDS("maximum"); } - auto transform(JSON &schema) const -> void override { + auto transform(JSON &schema, const Result &) const -> void override { const auto current{schema.at("maximum").to_real()}; const auto new_value{static_cast(std::floor(current))}; schema.assign("maximum", sourcemeta::core::JSON{new_value}); diff --git a/vendor/core/src/extension/alterschema/linter/min_contains_without_contains.h b/vendor/core/src/extension/alterschema/linter/min_contains_without_contains.h index 2af48115b..ec35d5acf 100644 --- a/vendor/core/src/extension/alterschema/linter/min_contains_without_contains.h +++ b/vendor/core/src/extension/alterschema/linter/min_contains_without_contains.h @@ -15,15 +15,17 @@ class MinContainsWithoutContains final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result override { - return contains_any( - vocabularies, - {"https://json-schema.org/draft/2020-12/vocab/validation", - "https://json-schema.org/draft/2019-09/vocab/validation"}) && - schema.is_object() && schema.defines("minContains") && - !schema.defines("contains"); + ONLY_CONTINUE_IF( + contains_any( + vocabularies, + {"https://json-schema.org/draft/2020-12/vocab/validation", + "https://json-schema.org/draft/2019-09/vocab/validation"}) && + schema.is_object() && schema.defines("minContains") && + !schema.defines("contains")); + return APPLIES_TO_KEYWORDS("minContains"); } - auto transform(JSON &schema) const -> void override { + auto transform(JSON &schema, const Result &) const -> void override { schema.erase("minContains"); } }; diff --git a/vendor/core/src/extension/alterschema/linter/minimum_real_for_integer.h b/vendor/core/src/extension/alterschema/linter/minimum_real_for_integer.h index 65f378cfc..dcf5c752b 100644 --- a/vendor/core/src/extension/alterschema/linter/minimum_real_for_integer.h +++ b/vendor/core/src/extension/alterschema/linter/minimum_real_for_integer.h @@ -15,23 +15,24 @@ class MinimumRealForInteger final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result override { - return contains_any( - vocabularies, - {"https://json-schema.org/draft/2020-12/vocab/validation", - "https://json-schema.org/draft/2019-09/vocab/validation", - "http://json-schema.org/draft-07/schema#", - "http://json-schema.org/draft-06/schema#", - "http://json-schema.org/draft-04/schema#", - "http://json-schema.org/draft-03/schema#", - "http://json-schema.org/draft-02/schema#", - "http://json-schema.org/draft-01/schema#"}) && - schema.is_object() && schema.defines("type") && - schema.at("type").is_string() && - schema.at("type").to_string() == "integer" && - schema.defines("minimum") && schema.at("minimum").is_real(); + ONLY_CONTINUE_IF( + contains_any(vocabularies, + {"https://json-schema.org/draft/2020-12/vocab/validation", + "https://json-schema.org/draft/2019-09/vocab/validation", + "http://json-schema.org/draft-07/schema#", + "http://json-schema.org/draft-06/schema#", + "http://json-schema.org/draft-04/schema#", + "http://json-schema.org/draft-03/schema#", + "http://json-schema.org/draft-02/schema#", + "http://json-schema.org/draft-01/schema#"}) && + schema.is_object() && schema.defines("type") && + schema.at("type").is_string() && + schema.at("type").to_string() == "integer" && + schema.defines("minimum") && schema.at("minimum").is_real()); + return APPLIES_TO_KEYWORDS("minimum"); } - auto transform(JSON &schema) const -> void override { + auto transform(JSON &schema, const Result &) const -> void override { const auto current{schema.at("minimum").to_real()}; const auto new_value{static_cast(std::ceil(current))}; schema.assign("minimum", sourcemeta::core::JSON{new_value}); diff --git a/vendor/core/src/extension/alterschema/linter/modern_official_dialect_with_empty_fragment.h b/vendor/core/src/extension/alterschema/linter/modern_official_dialect_with_empty_fragment.h index d4439ffb6..caa12e08e 100644 --- a/vendor/core/src/extension/alterschema/linter/modern_official_dialect_with_empty_fragment.h +++ b/vendor/core/src/extension/alterschema/linter/modern_official_dialect_with_empty_fragment.h @@ -15,22 +15,21 @@ class ModernOfficialDialectWithEmptyFragment final const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result override { - if (!schema.is_object() || !schema.defines("$schema") || - !schema.at("$schema").is_string()) { - return false; - } - - const auto &schema_value = schema.at("$schema").to_string(); - return ( - schema_value == "https://json-schema.org/draft/2019-09/schema#" || - schema_value == "https://json-schema.org/draft/2019-09/hyper-schema#" || - schema_value == "https://json-schema.org/draft/2020-12/schema#" || - schema_value == "https://json-schema.org/draft/2020-12/hyper-schema#"); + ONLY_CONTINUE_IF(schema.is_object() && schema.defines("$schema") && + schema.at("$schema").is_string()); + const auto &dialect{schema.at("$schema").to_string()}; + ONLY_CONTINUE_IF( + dialect == "https://json-schema.org/draft/2019-09/schema#" || + dialect == "https://json-schema.org/draft/2019-09/hyper-schema#" || + dialect == "https://json-schema.org/draft/2020-12/schema#" || + dialect == "https://json-schema.org/draft/2020-12/hyper-schema#"); + return APPLIES_TO_KEYWORDS("$schema"); } - auto transform(sourcemeta::core::JSON &schema) const -> void override { - auto schema_value = schema.at("$schema").to_string(); - schema_value.pop_back(); - schema.at("$schema").into(sourcemeta::core::JSON{schema_value}); + auto transform(sourcemeta::core::JSON &schema, const Result &) const + -> void override { + auto dialect{std::move(schema.at("$schema")).to_string()}; + dialect.pop_back(); + schema.at("$schema").into(sourcemeta::core::JSON{dialect}); } }; diff --git a/vendor/core/src/extension/alterschema/linter/multiple_of_default.h b/vendor/core/src/extension/alterschema/linter/multiple_of_default.h index 00bbf8dc3..8a6fb2299 100644 --- a/vendor/core/src/extension/alterschema/linter/multiple_of_default.h +++ b/vendor/core/src/extension/alterschema/linter/multiple_of_default.h @@ -14,21 +14,22 @@ class MultipleOfDefault final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result override { - return contains_any( - vocabularies, - {"https://json-schema.org/draft/2020-12/vocab/validation", - "https://json-schema.org/draft/2019-09/vocab/validation", - "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("multipleOf") && - ((schema.at("multipleOf").is_integer() && - schema.at("multipleOf").to_integer() == 1) || - (schema.at("multipleOf").is_real() && - schema.at("multipleOf").to_real() == 1.0)); + ONLY_CONTINUE_IF( + contains_any(vocabularies, + {"https://json-schema.org/draft/2020-12/vocab/validation", + "https://json-schema.org/draft/2019-09/vocab/validation", + "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("multipleOf") && + ((schema.at("multipleOf").is_integer() && + schema.at("multipleOf").to_integer() == 1) || + (schema.at("multipleOf").is_real() && + schema.at("multipleOf").to_real() == 1.0))); + return APPLIES_TO_KEYWORDS("multipleOf"); } - auto transform(JSON &schema) const -> void override { + auto transform(JSON &schema, const Result &) const -> void override { schema.erase("multipleOf"); } }; diff --git a/vendor/core/src/extension/alterschema/linter/non_applicable_type_specific_keywords.h b/vendor/core/src/extension/alterschema/linter/non_applicable_type_specific_keywords.h index 77f21e142..de67f91fd 100644 --- a/vendor/core/src/extension/alterschema/linter/non_applicable_type_specific_keywords.h +++ b/vendor/core/src/extension/alterschema/linter/non_applicable_type_specific_keywords.h @@ -14,12 +14,9 @@ class NonApplicableTypeSpecificKeywords final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &walker, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result override { - if (!schema.is_object()) { - return false; - } + ONLY_CONTINUE_IF(schema.is_object()); std::set current_types; - if (contains_any(vocabularies, {"https://json-schema.org/draft/2020-12/vocab/validation", "https://json-schema.org/draft/2019-09/vocab/validation", @@ -76,11 +73,9 @@ class NonApplicableTypeSpecificKeywords final : public SchemaTransformRule { // This means that the schema has no explicit type constraints, // so we cannot remove anything from it. - if (current_types.empty()) { - return false; - } + ONLY_CONTINUE_IF(!current_types.empty()); - this->blacklist.clear(); + std::vector positions; for (const auto &entry : schema.as_object()) { const auto metadata{walker(entry.first, vocabularies)}; @@ -95,27 +90,17 @@ class NonApplicableTypeSpecificKeywords final : public SchemaTransformRule { [¤t_types](const auto keyword_type) { return current_types.contains(keyword_type); })) { - this->blacklist.emplace_back(entry.first); + positions.push_back(Pointer{entry.first}); } } - if (this->blacklist.empty()) { - return false; - } - - // Print the offending keyword - std::ostringstream message; - for (const auto &entry : this->blacklist) { - message << "- " << entry << "\n"; - } - - return message.str(); + ONLY_CONTINUE_IF(!positions.empty()); + return APPLIES_TO_POINTERS(std::move(positions)); } - auto transform(JSON &schema) const -> void override { - schema.erase_keys(this->blacklist.cbegin(), this->blacklist.cend()); + auto transform(JSON &schema, const Result &result) const -> void override { + for (const auto &location : result.locations) { + schema.erase(location.at(0).to_property()); + } } - -private: - mutable std::vector blacklist; }; diff --git a/vendor/core/src/extension/alterschema/linter/not_false.h b/vendor/core/src/extension/alterschema/linter/not_false.h index 319b61209..2c40bd0c5 100644 --- a/vendor/core/src/extension/alterschema/linter/not_false.h +++ b/vendor/core/src/extension/alterschema/linter/not_false.h @@ -11,16 +11,19 @@ class NotFalse final : public SchemaTransformRule { const SchemaFrame &, const SchemaFrame::Location &, const SchemaWalker &, const SchemaResolver &) const -> SchemaTransformRule::Result 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("not") && - schema.at("not").is_boolean() && !schema.at("not").to_boolean(); + ONLY_CONTINUE_IF( + 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("not") && + schema.at("not").is_boolean() && !schema.at("not").to_boolean()); + return APPLIES_TO_KEYWORDS("not"); } - auto transform(JSON &schema) const -> void override { schema.erase("not"); } + auto transform(JSON &schema, const Result &) const -> void override { + schema.erase("not"); + } }; diff --git a/vendor/core/src/extension/alterschema/linter/pattern_properties_default.h b/vendor/core/src/extension/alterschema/linter/pattern_properties_default.h index 332510fda..f01e0d86d 100644 --- a/vendor/core/src/extension/alterschema/linter/pattern_properties_default.h +++ b/vendor/core/src/extension/alterschema/linter/pattern_properties_default.h @@ -15,20 +15,21 @@ class PatternPropertiesDefault final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result 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#", - "http://json-schema.org/draft-03/schema#"}) && - schema.is_object() && schema.defines("patternProperties") && - schema.at("patternProperties").is_object() && - schema.at("patternProperties").empty(); + ONLY_CONTINUE_IF( + 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#", + "http://json-schema.org/draft-03/schema#"}) && + schema.is_object() && schema.defines("patternProperties") && + schema.at("patternProperties").is_object() && + schema.at("patternProperties").empty()); + return APPLIES_TO_KEYWORDS("patternProperties"); } - auto transform(JSON &schema) const -> void override { + auto transform(JSON &schema, const Result &) const -> void override { schema.erase("patternProperties"); } }; diff --git a/vendor/core/src/extension/alterschema/linter/properties_default.h b/vendor/core/src/extension/alterschema/linter/properties_default.h index 37ee1bee8..963466da9 100644 --- a/vendor/core/src/extension/alterschema/linter/properties_default.h +++ b/vendor/core/src/extension/alterschema/linter/properties_default.h @@ -15,24 +15,24 @@ class PropertiesDefault final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result 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#", - "http://json-schema.org/draft-03/schema#", - "http://json-schema.org/draft-02/schema#", - "http://json-schema.org/draft-02/hyper-schema#", - "http://json-schema.org/draft-01/schema#", - "http://json-schema.org/draft-01/hyper-schema#"}) && - schema.is_object() && schema.defines("properties") && - schema.at("properties").is_object() && - schema.at("properties").empty(); + ONLY_CONTINUE_IF( + 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#", + "http://json-schema.org/draft-03/schema#", + "http://json-schema.org/draft-02/schema#", + "http://json-schema.org/draft-02/hyper-schema#", + "http://json-schema.org/draft-01/schema#", + "http://json-schema.org/draft-01/hyper-schema#"}) && + schema.is_object() && schema.defines("properties") && + schema.at("properties").is_object() && schema.at("properties").empty()); + return APPLIES_TO_KEYWORDS("properties"); } - auto transform(JSON &schema) const -> void override { + auto transform(JSON &schema, const Result &) const -> void override { schema.erase("properties"); } }; diff --git a/vendor/core/src/extension/alterschema/linter/property_names_default.h b/vendor/core/src/extension/alterschema/linter/property_names_default.h index 8c6a9d950..749b477ea 100644 --- a/vendor/core/src/extension/alterschema/linter/property_names_default.h +++ b/vendor/core/src/extension/alterschema/linter/property_names_default.h @@ -15,18 +15,19 @@ class PropertyNamesDefault final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result 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#"}) && - schema.is_object() && schema.defines("propertyNames") && - schema.at("propertyNames").is_object() && - schema.at("propertyNames").empty(); + ONLY_CONTINUE_IF( + 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#"}) && + schema.is_object() && schema.defines("propertyNames") && + schema.at("propertyNames").is_object() && + schema.at("propertyNames").empty()); + return APPLIES_TO_KEYWORDS("propertyNames"); } - auto transform(JSON &schema) const -> void override { + auto transform(JSON &schema, const Result &) const -> void override { schema.erase("propertyNames"); } }; diff --git a/vendor/core/src/extension/alterschema/linter/property_names_type_default.h b/vendor/core/src/extension/alterschema/linter/property_names_type_default.h index 54cee777b..4ad72ee23 100644 --- a/vendor/core/src/extension/alterschema/linter/property_names_type_default.h +++ b/vendor/core/src/extension/alterschema/linter/property_names_type_default.h @@ -15,27 +15,27 @@ class PropertyNamesTypeDefault final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result 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#"}) && - schema.is_object() && schema.defines("propertyNames") && - schema.at("propertyNames").is_object() && - schema.at("propertyNames").defines("type") && - ((schema.at("propertyNames").at("type").is_string() && - schema.at("propertyNames").at("type").to_string() == "string") || - (schema.at("propertyNames").at("type").is_array() && - std::all_of( - schema.at("propertyNames").at("type").as_array().begin(), - schema.at("propertyNames").at("type").as_array().end(), - [](const auto &item) { - return item.is_string() && item.to_string() == "string"; - }))); + ONLY_CONTINUE_IF( + 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#"}) && + schema.is_object() && schema.defines("propertyNames") && + schema.at("propertyNames").is_object() && + schema.at("propertyNames").defines("type") && + ((schema.at("propertyNames").at("type").is_string() && + schema.at("propertyNames").at("type").to_string() == "string") || + (schema.at("propertyNames").at("type").is_array() && + std::all_of(schema.at("propertyNames").at("type").as_array().begin(), + schema.at("propertyNames").at("type").as_array().end(), + [](const auto &item) { + return item.is_string() && item.to_string() == "string"; + })))); + return APPLIES_TO_POINTERS({{"propertyNames", "type"}}); } - auto transform(JSON &schema) const -> void override { + auto transform(JSON &schema, const Result &) const -> void override { schema.at("propertyNames").erase("type"); } }; diff --git a/vendor/core/src/extension/alterschema/linter/single_type_array.h b/vendor/core/src/extension/alterschema/linter/single_type_array.h index 6713f4078..5b2677477 100644 --- a/vendor/core/src/extension/alterschema/linter/single_type_array.h +++ b/vendor/core/src/extension/alterschema/linter/single_type_array.h @@ -14,23 +14,24 @@ class SingleTypeArray final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result override { - return contains_any( - vocabularies, - {"https://json-schema.org/draft/2020-12/vocab/validation", - "https://json-schema.org/draft/2019-09/vocab/validation", - "http://json-schema.org/draft-07/schema#", - "http://json-schema.org/draft-06/schema#", - "http://json-schema.org/draft-04/schema#", - "http://json-schema.org/draft-03/schema#", - "http://json-schema.org/draft-02/schema#", - "http://json-schema.org/draft-01/schema#", - "http://json-schema.org/draft-00/schema#"}) && - schema.is_object() && schema.defines("type") && - schema.at("type").is_array() && schema.at("type").size() == 1 && - schema.at("type").front().is_string(); + ONLY_CONTINUE_IF( + contains_any(vocabularies, + {"https://json-schema.org/draft/2020-12/vocab/validation", + "https://json-schema.org/draft/2019-09/vocab/validation", + "http://json-schema.org/draft-07/schema#", + "http://json-schema.org/draft-06/schema#", + "http://json-schema.org/draft-04/schema#", + "http://json-schema.org/draft-03/schema#", + "http://json-schema.org/draft-02/schema#", + "http://json-schema.org/draft-01/schema#", + "http://json-schema.org/draft-00/schema#"}) && + schema.is_object() && schema.defines("type") && + schema.at("type").is_array() && schema.at("type").size() == 1 && + schema.at("type").front().is_string()); + return APPLIES_TO_KEYWORDS("type"); } - auto transform(JSON &schema) const -> void override { + auto transform(JSON &schema, const Result &) const -> void override { auto type{schema.at("type").front()}; schema.at("type").into(std::move(type)); } diff --git a/vendor/core/src/extension/alterschema/linter/then_empty.h b/vendor/core/src/extension/alterschema/linter/then_empty.h index 556436b54..83bece0e4 100644 --- a/vendor/core/src/extension/alterschema/linter/then_empty.h +++ b/vendor/core/src/extension/alterschema/linter/then_empty.h @@ -10,17 +10,20 @@ class ThenEmpty final : public SchemaTransformRule { const SchemaFrame &, const SchemaFrame::Location &, const SchemaWalker &, const SchemaResolver &) const -> SchemaTransformRule::Result 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#"}) && - schema.is_object() && schema.defines("then") && - is_schema(schema.at("then")) && is_empty_schema(schema.at("then")) && - (schema.at("then").is_object() || - (!schema.defines("if") || - !(schema.at("if").is_boolean() && schema.at("if").to_boolean()))); + ONLY_CONTINUE_IF( + 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#"}) && + schema.is_object() && schema.defines("then") && + is_schema(schema.at("then")) && is_empty_schema(schema.at("then")) && + (schema.at("then").is_object() || + (!schema.defines("if") || + !(schema.at("if").is_boolean() && schema.at("if").to_boolean())))); + return APPLIES_TO_KEYWORDS("then"); } - auto transform(JSON &schema) const -> void override { schema.erase("then"); } + auto transform(JSON &schema, const Result &) const -> void override { + schema.erase("then"); + } }; diff --git a/vendor/core/src/extension/alterschema/linter/then_without_if.h b/vendor/core/src/extension/alterschema/linter/then_without_if.h index 683e07f81..afc53f205 100644 --- a/vendor/core/src/extension/alterschema/linter/then_without_if.h +++ b/vendor/core/src/extension/alterschema/linter/then_without_if.h @@ -14,14 +14,16 @@ class ThenWithoutIf final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result 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#"}) && - schema.is_object() && schema.defines("then") && - !schema.defines("if"); + ONLY_CONTINUE_IF( + 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#"}) && + schema.is_object() && schema.defines("then") && !schema.defines("if")); + return APPLIES_TO_KEYWORDS("then"); } - auto transform(JSON &schema) const -> void override { schema.erase("then"); } + auto transform(JSON &schema, const Result &) const -> void override { + schema.erase("then"); + } }; diff --git a/vendor/core/src/extension/alterschema/linter/unevaluated_items_default.h b/vendor/core/src/extension/alterschema/linter/unevaluated_items_default.h index ce6297ee6..bfe253956 100644 --- a/vendor/core/src/extension/alterschema/linter/unevaluated_items_default.h +++ b/vendor/core/src/extension/alterschema/linter/unevaluated_items_default.h @@ -15,18 +15,20 @@ class UnevaluatedItemsDefault final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result override { - return contains_any( - vocabularies, - {"https://json-schema.org/draft/2020-12/vocab/unevaluated", - "https://json-schema.org/draft/2019-09/vocab/applicator"}) && - schema.is_object() && schema.defines("unevaluatedItems") && - ((schema.at("unevaluatedItems").is_boolean() && - schema.at("unevaluatedItems").to_boolean()) || - (schema.at("unevaluatedItems").is_object() && - schema.at("unevaluatedItems").empty())); + ONLY_CONTINUE_IF( + contains_any( + vocabularies, + {"https://json-schema.org/draft/2020-12/vocab/unevaluated", + "https://json-schema.org/draft/2019-09/vocab/applicator"}) && + schema.is_object() && schema.defines("unevaluatedItems") && + ((schema.at("unevaluatedItems").is_boolean() && + schema.at("unevaluatedItems").to_boolean()) || + (schema.at("unevaluatedItems").is_object() && + schema.at("unevaluatedItems").empty()))); + return APPLIES_TO_KEYWORDS("unevaluatedItems"); } - auto transform(JSON &schema) const -> void override { + auto transform(JSON &schema, const Result &) const -> void override { schema.erase("unevaluatedItems"); } }; diff --git a/vendor/core/src/extension/alterschema/linter/unevaluated_properties_default.h b/vendor/core/src/extension/alterschema/linter/unevaluated_properties_default.h index 64b0b594a..81d4742d8 100644 --- a/vendor/core/src/extension/alterschema/linter/unevaluated_properties_default.h +++ b/vendor/core/src/extension/alterschema/linter/unevaluated_properties_default.h @@ -15,18 +15,20 @@ class UnevaluatedPropertiesDefault final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result override { - return contains_any( - vocabularies, - {"https://json-schema.org/draft/2020-12/vocab/unevaluated", - "https://json-schema.org/draft/2019-09/vocab/applicator"}) && - schema.is_object() && schema.defines("unevaluatedProperties") && - ((schema.at("unevaluatedProperties").is_boolean() && - schema.at("unevaluatedProperties").to_boolean()) || - (schema.at("unevaluatedProperties").is_object() && - schema.at("unevaluatedProperties").empty())); + ONLY_CONTINUE_IF( + contains_any( + vocabularies, + {"https://json-schema.org/draft/2020-12/vocab/unevaluated", + "https://json-schema.org/draft/2019-09/vocab/applicator"}) && + schema.is_object() && schema.defines("unevaluatedProperties") && + ((schema.at("unevaluatedProperties").is_boolean() && + schema.at("unevaluatedProperties").to_boolean()) || + (schema.at("unevaluatedProperties").is_object() && + schema.at("unevaluatedProperties").empty()))); + return APPLIES_TO_KEYWORDS("unevaluatedProperties"); } - auto transform(JSON &schema) const -> void override { + auto transform(JSON &schema, const Result &) const -> void override { schema.erase("unevaluatedProperties"); } }; diff --git a/vendor/core/src/extension/alterschema/linter/unknown_keywords_prefix.h b/vendor/core/src/extension/alterschema/linter/unknown_keywords_prefix.h new file mode 100644 index 000000000..67c03f85b --- /dev/null +++ b/vendor/core/src/extension/alterschema/linter/unknown_keywords_prefix.h @@ -0,0 +1,43 @@ +class UnknownKeywordsPrefix final : public SchemaTransformRule { +public: + UnknownKeywordsPrefix() + : SchemaTransformRule{ + "unknown_keywords_prefix", + "Future versions of JSON Schema will refuse to evaluate unknown " + "keywords that don't have an x- prefix"} {}; + + [[nodiscard]] auto + condition(const JSON &schema, const JSON &, const Vocabularies &vocabularies, + const SchemaFrame &, const SchemaFrame::Location &, + const SchemaWalker &walker, const SchemaResolver &) const + -> SchemaTransformRule::Result override { + ONLY_CONTINUE_IF(schema.is_object()); + std::vector locations; + for (const auto &entry : schema.as_object()) { + if (entry.first.starts_with("x-")) { + continue; + } + + const auto metadata = walker(entry.first, vocabularies); + if (metadata.type == SchemaKeywordType::Unknown) { + locations.push_back(Pointer{entry.first}); + } + } + + ONLY_CONTINUE_IF(!locations.empty()); + return APPLIES_TO_POINTERS(std::move(locations)); + } + + auto transform(JSON &schema, const Result &result) const -> void override { + for (const auto &location : result.locations) { + const auto &keyword{location.at(0).to_property()}; + assert(schema.defines(keyword)); + std::string prefixed_name = "x-" + keyword; + while (schema.defines(prefixed_name)) { + prefixed_name.insert(0, "x-"); + } + + schema.rename(keyword, std::move(prefixed_name)); + } + } +}; diff --git a/vendor/core/src/extension/alterschema/linter/unnecessary_allof_wrapper_draft.h b/vendor/core/src/extension/alterschema/linter/unnecessary_allof_wrapper_draft.h index e6c12f4d7..599701520 100644 --- a/vendor/core/src/extension/alterschema/linter/unnecessary_allof_wrapper_draft.h +++ b/vendor/core/src/extension/alterschema/linter/unnecessary_allof_wrapper_draft.h @@ -11,25 +11,20 @@ class UnnecessaryAllOfWrapperDraft final : public SchemaTransformRule { const sourcemeta::core::JSON &, const sourcemeta::core::Vocabularies &vocabularies, const sourcemeta::core::SchemaFrame &, - const sourcemeta::core::SchemaFrame::Location &location, - const sourcemeta::core::SchemaWalker &, + const sourcemeta::core::SchemaFrame::Location &, + const sourcemeta::core::SchemaWalker &walker, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result override { - if (!contains_any(vocabularies, - {"http://json-schema.org/draft-07/schema#", - "http://json-schema.org/draft-06/schema#", - "http://json-schema.org/draft-04/schema#"})) { - return false; - } - - if (!schema.is_object() || !schema.defines("allOf") || - !schema.at("allOf").is_array()) { - return false; - } + ONLY_CONTINUE_IF(contains_any(vocabularies, + {"http://json-schema.org/draft-07/schema#", + "http://json-schema.org/draft-06/schema#", + "http://json-schema.org/draft-04/schema#"})); + ONLY_CONTINUE_IF(schema.is_object() && schema.defines("allOf") && + schema.at("allOf").is_array()); - std::ostringstream message; - bool applies{false}; + std::vector locations; const auto &all_of{schema.at("allOf")}; + bool multi_ref_only{all_of.size() > 1}; for (std::size_t index = 0; index < all_of.size(); index++) { const auto &entry{all_of.at(index)}; if (entry.is_object()) { @@ -41,29 +36,28 @@ class UnnecessaryAllOfWrapperDraft final : public SchemaTransformRule { // TODO: Ideally we also check for intersection of types in type // arrays or whether one is contained in the other schema.at("type") != entry.at("type")) { + multi_ref_only = false; continue; } for (const auto &subentry : entry.as_object()) { + if (walker(subentry.first, vocabularies).type != + SchemaKeywordType::Reference) { + multi_ref_only = false; + } + if (subentry.first != "$ref" && !schema.defines(subentry.first)) { - applies = true; - message << "- "; - message << to_string( - location.pointer.concat({"allOf", index, subentry.first})); - message << "\n"; + locations.push_back(Pointer{"allOf", index, subentry.first}); } } } } - if (applies) { - return message.str(); - } else { - return false; - } + ONLY_CONTINUE_IF(!locations.empty() && !multi_ref_only); + return APPLIES_TO_POINTERS(std::move(locations)); } - auto transform(JSON &schema) const -> void override { + auto transform(JSON &schema, const Result &) const -> void override { for (auto &entry : schema.at("allOf").as_array()) { if (entry.is_object()) { std::vector blacklist; diff --git a/vendor/core/src/extension/alterschema/linter/unnecessary_allof_wrapper_modern.h b/vendor/core/src/extension/alterschema/linter/unnecessary_allof_wrapper_modern.h index cc7b0f9b7..65cfa6982 100644 --- a/vendor/core/src/extension/alterschema/linter/unnecessary_allof_wrapper_modern.h +++ b/vendor/core/src/extension/alterschema/linter/unnecessary_allof_wrapper_modern.h @@ -11,30 +11,25 @@ class UnnecessaryAllOfWrapperModern final : public SchemaTransformRule { const sourcemeta::core::JSON &, const sourcemeta::core::Vocabularies &vocabularies, const sourcemeta::core::SchemaFrame &, - const sourcemeta::core::SchemaFrame::Location &location, - const sourcemeta::core::SchemaWalker &, + const sourcemeta::core::SchemaFrame::Location &, + const sourcemeta::core::SchemaWalker &walker, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result override { - if (!contains_any( - vocabularies, - {"https://json-schema.org/draft/2020-12/vocab/applicator", - "https://json-schema.org/draft/2019-09/vocab/applicator"})) { - return false; - } - - if (!schema.is_object() || !schema.defines("allOf") || - !schema.at("allOf").is_array()) { - return false; - } + ONLY_CONTINUE_IF(contains_any( + vocabularies, + {"https://json-schema.org/draft/2020-12/vocab/applicator", + "https://json-schema.org/draft/2019-09/vocab/applicator"})); + ONLY_CONTINUE_IF(schema.is_object() && schema.defines("allOf") && + schema.at("allOf").is_array()); const auto has_validation{contains_any( vocabularies, {"https://json-schema.org/draft/2020-12/vocab/validation", "https://json-schema.org/draft/2019-09/vocab/validation"})}; - std::ostringstream message; - bool applies{false}; + std::vector locations; const auto &all_of{schema.at("allOf")}; + bool multi_ref_only{all_of.size() > 1}; for (std::size_t index = 0; index < all_of.size(); index++) { const auto &entry{all_of.at(index)}; if (entry.is_object()) { @@ -46,31 +41,30 @@ class UnnecessaryAllOfWrapperModern final : public SchemaTransformRule { // TODO: Ideally we also check for intersection of types in type // arrays or whether one is contained in the other schema.at("type") != entry.at("type")) { + multi_ref_only = false; continue; } for (const auto &subentry : entry.as_object()) { + if (walker(subentry.first, vocabularies).type != + SchemaKeywordType::Reference) { + multi_ref_only = false; + } + // TODO: Have another rule that removes a keyword if its exactly // equal to an instance of the same keyword outside the wrapper if (!schema.defines(subentry.first)) { - applies = true; - message << "- "; - message << to_string( - location.pointer.concat({"allOf", index, subentry.first})); - message << "\n"; + locations.push_back(Pointer{"allOf", index, subentry.first}); } } } } - if (applies) { - return message.str(); - } else { - return false; - } + ONLY_CONTINUE_IF(!locations.empty() && !multi_ref_only); + return APPLIES_TO_POINTERS(std::move(locations)); } - auto transform(JSON &schema) const -> void override { + auto transform(JSON &schema, const Result &) const -> void override { for (auto &entry : schema.at("allOf").as_array()) { if (entry.is_object()) { std::vector blacklist; diff --git a/vendor/core/src/extension/alterschema/linter/unnecessary_allof_wrapper_properties.h b/vendor/core/src/extension/alterschema/linter/unnecessary_allof_wrapper_properties.h index ac266a263..b57035e9d 100644 --- a/vendor/core/src/extension/alterschema/linter/unnecessary_allof_wrapper_properties.h +++ b/vendor/core/src/extension/alterschema/linter/unnecessary_allof_wrapper_properties.h @@ -16,7 +16,8 @@ class UnnecessaryAllOfWrapperProperties final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result override { - if (contains_any( + ONLY_CONTINUE_IF( + contains_any( vocabularies, // TODO: Make this work on older dialects. Right we can't do that // safely for Draft 7 and earlier if `properties` is a sibling of @@ -25,23 +26,27 @@ class UnnecessaryAllOfWrapperProperties final : public SchemaTransformRule { "https://json-schema.org/draft/2019-09/vocab/applicator"}) && schema.is_object() && schema.defines("allOf") && schema.at("allOf").is_array() && schema.defines("properties") && - schema.at("properties").is_object()) { - for (const auto &entry : schema.at("allOf").as_array()) { - if (entry.is_object() && entry.defines("properties") && - entry.at("properties").is_object()) { - for (const auto &subentry : entry.at("properties").as_object()) { - if (!schema.at("properties").defines(subentry.first)) { - return true; - } + schema.at("properties").is_object()); + + std::size_t cursor{0}; + for (const auto &entry : schema.at("allOf").as_array()) { + if (entry.is_object() && entry.defines("properties") && + entry.at("properties").is_object()) { + for (const auto &subentry : entry.at("properties").as_object()) { + if (!schema.at("properties").defines(subentry.first)) { + return APPLIES_TO_POINTERS( + {{"allOf", cursor, "properties", subentry.first}}); } } } + + cursor += 1; } return false; } - auto transform(JSON &schema) const -> void override { + auto transform(JSON &schema, const Result &) const -> void override { for (auto &entry : schema.at("allOf").as_array()) { if (entry.is_object() && entry.defines("properties")) { std::vector blacklist; diff --git a/vendor/core/src/extension/alterschema/linter/unsatisfiable_max_contains.h b/vendor/core/src/extension/alterschema/linter/unsatisfiable_max_contains.h index dd3c1bfb7..6f28212a8 100644 --- a/vendor/core/src/extension/alterschema/linter/unsatisfiable_max_contains.h +++ b/vendor/core/src/extension/alterschema/linter/unsatisfiable_max_contains.h @@ -16,18 +16,20 @@ class UnsatisfiableMaxContains final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result override { - return contains_any( - vocabularies, - {"https://json-schema.org/draft/2020-12/vocab/validation", - "https://json-schema.org/draft/2019-09/vocab/validation"}) && - schema.is_object() && schema.defines("maxContains") && - schema.at("maxContains").is_integer() && - schema.defines("maxItems") && schema.at("maxItems").is_integer() && - schema.at("maxContains").to_integer() >= - schema.at("maxItems").to_integer(); + ONLY_CONTINUE_IF( + contains_any( + vocabularies, + {"https://json-schema.org/draft/2020-12/vocab/validation", + "https://json-schema.org/draft/2019-09/vocab/validation"}) && + schema.is_object() && schema.defines("maxContains") && + schema.at("maxContains").is_integer() && schema.defines("maxItems") && + schema.at("maxItems").is_integer() && + schema.at("maxContains").to_integer() >= + schema.at("maxItems").to_integer()); + return APPLIES_TO_KEYWORDS("maxContains", "maxItems"); } - auto transform(JSON &schema) const -> void override { + auto transform(JSON &schema, const Result &) const -> void override { schema.erase("maxContains"); } }; diff --git a/vendor/core/src/extension/alterschema/linter/unsatisfiable_min_properties.h b/vendor/core/src/extension/alterschema/linter/unsatisfiable_min_properties.h index b345cebdf..7a38b2969 100644 --- a/vendor/core/src/extension/alterschema/linter/unsatisfiable_min_properties.h +++ b/vendor/core/src/extension/alterschema/linter/unsatisfiable_min_properties.h @@ -15,23 +15,23 @@ class UnsatisfiableMinProperties final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> sourcemeta::core::SchemaTransformRule::Result override { - return contains_any( - vocabularies, - {"https://json-schema.org/draft/2020-12/vocab/validation", - "https://json-schema.org/draft/2019-09/vocab/validation", - "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("minProperties") && - schema.at("minProperties").is_integer() && - schema.defines("required") && schema.at("required").is_array() && - schema.at("required").unique() && - std::cmp_greater_equal(schema.at("required").size(), - static_cast( - schema.at("minProperties").to_integer())); + ONLY_CONTINUE_IF( + contains_any(vocabularies, + {"https://json-schema.org/draft/2020-12/vocab/validation", + "https://json-schema.org/draft/2019-09/vocab/validation", + "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("minProperties") && + schema.at("minProperties").is_integer() && schema.defines("required") && + schema.at("required").is_array() && schema.at("required").unique() && + std::cmp_greater_equal(schema.at("required").size(), + static_cast( + schema.at("minProperties").to_integer()))); + return APPLIES_TO_KEYWORDS("minProperties", "required"); } - auto transform(JSON &schema) const -> void override { + auto transform(JSON &schema, const Result &) const -> void override { schema.erase("minProperties"); } }; diff --git a/vendor/core/src/extension/alterschema/strict/required_properties_in_properties.h b/vendor/core/src/extension/alterschema/strict/required_properties_in_properties.h new file mode 100644 index 000000000..0b161ad75 --- /dev/null +++ b/vendor/core/src/extension/alterschema/strict/required_properties_in_properties.h @@ -0,0 +1,100 @@ +class RequiredPropertiesInProperties final : public SchemaTransformRule { +public: + RequiredPropertiesInProperties() + : SchemaTransformRule{ + "strict/required_properties_in_properties", + "Every property listed in the `required` keyword must be " + "explicitly defined using the `properties` keyword"} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &root, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &frame, + const sourcemeta::core::SchemaFrame::Location &location, + const sourcemeta::core::SchemaWalker &walker, + const sourcemeta::core::SchemaResolver &resolver) const + -> sourcemeta::core::SchemaTransformRule::Result override { + ONLY_CONTINUE_IF( + ((vocabularies.contains( + "https://json-schema.org/draft/2020-12/vocab/validation") && + vocabularies.contains( + "https://json-schema.org/draft/2020-12/vocab/applicator")) || + (vocabularies.contains( + "https://json-schema.org/draft/2019-09/vocab/validation") && + vocabularies.contains( + "https://json-schema.org/draft/2019-09/vocab/applicator")) || + contains_any(vocabularies, + {"http://json-schema.org/draft-07/schema#", + "http://json-schema.org/draft-06/schema#", + "http://json-schema.org/draft-04/schema#", + "http://json-schema.org/draft-03/schema#"})) && + schema.is_object() && schema.defines("required") && + schema.at("required").is_array() && !schema.at("required").empty()); + + std::vector locations; + std::size_t index{0}; + for (const auto &property : schema.at("required").as_array()) { + if (property.is_string() && + !this->defined_in_properties_sibling(schema, property.to_string()) && + !this->defined_in_properties_parent(root, frame, location, walker, + resolver, property.to_string())) { + locations.push_back(Pointer{"required", index}); + } + + index += 1; + } + + ONLY_CONTINUE_IF(!locations.empty()); + return APPLIES_TO_POINTERS(std::move(locations)); + } + + auto transform(JSON &schema, const Result &result) const -> void override { + schema.assign_if_missing("properties", + sourcemeta::core::JSON::make_object()); + for (const auto &location : result.locations) { + const auto &property{ + schema.at("required").at(location.at(1).to_index()).to_string()}; + schema.at("properties").assign(property, sourcemeta::core::JSON{true}); + } + } + +private: + [[nodiscard]] auto + defined_in_properties_sibling(const JSON &schema, + const JSON::String &property) const -> bool { + assert(schema.is_object()); + return schema.defines("properties") && + schema.at("properties").is_object() && + schema.at("properties").defines(property); + }; + + [[nodiscard]] auto + defined_in_properties_parent(const JSON &root, const SchemaFrame &frame, + const SchemaFrame::Location &location, + const SchemaWalker &walker, + const SchemaResolver &resolver, + const JSON::String &property) const -> bool { + if (location.parent.has_value()) { + const auto relative_pointer{ + location.pointer.resolve_from(location.parent.value())}; + assert(!relative_pointer.empty() && relative_pointer.at(0).is_property()); + const auto parent{ + frame.traverse(frame.uri(location.parent.value()).value().get())}; + assert(parent.has_value()); + const auto type{walker(relative_pointer.at(0).to_property(), + frame.vocabularies(parent.value().get(), resolver)) + .type}; + if (type == SchemaKeywordType::ApplicatorElementsInPlaceSome || + type == SchemaKeywordType::ApplicatorElementsInPlace || + type == SchemaKeywordType::ApplicatorValueInPlaceMaybe || + type == SchemaKeywordType::ApplicatorValueInPlaceNegate || + type == SchemaKeywordType::ApplicatorValueInPlaceOther) { + return this->defined_in_properties_sibling( + get(root, location.parent.value()), property); + } + } + + return false; + }; +}; diff --git a/vendor/core/src/extension/build/adapter_filesystem.cc b/vendor/core/src/extension/build/adapter_filesystem.cc index edb7e11dd..d14636c40 100644 --- a/vendor/core/src/extension/build/adapter_filesystem.cc +++ b/vendor/core/src/extension/build/adapter_filesystem.cc @@ -4,6 +4,7 @@ #include // assert #include // std::ofstream +#include // std::unique_lock namespace sourcemeta::core { @@ -30,6 +31,11 @@ auto BuildAdapterFilesystem::read_dependencies(const node_type &path) const BuildDependencies deps; std::string line; while (std::getline(stream, line)) { + // Prevent CRLF on Windows + if (!line.empty() && line.back() == '\r') { + line.pop_back(); + } + if (!line.empty()) { deps.emplace_back(line); } @@ -71,24 +77,30 @@ auto BuildAdapterFilesystem::refresh(const node_type &path) -> void { // too much on file-system specific non-sense const auto value{std::filesystem::file_time_type::clock::now()}; // Because builds are typically ran in parallel - std::lock_guard lock{this->mutex}; + std::unique_lock lock{this->mutex}; this->marks.insert_or_assign(path, value); } -auto BuildAdapterFilesystem::mark(const node_type &path) const +auto BuildAdapterFilesystem::mark(const node_type &path) -> std::optional { assert(path.is_absolute()); - if (std::filesystem::exists(path)) { + + { + // Because a read operation while a write operation is taking place is + // undefined behaviour + std::shared_lock lock{this->mutex}; const auto match{this->marks.find(path)}; - if (match == this->marks.cend()) { - // Keep in mind that depending on the OS, filesystem, and even standard - // library implementation, this value might not be very reliable. In fact, - // in many cases it can be outdated. Therefore, we never cache this value - return std::filesystem::last_write_time(path); - } else { + if (match != this->marks.end()) { return match->second; } - } else { + } + + try { + // Keep in mind that depending on the OS, filesystem, and even standard + // library implementation, this value might not be very reliable. In fact, + // in many cases it can be outdated. Therefore, we never cache this value + return std::filesystem::last_write_time(path); + } catch (const std::filesystem::filesystem_error &) { return std::nullopt; } } diff --git a/vendor/core/src/extension/build/include/sourcemeta/core/build_adapter_filesystem.h b/vendor/core/src/extension/build/include/sourcemeta/core/build_adapter_filesystem.h index a1ef12ab9..a71fa6c2a 100644 --- a/vendor/core/src/extension/build/include/sourcemeta/core/build_adapter_filesystem.h +++ b/vendor/core/src/extension/build/include/sourcemeta/core/build_adapter_filesystem.h @@ -10,8 +10,8 @@ // NOLINTEND(misc-include-cleaner) #include // std::filesystem -#include // std::mutex, std::lock_guard #include // std::optional +#include // std::shared_mutex #include // std::string #include // std::unordered_map @@ -34,10 +34,7 @@ class SOURCEMETA_CORE_BUILD_EXPORT BuildAdapterFilesystem { const BuildDependencies &dependencies) -> void; auto refresh(const node_type &path) -> void; - - [[nodiscard]] auto mark(const node_type &path) const - -> std::optional; - + [[nodiscard]] auto mark(const node_type &path) -> std::optional; [[nodiscard]] auto is_newer_than(const mark_type left, const mark_type right) const -> bool; @@ -50,7 +47,7 @@ class SOURCEMETA_CORE_BUILD_EXPORT BuildAdapterFilesystem { #endif std::string extension{".deps"}; std::unordered_map marks; - std::mutex mutex; + std::shared_mutex mutex; #if defined(_MSC_VER) #pragma warning(default : 4251 4275) #endif diff --git a/vendor/core/src/lang/parallel/CMakeLists.txt b/vendor/core/src/lang/parallel/CMakeLists.txt new file mode 100644 index 000000000..ef981e99b --- /dev/null +++ b/vendor/core/src/lang/parallel/CMakeLists.txt @@ -0,0 +1,8 @@ +sourcemeta_library(NAMESPACE sourcemeta PROJECT core NAME parallel + PRIVATE_HEADERS for_each.h) + +if(SOURCEMETA_CORE_INSTALL) + sourcemeta_library_install(NAMESPACE sourcemeta PROJECT core NAME parallel) +endif() + +target_link_libraries(sourcemeta_core_parallel INTERFACE Threads::Threads) diff --git a/vendor/core/src/lang/parallel/include/sourcemeta/core/parallel.h b/vendor/core/src/lang/parallel/include/sourcemeta/core/parallel.h new file mode 100644 index 000000000..be6a9f14f --- /dev/null +++ b/vendor/core/src/lang/parallel/include/sourcemeta/core/parallel.h @@ -0,0 +1,17 @@ +#ifndef SOURCEMETA_CORE_PARALLEL_H_ +#define SOURCEMETA_CORE_PARALLEL_H_ + +// NOLINTBEGIN(misc-include-cleaner) +#include +// NOLINTEND(misc-include-cleaner) + +/// @defgroup parallel Parallel +/// @brief Growing collection of utilities for parallel computing +/// +/// This functionality is included as follows: +/// +/// ```cpp +/// #include +/// ``` + +#endif diff --git a/vendor/core/src/lang/parallel/include/sourcemeta/core/parallel_for_each.h b/vendor/core/src/lang/parallel/include/sourcemeta/core/parallel_for_each.h new file mode 100644 index 000000000..554029467 --- /dev/null +++ b/vendor/core/src/lang/parallel/include/sourcemeta/core/parallel_for_each.h @@ -0,0 +1,196 @@ +#ifndef SOURCEMETA_CORE_PARALLEL_FOR_EACH_H_ +#define SOURCEMETA_CORE_PARALLEL_FOR_EACH_H_ + +#include // std::max +#include // std::copyable, std::invocable +#include // std::exception_ptr, std::current_exception, std::rethrow_exception +#include // std::function +#include // std::input_iterator, std::iter_reference_t +#include // std::mutex, std::lock_guard +#include // std::queue +#include // std::runtime_error +#include // std::thread +#include // std::forward +#include // std::vector + +#if defined(_WIN32) +#include // _beginthreadex +#define NOMINMAX +#include +#else +#include +#endif + +namespace sourcemeta::core { + +#ifndef DOXYGEN +#if defined(_WIN32) +// See +// https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/beginthread-beginthreadex?view=msvc-170 +inline unsigned __stdcall parallel_for_each_windows_thread_start( + void *argument) { + auto *function_ptr = static_cast *>(argument); + (*function_ptr)(); + delete function_ptr; + return 0; +} +#endif +#endif + +/// @ingroup parallel +/// +/// Process a collection in parallel. If the parallelism is set to zero, the +/// function will run using the available number of cores. If the stack size is +/// set to zero, the platform default applies. For example: +/// +/// ```c++ +/// #include +/// #include +/// #include +/// #include +/// +/// std::vector input; +/// for (std::size_t index = 0; index < 20; index++) { +/// input.push_back(index); +/// } +/// +/// std::mutex mutex; +/// std::vector result; +/// +/// sourcemeta::core::parallel_for_each( +/// input.cbegin(), input.cend(), +/// [&mutex, &result](const auto value, +/// const auto parallelism, +/// const auto cursor) { +/// std::lock_guard lock{mutex}; +/// result.push_back(value); +/// std::cerr << "Processing " << cursor +/// << " with parallelism " << parallelism << "\n"; +/// }); +/// ``` +template + requires std::input_iterator && std::copyable && + std::invocable, + std::size_t, std::size_t> +auto parallel_for_each( + Iterator first, Iterator last, Callback &&callback, + const std::size_t parallelism = std::thread::hardware_concurrency(), + const std::size_t stack_size_bytes = 0) -> void { + std::queue tasks; + for (auto iterator = first; iterator != last; ++iterator) { + tasks.push(iterator); + } + + std::mutex queue_mutex; + std::mutex exception_mutex; + + auto effective_callback = std::forward(callback); + + std::exception_ptr exception = nullptr; + auto handle_exception = [&exception_mutex, + &exception](std::exception_ptr pointer) { + std::lock_guard lock{exception_mutex}; + if (!exception) { + exception = pointer; + } + }; + + const auto effective_parallelism{ + std::max(parallelism, static_cast(1))}; + std::vector workers; + workers.reserve(effective_parallelism); + + const auto total{tasks.size()}; + + // Worker function that runs the actual per-item work and captures the + // environment by reference. It will be heap-copied into the native thread + // API. + auto worker_callable = [&tasks, &queue_mutex, &effective_callback, + &handle_exception, effective_parallelism, total] { + try { + while (true) { + Iterator iterator; + std::size_t cursor{0}; + { + std::lock_guard lock{queue_mutex}; + if (tasks.empty()) { + return; + } + iterator = tasks.front(); + cursor = total - tasks.size() + 1; + tasks.pop(); + } + effective_callback(*iterator, effective_parallelism, cursor); + } + } catch (...) { + handle_exception(std::current_exception()); + } + }; + +#if defined(_WIN32) + for (std::size_t index = 0; index < effective_parallelism; ++index) { + auto *heap_function = new std::function(worker_callable); + if (stack_size_bytes > static_cast(UINT_MAX)) { + delete heap_function; + throw std::runtime_error( + "The requested stack size is too large for this platform"); + } + + auto raw_handle = _beginthreadex( + nullptr, static_cast(stack_size_bytes), + ¶llel_for_each_windows_thread_start, heap_function, 0, nullptr); + if (raw_handle == 0) { + delete heap_function; + throw std::runtime_error("Could not create thread"); + } + + HANDLE thread_handle = reinterpret_cast(raw_handle); + workers.emplace_back([thread_handle] { + WaitForSingleObject(thread_handle, INFINITE); + CloseHandle(thread_handle); + }); + } +#else + for (std::size_t index = 0; index < effective_parallelism; ++index) { + // We can't use std::thread, as it doesn't let us tweak the thread stack + // size + pthread_attr_t attr; + pthread_attr_init(&attr); + if (stack_size_bytes > 0) { + pthread_attr_setstacksize(&attr, stack_size_bytes); + } + + auto *heap_function = new std::function(worker_callable); + pthread_t pthread_handle; + auto raw_handle = pthread_create( + &pthread_handle, &attr, + [](void *arg) -> void * { + auto *function_ptr = static_cast *>(arg); + (*function_ptr)(); + delete function_ptr; + return nullptr; + }, + heap_function); + if (raw_handle != 0) { + pthread_attr_destroy(&attr); + delete heap_function; + throw std::runtime_error("Could not create thread"); + } + workers.emplace_back( + [pthread_handle] { pthread_join(pthread_handle, nullptr); }); + pthread_attr_destroy(&attr); + } +#endif + + for (auto &worker_thread : workers) { + worker_thread.join(); + } + + if (exception) { + std::rethrow_exception(exception); + } +} + +} // namespace sourcemeta::core + +#endif