diff --git a/DEPENDENCIES b/DEPENDENCIES index 27bb3e42..243f6f41 100644 --- a/DEPENDENCIES +++ b/DEPENDENCIES @@ -1,11 +1,11 @@ vendorpull https://github.com/sourcemeta/vendorpull 1dcbac42809cf87cb5b045106b863e17ad84ba02 uwebsockets https://github.com/uNetworking/uWebSockets v20.74.0 -core https://github.com/sourcemeta/core 45dd685ea143672fa7384e210f64f8b6de13487e -blaze https://github.com/sourcemeta/blaze 644a3582f3015a92cccdb5ca582c9b2c97e93878 -jsonbinpack https://github.com/sourcemeta/jsonbinpack 0c2340990bf31c630155991a93306990d9d94fd4 +core https://github.com/sourcemeta/core 6d1acddbe4888f0ca3b98a13f13f7b1fa44be964 +blaze https://github.com/sourcemeta/blaze ed6889f138b0b446cce79e5adb4c7facaefd6846 +jsonbinpack https://github.com/sourcemeta/jsonbinpack 5e0e10c4550a2e5d4c13af037d3bb295acb19726 hydra https://github.com/sourcemeta/hydra c86d2165a2f27f838837af1a5af24b1055a35317 -codegen https://github.com/sourcemeta/codegen 10466c0216d36cdc65cf8e951f1dcf85cc580502 -jsonschema https://github.com/sourcemeta/jsonschema c15220ffa8c168ab17ba2c02e11d95504e463ddb +codegen https://github.com/sourcemeta/codegen f1c1cc50799b69ce8a5bb970e606f229b7fe60a1 +jsonschema https://github.com/sourcemeta/jsonschema v14.6.0 bootstrap https://github.com/twbs/bootstrap v5.3.3 bootstrap-icons https://github.com/twbs/icons v1.11.3 collections/sourcemeta/std/v0 https://github.com/sourcemeta/std v0.4.0 diff --git a/collections/self/v1/schemas/api/schemas/locations/response.json b/collections/self/v1/schemas/api/schemas/locations/response.json index 7d36159a..deeafcbe 100644 --- a/collections/self/v1/schemas/api/schemas/locations/response.json +++ b/collections/self/v1/schemas/api/schemas/locations/response.json @@ -38,7 +38,8 @@ "relativePointer", "dialect", "baseDialect", - "propertyName" + "propertyName", + "orphan" ], "properties": { "type": { @@ -77,6 +78,9 @@ }, "propertyName": { "type": "boolean" + }, + "orphan": { + "type": "boolean" } }, "additionalProperties": false diff --git a/docs/api.md b/docs/api.md index 58a099bc..89cb0e1e 100644 --- a/docs/api.md +++ b/docs/api.md @@ -332,6 +332,7 @@ GET /self/v1/api/schemas/locations/{path} | `/static/*/position/2` | Integer | Yes | Ending line number | | `/static/*/position/3` | Integer | Yes | Ending column number | | `/static/*/propertyName` | Boolean | Yes | Whether the location applies to a property name | + | `/static/*/orphan` | Boolean | Yes | Whether the location is inside a definitions container | | `/dynamic` | Object | Yes | Dynamic URI locations within the schema | | `/dynamic/*` | Object | Yes | Metadata for a specific URI location | | `/dynamic/*/base` | String | Yes | The base URI of the location | @@ -348,6 +349,7 @@ GET /self/v1/api/schemas/locations/{path} | `/dynamic/*/position/2` | Integer | Yes | Ending line number | | `/dynamic/*/position/3` | Integer | Yes | Ending column number | | `/dynamic/*/propertyName` | Boolean | Yes | Whether the location applies to a property name | + | `/dynamic/*/orphan` | Boolean | Yes | Whether the location is inside a definitions container | === "404" diff --git a/vendor/blaze/src/compiler/compile.cc b/vendor/blaze/src/compiler/compile.cc index d36d2684..48b18cf6 100644 --- a/vendor/blaze/src/compiler/compile.cc +++ b/vendor/blaze/src/compiler/compile.cc @@ -236,6 +236,22 @@ auto compile(const sourcemeta::core::JSON &schema, continue; } + auto reference_origin{frame.traverse(reference.first.second)}; + assert(reference_origin.has_value()); + while (reference_origin->get().type == + sourcemeta::core::SchemaFrame::LocationType::Pointer && + reference_origin->get().parent.has_value()) { + reference_origin = frame.traverse(reference_origin->get().parent.value()); + assert(reference_origin.has_value()); + } + + // Skip unreachable targets + if (reference_origin->get().type != + sourcemeta::core::SchemaFrame::LocationType::Pointer && + !frame.is_reachable(reference_origin->get(), walker, resolver)) { + continue; + } + assert(target_types.contains(reference.second.destination)); const auto &[needs_name, needs_instance]{target_types.at(reference.second.destination)}; @@ -265,6 +281,11 @@ auto compile(const sourcemeta::core::JSON &schema, continue; } + // Skip unreachable dynamic anchors + if (!frame.is_reachable(entry.second, walker, resolver)) { + continue; + } + targets_map.emplace(std::make_tuple(entry.first.first, std::string_view{entry.first.second}, false), @@ -304,6 +325,12 @@ auto compile(const sourcemeta::core::JSON &schema, continue; } + // Skip unreachable dynamic anchors + if (!context.frame.is_reachable(entry.second, context.walker, + context.resolver)) { + continue; + } + // Compute the hash for this dynamic anchor const sourcemeta::core::URI anchor_uri{entry.first.second}; const auto label{Evaluator::hash( @@ -403,19 +430,26 @@ auto compile(const sourcemeta::core::JSON &schema, const std::string_view default_id, const std::optional &tweaks) -> Template { assert(is_schema(schema)); + const auto effective_tweaks{tweaks.value_or(Tweaks{})}; - // Make sure the input schema is bundled, otherwise we won't be able to - // resolve remote references here - const sourcemeta::core::JSON result{sourcemeta::core::bundle( - schema, walker, resolver, default_dialect, default_id)}; - - // Perform framing to resolve references later on - sourcemeta::core::SchemaFrame frame{ - sourcemeta::core::SchemaFrame::Mode::References}; - frame.analyse(result, walker, resolver, default_dialect, default_id); - - return compile(result, walker, resolver, compiler, frame, mode, - default_dialect, default_id, tweaks); + if (effective_tweaks.assume_bundled) { + sourcemeta::core::SchemaFrame frame{ + sourcemeta::core::SchemaFrame::Mode::References}; + frame.analyse(schema, walker, resolver, default_dialect, default_id); + return compile(schema, walker, resolver, compiler, frame, mode, + default_dialect, default_id, tweaks); + } else { + // Make sure the input schema is bundled, otherwise we won't be able to + // resolve remote references here + const sourcemeta::core::JSON result{sourcemeta::core::bundle( + schema, walker, resolver, default_dialect, default_id)}; + + sourcemeta::core::SchemaFrame frame{ + sourcemeta::core::SchemaFrame::Mode::References}; + frame.analyse(result, walker, resolver, default_dialect, default_id); + return compile(result, walker, resolver, compiler, frame, mode, + default_dialect, default_id, tweaks); + } } auto compile(const Context &context, const SchemaContext &schema_context, diff --git a/vendor/blaze/src/compiler/include/sourcemeta/blaze/compiler.h b/vendor/blaze/src/compiler/include/sourcemeta/blaze/compiler.h index 8bd56fc7..04c2ae46 100644 --- a/vendor/blaze/src/compiler/include/sourcemeta/blaze/compiler.h +++ b/vendor/blaze/src/compiler/include/sourcemeta/blaze/compiler.h @@ -94,6 +94,9 @@ struct Tweaks { bool properties_reorder{true}; /// Inline jump targets with fewer instructions than this threshold std::size_t target_inline_threshold{50}; + /// Assume the schema is already bundled with no pending unresolved external + /// references + bool assume_bundled{false}; }; /// @ingroup compiler diff --git a/vendor/blaze/src/linter/include/sourcemeta/blaze/linter.h b/vendor/blaze/src/linter/include/sourcemeta/blaze/linter.h index c8d4a3b6..bb7227a9 100644 --- a/vendor/blaze/src/linter/include/sourcemeta/blaze/linter.h +++ b/vendor/blaze/src/linter/include/sourcemeta/blaze/linter.h @@ -8,6 +8,8 @@ #include #include +#include // std::true_type + /// @defgroup linter Linter /// @brief A set of JSON Schema linter extensions powered by Blaze /// @@ -26,6 +28,8 @@ namespace sourcemeta::blaze { class SOURCEMETA_BLAZE_LINTER_EXPORT ValidExamples final : public sourcemeta::core::SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; ValidExamples(Compiler compiler); [[nodiscard]] auto condition(const sourcemeta::core::JSON &, const sourcemeta::core::JSON &, @@ -59,6 +63,8 @@ class SOURCEMETA_BLAZE_LINTER_EXPORT ValidExamples final class SOURCEMETA_BLAZE_LINTER_EXPORT ValidDefault final : public sourcemeta::core::SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; ValidDefault(Compiler compiler); [[nodiscard]] auto condition(const sourcemeta::core::JSON &, const sourcemeta::core::JSON &, diff --git a/vendor/blaze/src/linter/valid_default.cc b/vendor/blaze/src/linter/valid_default.cc index af058c38..2b922566 100644 --- a/vendor/blaze/src/linter/valid_default.cc +++ b/vendor/blaze/src/linter/valid_default.cc @@ -56,17 +56,18 @@ auto ValidDefault::condition( default_id = ""; } - const auto subschema{sourcemeta::core::wrap( - root, sourcemeta::core::to_pointer(location.pointer), resolver, - location.dialect)}; + sourcemeta::core::WeakPointer base; + const auto subschema{ + sourcemeta::core::wrap(root, frame, location, resolver, base)}; + // To avoid bundling twice in vain + Tweaks tweaks{.assume_bundled = frame.standalone()}; const auto schema_template{compile(subschema, walker, resolver, - this->compiler_, Mode::FastValidation, - location.dialect, default_id)}; + this->compiler_, Mode::Exhaustive, + location.dialect, default_id, tweaks)}; const auto &instance{schema.at("default")}; Evaluator evaluator; - const std::string ref{"$ref"}; - SimpleOutput output{instance, {std::cref(ref)}}; + SimpleOutput output{instance, base}; const auto result{ evaluator.validate(schema_template, instance, std::ref(output))}; if (result) { diff --git a/vendor/blaze/src/linter/valid_examples.cc b/vendor/blaze/src/linter/valid_examples.cc index 6a6a79c9..4723ffa0 100644 --- a/vendor/blaze/src/linter/valid_examples.cc +++ b/vendor/blaze/src/linter/valid_examples.cc @@ -60,18 +60,19 @@ auto ValidExamples::condition( default_id = ""; } - const auto subschema{sourcemeta::core::wrap( - root, sourcemeta::core::to_pointer(location.pointer), resolver, - location.dialect)}; + sourcemeta::core::WeakPointer base; + const auto subschema{ + sourcemeta::core::wrap(root, frame, location, resolver, base)}; + // To avoid bundling twice in vain + Tweaks tweaks{.assume_bundled = frame.standalone()}; const auto schema_template{compile(subschema, walker, resolver, - this->compiler_, Mode::FastValidation, - location.dialect, default_id)}; + this->compiler_, Mode::Exhaustive, + location.dialect, default_id, tweaks)}; Evaluator evaluator; std::size_t cursor{0}; for (const auto &example : schema.at("examples").as_array()) { - const std::string ref{"$ref"}; - SimpleOutput output{example, {std::cref(ref)}}; + SimpleOutput output{example, base}; const auto result{ evaluator.validate(schema_template, example, std::ref(output))}; if (!result) { diff --git a/vendor/codegen/src/generator/include/sourcemeta/codegen/generator_typescript.h b/vendor/codegen/src/generator/include/sourcemeta/codegen/generator_typescript.h index 96435c07..d8fce4d2 100644 --- a/vendor/codegen/src/generator/include/sourcemeta/codegen/generator_typescript.h +++ b/vendor/codegen/src/generator/include/sourcemeta/codegen/generator_typescript.h @@ -24,6 +24,7 @@ class SOURCEMETA_CODEGEN_GENERATOR_EXPORT TypeScript { auto operator()(const IREnumeration &entry) -> void; auto operator()(const IRObject &entry) -> void; auto operator()(const IRImpossible &entry) -> void; + auto operator()(const IRAny &entry) -> void; auto operator()(const IRArray &entry) -> void; auto operator()(const IRReference &entry) -> void; auto operator()(const IRTuple &entry) -> void; diff --git a/vendor/codegen/src/generator/typescript.cc b/vendor/codegen/src/generator/typescript.cc index 1bbc6d45..640dce9e 100644 --- a/vendor/codegen/src/generator/typescript.cc +++ b/vendor/codegen/src/generator/typescript.cc @@ -172,6 +172,12 @@ auto TypeScript::operator()(const IRImpossible &entry) -> void { << " = never;\n"; } +auto TypeScript::operator()(const IRAny &entry) -> void { + this->output << "export type " + << mangle(this->prefix, entry.pointer, entry.symbol, this->cache) + << " = unknown;\n"; +} + auto TypeScript::operator()(const IRArray &entry) -> void { this->output << "export type " << mangle(this->prefix, entry.pointer, entry.symbol, this->cache) diff --git a/vendor/codegen/src/ir/include/sourcemeta/codegen/ir.h b/vendor/codegen/src/ir/include/sourcemeta/codegen/ir.h index ce56d67a..70eedd86 100644 --- a/vendor/codegen/src/ir/include/sourcemeta/codegen/ir.h +++ b/vendor/codegen/src/ir/include/sourcemeta/codegen/ir.h @@ -83,14 +83,18 @@ struct IRTuple : IRType { /// @ingroup ir struct IRImpossible : IRType {}; +/// @ingroup ir +struct IRAny : IRType {}; + /// @ingroup ir struct IRReference : IRType { IRType target; }; /// @ingroup ir -using IREntity = std::variant; +using IREntity = + std::variant; /// @ingroup ir using IRResult = std::vector; diff --git a/vendor/codegen/src/ir/ir.cc b/vendor/codegen/src/ir/ir.cc index 86ffee3c..821d6a9f 100644 --- a/vendor/codegen/src/ir/ir.cc +++ b/vendor/codegen/src/ir/ir.cc @@ -46,7 +46,10 @@ auto compile(const sourcemeta::core::JSON &input, // (4) Convert every subschema into a code generation object // -------------------------------------------------------------------------- - std::unordered_set visited; + std::unordered_set + visited; IRResult result; for (const auto &[key, location] : frame.locations()) { if (location.type != diff --git a/vendor/codegen/src/ir/ir_default_compiler.h b/vendor/codegen/src/ir/ir_default_compiler.h index 745888f4..83a39e6a 100644 --- a/vendor/codegen/src/ir/ir_default_compiler.h +++ b/vendor/codegen/src/ir/ir_default_compiler.h @@ -37,6 +37,16 @@ auto handle_impossible(const sourcemeta::core::JSON &, .symbol = symbol(frame, location)}}; } +auto handle_any(const sourcemeta::core::JSON &, + const sourcemeta::core::SchemaFrame &frame, + const sourcemeta::core::SchemaFrame::Location &location, + const sourcemeta::core::Vocabularies &, + const sourcemeta::core::SchemaResolver &, + const sourcemeta::core::JSON &) -> IRAny { + return IRAny{{.pointer = sourcemeta::core::to_pointer(location.pointer), + .symbol = symbol(frame, location)}}; +} + auto handle_string(const sourcemeta::core::JSON &schema, const sourcemeta::core::SchemaFrame &frame, const sourcemeta::core::SchemaFrame::Location &location, @@ -450,9 +460,13 @@ auto default_compiler(const sourcemeta::core::JSON &schema, // following shapes if (subschema.is_boolean()) { - assert(!subschema.to_boolean()); - return handle_impossible(schema, frame, location, vocabularies, resolver, - subschema); + if (subschema.to_boolean()) { + return handle_any(schema, frame, location, vocabularies, resolver, + subschema); + } else { + return handle_impossible(schema, frame, location, vocabularies, resolver, + subschema); + } } else if (subschema.defines("type")) { const auto &type_value{subschema.at("type")}; if (!type_value.is_string()) { diff --git a/vendor/core/src/core/jsonpointer/include/sourcemeta/core/jsonpointer.h b/vendor/core/src/core/jsonpointer/include/sourcemeta/core/jsonpointer.h index b9837b63..7682ae53 100644 --- a/vendor/core/src/core/jsonpointer/include/sourcemeta/core/jsonpointer.h +++ b/vendor/core/src/core/jsonpointer/include/sourcemeta/core/jsonpointer.h @@ -638,39 +638,4 @@ auto from_json(const JSON &value) -> std::optional { } // namespace sourcemeta::core -// This hash specialisation is intentationally constant with a decent tolerance -// to collisions -namespace std { -template -struct hash>> { - auto - operator()(const sourcemeta::core::GenericPointer< - PropertyT, - sourcemeta::core::PropertyHashJSON> - &pointer) const noexcept -> std::size_t { - const auto size{pointer.size()}; - if (size == 0) { - return size; - } - - const auto &first{pointer.at(0)}; - const auto &middle{pointer.at(size / 2)}; - const auto &last{pointer.at(size - 1)}; - - return size + - (first.is_property() - ? static_cast(first.property_hash().a) - : first.to_index()) + - (middle.is_property() - ? static_cast(middle.property_hash().a) - : middle.to_index()) + - (last.is_property() - ? static_cast(last.property_hash().a) - : last.to_index()); - } -}; -} // namespace std - #endif diff --git a/vendor/core/src/core/jsonpointer/include/sourcemeta/core/jsonpointer_pointer.h b/vendor/core/src/core/jsonpointer/include/sourcemeta/core/jsonpointer_pointer.h index f5fbdf5c..e2da4426 100644 --- a/vendor/core/src/core/jsonpointer/include/sourcemeta/core/jsonpointer_pointer.h +++ b/vendor/core/src/core/jsonpointer/include/sourcemeta/core/jsonpointer_pointer.h @@ -705,6 +705,13 @@ template class GenericPointer { return this->data == other.data; } + /// Compare with a reference wrapper + [[nodiscard]] auto + operator==(const std::reference_wrapper> + &other) const noexcept -> bool { + return this->data == other.get().data; + } + /// Overload to support ordering of JSON Pointers. Typically for sorting /// reasons. [[nodiscard]] auto @@ -713,6 +720,90 @@ template class GenericPointer { return this->data < other.data; } + /// Compare with a reference wrapper for ordering + [[nodiscard]] auto + operator<(const std::reference_wrapper> + &other) const noexcept -> bool { + return this->data < other.get().data; + } + + /// Hash functor for use with containers + struct Hasher { + using is_transparent = void; + + auto + operator()(const GenericPointer &pointer) const noexcept + -> std::size_t { + const auto size{pointer.size()}; + if (size == 0) { + return size; + } + + const auto &first{pointer.at(0)}; + const auto &middle{pointer.at(size / 2)}; + const auto &last{pointer.at(size - 1)}; + + return size + + (first.is_property() ? property_hash(first.property_hash()) + : first.to_index()) + + (middle.is_property() ? property_hash(middle.property_hash()) + : middle.to_index()) + + (last.is_property() ? property_hash(last.property_hash()) + : last.to_index()); + } + + auto operator()( + const std::reference_wrapper> + &reference) const noexcept -> std::size_t { + return (*this)(reference.get()); + } + + private: + static auto property_hash(const typename Hash::hash_type &hash) noexcept + -> std::size_t { +#if defined(__SIZEOF_INT128__) + const auto *parts = + reinterpret_cast(&hash.a); // NOLINT + return parts[0] ^ parts[1]; +#else + return hash.a ^ hash.b; +#endif + } + }; + + /// Comparator for use with containers + struct Comparator { + using is_transparent = void; + + auto operator()(const GenericPointer &left, + const GenericPointer &right) const noexcept + -> bool { + return left == right; + } + + auto operator()( + const std::reference_wrapper> + &left, + const std::reference_wrapper> + &right) const noexcept -> bool { + return left.get() == right.get(); + } + + auto operator()( + const std::reference_wrapper> + &left, + const GenericPointer &right) const noexcept -> bool { + return left.get() == right; + } + + auto operator()( + const GenericPointer &left, + const std::reference_wrapper> + &right) const noexcept -> bool { + return left == right.get(); + } + }; + private: Container data; }; diff --git a/vendor/core/src/core/jsonpointer/include/sourcemeta/core/jsonpointer_token.h b/vendor/core/src/core/jsonpointer/include/sourcemeta/core/jsonpointer_token.h index e4ab00ad..69d63994 100644 --- a/vendor/core/src/core/jsonpointer/include/sourcemeta/core/jsonpointer_token.h +++ b/vendor/core/src/core/jsonpointer/include/sourcemeta/core/jsonpointer_token.h @@ -243,7 +243,14 @@ template class GenericToken { if (this->as_property != other.as_property) { return false; } else if (this->as_property) { - return this->to_property() == other.to_property(); + if constexpr (requires { hasher.is_perfect(this->hash); }) { + if (hasher.is_perfect(this->hash) && hasher.is_perfect(other.hash)) { + return this->hash == other.hash; + } + } + + return this->hash == other.hash && + this->to_property() == other.to_property(); } else { return this->index == other.index; } diff --git a/vendor/core/src/core/jsonschema/frame.cc b/vendor/core/src/core/jsonschema/frame.cc index 64fe89ac..40b351c6 100644 --- a/vendor/core/src/core/jsonschema/frame.cc +++ b/vendor/core/src/core/jsonschema/frame.cc @@ -281,7 +281,8 @@ auto store(sourcemeta::core::SchemaFrame::Locations &frame, const std::string_view dialect, const sourcemeta::core::SchemaBaseDialect base_dialect, const std::optional &parent, - const bool property_name, const bool ignore_if_present = false, + const bool property_name, const bool orphan, + const bool ignore_if_present = false, const bool already_canonical = false) -> void { auto canonical{already_canonical ? std::move(uri) : sourcemeta::core::URI::canonicalize(uri)}; @@ -294,7 +295,8 @@ auto store(sourcemeta::core::SchemaFrame::Locations &frame, .relative_pointer = relative_pointer_offset, .dialect = dialect, .base_dialect = base_dialect, - .property_name = property_name}}); + .property_name = property_name, + .orphan = orphan}}); if (!ignore_if_present && !inserted) { throw_already_exists(iterator->first.second); } @@ -380,6 +382,7 @@ auto SchemaFrame::to_json( JSON{JSON::String{to_string(location.second.base_dialect)}}); entry.assign_assume_new("propertyName", JSON{location.second.property_name}); + entry.assign_assume_new("orphan", JSON{location.second.orphan}); switch (location.first.first) { case SchemaReferenceType::Static: @@ -437,12 +440,18 @@ auto SchemaFrame::analyse(const JSON &root, const SchemaWalker &walker, std::string_view default_id, const SchemaFrame::Paths &paths) -> void { this->reset(); - assert(std::unordered_set(paths.cbegin(), paths.cend()).size() == - paths.size()); + assert((std::unordered_set(paths.cbegin(), + paths.cend()) + .size() == paths.size())); std::vector subschema_entries; - std::map subschemas; - std::map> base_uris; - std::map> base_dialects; + std::unordered_map + subschemas; + std::unordered_map, + WeakPointer::Hasher> + base_uris; + std::unordered_map, + WeakPointer::Hasher> + base_dialects; for (const auto &path : paths) { // Passing paths that overlap is undefined behavior. No path should @@ -494,7 +503,7 @@ auto SchemaFrame::analyse(const JSON &root, const SchemaWalker &walker, store(this->locations_, SchemaReferenceType::Static, SchemaFrame::LocationType::Resource, default_id_canonical, this->root_, path, path.size(), root_dialect, - root_base_dialect.value(), std::nullopt, false); + root_base_dialect.value(), std::nullopt, false, false); base_uris.insert({path, {root_id.value(), default_id_canonical}}); } @@ -612,7 +621,8 @@ auto SchemaFrame::analyse(const JSON &root, const SchemaWalker &walker, SchemaFrame::LocationType::Resource, new_id, new_id, common_pointer_weak, common_pointer_weak.size(), entry.common.dialect, entry.common.base_dialect.value(), - common_parent, entry.common.property_name); + common_parent, entry.common.property_name, + entry.common.orphan); } auto base_uri_match{base_uris.find(common_pointer_weak)}; @@ -682,7 +692,8 @@ auto SchemaFrame::analyse(const JSON &root, const SchemaWalker &walker, SchemaFrame::LocationType::Anchor, relative_anchor_uri, "", common_pointer_weak, bases.second.size(), entry.common.dialect, entry.common.base_dialect.value(), - common_parent, entry.common.property_name); + common_parent, entry.common.property_name, + entry.common.orphan); } if (type == AnchorType::Dynamic || type == AnchorType::All) { @@ -690,7 +701,8 @@ auto SchemaFrame::analyse(const JSON &root, const SchemaWalker &walker, SchemaFrame::LocationType::Anchor, relative_anchor_uri, "", common_pointer_weak, bases.second.size(), entry.common.dialect, entry.common.base_dialect.value(), - common_parent, entry.common.property_name); + common_parent, entry.common.property_name, + entry.common.orphan); // Register a dynamic anchor as a static anchor if possible too if (entry.common.vocabularies.contains( @@ -699,7 +711,8 @@ auto SchemaFrame::analyse(const JSON &root, const SchemaWalker &walker, SchemaFrame::LocationType::Anchor, relative_anchor_uri, "", common_pointer_weak, bases.second.size(), entry.common.dialect, entry.common.base_dialect.value(), - common_parent, entry.common.property_name, true); + common_parent, entry.common.property_name, + entry.common.orphan, true); } } } else { @@ -729,7 +742,8 @@ auto SchemaFrame::analyse(const JSON &root, const SchemaWalker &walker, SchemaFrame::LocationType::Anchor, anchor_uri, base_view, common_pointer_weak, bases.second.size(), entry.common.dialect, entry.common.base_dialect.value(), - common_parent, entry.common.property_name); + common_parent, entry.common.property_name, + entry.common.orphan); } if (type == AnchorType::Dynamic || type == AnchorType::All) { @@ -738,9 +752,9 @@ auto SchemaFrame::analyse(const JSON &root, const SchemaWalker &walker, SchemaFrame::LocationType::Anchor, anchor_uri, base_view, common_pointer_weak, bases.second.size(), entry.common.dialect, entry.common.base_dialect.value(), - common_parent, entry.common.property_name); + common_parent, entry.common.property_name, + entry.common.orphan); - // Register a dynamic anchor as a static anchor if possible too if (entry.common.vocabularies.contains( Vocabularies::Known::JSON_Schema_2020_12_Core)) { store(this->locations_, @@ -748,7 +762,8 @@ auto SchemaFrame::analyse(const JSON &root, const SchemaWalker &walker, SchemaFrame::LocationType::Anchor, anchor_uri, base_view, common_pointer_weak, bases.second.size(), entry.common.dialect, entry.common.base_dialect.value(), - common_parent, entry.common.property_name, true); + common_parent, entry.common.property_name, + entry.common.orphan, true); } } @@ -851,7 +866,8 @@ auto SchemaFrame::analyse(const JSON &root, const SchemaWalker &walker, base_view, pointer_weak, nearest_base_depth, dialect_for_pointer, current_base_dialect, subschema_it->second.parent, - subschema_it->second.property_name, false, true); + subschema_it->second.property_name, + subschema_it->second.orphan, false, true); } else { const auto &parent_pointer{combined.dialect_match.has_value() ? combined.dialect_match->second @@ -860,12 +876,14 @@ auto SchemaFrame::analyse(const JSON &root, const SchemaWalker &walker, const bool parent_property_name{ parent_subschema_it != subschemas.cend() && parent_subschema_it->second.property_name}; + const bool parent_orphan{parent_subschema_it != subschemas.cend() && + parent_subschema_it->second.orphan}; store(this->locations_, SchemaReferenceType::Static, SchemaFrame::LocationType::Pointer, std::move(result), base_view, pointer_weak, nearest_base_depth, dialect_for_pointer, current_base_dialect, parent_pointer, - parent_property_name, false, true); + parent_property_name, parent_orphan, false, true); } } } @@ -1022,7 +1040,20 @@ auto SchemaFrame::analyse(const JSON &root, const SchemaWalker &walker, } // A schema is standalone if all references can be resolved within itself - if (this->standalone()) { + this->standalone_ = + std::ranges::all_of(this->references_, [&](const auto &reference) { + assert(!reference.first.second.empty()); + assert(reference.first.second.back().is_property()); + // TODO: This check might need to be more elaborate given + // https://github.com/sourcemeta/core/issues/1390 + return reference.first.second.back().to_property() == "$schema" || + this->locations_.contains({SchemaReferenceType::Static, + reference.second.destination}) || + this->locations_.contains({SchemaReferenceType::Dynamic, + reference.second.destination}); + }); + + if (this->standalone_) { // Find all dynamic anchors // Values are pointers to full URIs in locations_ std::unordered_map> @@ -1100,18 +1131,8 @@ auto SchemaFrame::reference(const SchemaReferenceType type, return std::nullopt; } -auto SchemaFrame::standalone() const -> bool { - return std::ranges::all_of(this->references_, [&](const auto &reference) { - assert(!reference.first.second.empty()); - assert(reference.first.second.back().is_property()); - // TODO: This check might need to be more elaborate given - // https://github.com/sourcemeta/core/issues/1390 - return reference.first.second.back().to_property() == "$schema" || - this->locations_.contains( - {SchemaReferenceType::Static, reference.second.destination}) || - this->locations_.contains( - {SchemaReferenceType::Dynamic, reference.second.destination}); - }); +auto SchemaFrame::standalone() const noexcept -> bool { + return this->standalone_; } auto SchemaFrame::root() const noexcept -> const JSON::String & { @@ -1170,11 +1191,28 @@ auto SchemaFrame::traverse(const std::string_view uri) const auto SchemaFrame::traverse(const WeakPointer &pointer) const -> std::optional> { - // TODO: This is slow. Consider adding a pointer-indexed secondary - // lookup structure to SchemaFrame - for (const auto &entry : this->locations_) { - if (entry.second.pointer == pointer) { - return entry.second; + this->populate_pointer_to_location(); + const auto iterator{this->pointer_to_location_.find(std::cref(pointer))}; + if (iterator == this->pointer_to_location_.cend() || + iterator->second.empty()) { + return std::nullopt; + } + + return *(iterator->second.front()); +} + +auto SchemaFrame::traverse(const WeakPointer &pointer, + const LocationType type) const + -> std::optional> { + this->populate_pointer_to_location(); + const auto iterator{this->pointer_to_location_.find(std::cref(pointer))}; + if (iterator == this->pointer_to_location_.cend()) { + return std::nullopt; + } + + for (const auto *location : iterator->second) { + if (location->type == type) { + return *location; } } @@ -1183,9 +1221,24 @@ auto SchemaFrame::traverse(const WeakPointer &pointer) const auto SchemaFrame::uri(const WeakPointer &pointer) const -> std::optional> { - for (const auto &entry : this->locations_) { - if (entry.second.pointer == pointer) { - return entry.first.second; + this->populate_pointer_to_location(); + const auto iterator{this->pointer_to_location_.find(std::cref(pointer))}; + if (iterator == this->pointer_to_location_.cend()) { + return std::nullopt; + } + + const Location *best{nullptr}; + for (const auto *location : iterator->second) { + if (best == nullptr || location->type < best->type) { + best = location; + } + } + + if (best != nullptr) { + for (const auto &entry : this->locations_) { + if (&entry.second == best) { + return entry.first.second; + } } } @@ -1341,9 +1394,295 @@ auto SchemaFrame::empty() const noexcept -> bool { } auto SchemaFrame::reset() -> void { + // Note that order of removal is important to avoid undefined behaviour + this->pointer_to_location_.clear(); + this->reachability_.clear(); this->root_.clear(); this->locations_.clear(); this->references_.clear(); + this->standalone_ = false; +} + +auto SchemaFrame::populate_pointer_to_location() const -> void { + if (!this->pointer_to_location_.empty()) { + return; + } + + this->pointer_to_location_.reserve(this->locations_.size()); + for (const auto &entry : this->locations_) { + this->pointer_to_location_[std::cref(entry.second.pointer)].push_back( + &entry.second); + } +} + +// TODO: Find a way to split or simplify this monster while preserving +// its performance? +auto SchemaFrame::populate_reachability(const SchemaWalker &walker, + const SchemaResolver &resolver) const + -> void { + if (!this->reachability_.empty()) { + return; + } + + // --------------------------------------------------------------------------- + // (1) Find all unreachable pointers + // --------------------------------------------------------------------------- + + std::vector> unreachable_pointers; + + if (this->pointer_to_location_.empty()) { + std::unordered_set, + WeakPointer::Hasher, WeakPointer::Comparator> + has_non_pointer_location; + std::unordered_set, + WeakPointer::Hasher, WeakPointer::Comparator> + has_non_orphan; + + for (const auto &entry : this->locations_) { + auto [iterator, inserted] = this->pointer_to_location_.try_emplace( + std::cref(entry.second.pointer), std::vector{}); + iterator->second.push_back(&entry.second); + if (entry.second.type != LocationType::Pointer) { + has_non_pointer_location.insert(iterator->first); + if (!entry.second.orphan) { + has_non_orphan.insert(iterator->first); + } + } + } + + for (const auto &pointer_reference : has_non_pointer_location) { + const bool is_reachable = has_non_orphan.contains(pointer_reference); + this->reachability_.emplace(pointer_reference, is_reachable); + if (!is_reachable) { + unreachable_pointers.push_back(pointer_reference); + } + } + } else { + for (const auto &[pointer_reference, locations] : + this->pointer_to_location_) { + const auto has_non_pointer{ + std::ranges::any_of(locations, [](const Location *location) { + return location->type != LocationType::Pointer; + })}; + if (!has_non_pointer) { + continue; + } + + const auto any_non_orphan{ + std::ranges::any_of(locations, [](const Location *location) { + return location->type != LocationType::Pointer && !location->orphan; + })}; + this->reachability_.emplace(pointer_reference, any_non_orphan); + if (!any_non_orphan) { + unreachable_pointers.push_back(pointer_reference); + } + } + } + + // --------------------------------------------------------------------------- + // (2) Build a reverse mapping from reference destinations to their sources + // --------------------------------------------------------------------------- + + std::unordered_map> + dynamic_anchors_by_fragment; + for (const auto &location : this->locations_) { + if (location.first.first == SchemaReferenceType::Dynamic && + location.second.type == LocationType::Anchor) { + const auto &uri{location.first.second}; + const auto hash_pos{uri.rfind('#')}; + if (hash_pos != std::string::npos) { + std::string_view fragment{uri.data() + hash_pos + 1, + uri.size() - hash_pos - 1}; + dynamic_anchors_by_fragment[fragment].push_back( + &location.second.pointer); + } + } + } + + std::vector> + reference_destinations; + reference_destinations.reserve(this->references_.size()); + + for (const auto &reference : this->references_) { + const auto &source_pointer{reference.first.second}; + if (source_pointer.empty()) { + continue; + } + + if (reference.first.first == SchemaReferenceType::Dynamic && + reference.second.fragment.has_value()) { + const auto &fragment{reference.second.fragment.value()}; + const auto match{dynamic_anchors_by_fragment.find(fragment)}; + if (match != dynamic_anchors_by_fragment.cend()) { + for (const auto *destination_pointer : match->second) { + reference_destinations.emplace_back(&source_pointer, + destination_pointer); + } + } + + continue; + } + + const auto destination_location{this->locations_.find( + {SchemaReferenceType::Static, reference.second.destination})}; + if (destination_location != this->locations_.cend()) { + reference_destinations.emplace_back( + &source_pointer, &destination_location->second.pointer); + } + } + + std::unordered_map, + std::vector, WeakPointer::Hasher, + WeakPointer::Comparator> + references_by_destination; + for (const auto &[source, destination] : reference_destinations) { + references_by_destination[std::cref(*destination)].push_back(source); + } + + // --------------------------------------------------------------------------- + // (3) Precompute which references could make each orphan reachable + // --------------------------------------------------------------------------- + + struct PotentialSource { + const WeakPointer *source_pointer; + bool crosses; + }; + struct PotentialReach { + std::reference_wrapper pointer; + std::vector potential_sources; + }; + std::vector unreachable_with_sources; + unreachable_with_sources.reserve(unreachable_pointers.size()); + + std::unordered_map vocabularies_cache; + + for (const auto &pointer_reference : unreachable_pointers) { + const auto &pointer{pointer_reference.get()}; + PotentialReach entry{.pointer = pointer_reference, .potential_sources = {}}; + + WeakPointer ancestor = pointer; + while (!ancestor.empty()) { + auto destination_iterator = + references_by_destination.find(std::cref(ancestor)); + if (destination_iterator != references_by_destination.end()) { + bool crosses{false}; + if (ancestor != pointer) { + auto check_location{this->traverse(pointer)}; + while (check_location.has_value()) { + const auto &location{check_location->get()}; + if (location.pointer == ancestor) { + break; + } + + if (!location.parent.has_value()) { + break; + } + + const auto parent_location{this->traverse(location.parent.value())}; + if (!parent_location.has_value()) { + break; + } + + const auto relative{ + location.pointer.slice(location.parent.value().size())}; + if (!relative.empty() && relative.at(0).is_property()) { + const auto &parent_loc{parent_location->get()}; + auto vocab_iterator = + vocabularies_cache.find(parent_loc.base_dialect); + if (vocab_iterator == vocabularies_cache.end()) { + auto [inserted_iterator, inserted] = vocabularies_cache.emplace( + parent_loc.base_dialect, + this->vocabularies(parent_loc, resolver)); + vocab_iterator = inserted_iterator; + } + + const auto &keyword_result{ + walker(relative.at(0).to_property(), vocab_iterator->second)}; + if (keyword_result.type == SchemaKeywordType::LocationMembers) { + crosses = true; + break; + } + } + + check_location = parent_location; + } + } + + for (const auto *source_pointer : destination_iterator->second) { + entry.potential_sources.push_back(PotentialSource{ + .source_pointer = source_pointer, .crosses = crosses}); + } + } + ancestor = ancestor.initial(); + } + + if (!entry.potential_sources.empty()) { + unreachable_with_sources.push_back(std::move(entry)); + } + } + + std::ranges::sort(unreachable_with_sources, [](const PotentialReach &left, + const PotentialReach &right) { + return left.pointer.get().size() < right.pointer.get().size(); + }); + + // --------------------------------------------------------------------------- + // (4) Propagate reachability through references using fixpoint iteration + // --------------------------------------------------------------------------- + + bool changed{true}; + while (changed) { + changed = false; + + auto write_iterator = unreachable_with_sources.begin(); + for (auto read_iterator = unreachable_with_sources.begin(); + read_iterator != unreachable_with_sources.end(); ++read_iterator) { + bool became_reachable = false; + + for (const auto &potential_source : read_iterator->potential_sources) { + if (potential_source.crosses) { + continue; + } + + const auto &source_parent{potential_source.source_pointer->initial()}; + bool source_parent_reachable{source_parent.empty()}; + if (!source_parent_reachable) { + const auto reachability_iterator{ + this->reachability_.find(std::cref(source_parent))}; + source_parent_reachable = + reachability_iterator != this->reachability_.end() && + reachability_iterator->second; + } + + if (source_parent_reachable) { + became_reachable = true; + break; + } + } + + if (became_reachable) { + this->reachability_[read_iterator->pointer] = true; + changed = true; + } else { + if (write_iterator != read_iterator) { + *write_iterator = std::move(*read_iterator); + } + ++write_iterator; + } + } + unreachable_with_sources.erase(write_iterator, + unreachable_with_sources.end()); + } +} + +auto SchemaFrame::is_reachable(const Location &location, + const SchemaWalker &walker, + const SchemaResolver &resolver) const -> bool { + assert(location.type != LocationType::Pointer); + this->populate_reachability(walker, resolver); + const auto iterator{this->reachability_.find(std::cref(location.pointer))}; + assert(iterator != this->reachability_.end()); + return iterator->second; } } // namespace sourcemeta::core diff --git a/vendor/core/src/core/jsonschema/include/sourcemeta/core/jsonschema.h b/vendor/core/src/core/jsonschema/include/sourcemeta/core/jsonschema.h index a18eeada..54005110 100644 --- a/vendor/core/src/core/jsonschema/include/sourcemeta/core/jsonschema.h +++ b/vendor/core/src/core/jsonschema/include/sourcemeta/core/jsonschema.h @@ -422,17 +422,27 @@ auto wrap(std::string_view identifier) -> JSON; /// "items": { "type": "string" } /// })JSON"); /// +/// sourcemeta::core::SchemaFrame frame{ +/// sourcemeta::core::SchemaFrame::Mode::References}; +/// frame.analyse(document, sourcemeta::core::schema_walker, +/// sourcemeta::core::schema_resolver); +/// +/// const auto location{frame.traverse( +/// sourcemeta::core::WeakPointer{"items"}, +/// sourcemeta::core::SchemaFrame::LocationType::Subschema)}; +/// +/// sourcemeta::core::WeakPointer base; /// const sourcemeta::core::JSON result = -/// sourcemeta::core::wrap(document, { "items" }, -/// sourcemeta::core::schema_resolver); +/// sourcemeta::core::wrap(document, frame, location.value().get(), +/// sourcemeta::core::schema_resolver, base); /// /// sourcemeta::core::prettify(result, std::cerr); /// std::cerr << "\n"; /// ``` SOURCEMETA_CORE_JSONSCHEMA_EXPORT -auto wrap(const JSON &schema, const Pointer &pointer, - const SchemaResolver &resolver, std::string_view default_dialect = "") - -> JSON; +auto wrap(const JSON &schema, const SchemaFrame &frame, + const SchemaFrame::Location &location, const SchemaResolver &resolver, + WeakPointer &base) -> JSON; /// @ingroup jsonschema /// diff --git a/vendor/core/src/core/jsonschema/include/sourcemeta/core/jsonschema_frame.h b/vendor/core/src/core/jsonschema/include/sourcemeta/core/jsonschema_frame.h index 50dffa54..522dbfa0 100644 --- a/vendor/core/src/core/jsonschema/include/sourcemeta/core/jsonschema_frame.h +++ b/vendor/core/src/core/jsonschema/include/sourcemeta/core/jsonschema_frame.h @@ -17,6 +17,7 @@ #include // std::optional #include // std::set #include // std::tuple +#include // std::unordered_map #include // std::unordered_set #include // std::pair #include // std::vector @@ -61,6 +62,12 @@ class SOURCEMETA_CORE_JSONSCHEMA_EXPORT SchemaFrame { SchemaFrame(const Mode mode) : mode_{mode} {} + // We rely on internal caches that would be dangling otherwise + SchemaFrame(const SchemaFrame &) = delete; + auto operator=(const SchemaFrame &) -> SchemaFrame & = delete; + SchemaFrame(SchemaFrame &&) = delete; + auto operator=(SchemaFrame &&) -> SchemaFrame & = delete; + // Query the current mode that the schema frame was configured with [[nodiscard]] auto mode() const noexcept -> Mode { return this->mode_; } @@ -114,6 +121,7 @@ class SOURCEMETA_CORE_JSONSCHEMA_EXPORT SchemaFrame { std::string_view dialect; SchemaBaseDialect base_dialect; bool property_name; + bool orphan; }; /// A JSON Schema reference frame is a mapping of URIs to schema identifiers, @@ -155,7 +163,7 @@ class SOURCEMETA_CORE_JSONSCHEMA_EXPORT SchemaFrame { -> std::optional>; /// Check whether the analysed schema has no external references - [[nodiscard]] auto standalone() const -> bool; + [[nodiscard]] auto standalone() const noexcept -> bool; /// Get the root schema identifier (empty if none) [[nodiscard]] auto root() const noexcept -> const JSON::String &; @@ -184,6 +192,11 @@ class SOURCEMETA_CORE_JSONSCHEMA_EXPORT SchemaFrame { [[nodiscard]] auto traverse(const WeakPointer &pointer) const -> std::optional>; + /// Get the location of a specific type associated with a given pointer + [[nodiscard]] auto traverse(const WeakPointer &pointer, + const LocationType type) const + -> std::optional>; + /// Turn an absolute pointer into a location URI [[nodiscard]] auto uri(const WeakPointer &pointer) const -> std::optional>; @@ -228,7 +241,16 @@ class SOURCEMETA_CORE_JSONSCHEMA_EXPORT SchemaFrame { /// Reset the frame, clearing all analysed data auto reset() -> void; + /// Determines if a location could be evaluated during validation + [[nodiscard]] auto is_reachable(const Location &location, + const SchemaWalker &walker, + const SchemaResolver &resolver) const -> bool; + private: + auto populate_pointer_to_location() const -> void; + auto populate_reachability(const SchemaWalker &walker, + const SchemaResolver &resolver) const -> void; + Mode mode_; // Exporting symbols that depends on the standard C++ library is considered // safe. @@ -239,6 +261,14 @@ class SOURCEMETA_CORE_JSONSCHEMA_EXPORT SchemaFrame { JSON::String root_; Locations locations_; References references_; + mutable std::unordered_map, + std::vector, WeakPointer::Hasher, + WeakPointer::Comparator> + pointer_to_location_; + mutable std::unordered_map, bool, + WeakPointer::Hasher, WeakPointer::Comparator> + reachability_; + bool standalone_{false}; #if defined(_MSC_VER) #pragma warning(default : 4251 4275) #endif 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 b4470d35..55ff68d4 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 @@ -18,7 +18,9 @@ #include // std::set #include // std::string #include // std::string_view -#include // std::move, std::forward, std::pair +#include // std::tuple +#include // std::is_same_v, std::true_type +#include // std::move, std::forward #include // std::vector namespace sourcemeta::core { @@ -130,7 +132,7 @@ class SOURCEMETA_CORE_JSONSCHEMA_EXPORT SchemaTransformRule { const SchemaResolver &resolver) const -> Result = 0; /// The rule transformation. If this virtual method is not overriden, - /// then the rule condition is considered to not be fixable. + /// then the rule is considered to not mutate the schema. virtual auto transform(JSON &schema, const Result &result) const -> void; private: @@ -163,6 +165,9 @@ class SOURCEMETA_CORE_JSONSCHEMA_EXPORT SchemaTransformRule { /// public: /// MyRule() : sourcemeta::core::SchemaTransformRule("my_rule") {}; /// +/// using mutates = std::true_type; +/// using reframe_after_transform = std::true_type; +/// /// [[nodiscard]] auto condition(const sourcemeta::core::JSON &schema, /// const sourcemeta::core::Vocabularies /// &vocabularies, @@ -227,7 +232,15 @@ class SOURCEMETA_CORE_JSONSCHEMA_EXPORT SchemaTransformer { /// It is the caller's responsibility to not add duplicate rules. template T, typename... Args> auto add(Args &&...args) -> void { - this->rules.push_back(std::make_unique(std::forward(args)...)); + static_assert(requires { typename T::mutates; }); + static_assert(requires { typename T::reframe_after_transform; }); + static_assert( + std::is_same_v || + std::is_same_v); + this->rules.emplace_back( + std::make_unique(std::forward(args)...), + std::is_same_v, + std::is_same_v); } /// Remove a rule from the bundle @@ -270,7 +283,8 @@ class SOURCEMETA_CORE_JSONSCHEMA_EXPORT SchemaTransformer { #if defined(_MSC_VER) #pragma warning(disable : 4251) #endif - std::vector> rules; + std::vector, bool, bool>> + rules; #if defined(_MSC_VER) #pragma warning(default : 4251) #endif diff --git a/vendor/core/src/core/jsonschema/jsonschema.cc b/vendor/core/src/core/jsonschema/jsonschema.cc index aad33094..408f911e 100644 --- a/vendor/core/src/core/jsonschema/jsonschema.cc +++ b/vendor/core/src/core/jsonschema/jsonschema.cc @@ -590,24 +590,44 @@ auto sourcemeta::core::wrap(const std::string_view identifier) return result; } -auto sourcemeta::core::wrap(const sourcemeta::core::JSON &schema, - const sourcemeta::core::Pointer &pointer, - const sourcemeta::core::SchemaResolver &resolver, - std::string_view default_dialect) - -> sourcemeta::core::JSON { - assert(try_get(schema, pointer)); +auto sourcemeta::core::wrap( + const sourcemeta::core::JSON &schema, + const sourcemeta::core::SchemaFrame &frame, + const sourcemeta::core::SchemaFrame::Location &location, + const sourcemeta::core::SchemaResolver &resolver, + sourcemeta::core::WeakPointer &base) -> sourcemeta::core::JSON { + assert(frame.mode() == SchemaFrame::Mode::References); + assert(location.type != SchemaFrame::LocationType::Pointer); + + const auto &pointer{location.pointer}; if (pointer.empty()) { - return schema; + auto copy = schema; + if (copy.is_object()) { + copy.assign("$schema", JSON{location.dialect}); + } + + return copy; } - auto copy = schema; - const auto effective_dialect{dialect(copy, default_dialect)}; - if (!effective_dialect.empty()) { - copy.assign("$schema", JSON{effective_dialect}); - } else { - throw SchemaUnknownBaseDialectError(); + assert(try_get(schema, pointer)); + const auto has_internal_references{ + std::any_of(frame.references().cbegin(), frame.references().cend(), + [&pointer](const auto &reference) { + return reference.first.second.starts_with(pointer); + })}; + + if (!has_internal_references) { + auto subschema{get(schema, pointer)}; + if (subschema.is_object()) { + subschema.assign("$schema", JSON{location.dialect}); + } + + return subschema; } + auto copy = schema; + copy.assign("$schema", JSON{location.dialect}); + auto result{JSON::make_object()}; // JSON Schema 2020-12 is the first dialect that truly supports // cross-dialect references In practice, others do, but we can @@ -622,13 +642,13 @@ auto sourcemeta::core::wrap(const sourcemeta::core::JSON &schema, // other schemas whose top-level identifiers are relative URIs don't // get affected. Otherwise, we would cause unintended base resolution. constexpr std::string_view WRAPPER_IDENTIFIER{"__sourcemeta-core-wrap__"}; - const auto maybe_id{identify(copy, resolver, default_dialect)}; + const auto maybe_id{identify(copy, resolver, location.dialect)}; const auto id{maybe_id.empty() ? WRAPPER_IDENTIFIER : maybe_id}; URI uri{id}; try { - reidentify(copy, id, resolver, default_dialect); + reidentify(copy, id, resolver, location.dialect); // Otherwise we will get an error with the `WRAPPER_IDENTIFIER`, which will // be confusing to end users @@ -647,11 +667,17 @@ auto sourcemeta::core::wrap(const sourcemeta::core::JSON &schema, uri.fragment(to_string(pointer)); result.assign_assume_new("$ref", JSON{uri.recompose()}); } else { + static const JSON::String DEFS{"$defs"}; + static const JSON::String SCHEMA{"schema"}; result.assign_assume_new( "$ref", - JSON{to_uri(Pointer{"$defs", "schema"}.concat(pointer)).recompose()}); + JSON{to_uri(WeakPointer{std::cref(DEFS), std::cref(SCHEMA)}.concat( + pointer)) + .recompose()}); } + static const JSON::String REF{"$ref"}; + base.push_back(REF); return result; } diff --git a/vendor/core/src/core/jsonschema/transformer.cc b/vendor/core/src/core/jsonschema/transformer.cc index d857f0b5..2ff1010e 100644 --- a/vendor/core/src/core/jsonschema/transformer.cc +++ b/vendor/core/src/core/jsonschema/transformer.cc @@ -36,6 +36,87 @@ auto calculate_health_percentage(const std::size_t subschemas, return static_cast(result); } +auto check_rules( + const sourcemeta::core::JSON &schema, + const sourcemeta::core::SchemaFrame &frame, + const std::vector, bool, bool>> + &rules, + const sourcemeta::core::SchemaWalker &walker, + const sourcemeta::core::SchemaResolver &resolver, + const sourcemeta::core::SchemaTransformer::Callback &callback, + const sourcemeta::core::JSON::String &exclude_keyword, + const bool non_mutating_only) -> std::pair { + std::unordered_set + visited; + bool result{true}; + std::size_t subschema_count{0}; + std::size_t subschema_failures{0}; + + for (const auto &entry : frame.locations()) { + if (entry.second.type != + sourcemeta::core::SchemaFrame::LocationType::Resource && + entry.second.type != + sourcemeta::core::SchemaFrame::LocationType::Subschema) { + continue; + } + + const auto [visited_iterator, inserted] = + visited.insert(sourcemeta::core::to_pointer(entry.second.pointer)); + if (!inserted) { + continue; + } + const auto &entry_pointer{*visited_iterator}; + + subschema_count += 1; + + const auto ¤t{sourcemeta::core::get(schema, entry_pointer)}; + const auto current_vocabularies{frame.vocabularies(entry.second, resolver)}; + + bool subschema_failed{false}; + for (const auto &[rule, mutates, _] : rules) { + // TODO: In this case, can we avoid framing and the entire subschema loop + // if there will be no rules to execute that match this criteria? + if (non_mutating_only && mutates) { + continue; + } + + const auto outcome{rule->check(current, schema, current_vocabularies, + walker, resolver, frame, entry.second, + exclude_keyword)}; + if (outcome.applies) { + subschema_failed = true; + callback(entry_pointer, rule->name(), rule->message(), outcome); + } + } + + if (subschema_failed) { + subschema_failures += 1; + result = false; + } + } + + return {result, + calculate_health_percentage(subschema_count, subschema_failures)}; +} + +auto analyse_frame(sourcemeta::core::SchemaFrame &frame, + const sourcemeta::core::JSON &schema, + const sourcemeta::core::SchemaWalker &walker, + const sourcemeta::core::SchemaResolver &resolver, + const std::string_view default_dialect, + const std::string_view default_id) -> void { + + // If we use the default id when there is already one, framing will duplicate + // the locations leading to duplicate check reports + if (!sourcemeta::core::identify(schema, resolver, default_dialect).empty()) { + frame.analyse(schema, walker, resolver, default_dialect); + } else { + frame.analyse(schema, walker, resolver, default_dialect, default_id); + } +} + } // namespace namespace sourcemeta::core { @@ -99,57 +180,9 @@ auto SchemaTransformer::check(const JSON &schema, const SchemaWalker &walker, const JSON::String &exclude_keyword) const -> std::pair { SchemaFrame frame{SchemaFrame::Mode::References}; - - // If we use the default id when there is already one, framing will duplicate - // the locations leading to duplicate check reports - if (!sourcemeta::core::identify(schema, resolver, default_dialect).empty()) { - frame.analyse(schema, walker, resolver, default_dialect); - } else { - frame.analyse(schema, walker, resolver, default_dialect, default_id); - } - - std::unordered_set visited; - bool result{true}; - std::size_t subschema_count{0}; - std::size_t subschema_failures{0}; - for (const auto &entry : frame.locations()) { - if (entry.second.type != SchemaFrame::LocationType::Resource && - entry.second.type != SchemaFrame::LocationType::Subschema) { - continue; - } - - // Framing may report resource twice or more given default identifiers and - // nested resources, risking reporting the same errors twice - const auto [visited_iterator, inserted] = - visited.insert(to_pointer(entry.second.pointer)); - if (!inserted) { - continue; - } - const auto &entry_pointer{*visited_iterator}; - - subschema_count += 1; - - const auto ¤t{get(schema, entry_pointer)}; - const auto current_vocabularies{frame.vocabularies(entry.second, resolver)}; - bool subresult{true}; - for (const auto &rule : this->rules) { - const auto outcome{rule->check(current, schema, current_vocabularies, - walker, resolver, frame, entry.second, - exclude_keyword)}; - if (outcome.applies) { - subresult = false; - callback(entry_pointer, rule->name(), rule->message(), outcome); - } - } - - if (!subresult) { - subschema_failures += 1; - result = false; - } - } - - return {result, - calculate_health_percentage(subschema_count, subschema_failures)}; + analyse_frame(frame, schema, walker, resolver, default_dialect, default_id); + return check_rules(schema, frame, this->rules, walker, resolver, callback, + exclude_keyword, false); } auto SchemaTransformer::apply(JSON &schema, const SchemaWalker &walker, @@ -164,10 +197,6 @@ auto SchemaTransformer::apply(JSON &schema, const SchemaWalker &walker, ProcessedRuleHasher> processed_rules; - bool result{true}; - std::size_t subschema_count{0}; - std::size_t subschema_failures{0}; - SchemaFrame frame{SchemaFrame::Mode::References}; struct PotentiallyBrokenReference { @@ -182,13 +211,12 @@ auto SchemaTransformer::apply(JSON &schema, const SchemaWalker &walker, while (true) { if (frame.empty()) { - frame.analyse(schema, walker, resolver, default_dialect, default_id); + analyse_frame(frame, schema, walker, resolver, default_dialect, + default_id); } - std::unordered_set visited; + std::unordered_set visited; bool applied{false}; - subschema_count = 0; - subschema_failures = 0; for (const auto &entry : frame.locations()) { if (entry.second.type != SchemaFrame::LocationType::Resource && @@ -204,15 +232,17 @@ auto SchemaTransformer::apply(JSON &schema, const SchemaWalker &walker, continue; } const auto &entry_pointer{*visited_iterator}; - - subschema_count += 1; - auto ¤t{get(schema, entry_pointer)}; const auto current_vocabularies{ frame.vocabularies(entry.second, resolver)}; - bool subschema_failed{false}; - for (const auto &rule : this->rules) { + for (const auto &[rule, mutates, reframe_after_transform] : this->rules) { + // Only process mutating rules in the main loop. + // Non-mutating rules will be processed once at the end. + if (!mutates) { + continue; + } + auto outcome{rule->check(current, schema, current_vocabularies, walker, resolver, frame, entry.second, exclude_keyword)}; @@ -242,18 +272,14 @@ auto SchemaTransformer::apply(JSON &schema, const SchemaWalker &walker, target.relative_pointer}); } - try { - rule->transform(current, outcome); - } catch (const SchemaAbortError &) { - result = false; - subschema_failed = true; - callback(entry_pointer, rule->name(), rule->message(), outcome); - continue; - } + rule->transform(current, outcome); applied = true; - frame.analyse(schema, walker, resolver, default_dialect, default_id); + if (reframe_after_transform) { + analyse_frame(frame, schema, walker, resolver, default_dialect, + default_id); + } const auto new_location{frame.traverse(to_weak_pointer(entry_pointer))}; // The location should still exist after transform @@ -322,11 +348,9 @@ auto SchemaTransformer::apply(JSON &schema, const SchemaWalker &walker, frame.reset(); } - goto core_transformer_start_again; - } - - if (subschema_failed) { - subschema_failures += 1; + if (references_fixed || reframe_after_transform) { + goto core_transformer_start_again; + } } } @@ -336,13 +360,17 @@ auto SchemaTransformer::apply(JSON &schema, const SchemaWalker &walker, } } - return {result, - calculate_health_percentage(subschema_count, subschema_failures)}; + if (frame.empty()) { + analyse_frame(frame, schema, walker, resolver, default_dialect, default_id); + } + + return check_rules(schema, frame, this->rules, walker, resolver, callback, + exclude_keyword, true); } auto SchemaTransformer::remove(const std::string_view name) -> bool { - return std::erase_if(this->rules, [&name](const auto &rule) { - return rule->name() == name; + return std::erase_if(this->rules, [&name](const auto &entry) { + return std::get<0>(entry)->name() == name; }) > 0; } diff --git a/vendor/core/src/extension/alterschema/CMakeLists.txt b/vendor/core/src/extension/alterschema/CMakeLists.txt index 506936b5..ee43cbba 100644 --- a/vendor/core/src/extension/alterschema/CMakeLists.txt +++ b/vendor/core/src/extension/alterschema/CMakeLists.txt @@ -1,10 +1,10 @@ sourcemeta_library(NAMESPACE sourcemeta PROJECT core NAME alterschema SOURCES alterschema.cc # Canonicalizer - canonicalizer/boolean_true.h canonicalizer/const_as_enum.h canonicalizer/exclusive_maximum_integer_to_maximum.h canonicalizer/exclusive_minimum_integer_to_minimum.h + canonicalizer/items_implicit.h canonicalizer/max_contains_covered_by_max_items.h canonicalizer/min_items_given_min_contains.h canonicalizer/min_items_implicit.h @@ -20,6 +20,10 @@ sourcemeta_library(NAMESPACE sourcemeta PROJECT core NAME alterschema canonicalizer/type_union_implicit.h # Common + common/allof_false_simplify.h + common/anyof_false_simplify.h + common/anyof_remove_false_schemas.h + common/anyof_true_simplify.h common/const_with_type.h common/orphan_definitions.h common/content_media_type_without_encoding.h @@ -33,6 +37,7 @@ sourcemeta_library(NAMESPACE sourcemeta PROJECT core NAME alterschema common/duplicate_anyof_branches.h common/duplicate_enum_values.h common/duplicate_required_values.h + common/empty_object_as_true.h common/else_empty.h common/else_without_if.h common/enum_with_type.h @@ -50,19 +55,21 @@ sourcemeta_library(NAMESPACE sourcemeta PROJECT core NAME alterschema common/non_applicable_enum_validation_keywords.h common/non_applicable_type_specific_keywords.h common/not_false.h + common/oneof_false_simplify.h + common/oneof_to_anyof_disjoint_types.h common/required_properties_in_properties.h common/single_type_array.h common/then_empty.h common/then_without_if.h common/unknown_keywords_prefix.h common/unknown_local_ref.h + common/unsatisfiable_drop_validation.h common/unnecessary_allof_ref_wrapper_draft.h common/unnecessary_allof_ref_wrapper_modern.h common/unnecessary_allof_wrapper.h common/unsatisfiable_in_place_applicator_type.h # Linter - linter/additional_properties_default.h linter/comment_trim.h linter/content_schema_default.h linter/definitions_to_defs.h diff --git a/vendor/core/src/extension/alterschema/alterschema.cc b/vendor/core/src/extension/alterschema/alterschema.cc index bd2af602..131b20c7 100644 --- a/vendor/core/src/extension/alterschema/alterschema.cc +++ b/vendor/core/src/extension/alterschema/alterschema.cc @@ -29,10 +29,10 @@ inline auto APPLIES_TO_POINTERS(std::vector &&keywords) } // Canonicalizer -#include "canonicalizer/boolean_true.h" #include "canonicalizer/const_as_enum.h" #include "canonicalizer/exclusive_maximum_integer_to_maximum.h" #include "canonicalizer/exclusive_minimum_integer_to_minimum.h" +#include "canonicalizer/items_implicit.h" #include "canonicalizer/max_contains_covered_by_max_items.h" #include "canonicalizer/min_items_given_min_contains.h" #include "canonicalizer/min_items_implicit.h" @@ -48,6 +48,10 @@ inline auto APPLIES_TO_POINTERS(std::vector &&keywords) #include "canonicalizer/type_union_implicit.h" // Common +#include "common/allof_false_simplify.h" +#include "common/anyof_false_simplify.h" +#include "common/anyof_remove_false_schemas.h" +#include "common/anyof_true_simplify.h" #include "common/const_with_type.h" #include "common/content_media_type_without_encoding.h" #include "common/content_schema_without_media_type.h" @@ -62,6 +66,7 @@ inline auto APPLIES_TO_POINTERS(std::vector &&keywords) #include "common/duplicate_required_values.h" #include "common/else_empty.h" #include "common/else_without_if.h" +#include "common/empty_object_as_true.h" #include "common/enum_with_type.h" #include "common/equal_numeric_bounds_to_enum.h" #include "common/exclusive_maximum_number_and_maximum.h" @@ -77,6 +82,8 @@ inline auto APPLIES_TO_POINTERS(std::vector &&keywords) #include "common/non_applicable_enum_validation_keywords.h" #include "common/non_applicable_type_specific_keywords.h" #include "common/not_false.h" +#include "common/oneof_false_simplify.h" +#include "common/oneof_to_anyof_disjoint_types.h" #include "common/orphan_definitions.h" #include "common/required_properties_in_properties.h" #include "common/single_type_array.h" @@ -87,10 +94,10 @@ inline auto APPLIES_TO_POINTERS(std::vector &&keywords) #include "common/unnecessary_allof_ref_wrapper_draft.h" #include "common/unnecessary_allof_ref_wrapper_modern.h" #include "common/unnecessary_allof_wrapper.h" +#include "common/unsatisfiable_drop_validation.h" #include "common/unsatisfiable_in_place_applicator_type.h" // Linter -#include "linter/additional_properties_default.h" #include "linter/comment_trim.h" #include "linter/content_schema_default.h" #include "linter/definitions_to_defs.h" @@ -139,9 +146,16 @@ auto add(SchemaTransformer &bundle, const AlterSchemaMode mode) -> void { 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(); @@ -173,7 +187,6 @@ auto add(SchemaTransformer &bundle, const AlterSchemaMode mode) -> void { bundle.add(); if (mode == AlterSchemaMode::Canonicalizer) { - bundle.add(); bundle.add(); bundle.add(); bundle.add(); @@ -189,11 +202,11 @@ auto add(SchemaTransformer &bundle, const AlterSchemaMode mode) -> void { bundle.add(); bundle.add(); bundle.add(); + bundle.add(); } if (mode == AlterSchemaMode::Linter) { bundle.add(); - bundle.add(); bundle.add(); bundle.add(); bundle.add(); @@ -226,6 +239,7 @@ auto add(SchemaTransformer &bundle, const AlterSchemaMode mode) -> void { bundle.add(); bundle.add(); bundle.add(); + bundle.add(); } } // namespace sourcemeta::core diff --git a/vendor/core/src/extension/alterschema/canonicalizer/boolean_true.h b/vendor/core/src/extension/alterschema/canonicalizer/boolean_true.h deleted file mode 100644 index 18cdb035..00000000 --- a/vendor/core/src/extension/alterschema/canonicalizer/boolean_true.h +++ /dev/null @@ -1,23 +0,0 @@ -class BooleanTrue final : public SchemaTransformRule { -public: - BooleanTrue() - : SchemaTransformRule{ - "boolean_true", - "The boolean schema `true` is syntax sugar for the empty schema"} { - }; - - [[nodiscard]] auto condition(const sourcemeta::core::JSON &schema, - const sourcemeta::core::JSON &, - const sourcemeta::core::Vocabularies &, - const sourcemeta::core::SchemaFrame &, - const sourcemeta::core::SchemaFrame::Location &, - const sourcemeta::core::SchemaWalker &, - const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { - return schema.is_boolean() && schema.to_boolean(); - } - - 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 bc9dde04..138f718a 100644 --- a/vendor/core/src/extension/alterschema/canonicalizer/const_as_enum.h +++ b/vendor/core/src/extension/alterschema/canonicalizer/const_as_enum.h @@ -1,5 +1,7 @@ class ConstAsEnum final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; ConstAsEnum() : SchemaTransformRule{"const_as_enum", "Setting `const` is syntax sugar for an " 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 5534bcb2..16246872 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 @@ -1,5 +1,7 @@ class ExclusiveMaximumIntegerToMaximum final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; ExclusiveMaximumIntegerToMaximum() : SchemaTransformRule{ "exclusive_maximum_integer_to_maximum", 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 9fd09f63..87479ab7 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 @@ -1,5 +1,7 @@ class ExclusiveMinimumIntegerToMinimum final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; ExclusiveMinimumIntegerToMinimum() : SchemaTransformRule{ "exclusive_minimum_integer_to_minimum", diff --git a/vendor/core/src/extension/alterschema/canonicalizer/items_implicit.h b/vendor/core/src/extension/alterschema/canonicalizer/items_implicit.h new file mode 100644 index 00000000..50a361e0 --- /dev/null +++ b/vendor/core/src/extension/alterschema/canonicalizer/items_implicit.h @@ -0,0 +1,40 @@ +class ItemsImplicit final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + ItemsImplicit() + : SchemaTransformRule{"items_implicit", + "Every array has an implicit `items` " + "that consists of the boolean schema `true`"} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &, + const sourcemeta::core::SchemaFrame::Location &, + const sourcemeta::core::SchemaWalker &, + const sourcemeta::core::SchemaResolver &) const + -> sourcemeta::core::SchemaTransformRule::Result override { + ONLY_CONTINUE_IF( + ((vocabularies.contains( + Vocabularies::Known::JSON_Schema_2020_12_Validation) && + vocabularies.contains( + Vocabularies::Known::JSON_Schema_2020_12_Applicator)) || + (vocabularies.contains( + Vocabularies::Known::JSON_Schema_2019_09_Validation) && + vocabularies.contains( + Vocabularies::Known::JSON_Schema_2019_09_Applicator)) || + vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_Draft_7, + Vocabularies::Known::JSON_Schema_Draft_6})) && + schema.is_object() && schema.defines("type") && + schema.at("type").is_string() && + schema.at("type").to_string() == "array" && !schema.defines("items")); + return true; + } + + auto transform(JSON &schema, const Result &) const -> void override { + schema.assign("items", JSON{true}); + } +}; 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 3946bcc1..225a2892 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 @@ -1,5 +1,7 @@ class MaxContainsCoveredByMaxItems final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::false_type; MaxContainsCoveredByMaxItems() : SchemaTransformRule{ "max_contains_covered_by_max_items", 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 c3337f04..56a23358 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 @@ -1,5 +1,7 @@ class MinItemsGivenMinContains final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; MinItemsGivenMinContains() : SchemaTransformRule{ "min_items_given_min_contains", 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 ca0d00c8..dfdeae06 100644 --- a/vendor/core/src/extension/alterschema/canonicalizer/min_items_implicit.h +++ b/vendor/core/src/extension/alterschema/canonicalizer/min_items_implicit.h @@ -1,5 +1,7 @@ class MinItemsImplicit final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; MinItemsImplicit() : SchemaTransformRule{"min_items_implicit", "Every array has a minimum size of zero items"} {}; 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 1eba9e35..7414758d 100644 --- a/vendor/core/src/extension/alterschema/canonicalizer/min_length_implicit.h +++ b/vendor/core/src/extension/alterschema/canonicalizer/min_length_implicit.h @@ -1,5 +1,7 @@ class MinLengthImplicit final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; MinLengthImplicit() : SchemaTransformRule{ "min_length_implicit", 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 40f8a613..60a31913 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 @@ -1,5 +1,7 @@ class MinPropertiesCoveredByRequired final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::false_type; MinPropertiesCoveredByRequired() : SchemaTransformRule{ "min_properties_covered_by_required", 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 b1cb98ec..9a4dca7d 100644 --- a/vendor/core/src/extension/alterschema/canonicalizer/min_properties_implicit.h +++ b/vendor/core/src/extension/alterschema/canonicalizer/min_properties_implicit.h @@ -1,5 +1,7 @@ class MinPropertiesImplicit final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; MinPropertiesImplicit() : SchemaTransformRule{ "min_properties_implicit", 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 32a2c864..b04a47f2 100644 --- a/vendor/core/src/extension/alterschema/canonicalizer/multiple_of_implicit.h +++ b/vendor/core/src/extension/alterschema/canonicalizer/multiple_of_implicit.h @@ -1,5 +1,7 @@ class MultipleOfImplicit final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; MultipleOfImplicit() : SchemaTransformRule{"multiple_of_implicit", "The unit of `multipleOf` is the integer 1"} {}; diff --git a/vendor/core/src/extension/alterschema/canonicalizer/no_metadata.h b/vendor/core/src/extension/alterschema/canonicalizer/no_metadata.h index 80b53c25..3a2ff336 100644 --- a/vendor/core/src/extension/alterschema/canonicalizer/no_metadata.h +++ b/vendor/core/src/extension/alterschema/canonicalizer/no_metadata.h @@ -1,5 +1,7 @@ class NoMetadata final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; NoMetadata() : SchemaTransformRule{"no_metadata", "Annotations, comments, and unknown keywords have " diff --git a/vendor/core/src/extension/alterschema/canonicalizer/properties_implicit.h b/vendor/core/src/extension/alterschema/canonicalizer/properties_implicit.h index 6b36eb87..780f0959 100644 --- a/vendor/core/src/extension/alterschema/canonicalizer/properties_implicit.h +++ b/vendor/core/src/extension/alterschema/canonicalizer/properties_implicit.h @@ -1,5 +1,7 @@ class PropertiesImplicit final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; PropertiesImplicit() : SchemaTransformRule{"properties_implicit", "Every object has an implicit `properties` " diff --git a/vendor/core/src/extension/alterschema/canonicalizer/type_array_to_any_of.h b/vendor/core/src/extension/alterschema/canonicalizer/type_array_to_any_of.h index 7c55c172..1e9c74b3 100644 --- a/vendor/core/src/extension/alterschema/canonicalizer/type_array_to_any_of.h +++ b/vendor/core/src/extension/alterschema/canonicalizer/type_array_to_any_of.h @@ -1,5 +1,7 @@ class TypeArrayToAnyOf final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; TypeArrayToAnyOf() : SchemaTransformRule{ "type_array_to_any_of", 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 565a65b5..bf689d9f 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 @@ -1,5 +1,7 @@ class TypeBooleanAsEnum final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; TypeBooleanAsEnum() : SchemaTransformRule{ "type_boolean_as_enum", 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 92eb429b..81a01c2f 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 @@ -1,5 +1,7 @@ class TypeNullAsEnum final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; TypeNullAsEnum() : SchemaTransformRule{ "type_null_as_enum", 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 11fb779d..aff2a463 100644 --- a/vendor/core/src/extension/alterschema/canonicalizer/type_union_implicit.h +++ b/vendor/core/src/extension/alterschema/canonicalizer/type_union_implicit.h @@ -1,5 +1,7 @@ class TypeUnionImplicit final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; TypeUnionImplicit() : SchemaTransformRule{ "type_union_implicit", diff --git a/vendor/core/src/extension/alterschema/common/allof_false_simplify.h b/vendor/core/src/extension/alterschema/common/allof_false_simplify.h new file mode 100644 index 00000000..f8a59b1a --- /dev/null +++ b/vendor/core/src/extension/alterschema/common/allof_false_simplify.h @@ -0,0 +1,45 @@ +class AllOfFalseSimplify final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + AllOfFalseSimplify() + : SchemaTransformRule{"allof_false_simplify", + "When `allOf` contains a `false` branch, the " + "schema is unsatisfiable"} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &frame, + const sourcemeta::core::SchemaFrame::Location &location, + const sourcemeta::core::SchemaWalker &, + const sourcemeta::core::SchemaResolver &) const + -> sourcemeta::core::SchemaTransformRule::Result override { + static const JSON::String KEYWORD{"allOf"}; + ONLY_CONTINUE_IF(vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_2020_12_Applicator, + Vocabularies::Known::JSON_Schema_2019_09_Applicator, + Vocabularies::Known::JSON_Schema_Draft_7, + Vocabularies::Known::JSON_Schema_Draft_6}) && + schema.is_object() && schema.defines(KEYWORD) && + !schema.defines("not") && schema.at(KEYWORD).is_array()); + + const auto &allof{schema.at(KEYWORD)}; + for (std::size_t index = 0; index < allof.size(); ++index) { + const auto &entry{allof.at(index)}; + if (entry.is_boolean() && !entry.to_boolean()) { + ONLY_CONTINUE_IF(!frame.has_references_through( + location.pointer, WeakPointer::Token{std::cref(KEYWORD)})); + return APPLIES_TO_POINTERS({Pointer{KEYWORD, index}}); + } + } + + return false; + } + + auto transform(JSON &schema, const Result &) const -> void override { + schema.at("allOf").into(JSON{true}); + schema.rename("allOf", "not"); + } +}; diff --git a/vendor/core/src/extension/alterschema/common/anyof_false_simplify.h b/vendor/core/src/extension/alterschema/common/anyof_false_simplify.h new file mode 100644 index 00000000..562ba54f --- /dev/null +++ b/vendor/core/src/extension/alterschema/common/anyof_false_simplify.h @@ -0,0 +1,40 @@ +class AnyOfFalseSimplify final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + AnyOfFalseSimplify() + : SchemaTransformRule{"anyof_false_simplify", + "An `anyOf` of a single `false` branch is " + "unsatisfiable"} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &frame, + const sourcemeta::core::SchemaFrame::Location &location, + const sourcemeta::core::SchemaWalker &, + const sourcemeta::core::SchemaResolver &) const + -> sourcemeta::core::SchemaTransformRule::Result override { + static const JSON::String KEYWORD{"anyOf"}; + ONLY_CONTINUE_IF(vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_2020_12_Applicator, + Vocabularies::Known::JSON_Schema_2019_09_Applicator, + Vocabularies::Known::JSON_Schema_Draft_7, + Vocabularies::Known::JSON_Schema_Draft_6}) && + schema.is_object() && schema.defines(KEYWORD) && + !schema.defines("not") && schema.at(KEYWORD).is_array() && + schema.at(KEYWORD).size() == 1); + + const auto &entry{schema.at(KEYWORD).front()}; + ONLY_CONTINUE_IF(entry.is_boolean() && !entry.to_boolean()); + ONLY_CONTINUE_IF(!frame.has_references_through( + location.pointer, WeakPointer::Token{std::cref(KEYWORD)})); + return APPLIES_TO_POINTERS({Pointer{KEYWORD, 0}}); + } + + auto transform(JSON &schema, const Result &) const -> void override { + schema.at("anyOf").into(JSON{true}); + schema.rename("anyOf", "not"); + } +}; diff --git a/vendor/core/src/extension/alterschema/common/anyof_remove_false_schemas.h b/vendor/core/src/extension/alterschema/common/anyof_remove_false_schemas.h new file mode 100644 index 00000000..d8bee7dc --- /dev/null +++ b/vendor/core/src/extension/alterschema/common/anyof_remove_false_schemas.h @@ -0,0 +1,66 @@ +class AnyOfRemoveFalseSchemas final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + AnyOfRemoveFalseSchemas() + : SchemaTransformRule{ + "anyof_remove_false_schemas", + "The boolean schema `false` is guaranteed to never match in " + "`anyOf`, as it is sufficient for any other branch to match"} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &frame, + const sourcemeta::core::SchemaFrame::Location &location, + const sourcemeta::core::SchemaWalker &, + const sourcemeta::core::SchemaResolver &) const + -> sourcemeta::core::SchemaTransformRule::Result override { + static const JSON::String KEYWORD{"anyOf"}; + ONLY_CONTINUE_IF(vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_2020_12_Applicator, + Vocabularies::Known::JSON_Schema_2019_09_Applicator, + Vocabularies::Known::JSON_Schema_Draft_7, + Vocabularies::Known::JSON_Schema_Draft_6}) && + schema.is_object() && schema.defines(KEYWORD) && + schema.at(KEYWORD).is_array() && + schema.at(KEYWORD).contains(JSON{false})); + ONLY_CONTINUE_IF(!frame.has_references_through( + location.pointer, WeakPointer::Token{std::cref(KEYWORD)})); + + std::vector false_locations; + bool has_non_false{false}; + const auto &anyof{schema.at(KEYWORD)}; + for (std::size_t index = 0; index < anyof.size(); ++index) { + const auto &entry{anyof.at(index)}; + if (entry.is_boolean() && !entry.to_boolean()) { + false_locations.push_back(Pointer{KEYWORD, index}); + } else { + has_non_false = true; + } + } + + ONLY_CONTINUE_IF(has_non_false); + return APPLIES_TO_POINTERS(std::move(false_locations)); + } + + auto transform(JSON &schema, const Result &result) const -> void override { + static const JSON::String KEYWORD{"anyOf"}; + + std::unordered_set indices_to_remove; + for (const auto &location : result.locations) { + indices_to_remove.insert(location.at(1).to_index()); + } + + auto new_anyof{JSON::make_array()}; + const auto &anyof{schema.at(KEYWORD)}; + for (std::size_t index = 0; index < anyof.size(); ++index) { + if (!indices_to_remove.contains(index)) { + new_anyof.push_back(anyof.at(index)); + } + } + + schema.assign(KEYWORD, std::move(new_anyof)); + } +}; diff --git a/vendor/core/src/extension/alterschema/common/anyof_true_simplify.h b/vendor/core/src/extension/alterschema/common/anyof_true_simplify.h new file mode 100644 index 00000000..8cdef08b --- /dev/null +++ b/vendor/core/src/extension/alterschema/common/anyof_true_simplify.h @@ -0,0 +1,46 @@ +class AnyOfTrueSimplify final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + AnyOfTrueSimplify() + : SchemaTransformRule{ + "anyof_true_simplify", + "An `anyOf` with a `true` or `{}` branch always succeeds"} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &frame, + const sourcemeta::core::SchemaFrame::Location &location, + const sourcemeta::core::SchemaWalker &, + const sourcemeta::core::SchemaResolver &) const + -> sourcemeta::core::SchemaTransformRule::Result override { + static const JSON::String KEYWORD{"anyOf"}; + ONLY_CONTINUE_IF(vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_2020_12_Applicator, + Vocabularies::Known::JSON_Schema_2019_09_Applicator, + Vocabularies::Known::JSON_Schema_Draft_7, + Vocabularies::Known::JSON_Schema_Draft_6, + Vocabularies::Known::JSON_Schema_Draft_4}) && + schema.is_object() && schema.defines(KEYWORD) && + schema.at(KEYWORD).is_array()); + + const auto &anyof{schema.at(KEYWORD)}; + for (std::size_t index = 0; index < anyof.size(); ++index) { + const auto &entry{anyof.at(index)}; + if ((entry.is_boolean() && entry.to_boolean()) || + (entry.is_object() && entry.empty())) { + ONLY_CONTINUE_IF(!frame.has_references_through( + location.pointer, WeakPointer::Token{std::cref(KEYWORD)})); + return APPLIES_TO_POINTERS({Pointer{KEYWORD, index}}); + } + } + + return false; + } + + auto transform(JSON &schema, const Result &) const -> void override { + schema.erase("anyOf"); + } +}; diff --git a/vendor/core/src/extension/alterschema/common/const_with_type.h b/vendor/core/src/extension/alterschema/common/const_with_type.h index 261f528f..820f5ae6 100644 --- a/vendor/core/src/extension/alterschema/common/const_with_type.h +++ b/vendor/core/src/extension/alterschema/common/const_with_type.h @@ -1,5 +1,7 @@ class ConstWithType final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; ConstWithType() : SchemaTransformRule{ "const_with_type", diff --git a/vendor/core/src/extension/alterschema/common/content_media_type_without_encoding.h b/vendor/core/src/extension/alterschema/common/content_media_type_without_encoding.h index 745f3a94..7c8e3e00 100644 --- a/vendor/core/src/extension/alterschema/common/content_media_type_without_encoding.h +++ b/vendor/core/src/extension/alterschema/common/content_media_type_without_encoding.h @@ -1,5 +1,7 @@ class ContentMediaTypeWithoutEncoding final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; ContentMediaTypeWithoutEncoding() : SchemaTransformRule{ "content_media_type_without_encoding", diff --git a/vendor/core/src/extension/alterschema/common/content_schema_without_media_type.h b/vendor/core/src/extension/alterschema/common/content_schema_without_media_type.h index fce5a85e..bfa89eff 100644 --- a/vendor/core/src/extension/alterschema/common/content_schema_without_media_type.h +++ b/vendor/core/src/extension/alterschema/common/content_schema_without_media_type.h @@ -3,6 +3,8 @@ class ContentSchemaWithoutMediaType final : public SchemaTransformRule { static inline const std::string KEYWORD{"contentSchema"}; public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; ContentSchemaWithoutMediaType() : SchemaTransformRule{ "content_schema_without_media_type", diff --git a/vendor/core/src/extension/alterschema/common/dependencies_property_tautology.h b/vendor/core/src/extension/alterschema/common/dependencies_property_tautology.h index 125a20fb..e7eec435 100644 --- a/vendor/core/src/extension/alterschema/common/dependencies_property_tautology.h +++ b/vendor/core/src/extension/alterschema/common/dependencies_property_tautology.h @@ -1,5 +1,7 @@ class DependenciesPropertyTautology final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::false_type; DependenciesPropertyTautology() : SchemaTransformRule{ "dependencies_property_tautology", diff --git a/vendor/core/src/extension/alterschema/common/dependent_required_tautology.h b/vendor/core/src/extension/alterschema/common/dependent_required_tautology.h index 30f174e0..2cebac02 100644 --- a/vendor/core/src/extension/alterschema/common/dependent_required_tautology.h +++ b/vendor/core/src/extension/alterschema/common/dependent_required_tautology.h @@ -1,5 +1,7 @@ class DependentRequiredTautology final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::false_type; DependentRequiredTautology() : SchemaTransformRule{ "dependent_required_tautology", diff --git a/vendor/core/src/extension/alterschema/common/draft_official_dialect_without_empty_fragment.h b/vendor/core/src/extension/alterschema/common/draft_official_dialect_without_empty_fragment.h index 060752ec..ff47fa63 100644 --- a/vendor/core/src/extension/alterschema/common/draft_official_dialect_without_empty_fragment.h +++ b/vendor/core/src/extension/alterschema/common/draft_official_dialect_without_empty_fragment.h @@ -1,6 +1,8 @@ class DraftOfficialDialectWithoutEmptyFragment final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; DraftOfficialDialectWithoutEmptyFragment() : SchemaTransformRule{"draft_official_dialect_without_empty_fragment", "The official dialect URI of Draft 7 and older " diff --git a/vendor/core/src/extension/alterschema/common/draft_ref_siblings.h b/vendor/core/src/extension/alterschema/common/draft_ref_siblings.h index d7203637..2b4e02d6 100644 --- a/vendor/core/src/extension/alterschema/common/draft_ref_siblings.h +++ b/vendor/core/src/extension/alterschema/common/draft_ref_siblings.h @@ -1,5 +1,7 @@ class DraftRefSiblings final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; DraftRefSiblings() : SchemaTransformRule{"draft_ref_siblings", "In Draft 7 and older dialects, keywords sibling " diff --git a/vendor/core/src/extension/alterschema/common/drop_allof_empty_schemas.h b/vendor/core/src/extension/alterschema/common/drop_allof_empty_schemas.h index 31abc14e..346844fa 100644 --- a/vendor/core/src/extension/alterschema/common/drop_allof_empty_schemas.h +++ b/vendor/core/src/extension/alterschema/common/drop_allof_empty_schemas.h @@ -1,5 +1,7 @@ class DropAllOfEmptySchemas final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; DropAllOfEmptySchemas() : SchemaTransformRule{"drop_allof_empty_schemas", "Empty schemas in `allOf` are redundant and can be " diff --git a/vendor/core/src/extension/alterschema/common/duplicate_allof_branches.h b/vendor/core/src/extension/alterschema/common/duplicate_allof_branches.h index cf39f146..2b84e54e 100644 --- a/vendor/core/src/extension/alterschema/common/duplicate_allof_branches.h +++ b/vendor/core/src/extension/alterschema/common/duplicate_allof_branches.h @@ -1,5 +1,7 @@ class DuplicateAllOfBranches final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; DuplicateAllOfBranches() : SchemaTransformRule{ "duplicate_allof_branches", diff --git a/vendor/core/src/extension/alterschema/common/duplicate_anyof_branches.h b/vendor/core/src/extension/alterschema/common/duplicate_anyof_branches.h index 1465628b..50719fe7 100644 --- a/vendor/core/src/extension/alterschema/common/duplicate_anyof_branches.h +++ b/vendor/core/src/extension/alterschema/common/duplicate_anyof_branches.h @@ -1,5 +1,7 @@ class DuplicateAnyOfBranches final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; DuplicateAnyOfBranches() : SchemaTransformRule{ "duplicate_anyof_branches", diff --git a/vendor/core/src/extension/alterschema/common/duplicate_enum_values.h b/vendor/core/src/extension/alterschema/common/duplicate_enum_values.h index c3fec367..8b422cf6 100644 --- a/vendor/core/src/extension/alterschema/common/duplicate_enum_values.h +++ b/vendor/core/src/extension/alterschema/common/duplicate_enum_values.h @@ -1,5 +1,7 @@ class DuplicateEnumValues final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::false_type; DuplicateEnumValues() : SchemaTransformRule{"duplicate_enum_values", "Setting duplicate values in `enum` is " diff --git a/vendor/core/src/extension/alterschema/common/duplicate_required_values.h b/vendor/core/src/extension/alterschema/common/duplicate_required_values.h index d1fd6ad2..9e3f3cbd 100644 --- a/vendor/core/src/extension/alterschema/common/duplicate_required_values.h +++ b/vendor/core/src/extension/alterschema/common/duplicate_required_values.h @@ -1,5 +1,7 @@ class DuplicateRequiredValues final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::false_type; DuplicateRequiredValues() : SchemaTransformRule{ "duplicate_required_values", diff --git a/vendor/core/src/extension/alterschema/common/else_empty.h b/vendor/core/src/extension/alterschema/common/else_empty.h index fe883ad5..c4f64a0b 100644 --- a/vendor/core/src/extension/alterschema/common/else_empty.h +++ b/vendor/core/src/extension/alterschema/common/else_empty.h @@ -3,6 +3,8 @@ class ElseEmpty final : public SchemaTransformRule { static inline const std::string KEYWORD{"else"}; public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; ElseEmpty() : SchemaTransformRule{"else_empty", "Setting the `else` keyword to the empty schema " diff --git a/vendor/core/src/extension/alterschema/common/else_without_if.h b/vendor/core/src/extension/alterschema/common/else_without_if.h index 7b871dd0..475c6222 100644 --- a/vendor/core/src/extension/alterschema/common/else_without_if.h +++ b/vendor/core/src/extension/alterschema/common/else_without_if.h @@ -3,6 +3,8 @@ class ElseWithoutIf final : public SchemaTransformRule { static inline const std::string KEYWORD{"else"}; public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; ElseWithoutIf() : SchemaTransformRule{"else_without_if", "The `else` keyword is meaningless " diff --git a/vendor/core/src/extension/alterschema/common/empty_object_as_true.h b/vendor/core/src/extension/alterschema/common/empty_object_as_true.h new file mode 100644 index 00000000..288cab7d --- /dev/null +++ b/vendor/core/src/extension/alterschema/common/empty_object_as_true.h @@ -0,0 +1,32 @@ +class EmptyObjectAsTrue final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::false_type; + EmptyObjectAsTrue() + : SchemaTransformRule{ + "empty_object_as_true", + "The empty schema `{}` accepts all values and is equivalent to the " + "boolean schema `true`"} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &, + const sourcemeta::core::SchemaFrame::Location &, + const sourcemeta::core::SchemaWalker &, + const sourcemeta::core::SchemaResolver &) const + -> sourcemeta::core::SchemaTransformRule::Result override { + ONLY_CONTINUE_IF(vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_2020_12_Core, + Vocabularies::Known::JSON_Schema_2019_09_Core, + Vocabularies::Known::JSON_Schema_Draft_7, + Vocabularies::Known::JSON_Schema_Draft_6}) && + schema.is_object() && schema.empty()); + return true; + } + + auto transform(JSON &schema, const Result &) const -> void override { + schema.into(JSON{true}); + } +}; diff --git a/vendor/core/src/extension/alterschema/common/enum_with_type.h b/vendor/core/src/extension/alterschema/common/enum_with_type.h index 350f73d8..c2a8949c 100644 --- a/vendor/core/src/extension/alterschema/common/enum_with_type.h +++ b/vendor/core/src/extension/alterschema/common/enum_with_type.h @@ -1,5 +1,7 @@ class EnumWithType final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; EnumWithType() : SchemaTransformRule{ "enum_with_type", diff --git a/vendor/core/src/extension/alterschema/common/equal_numeric_bounds_to_enum.h b/vendor/core/src/extension/alterschema/common/equal_numeric_bounds_to_enum.h index 698fa93a..c7cf7de8 100644 --- a/vendor/core/src/extension/alterschema/common/equal_numeric_bounds_to_enum.h +++ b/vendor/core/src/extension/alterschema/common/equal_numeric_bounds_to_enum.h @@ -1,5 +1,7 @@ class EqualNumericBoundsToEnum final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; EqualNumericBoundsToEnum() : SchemaTransformRule{ "equal_numeric_bounds_to_enum", diff --git a/vendor/core/src/extension/alterschema/common/exclusive_maximum_number_and_maximum.h b/vendor/core/src/extension/alterschema/common/exclusive_maximum_number_and_maximum.h index 6a1426b7..dd096d02 100644 --- a/vendor/core/src/extension/alterschema/common/exclusive_maximum_number_and_maximum.h +++ b/vendor/core/src/extension/alterschema/common/exclusive_maximum_number_and_maximum.h @@ -1,5 +1,7 @@ class ExclusiveMaximumNumberAndMaximum final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; ExclusiveMaximumNumberAndMaximum() : SchemaTransformRule{ "exclusive_maximum_number_and_maximum", diff --git a/vendor/core/src/extension/alterschema/common/exclusive_minimum_number_and_minimum.h b/vendor/core/src/extension/alterschema/common/exclusive_minimum_number_and_minimum.h index 53c0e997..3fb192f4 100644 --- a/vendor/core/src/extension/alterschema/common/exclusive_minimum_number_and_minimum.h +++ b/vendor/core/src/extension/alterschema/common/exclusive_minimum_number_and_minimum.h @@ -1,5 +1,7 @@ class ExclusiveMinimumNumberAndMinimum final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; ExclusiveMinimumNumberAndMinimum() : SchemaTransformRule{ "exclusive_minimum_number_and_minimum", diff --git a/vendor/core/src/extension/alterschema/common/if_without_then_else.h b/vendor/core/src/extension/alterschema/common/if_without_then_else.h index 3f19961c..93bafb3b 100644 --- a/vendor/core/src/extension/alterschema/common/if_without_then_else.h +++ b/vendor/core/src/extension/alterschema/common/if_without_then_else.h @@ -3,6 +3,8 @@ class IfWithoutThenElse final : public SchemaTransformRule { static inline const std::string KEYWORD{"if"}; public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; IfWithoutThenElse() : SchemaTransformRule{ "if_without_then_else", diff --git a/vendor/core/src/extension/alterschema/common/ignored_metaschema.h b/vendor/core/src/extension/alterschema/common/ignored_metaschema.h index 4fa07a18..3faed03b 100644 --- a/vendor/core/src/extension/alterschema/common/ignored_metaschema.h +++ b/vendor/core/src/extension/alterschema/common/ignored_metaschema.h @@ -1,5 +1,7 @@ class IgnoredMetaschema final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; IgnoredMetaschema() : SchemaTransformRule{ "ignored_metaschema", diff --git a/vendor/core/src/extension/alterschema/common/max_contains_without_contains.h b/vendor/core/src/extension/alterschema/common/max_contains_without_contains.h index 00944a49..4cad64f2 100644 --- a/vendor/core/src/extension/alterschema/common/max_contains_without_contains.h +++ b/vendor/core/src/extension/alterschema/common/max_contains_without_contains.h @@ -1,5 +1,7 @@ class MaxContainsWithoutContains final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; MaxContainsWithoutContains() : SchemaTransformRule{"max_contains_without_contains", "The `maxContains` keyword is meaningless " diff --git a/vendor/core/src/extension/alterschema/common/maximum_real_for_integer.h b/vendor/core/src/extension/alterschema/common/maximum_real_for_integer.h index 6494672c..65b77b9e 100644 --- a/vendor/core/src/extension/alterschema/common/maximum_real_for_integer.h +++ b/vendor/core/src/extension/alterschema/common/maximum_real_for_integer.h @@ -1,5 +1,7 @@ class MaximumRealForInteger final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::false_type; MaximumRealForInteger() : SchemaTransformRule{ "maximum_real_for_integer", diff --git a/vendor/core/src/extension/alterschema/common/min_contains_without_contains.h b/vendor/core/src/extension/alterschema/common/min_contains_without_contains.h index 5f14de34..1bd56a75 100644 --- a/vendor/core/src/extension/alterschema/common/min_contains_without_contains.h +++ b/vendor/core/src/extension/alterschema/common/min_contains_without_contains.h @@ -1,5 +1,7 @@ class MinContainsWithoutContains final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; MinContainsWithoutContains() : SchemaTransformRule{"min_contains_without_contains", "The `minContains` keyword is meaningless " diff --git a/vendor/core/src/extension/alterschema/common/minimum_real_for_integer.h b/vendor/core/src/extension/alterschema/common/minimum_real_for_integer.h index 65db9098..ba46ddbc 100644 --- a/vendor/core/src/extension/alterschema/common/minimum_real_for_integer.h +++ b/vendor/core/src/extension/alterschema/common/minimum_real_for_integer.h @@ -1,5 +1,7 @@ class MinimumRealForInteger final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::false_type; MinimumRealForInteger() : SchemaTransformRule{ "minimum_real_for_integer", diff --git a/vendor/core/src/extension/alterschema/common/modern_official_dialect_with_empty_fragment.h b/vendor/core/src/extension/alterschema/common/modern_official_dialect_with_empty_fragment.h index caa12e08..555dc687 100644 --- a/vendor/core/src/extension/alterschema/common/modern_official_dialect_with_empty_fragment.h +++ b/vendor/core/src/extension/alterschema/common/modern_official_dialect_with_empty_fragment.h @@ -1,6 +1,8 @@ class ModernOfficialDialectWithEmptyFragment final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; ModernOfficialDialectWithEmptyFragment() : SchemaTransformRule{ "modern_official_dialect_with_empty_fragment", diff --git a/vendor/core/src/extension/alterschema/common/non_applicable_additional_items.h b/vendor/core/src/extension/alterschema/common/non_applicable_additional_items.h index 6b7f39e2..4ac055cd 100644 --- a/vendor/core/src/extension/alterschema/common/non_applicable_additional_items.h +++ b/vendor/core/src/extension/alterschema/common/non_applicable_additional_items.h @@ -3,6 +3,8 @@ class NonApplicableAdditionalItems final : public SchemaTransformRule { static inline const std::string KEYWORD{"additionalItems"}; public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; NonApplicableAdditionalItems() : SchemaTransformRule{ "non_applicable_additional_items", diff --git a/vendor/core/src/extension/alterschema/common/non_applicable_enum_validation_keywords.h b/vendor/core/src/extension/alterschema/common/non_applicable_enum_validation_keywords.h index 6de098fe..59303700 100644 --- a/vendor/core/src/extension/alterschema/common/non_applicable_enum_validation_keywords.h +++ b/vendor/core/src/extension/alterschema/common/non_applicable_enum_validation_keywords.h @@ -1,5 +1,7 @@ class NonApplicableEnumValidationKeywords final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; NonApplicableEnumValidationKeywords() : SchemaTransformRule{ "non_applicable_enum_validation_keywords", diff --git a/vendor/core/src/extension/alterschema/common/non_applicable_type_specific_keywords.h b/vendor/core/src/extension/alterschema/common/non_applicable_type_specific_keywords.h index 98bebe19..db188c27 100644 --- a/vendor/core/src/extension/alterschema/common/non_applicable_type_specific_keywords.h +++ b/vendor/core/src/extension/alterschema/common/non_applicable_type_specific_keywords.h @@ -1,5 +1,7 @@ class NonApplicableTypeSpecificKeywords final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; NonApplicableTypeSpecificKeywords() : SchemaTransformRule{"non_applicable_type_specific_keywords", "Avoid keywords that don't apply to the type or " diff --git a/vendor/core/src/extension/alterschema/common/not_false.h b/vendor/core/src/extension/alterschema/common/not_false.h index f9016890..65de31f6 100644 --- a/vendor/core/src/extension/alterschema/common/not_false.h +++ b/vendor/core/src/extension/alterschema/common/not_false.h @@ -3,6 +3,8 @@ class NotFalse final : public SchemaTransformRule { static inline const std::string KEYWORD{"not"}; public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; NotFalse() : SchemaTransformRule{"not_false", "Setting the `not` keyword to `false` imposes no " diff --git a/vendor/core/src/extension/alterschema/common/oneof_false_simplify.h b/vendor/core/src/extension/alterschema/common/oneof_false_simplify.h new file mode 100644 index 00000000..64c4adf6 --- /dev/null +++ b/vendor/core/src/extension/alterschema/common/oneof_false_simplify.h @@ -0,0 +1,40 @@ +class OneOfFalseSimplify final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + OneOfFalseSimplify() + : SchemaTransformRule{"oneof_false_simplify", + "A `oneOf` of a single `false` branch is " + "unsatisfiable"} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &frame, + const sourcemeta::core::SchemaFrame::Location &location, + const sourcemeta::core::SchemaWalker &, + const sourcemeta::core::SchemaResolver &) const + -> sourcemeta::core::SchemaTransformRule::Result override { + static const JSON::String KEYWORD{"oneOf"}; + ONLY_CONTINUE_IF(vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_2020_12_Applicator, + Vocabularies::Known::JSON_Schema_2019_09_Applicator, + Vocabularies::Known::JSON_Schema_Draft_7, + Vocabularies::Known::JSON_Schema_Draft_6}) && + schema.is_object() && schema.defines(KEYWORD) && + !schema.defines("not") && schema.at(KEYWORD).is_array() && + schema.at(KEYWORD).size() == 1); + + const auto &entry{schema.at(KEYWORD).front()}; + ONLY_CONTINUE_IF(entry.is_boolean() && !entry.to_boolean()); + ONLY_CONTINUE_IF(!frame.has_references_through( + location.pointer, WeakPointer::Token{std::cref(KEYWORD)})); + return APPLIES_TO_POINTERS({Pointer{KEYWORD, 0}}); + } + + auto transform(JSON &schema, const Result &) const -> void override { + schema.at("oneOf").into(JSON{true}); + schema.rename("oneOf", "not"); + } +}; diff --git a/vendor/core/src/extension/alterschema/common/oneof_to_anyof_disjoint_types.h b/vendor/core/src/extension/alterschema/common/oneof_to_anyof_disjoint_types.h new file mode 100644 index 00000000..d9a16bc5 --- /dev/null +++ b/vendor/core/src/extension/alterschema/common/oneof_to_anyof_disjoint_types.h @@ -0,0 +1,97 @@ +class OneOfToAnyOfDisjointTypes final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + OneOfToAnyOfDisjointTypes() + : SchemaTransformRule{ + "oneof_to_anyof_disjoint_types", + "A `oneOf` where all branches have disjoint types can be safely " + "converted to `anyOf`"} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &, + const sourcemeta::core::SchemaFrame::Location &, + const sourcemeta::core::SchemaWalker &, + const sourcemeta::core::SchemaResolver &) const + -> sourcemeta::core::SchemaTransformRule::Result override { + static const JSON::String KEYWORD{"oneOf"}; + ONLY_CONTINUE_IF(vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_2020_12_Applicator, + Vocabularies::Known::JSON_Schema_2019_09_Applicator, + Vocabularies::Known::JSON_Schema_Draft_7, + Vocabularies::Known::JSON_Schema_Draft_6, + Vocabularies::Known::JSON_Schema_Draft_4}) && + schema.is_object() && schema.defines(KEYWORD) && + schema.at(KEYWORD).is_array() && + schema.at(KEYWORD).size() > 1); + + const auto has_validation_vocabulary{vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_2020_12_Validation, + Vocabularies::Known::JSON_Schema_2019_09_Validation, + Vocabularies::Known::JSON_Schema_Draft_7, + Vocabularies::Known::JSON_Schema_Draft_6, + Vocabularies::Known::JSON_Schema_Draft_4, + Vocabularies::Known::JSON_Schema_Draft_3, + Vocabularies::Known::JSON_Schema_Draft_2, + Vocabularies::Known::JSON_Schema_Draft_1})}; + + const auto has_const_vocabulary{vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_2020_12_Validation, + Vocabularies::Known::JSON_Schema_2019_09_Validation, + Vocabularies::Known::JSON_Schema_Draft_7, + Vocabularies::Known::JSON_Schema_Draft_6})}; + + const auto &oneof{schema.at(KEYWORD)}; + std::vector type_sets; + type_sets.reserve(oneof.size()); + + for (const auto &branch : oneof.as_array()) { + ONLY_CONTINUE_IF(branch.is_object()); + + const auto has_type{branch.defines("type")}; + const auto has_const{has_const_vocabulary && branch.defines("const")}; + const auto has_enum{has_validation_vocabulary && branch.defines("enum") && + branch.at("enum").is_array()}; + + if (has_type) { + type_sets.push_back(parse_schema_type(branch.at("type"))); + } else if (has_const && !has_enum) { + JSON::TypeSet branch_types; + branch_types.set(static_cast(branch.at("const").type())); + type_sets.push_back(branch_types); + } else if (has_enum && !has_const) { + JSON::TypeSet branch_types; + for (const auto &item : branch.at("enum").as_array()) { + branch_types.set(static_cast(item.type())); + } + type_sets.push_back(branch_types); + } else { + return false; + } + } + + for (std::size_t index = 0; index < type_sets.size(); ++index) { + for (std::size_t other = index + 1; other < type_sets.size(); ++other) { + ONLY_CONTINUE_IF((type_sets[index] & type_sets[other]).none()); + } + } + + return APPLIES_TO_KEYWORDS(KEYWORD); + } + + auto transform(JSON &schema, const Result &) const -> void override { + schema.rename("oneOf", "anyOf"); + } + + [[nodiscard]] auto + rereference(const std::string_view, const Pointer &origin [[maybe_unused]], + const Pointer &target, const Pointer ¤t) const + -> Pointer override { + const Pointer oneof_prefix{current.concat({"oneOf"})}; + const Pointer anyof_prefix{current.concat({"anyOf"})}; + return target.rebase(oneof_prefix, anyof_prefix); + } +}; diff --git a/vendor/core/src/extension/alterschema/common/orphan_definitions.h b/vendor/core/src/extension/alterschema/common/orphan_definitions.h index 426cd562..047156fe 100644 --- a/vendor/core/src/extension/alterschema/common/orphan_definitions.h +++ b/vendor/core/src/extension/alterschema/common/orphan_definitions.h @@ -1,5 +1,7 @@ class OrphanDefinitions final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; OrphanDefinitions() : SchemaTransformRule{ "orphan_definitions", @@ -12,8 +14,8 @@ class OrphanDefinitions final : public SchemaTransformRule { const sourcemeta::core::Vocabularies &vocabularies, const sourcemeta::core::SchemaFrame &frame, const sourcemeta::core::SchemaFrame::Location &location, - const sourcemeta::core::SchemaWalker &, - const sourcemeta::core::SchemaResolver &) const + const sourcemeta::core::SchemaWalker &walker, + const sourcemeta::core::SchemaResolver &resolver) const -> sourcemeta::core::SchemaTransformRule::Result override { ONLY_CONTINUE_IF(schema.is_object()); const bool has_modern_core{ @@ -28,35 +30,11 @@ class OrphanDefinitions final : public SchemaTransformRule { schema.defines("definitions")}; ONLY_CONTINUE_IF(has_defs || has_definitions); - bool has_external_to_defs{false}; - bool has_external_to_definitions{false}; - std::unordered_set outside_referenced_defs; - std::unordered_set outside_referenced_definitions; - - for (const auto &[key, reference] : frame.references()) { - const auto destination_location{frame.traverse(reference.destination)}; - if (destination_location.has_value()) { - const auto &destination_pointer{destination_location->get().pointer}; - if (has_defs) { - process_reference(key.second, destination_pointer, location.pointer, - "$defs", has_external_to_defs, - outside_referenced_defs); - } - - if (has_definitions) { - process_reference(key.second, destination_pointer, location.pointer, - "definitions", has_external_to_definitions, - outside_referenced_definitions); - } - } - } - std::vector orphans; - collect_orphans(schema, "$defs", has_defs, has_external_to_defs, - outside_referenced_defs, orphans); - collect_orphans(schema, "definitions", has_definitions, - has_external_to_definitions, outside_referenced_definitions, - orphans); + collect_orphans(frame, walker, resolver, location.pointer, schema, "$defs", + has_defs, orphans); + collect_orphans(frame, walker, resolver, location.pointer, schema, + "definitions", has_definitions, orphans); ONLY_CONTINUE_IF(!orphans.empty()); return APPLIES_TO_POINTERS(std::move(orphans)); @@ -71,55 +49,72 @@ class OrphanDefinitions final : public SchemaTransformRule { schema.at(container).erase(pointer.at(1).to_property()); } - remove_empty_container(schema, "$defs"); - remove_empty_container(schema, "definitions"); + if (schema.defines("$defs") && schema.at("$defs").empty()) { + schema.erase("$defs"); + } + + if (schema.defines("definitions") && schema.at("definitions").empty()) { + schema.erase("definitions"); + } } private: - static auto process_reference( - const WeakPointer &source_pointer, const WeakPointer &destination_pointer, - const WeakPointer &prefix, std::string_view container, bool &has_external, - std::unordered_set &referenced) -> void { - if (!destination_pointer.starts_with(prefix, container) || - destination_pointer.size() <= prefix.size() + 1) { - return; - } + static auto has_reachable_reference_through( + const sourcemeta::core::SchemaFrame &frame, + const sourcemeta::core::SchemaWalker &walker, + const sourcemeta::core::SchemaResolver &resolver, + const WeakPointer &pointer) -> bool { + for (const auto &reference : frame.references()) { + const auto destination{frame.traverse(reference.second.destination)}; + if (!destination.has_value()) { + continue; + } - const auto &entry_token{destination_pointer.at(prefix.size() + 1)}; - if (entry_token.is_property()) { - const auto &entry_name{entry_token.to_property()}; - if (!source_pointer.starts_with(prefix, container)) { - has_external = true; - referenced.insert(entry_name); - } else if (!source_pointer.starts_with(prefix, container, entry_name)) { - referenced.insert(entry_name); + if (!destination->get().pointer.starts_with(pointer)) { + continue; + } + + const auto &source_pointer{reference.first.second}; + if (source_pointer.empty()) { + return true; } - } - } - static auto - collect_orphans(const JSON &schema, const JSON::String &container, - const bool has_container, const bool has_external_reference, - const std::unordered_set &referenced, - std::vector &orphans) -> void { - if (has_container) { - const auto &maybe_object{schema.at(container)}; - if (maybe_object.is_object()) { - // If no external references to container, all definitions are orphans - // Otherwise, only unreferenced definitions are orphans - for (const auto &entry : maybe_object.as_object()) { - if (!has_external_reference || !referenced.contains(entry.first)) { - orphans.push_back(Pointer{container, entry.first}); - } - } + const auto source_location{frame.traverse( + source_pointer.initial(), + sourcemeta::core::SchemaFrame::LocationType::Subschema)}; + if (source_location.has_value() && + frame.is_reachable(source_location->get(), walker, resolver)) { + return true; } } + + return false; } - static auto remove_empty_container(JSON &schema, const JSON::String &name) - -> void { - if (schema.defines(name) && schema.at(name).empty()) { - schema.erase(name); + static auto collect_orphans(const sourcemeta::core::SchemaFrame &frame, + const sourcemeta::core::SchemaWalker &walker, + const sourcemeta::core::SchemaResolver &resolver, + const WeakPointer &prefix, const JSON &schema, + const JSON::String &container, + const bool has_container, + std::vector &orphans) -> void { + if (!has_container || !schema.at(container).is_object()) { + return; + } + + for (const auto &entry : schema.at(container).as_object()) { + const WeakPointer entry_pointer{std::cref(container), + std::cref(entry.first)}; + const auto absolute_entry_pointer{prefix.concat(entry_pointer)}; + const auto entry_location{frame.traverse( + absolute_entry_pointer, + sourcemeta::core::SchemaFrame::LocationType::Subschema)}; + if (entry_location.has_value() && + !frame.is_reachable(entry_location->get(), walker, resolver) && + !has_reachable_reference_through(frame, walker, resolver, + absolute_entry_pointer)) { + orphans.push_back(Pointer{container, entry.first}); + } } } }; diff --git a/vendor/core/src/extension/alterschema/common/required_properties_in_properties.h b/vendor/core/src/extension/alterschema/common/required_properties_in_properties.h index 8cddcb46..26739fae 100644 --- a/vendor/core/src/extension/alterschema/common/required_properties_in_properties.h +++ b/vendor/core/src/extension/alterschema/common/required_properties_in_properties.h @@ -1,5 +1,7 @@ class RequiredPropertiesInProperties final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; RequiredPropertiesInProperties() : SchemaTransformRule{ "required_properties_in_properties", diff --git a/vendor/core/src/extension/alterschema/common/single_type_array.h b/vendor/core/src/extension/alterschema/common/single_type_array.h index 9aea9a30..742ea104 100644 --- a/vendor/core/src/extension/alterschema/common/single_type_array.h +++ b/vendor/core/src/extension/alterschema/common/single_type_array.h @@ -1,5 +1,7 @@ class SingleTypeArray final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::false_type; SingleTypeArray() : SchemaTransformRule{"single_type_array", "Setting `type` to an array of a single type is " diff --git a/vendor/core/src/extension/alterschema/common/then_empty.h b/vendor/core/src/extension/alterschema/common/then_empty.h index 36c1d220..eacf299a 100644 --- a/vendor/core/src/extension/alterschema/common/then_empty.h +++ b/vendor/core/src/extension/alterschema/common/then_empty.h @@ -3,6 +3,8 @@ class ThenEmpty final : public SchemaTransformRule { static inline const std::string KEYWORD{"then"}; public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; ThenEmpty() : SchemaTransformRule{"then_empty", "Setting the `then` keyword to the empty schema " diff --git a/vendor/core/src/extension/alterschema/common/then_without_if.h b/vendor/core/src/extension/alterschema/common/then_without_if.h index ed557568..75d526d8 100644 --- a/vendor/core/src/extension/alterschema/common/then_without_if.h +++ b/vendor/core/src/extension/alterschema/common/then_without_if.h @@ -3,6 +3,8 @@ class ThenWithoutIf final : public SchemaTransformRule { static inline const std::string KEYWORD{"then"}; public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; ThenWithoutIf() : SchemaTransformRule{"then_without_if", "The `then` keyword is meaningless " diff --git a/vendor/core/src/extension/alterschema/common/unknown_keywords_prefix.h b/vendor/core/src/extension/alterschema/common/unknown_keywords_prefix.h index 19c418a4..37691bee 100644 --- a/vendor/core/src/extension/alterschema/common/unknown_keywords_prefix.h +++ b/vendor/core/src/extension/alterschema/common/unknown_keywords_prefix.h @@ -1,5 +1,7 @@ class UnknownKeywordsPrefix final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; UnknownKeywordsPrefix() : SchemaTransformRule{ "unknown_keywords_prefix", diff --git a/vendor/core/src/extension/alterschema/common/unknown_local_ref.h b/vendor/core/src/extension/alterschema/common/unknown_local_ref.h index c204a319..1901e91a 100644 --- a/vendor/core/src/extension/alterschema/common/unknown_local_ref.h +++ b/vendor/core/src/extension/alterschema/common/unknown_local_ref.h @@ -3,6 +3,8 @@ class UnknownLocalRef final : public SchemaTransformRule { static inline const std::string KEYWORD{"$ref"}; public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; UnknownLocalRef() : SchemaTransformRule{ "unknown_local_ref", diff --git a/vendor/core/src/extension/alterschema/common/unnecessary_allof_ref_wrapper_draft.h b/vendor/core/src/extension/alterschema/common/unnecessary_allof_ref_wrapper_draft.h index e5992307..f5f8d38d 100644 --- a/vendor/core/src/extension/alterschema/common/unnecessary_allof_ref_wrapper_draft.h +++ b/vendor/core/src/extension/alterschema/common/unnecessary_allof_ref_wrapper_draft.h @@ -1,5 +1,7 @@ class UnnecessaryAllOfRefWrapperDraft final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; UnnecessaryAllOfRefWrapperDraft() : SchemaTransformRule{"unnecessary_allof_ref_wrapper_draft", "Wrapping `$ref` in `allOf` is only necessary if " diff --git a/vendor/core/src/extension/alterschema/common/unnecessary_allof_ref_wrapper_modern.h b/vendor/core/src/extension/alterschema/common/unnecessary_allof_ref_wrapper_modern.h index 4f83e63c..ade6bd7e 100644 --- a/vendor/core/src/extension/alterschema/common/unnecessary_allof_ref_wrapper_modern.h +++ b/vendor/core/src/extension/alterschema/common/unnecessary_allof_ref_wrapper_modern.h @@ -1,5 +1,7 @@ class UnnecessaryAllOfRefWrapperModern final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; UnnecessaryAllOfRefWrapperModern() : SchemaTransformRule{"unnecessary_allof_ref_wrapper_modern", "Wrapping `$ref` in `allOf` was only necessary in " diff --git a/vendor/core/src/extension/alterschema/common/unnecessary_allof_wrapper.h b/vendor/core/src/extension/alterschema/common/unnecessary_allof_wrapper.h index 0ab990ba..33c78e0e 100644 --- a/vendor/core/src/extension/alterschema/common/unnecessary_allof_wrapper.h +++ b/vendor/core/src/extension/alterschema/common/unnecessary_allof_wrapper.h @@ -3,6 +3,8 @@ class UnnecessaryAllOfWrapper final : public SchemaTransformRule { static inline const std::string KEYWORD{"allOf"}; public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; UnnecessaryAllOfWrapper() : SchemaTransformRule{"unnecessary_allof_wrapper", "Keywords inside `allOf` that do not conflict with " diff --git a/vendor/core/src/extension/alterschema/common/unsatisfiable_drop_validation.h b/vendor/core/src/extension/alterschema/common/unsatisfiable_drop_validation.h new file mode 100644 index 00000000..4e5b44c0 --- /dev/null +++ b/vendor/core/src/extension/alterschema/common/unsatisfiable_drop_validation.h @@ -0,0 +1,85 @@ +class UnsatisfiableDropValidation final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + UnsatisfiableDropValidation() + : SchemaTransformRule{"unsatisfiable_drop_validation", + "Do not place assertions or applicators next to an " + "unsatisfiable negation"} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, + 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 &) const + -> sourcemeta::core::SchemaTransformRule::Result override { + ONLY_CONTINUE_IF(vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_2020_12_Applicator, + Vocabularies::Known::JSON_Schema_2019_09_Applicator, + Vocabularies::Known::JSON_Schema_Draft_7, + Vocabularies::Known::JSON_Schema_Draft_6}) && + schema.is_object() && schema.defines("not") && + schema.at("not").is_boolean() && + schema.at("not").to_boolean()); + + std::vector positions; + for (const auto &entry : schema.as_object()) { + if (entry.first == "not") { + continue; + } + + const auto &metadata{walker(entry.first, vocabularies)}; + if (!is_removable_keyword_type(metadata.type)) { + continue; + } + + if (frame.has_references_through( + location.pointer, WeakPointer::Token{std::cref(entry.first)})) { + continue; + } + + positions.push_back(Pointer{entry.first}); + } + + ONLY_CONTINUE_IF(!positions.empty()); + return APPLIES_TO_POINTERS(std::move(positions)); + } + + auto transform(JSON &schema, const Result &result) const -> void override { + for (const auto &location : result.locations) { + schema.erase(location.at(0).to_property()); + } + } + +private: + static auto is_removable_keyword_type(const SchemaKeywordType type) -> bool { + switch (type) { + case SchemaKeywordType::Assertion: + case SchemaKeywordType::Reference: + case SchemaKeywordType::LocationMembers: + case SchemaKeywordType::ApplicatorMembersTraversePropertyStatic: + case SchemaKeywordType::ApplicatorMembersTraversePropertyRegex: + case SchemaKeywordType::ApplicatorValueTraverseSomeProperty: + case SchemaKeywordType::ApplicatorValueTraverseAnyPropertyKey: + case SchemaKeywordType::ApplicatorValueTraverseAnyItem: + case SchemaKeywordType::ApplicatorValueTraverseSomeItem: + case SchemaKeywordType::ApplicatorValueTraverseParent: + case SchemaKeywordType::ApplicatorElementsTraverseItem: + case SchemaKeywordType::ApplicatorValueOrElementsTraverseAnyItemOrItem: + case SchemaKeywordType::ApplicatorValueOrElementsInPlace: + case SchemaKeywordType::ApplicatorMembersInPlaceSome: + case SchemaKeywordType::ApplicatorElementsInPlace: + case SchemaKeywordType::ApplicatorElementsInPlaceSome: + case SchemaKeywordType::ApplicatorElementsInPlaceSomeNegate: + case SchemaKeywordType::ApplicatorValueInPlaceMaybe: + case SchemaKeywordType::ApplicatorValueInPlaceOther: + case SchemaKeywordType::ApplicatorValueInPlaceNegate: + return true; + default: + return false; + } + } +}; diff --git a/vendor/core/src/extension/alterschema/common/unsatisfiable_in_place_applicator_type.h b/vendor/core/src/extension/alterschema/common/unsatisfiable_in_place_applicator_type.h index 7c677d39..08152fab 100644 --- a/vendor/core/src/extension/alterschema/common/unsatisfiable_in_place_applicator_type.h +++ b/vendor/core/src/extension/alterschema/common/unsatisfiable_in_place_applicator_type.h @@ -1,5 +1,7 @@ class UnsatisfiableInPlaceApplicatorType final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; UnsatisfiableInPlaceApplicatorType() : SchemaTransformRule{ "unsatisfiable_in_place_applicator_type", diff --git a/vendor/core/src/extension/alterschema/linter/additional_properties_default.h b/vendor/core/src/extension/alterschema/linter/additional_properties_default.h deleted file mode 100644 index 0e1eed09..00000000 --- a/vendor/core/src/extension/alterschema/linter/additional_properties_default.h +++ /dev/null @@ -1,44 +0,0 @@ -class AdditionalPropertiesDefault final : public SchemaTransformRule { -private: - static inline const std::string KEYWORD{"additionalProperties"}; - -public: - AdditionalPropertiesDefault() - : SchemaTransformRule{ - "additional_properties_default", - "Setting the `additionalProperties` keyword to the true schema " - "does not add any further constraint"} {}; - - [[nodiscard]] auto - condition(const sourcemeta::core::JSON &schema, - const sourcemeta::core::JSON &, - const sourcemeta::core::Vocabularies &vocabularies, - const sourcemeta::core::SchemaFrame &frame, - const sourcemeta::core::SchemaFrame::Location &location, - const sourcemeta::core::SchemaWalker &, - const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { - ONLY_CONTINUE_IF( - vocabularies.contains_any( - {Vocabularies::Known::JSON_Schema_2020_12_Applicator, - Vocabularies::Known::JSON_Schema_2019_09_Applicator, - Vocabularies::Known::JSON_Schema_Draft_7, - Vocabularies::Known::JSON_Schema_Draft_6, - Vocabularies::Known::JSON_Schema_Draft_4, - Vocabularies::Known::JSON_Schema_Draft_3, - Vocabularies::Known::JSON_Schema_Draft_2, - Vocabularies::Known::JSON_Schema_Draft_2_Hyper, - Vocabularies::Known::JSON_Schema_Draft_1, - Vocabularies::Known::JSON_Schema_Draft_1_Hyper}) && - schema.is_object() && schema.defines(KEYWORD) && - ((schema.at(KEYWORD).is_boolean() && schema.at(KEYWORD).to_boolean()) || - (schema.at(KEYWORD).is_object() && schema.at(KEYWORD).empty()))); - ONLY_CONTINUE_IF(!frame.has_references_through( - location.pointer, WeakPointer::Token{std::cref(KEYWORD)})); - return APPLIES_TO_KEYWORDS(KEYWORD); - } - - auto transform(JSON &schema, const Result &) const -> void override { - schema.erase(KEYWORD); - } -}; diff --git a/vendor/core/src/extension/alterschema/linter/comment_trim.h b/vendor/core/src/extension/alterschema/linter/comment_trim.h index eb9e57e0..c2969081 100644 --- a/vendor/core/src/extension/alterschema/linter/comment_trim.h +++ b/vendor/core/src/extension/alterschema/linter/comment_trim.h @@ -1,5 +1,7 @@ class CommentTrim final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::false_type; CommentTrim() : SchemaTransformRule{ "comment_trim", 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 b4827230..411d35a8 100644 --- a/vendor/core/src/extension/alterschema/linter/content_schema_default.h +++ b/vendor/core/src/extension/alterschema/linter/content_schema_default.h @@ -3,6 +3,8 @@ class ContentSchemaDefault final : public SchemaTransformRule { static inline const std::string KEYWORD{"contentSchema"}; public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; ContentSchemaDefault() : SchemaTransformRule{ "content_schema_default", 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 9efe13e3..016bec71 100644 --- a/vendor/core/src/extension/alterschema/linter/definitions_to_defs.h +++ b/vendor/core/src/extension/alterschema/linter/definitions_to_defs.h @@ -1,5 +1,7 @@ class DefinitionsToDefs final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; DefinitionsToDefs() : SchemaTransformRule{"definitions_to_defs", "`definitions` was superseded by `$defs` in " diff --git a/vendor/core/src/extension/alterschema/linter/dependencies_default.h b/vendor/core/src/extension/alterschema/linter/dependencies_default.h index cb84cfe3..29dde6bd 100644 --- a/vendor/core/src/extension/alterschema/linter/dependencies_default.h +++ b/vendor/core/src/extension/alterschema/linter/dependencies_default.h @@ -3,6 +3,8 @@ class DependenciesDefault final : public SchemaTransformRule { static inline const std::string KEYWORD{"dependencies"}; public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; DependenciesDefault() : SchemaTransformRule{ "dependencies_default", 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 9e2a460c..f108dc3b 100644 --- a/vendor/core/src/extension/alterschema/linter/dependent_required_default.h +++ b/vendor/core/src/extension/alterschema/linter/dependent_required_default.h @@ -1,5 +1,7 @@ class DependentRequiredDefault final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; DependentRequiredDefault() : SchemaTransformRule{ "dependent_required_default", diff --git a/vendor/core/src/extension/alterschema/linter/description_trailing_period.h b/vendor/core/src/extension/alterschema/linter/description_trailing_period.h index 125cc330..31e6bc40 100644 --- a/vendor/core/src/extension/alterschema/linter/description_trailing_period.h +++ b/vendor/core/src/extension/alterschema/linter/description_trailing_period.h @@ -1,5 +1,7 @@ class DescriptionTrailingPeriod final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::false_type; DescriptionTrailingPeriod() : SchemaTransformRule{ "description_trailing_period", diff --git a/vendor/core/src/extension/alterschema/linter/description_trim.h b/vendor/core/src/extension/alterschema/linter/description_trim.h index b7ff154a..7b174425 100644 --- a/vendor/core/src/extension/alterschema/linter/description_trim.h +++ b/vendor/core/src/extension/alterschema/linter/description_trim.h @@ -1,5 +1,7 @@ class DescriptionTrim final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::false_type; DescriptionTrim() : SchemaTransformRule{ "description_trim", diff --git a/vendor/core/src/extension/alterschema/linter/duplicate_examples.h b/vendor/core/src/extension/alterschema/linter/duplicate_examples.h index 8cba86b6..8471bd39 100644 --- a/vendor/core/src/extension/alterschema/linter/duplicate_examples.h +++ b/vendor/core/src/extension/alterschema/linter/duplicate_examples.h @@ -1,5 +1,7 @@ class DuplicateExamples final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::false_type; DuplicateExamples() : SchemaTransformRule{ "duplicate_examples", 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 930ec6fb..7e1ffb2d 100644 --- a/vendor/core/src/extension/alterschema/linter/enum_to_const.h +++ b/vendor/core/src/extension/alterschema/linter/enum_to_const.h @@ -1,5 +1,7 @@ class EnumToConst final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; EnumToConst() : SchemaTransformRule{ "enum_to_const", 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 041f9ed3..603d5692 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 @@ -1,5 +1,7 @@ class EqualNumericBoundsToConst final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; EqualNumericBoundsToConst() : SchemaTransformRule{ "equal_numeric_bounds_to_const", 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 a21bd02a..0afbc43a 100644 --- a/vendor/core/src/extension/alterschema/linter/items_array_default.h +++ b/vendor/core/src/extension/alterschema/linter/items_array_default.h @@ -1,5 +1,7 @@ class ItemsArrayDefault final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; ItemsArrayDefault() : SchemaTransformRule{"items_array_default", "Setting the `items` keyword to the empty array " 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 8d77ee8a..aee24af9 100644 --- a/vendor/core/src/extension/alterschema/linter/items_schema_default.h +++ b/vendor/core/src/extension/alterschema/linter/items_schema_default.h @@ -3,6 +3,8 @@ class ItemsSchemaDefault final : public SchemaTransformRule { static inline const std::string KEYWORD{"items"}; public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; ItemsSchemaDefault() : SchemaTransformRule{"items_schema_default", "Setting the `items` keyword to the true schema " 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 3f9c4778..6915657b 100644 --- a/vendor/core/src/extension/alterschema/linter/multiple_of_default.h +++ b/vendor/core/src/extension/alterschema/linter/multiple_of_default.h @@ -1,5 +1,7 @@ class MultipleOfDefault final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; MultipleOfDefault() : SchemaTransformRule{ "multiple_of_default", 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 d3e437b8..6decf0eb 100644 --- a/vendor/core/src/extension/alterschema/linter/pattern_properties_default.h +++ b/vendor/core/src/extension/alterschema/linter/pattern_properties_default.h @@ -1,5 +1,7 @@ class PatternPropertiesDefault final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; PatternPropertiesDefault() : SchemaTransformRule{ "pattern_properties_default", diff --git a/vendor/core/src/extension/alterschema/linter/properties_default.h b/vendor/core/src/extension/alterschema/linter/properties_default.h index 5c824185..dd43363b 100644 --- a/vendor/core/src/extension/alterschema/linter/properties_default.h +++ b/vendor/core/src/extension/alterschema/linter/properties_default.h @@ -1,5 +1,7 @@ class PropertiesDefault final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; PropertiesDefault() : SchemaTransformRule{ "properties_default", 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 49cd48f0..479ca096 100644 --- a/vendor/core/src/extension/alterschema/linter/property_names_default.h +++ b/vendor/core/src/extension/alterschema/linter/property_names_default.h @@ -3,6 +3,8 @@ class PropertyNamesDefault final : public SchemaTransformRule { static inline const std::string KEYWORD{"propertyNames"}; public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; PropertyNamesDefault() : SchemaTransformRule{ "property_names_default", 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 7bb20ddf..4ced15cf 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 @@ -1,5 +1,7 @@ class PropertyNamesTypeDefault final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; PropertyNamesTypeDefault() : SchemaTransformRule{ "property_names_type_default", diff --git a/vendor/core/src/extension/alterschema/linter/simple_properties_identifiers.h b/vendor/core/src/extension/alterschema/linter/simple_properties_identifiers.h index 07cf73ef..7f4f30d9 100644 --- a/vendor/core/src/extension/alterschema/linter/simple_properties_identifiers.h +++ b/vendor/core/src/extension/alterschema/linter/simple_properties_identifiers.h @@ -1,5 +1,7 @@ class SimplePropertiesIdentifiers final : public SchemaTransformRule { public: + using mutates = std::false_type; + using reframe_after_transform = std::false_type; SimplePropertiesIdentifiers() // Inspired by // https://json-structure.github.io/core/draft-vasters-json-structure-core.html#section-3.6 diff --git a/vendor/core/src/extension/alterschema/linter/title_description_equal.h b/vendor/core/src/extension/alterschema/linter/title_description_equal.h index 9c6cc520..01320ba7 100644 --- a/vendor/core/src/extension/alterschema/linter/title_description_equal.h +++ b/vendor/core/src/extension/alterschema/linter/title_description_equal.h @@ -1,5 +1,7 @@ class TitleDescriptionEqual final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; TitleDescriptionEqual() : SchemaTransformRule{ "title_description_equal", diff --git a/vendor/core/src/extension/alterschema/linter/title_trailing_period.h b/vendor/core/src/extension/alterschema/linter/title_trailing_period.h index b8c81588..bf7b9ad2 100644 --- a/vendor/core/src/extension/alterschema/linter/title_trailing_period.h +++ b/vendor/core/src/extension/alterschema/linter/title_trailing_period.h @@ -1,5 +1,7 @@ class TitleTrailingPeriod final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::false_type; TitleTrailingPeriod() : SchemaTransformRule{ "title_trailing_period", diff --git a/vendor/core/src/extension/alterschema/linter/title_trim.h b/vendor/core/src/extension/alterschema/linter/title_trim.h index e523fc88..3846fc30 100644 --- a/vendor/core/src/extension/alterschema/linter/title_trim.h +++ b/vendor/core/src/extension/alterschema/linter/title_trim.h @@ -1,5 +1,7 @@ class TitleTrim final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::false_type; TitleTrim() : SchemaTransformRule{ "title_trim", diff --git a/vendor/core/src/extension/alterschema/linter/top_level_description.h b/vendor/core/src/extension/alterschema/linter/top_level_description.h index b9034e23..d1da3562 100644 --- a/vendor/core/src/extension/alterschema/linter/top_level_description.h +++ b/vendor/core/src/extension/alterschema/linter/top_level_description.h @@ -1,5 +1,7 @@ class TopLevelDescription final : public SchemaTransformRule { public: + using mutates = std::false_type; + using reframe_after_transform = std::false_type; TopLevelDescription() : SchemaTransformRule{ "top_level_description", diff --git a/vendor/core/src/extension/alterschema/linter/top_level_examples.h b/vendor/core/src/extension/alterschema/linter/top_level_examples.h index 2a453fab..c92c407b 100644 --- a/vendor/core/src/extension/alterschema/linter/top_level_examples.h +++ b/vendor/core/src/extension/alterschema/linter/top_level_examples.h @@ -1,5 +1,7 @@ class TopLevelExamples final : public SchemaTransformRule { public: + using mutates = std::false_type; + using reframe_after_transform = std::false_type; TopLevelExamples() : SchemaTransformRule{ "top_level_examples", diff --git a/vendor/core/src/extension/alterschema/linter/top_level_title.h b/vendor/core/src/extension/alterschema/linter/top_level_title.h index 1f56d083..cc7fa2cf 100644 --- a/vendor/core/src/extension/alterschema/linter/top_level_title.h +++ b/vendor/core/src/extension/alterschema/linter/top_level_title.h @@ -1,5 +1,7 @@ class TopLevelTitle final : public SchemaTransformRule { public: + using mutates = std::false_type; + using reframe_after_transform = std::false_type; TopLevelTitle() : SchemaTransformRule{ "top_level_title", 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 7139b483..6ce4570e 100644 --- a/vendor/core/src/extension/alterschema/linter/unevaluated_items_default.h +++ b/vendor/core/src/extension/alterschema/linter/unevaluated_items_default.h @@ -3,6 +3,8 @@ class UnevaluatedItemsDefault final : public SchemaTransformRule { static inline const std::string KEYWORD{"unevaluatedItems"}; public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; UnevaluatedItemsDefault() : SchemaTransformRule{ "unevaluated_items_default", 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 f241c66f..b7fff5b0 100644 --- a/vendor/core/src/extension/alterschema/linter/unevaluated_properties_default.h +++ b/vendor/core/src/extension/alterschema/linter/unevaluated_properties_default.h @@ -3,6 +3,8 @@ class UnevaluatedPropertiesDefault final : public SchemaTransformRule { static inline const std::string KEYWORD{"unevaluatedProperties"}; public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; UnevaluatedPropertiesDefault() : SchemaTransformRule{ "unevaluated_properties_default", 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 e206f2e9..6ff7c0fe 100644 --- a/vendor/core/src/extension/alterschema/linter/unsatisfiable_max_contains.h +++ b/vendor/core/src/extension/alterschema/linter/unsatisfiable_max_contains.h @@ -1,5 +1,7 @@ class UnsatisfiableMaxContains final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; UnsatisfiableMaxContains() : SchemaTransformRule{ "unsatisfiable_max_contains", 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 fb63ac19..30c72c7f 100644 --- a/vendor/core/src/extension/alterschema/linter/unsatisfiable_min_properties.h +++ b/vendor/core/src/extension/alterschema/linter/unsatisfiable_min_properties.h @@ -1,5 +1,7 @@ class UnsatisfiableMinProperties final : public SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; UnsatisfiableMinProperties() : SchemaTransformRule{ "unsatisfiable_min_properties", diff --git a/vendor/jsonbinpack/src/compiler/compiler.cc b/vendor/jsonbinpack/src/compiler/compiler.cc index c53742d8..9e5b8f36 100644 --- a/vendor/jsonbinpack/src/compiler/compiler.cc +++ b/vendor/jsonbinpack/src/compiler/compiler.cc @@ -6,7 +6,8 @@ #include "encoding.h" -#include // assert +#include // assert +#include // std::true_type static auto transformer_callback_noop(const sourcemeta::core::Pointer &, diff --git a/vendor/jsonbinpack/src/compiler/mapper/enum_8_bit.h b/vendor/jsonbinpack/src/compiler/mapper/enum_8_bit.h index 15e01125..f85450eb 100644 --- a/vendor/jsonbinpack/src/compiler/mapper/enum_8_bit.h +++ b/vendor/jsonbinpack/src/compiler/mapper/enum_8_bit.h @@ -1,6 +1,8 @@ // TODO: Unit test this mapping once we have container encodings class Enum8Bit final : public sourcemeta::core::SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; Enum8Bit() : sourcemeta::core::SchemaTransformRule{"enum_8_bit", ""} {}; [[nodiscard]] auto @@ -15,8 +17,9 @@ class Enum8Bit final : public sourcemeta::core::SchemaTransformRule { return location.dialect == "https://json-schema.org/draft/2020-12/schema" && vocabularies.contains(sourcemeta::core::Vocabularies::Known:: JSON_Schema_2020_12_Validation) && - schema.defines("enum") && schema.at("enum").is_array() && - !location.pointer.empty() && schema.at("enum").size() > 1 && + schema.is_object() && schema.defines("enum") && + schema.at("enum").is_array() && !location.pointer.empty() && + schema.at("enum").size() > 1 && is_byte(schema.at("enum").size() - 1); } diff --git a/vendor/jsonbinpack/src/compiler/mapper/enum_8_bit_top_level.h b/vendor/jsonbinpack/src/compiler/mapper/enum_8_bit_top_level.h index 2edd0885..7b516c15 100644 --- a/vendor/jsonbinpack/src/compiler/mapper/enum_8_bit_top_level.h +++ b/vendor/jsonbinpack/src/compiler/mapper/enum_8_bit_top_level.h @@ -1,5 +1,7 @@ class Enum8BitTopLevel final : public sourcemeta::core::SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; Enum8BitTopLevel() : sourcemeta::core::SchemaTransformRule{"enum_8_bit_top_level", ""} {}; @@ -15,8 +17,9 @@ class Enum8BitTopLevel final : public sourcemeta::core::SchemaTransformRule { return location.dialect == "https://json-schema.org/draft/2020-12/schema" && vocabularies.contains(sourcemeta::core::Vocabularies::Known:: JSON_Schema_2020_12_Validation) && - schema.defines("enum") && schema.at("enum").is_array() && - location.pointer.empty() && schema.at("enum").size() > 1 && + schema.is_object() && schema.defines("enum") && + schema.at("enum").is_array() && location.pointer.empty() && + schema.at("enum").size() > 1 && is_byte(schema.at("enum").size() - 1); } diff --git a/vendor/jsonbinpack/src/compiler/mapper/enum_arbitrary.h b/vendor/jsonbinpack/src/compiler/mapper/enum_arbitrary.h index aef6cd5c..3ae6c496 100644 --- a/vendor/jsonbinpack/src/compiler/mapper/enum_arbitrary.h +++ b/vendor/jsonbinpack/src/compiler/mapper/enum_arbitrary.h @@ -1,6 +1,8 @@ // TODO: Unit test this mapping once we have container encodings class EnumArbitrary final : public sourcemeta::core::SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; EnumArbitrary() : sourcemeta::core::SchemaTransformRule{"enum_arbitrary", ""} {}; @@ -16,8 +18,9 @@ class EnumArbitrary final : public sourcemeta::core::SchemaTransformRule { return location.dialect == "https://json-schema.org/draft/2020-12/schema" && vocabularies.contains(sourcemeta::core::Vocabularies::Known:: JSON_Schema_2020_12_Validation) && - schema.defines("enum") && schema.at("enum").is_array() && - !location.pointer.empty() && schema.at("enum").size() > 1 && + schema.is_object() && schema.defines("enum") && + schema.at("enum").is_array() && !location.pointer.empty() && + schema.at("enum").size() > 1 && !is_byte(schema.at("enum").size() - 1); } diff --git a/vendor/jsonbinpack/src/compiler/mapper/enum_singleton.h b/vendor/jsonbinpack/src/compiler/mapper/enum_singleton.h index 27fae5a0..47f18919 100644 --- a/vendor/jsonbinpack/src/compiler/mapper/enum_singleton.h +++ b/vendor/jsonbinpack/src/compiler/mapper/enum_singleton.h @@ -1,5 +1,7 @@ class EnumSingleton final : public sourcemeta::core::SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; EnumSingleton() : sourcemeta::core::SchemaTransformRule{"enum_singleton", ""} {}; @@ -15,8 +17,8 @@ class EnumSingleton final : public sourcemeta::core::SchemaTransformRule { return location.dialect == "https://json-schema.org/draft/2020-12/schema" && vocabularies.contains(sourcemeta::core::Vocabularies::Known:: JSON_Schema_2020_12_Validation) && - schema.defines("enum") && schema.at("enum").is_array() && - schema.at("enum").size() == 1; + schema.is_object() && schema.defines("enum") && + schema.at("enum").is_array() && schema.at("enum").size() == 1; } auto transform(sourcemeta::core::JSON &schema, diff --git a/vendor/jsonbinpack/src/compiler/mapper/integer_bounded_8_bit.h b/vendor/jsonbinpack/src/compiler/mapper/integer_bounded_8_bit.h index bd94e55c..a2518cb6 100644 --- a/vendor/jsonbinpack/src/compiler/mapper/integer_bounded_8_bit.h +++ b/vendor/jsonbinpack/src/compiler/mapper/integer_bounded_8_bit.h @@ -1,5 +1,7 @@ class IntegerBounded8Bit final : public sourcemeta::core::SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; IntegerBounded8Bit() : sourcemeta::core::SchemaTransformRule{"integer_bounded_8_bit", ""} {}; @@ -15,7 +17,7 @@ class IntegerBounded8Bit final : public sourcemeta::core::SchemaTransformRule { return location.dialect == "https://json-schema.org/draft/2020-12/schema" && vocabularies.contains(sourcemeta::core::Vocabularies::Known:: JSON_Schema_2020_12_Validation) && - schema.defines("type") && + schema.is_object() && schema.defines("type") && schema.at("type").to_string() == "integer" && schema.defines("minimum") && schema.defines("maximum") && is_byte(schema.at("maximum").to_integer() - diff --git a/vendor/jsonbinpack/src/compiler/mapper/integer_bounded_greater_than_8_bit.h b/vendor/jsonbinpack/src/compiler/mapper/integer_bounded_greater_than_8_bit.h index ededa589..1711d697 100644 --- a/vendor/jsonbinpack/src/compiler/mapper/integer_bounded_greater_than_8_bit.h +++ b/vendor/jsonbinpack/src/compiler/mapper/integer_bounded_greater_than_8_bit.h @@ -1,6 +1,8 @@ class IntegerBoundedGreaterThan8Bit final : public sourcemeta::core::SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; IntegerBoundedGreaterThan8Bit() : sourcemeta::core::SchemaTransformRule{ "integer_bounded_greater_than_8_bit", ""} {}; @@ -17,7 +19,7 @@ class IntegerBoundedGreaterThan8Bit final return location.dialect == "https://json-schema.org/draft/2020-12/schema" && vocabularies.contains(sourcemeta::core::Vocabularies::Known:: JSON_Schema_2020_12_Validation) && - schema.defines("type") && + schema.is_object() && schema.defines("type") && schema.at("type").to_string() == "integer" && schema.defines("minimum") && schema.defines("maximum") && !is_byte(schema.at("maximum").to_integer() - diff --git a/vendor/jsonbinpack/src/compiler/mapper/integer_bounded_multiplier_8_bit.h b/vendor/jsonbinpack/src/compiler/mapper/integer_bounded_multiplier_8_bit.h index f46a5ec5..a4f7fbf7 100644 --- a/vendor/jsonbinpack/src/compiler/mapper/integer_bounded_multiplier_8_bit.h +++ b/vendor/jsonbinpack/src/compiler/mapper/integer_bounded_multiplier_8_bit.h @@ -1,6 +1,8 @@ class IntegerBoundedMultiplier8Bit final : public sourcemeta::core::SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; IntegerBoundedMultiplier8Bit() : sourcemeta::core::SchemaTransformRule{ "integer_bounded_multiplier_8_bit", ""} {}; @@ -17,7 +19,8 @@ class IntegerBoundedMultiplier8Bit final if (location.dialect != "https://json-schema.org/draft/2020-12/schema" || !vocabularies.contains(sourcemeta::core::Vocabularies::Known:: JSON_Schema_2020_12_Validation) || - !schema.defines("type") || schema.at("type").to_string() != "integer" || + !schema.is_object() || !schema.defines("type") || + schema.at("type").to_string() != "integer" || !schema.defines("minimum") || !schema.at("minimum").is_integer() || !schema.defines("maximum") || !schema.at("maximum").is_integer() || !schema.defines("multipleOf") || diff --git a/vendor/jsonbinpack/src/compiler/mapper/integer_bounded_multiplier_greater_than_8_bit.h b/vendor/jsonbinpack/src/compiler/mapper/integer_bounded_multiplier_greater_than_8_bit.h index b681d0f9..de19a94c 100644 --- a/vendor/jsonbinpack/src/compiler/mapper/integer_bounded_multiplier_greater_than_8_bit.h +++ b/vendor/jsonbinpack/src/compiler/mapper/integer_bounded_multiplier_greater_than_8_bit.h @@ -1,6 +1,8 @@ class IntegerBoundedMultiplierGreaterThan8Bit final : public sourcemeta::core::SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; IntegerBoundedMultiplierGreaterThan8Bit() : sourcemeta::core::SchemaTransformRule{ "integer_bounded_multiplier_greater_than_8_bit", ""} {}; @@ -17,7 +19,8 @@ class IntegerBoundedMultiplierGreaterThan8Bit final if (location.dialect != "https://json-schema.org/draft/2020-12/schema" || !vocabularies.contains(sourcemeta::core::Vocabularies::Known:: JSON_Schema_2020_12_Validation) || - !schema.defines("type") || schema.at("type").to_string() != "integer" || + !schema.is_object() || !schema.defines("type") || + schema.at("type").to_string() != "integer" || !schema.defines("minimum") || !schema.at("minimum").is_integer() || !schema.defines("maximum") || !schema.at("maximum").is_integer() || !schema.defines("multipleOf") || diff --git a/vendor/jsonbinpack/src/compiler/mapper/integer_lower_bound.h b/vendor/jsonbinpack/src/compiler/mapper/integer_lower_bound.h index 5a4b9577..d5f04cdf 100644 --- a/vendor/jsonbinpack/src/compiler/mapper/integer_lower_bound.h +++ b/vendor/jsonbinpack/src/compiler/mapper/integer_lower_bound.h @@ -1,5 +1,7 @@ class IntegerLowerBound final : public sourcemeta::core::SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; IntegerLowerBound() : sourcemeta::core::SchemaTransformRule{"integer_lower_bound", ""} {}; @@ -15,7 +17,7 @@ class IntegerLowerBound final : public sourcemeta::core::SchemaTransformRule { return location.dialect == "https://json-schema.org/draft/2020-12/schema" && vocabularies.contains(sourcemeta::core::Vocabularies::Known:: JSON_Schema_2020_12_Validation) && - schema.defines("type") && + schema.is_object() && schema.defines("type") && schema.at("type").to_string() == "integer" && schema.defines("minimum") && !schema.defines("maximum") && !schema.defines("multipleOf"); diff --git a/vendor/jsonbinpack/src/compiler/mapper/integer_lower_bound_multiplier.h b/vendor/jsonbinpack/src/compiler/mapper/integer_lower_bound_multiplier.h index 47b5a137..826388a1 100644 --- a/vendor/jsonbinpack/src/compiler/mapper/integer_lower_bound_multiplier.h +++ b/vendor/jsonbinpack/src/compiler/mapper/integer_lower_bound_multiplier.h @@ -1,6 +1,8 @@ class IntegerLowerBoundMultiplier final : public sourcemeta::core::SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; IntegerLowerBoundMultiplier() : sourcemeta::core::SchemaTransformRule{"integer_lower_bound_multiplier", ""} {}; @@ -17,7 +19,7 @@ class IntegerLowerBoundMultiplier final return location.dialect == "https://json-schema.org/draft/2020-12/schema" && vocabularies.contains(sourcemeta::core::Vocabularies::Known:: JSON_Schema_2020_12_Validation) && - schema.defines("type") && + schema.is_object() && schema.defines("type") && schema.at("type").to_string() == "integer" && schema.defines("minimum") && !schema.defines("maximum") && schema.defines("multipleOf") && schema.at("multipleOf").is_integer(); diff --git a/vendor/jsonbinpack/src/compiler/mapper/integer_unbound.h b/vendor/jsonbinpack/src/compiler/mapper/integer_unbound.h index 5c129fd8..4bcda4ea 100644 --- a/vendor/jsonbinpack/src/compiler/mapper/integer_unbound.h +++ b/vendor/jsonbinpack/src/compiler/mapper/integer_unbound.h @@ -1,5 +1,7 @@ class IntegerUnbound final : public sourcemeta::core::SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; IntegerUnbound() : sourcemeta::core::SchemaTransformRule{"integer_unbound", ""} {}; @@ -15,7 +17,7 @@ class IntegerUnbound final : public sourcemeta::core::SchemaTransformRule { return location.dialect == "https://json-schema.org/draft/2020-12/schema" && vocabularies.contains(sourcemeta::core::Vocabularies::Known:: JSON_Schema_2020_12_Validation) && - schema.defines("type") && + schema.is_object() && schema.defines("type") && schema.at("type").to_string() == "integer" && !schema.defines("minimum") && !schema.defines("maximum") && !schema.defines("multipleOf"); diff --git a/vendor/jsonbinpack/src/compiler/mapper/integer_unbound_multiplier.h b/vendor/jsonbinpack/src/compiler/mapper/integer_unbound_multiplier.h index 18530a0f..30e7d8e0 100644 --- a/vendor/jsonbinpack/src/compiler/mapper/integer_unbound_multiplier.h +++ b/vendor/jsonbinpack/src/compiler/mapper/integer_unbound_multiplier.h @@ -1,6 +1,8 @@ class IntegerUnboundMultiplier final : public sourcemeta::core::SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; IntegerUnboundMultiplier() : sourcemeta::core::SchemaTransformRule{"integer_unbound_multiplier", ""} {}; @@ -17,7 +19,7 @@ class IntegerUnboundMultiplier final return location.dialect == "https://json-schema.org/draft/2020-12/schema" && vocabularies.contains(sourcemeta::core::Vocabularies::Known:: JSON_Schema_2020_12_Validation) && - schema.defines("type") && + schema.is_object() && schema.defines("type") && schema.at("type").to_string() == "integer" && !schema.defines("minimum") && !schema.defines("maximum") && schema.defines("multipleOf") && schema.at("multipleOf").is_integer(); diff --git a/vendor/jsonbinpack/src/compiler/mapper/integer_upper_bound.h b/vendor/jsonbinpack/src/compiler/mapper/integer_upper_bound.h index 06be229f..fc2c3451 100644 --- a/vendor/jsonbinpack/src/compiler/mapper/integer_upper_bound.h +++ b/vendor/jsonbinpack/src/compiler/mapper/integer_upper_bound.h @@ -1,5 +1,7 @@ class IntegerUpperBound final : public sourcemeta::core::SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; IntegerUpperBound() : sourcemeta::core::SchemaTransformRule{"integer_upper_bound", ""} {}; @@ -15,7 +17,7 @@ class IntegerUpperBound final : public sourcemeta::core::SchemaTransformRule { return location.dialect == "https://json-schema.org/draft/2020-12/schema" && vocabularies.contains(sourcemeta::core::Vocabularies::Known:: JSON_Schema_2020_12_Validation) && - schema.defines("type") && + schema.is_object() && schema.defines("type") && schema.at("type").to_string() == "integer" && !schema.defines("minimum") && schema.defines("maximum") && !schema.defines("multipleOf"); diff --git a/vendor/jsonbinpack/src/compiler/mapper/integer_upper_bound_multiplier.h b/vendor/jsonbinpack/src/compiler/mapper/integer_upper_bound_multiplier.h index dd15a006..ef2342dc 100644 --- a/vendor/jsonbinpack/src/compiler/mapper/integer_upper_bound_multiplier.h +++ b/vendor/jsonbinpack/src/compiler/mapper/integer_upper_bound_multiplier.h @@ -1,6 +1,8 @@ class IntegerUpperBoundMultiplier final : public sourcemeta::core::SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; IntegerUpperBoundMultiplier() : sourcemeta::core::SchemaTransformRule{"integer_upper_bound_multiplier", ""} {}; @@ -17,7 +19,7 @@ class IntegerUpperBoundMultiplier final return location.dialect == "https://json-schema.org/draft/2020-12/schema" && vocabularies.contains(sourcemeta::core::Vocabularies::Known:: JSON_Schema_2020_12_Validation) && - schema.defines("type") && + schema.is_object() && schema.defines("type") && schema.at("type").to_string() == "integer" && !schema.defines("minimum") && schema.defines("maximum") && schema.defines("multipleOf") && schema.at("multipleOf").is_integer(); diff --git a/vendor/jsonbinpack/src/compiler/mapper/number_arbitrary.h b/vendor/jsonbinpack/src/compiler/mapper/number_arbitrary.h index 6ef18477..dd106ee0 100644 --- a/vendor/jsonbinpack/src/compiler/mapper/number_arbitrary.h +++ b/vendor/jsonbinpack/src/compiler/mapper/number_arbitrary.h @@ -1,5 +1,7 @@ class NumberArbitrary final : public sourcemeta::core::SchemaTransformRule { public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; NumberArbitrary() : sourcemeta::core::SchemaTransformRule{"number_arbitrary", ""} {}; @@ -15,7 +17,8 @@ class NumberArbitrary final : public sourcemeta::core::SchemaTransformRule { return location.dialect == "https://json-schema.org/draft/2020-12/schema" && vocabularies.contains(sourcemeta::core::Vocabularies::Known:: JSON_Schema_2020_12_Validation) && - schema.defines("type") && schema.at("type").to_string() == "number"; + schema.is_object() && schema.defines("type") && + schema.at("type").to_string() == "number"; } auto transform(sourcemeta::core::JSON &schema, diff --git a/vendor/jsonschema/VERSION b/vendor/jsonschema/VERSION index 72f51351..df249f48 100644 --- a/vendor/jsonschema/VERSION +++ b/vendor/jsonschema/VERSION @@ -1 +1 @@ -14.4.0 +14.6.0 diff --git a/vendor/jsonschema/src/command_inspect.cc b/vendor/jsonschema/src/command_inspect.cc index b6979165..45adad42 100644 --- a/vendor/jsonschema/src/command_inspect.cc +++ b/vendor/jsonschema/src/command_inspect.cc @@ -103,6 +103,12 @@ auto print_frame(std::ostream &stream, stream << " Property Name : no\n"; } + if (location.second.orphan) { + stream << " Orphan : yes\n"; + } else { + stream << " Orphan : no\n"; + } + if (std::next(iterator) != frame.locations().cend()) { stream << "\n"; } diff --git a/vendor/jsonschema/src/command_lint.cc b/vendor/jsonschema/src/command_lint.cc index 6b0350f5..54d77926 100644 --- a/vendor/jsonschema/src/command_lint.cc +++ b/vendor/jsonschema/src/command_lint.cc @@ -138,7 +138,7 @@ auto sourcemeta::jsonschema::lint(const sourcemeta::core::Options &options) std::unordered_set blacklist; for (const auto &entry : bundle) { - blacklist.emplace(entry->name()); + blacklist.emplace(std::get<0>(entry)->name()); } for (const auto &only : options.at("only")) { @@ -160,7 +160,8 @@ auto sourcemeta::jsonschema::lint(const sourcemeta::core::Options &options) if (options.contains("list")) { std::vector> rules; for (const auto &entry : bundle) { - rules.emplace_back(entry->name(), entry->message()); + rules.emplace_back(std::get<0>(entry)->name(), + std::get<0>(entry)->message()); } std::sort(