diff --git a/DEPENDENCIES b/DEPENDENCIES index 6426448..478ffbf 100644 --- a/DEPENDENCIES +++ b/DEPENDENCIES @@ -1,2 +1,2 @@ vendorpull https://github.com/sourcemeta/vendorpull 1dcbac42809cf87cb5b045106b863e17ad84ba02 -core https://github.com/sourcemeta/core 4e9d280a8a452885c7cd2bc488799a4f6410f4d8 +core https://github.com/sourcemeta/core fe450b982907f99e542a0cfc78bc60d2b600ff7a diff --git a/src/ir/ir.cc b/src/ir/ir.cc index 821d6a9..faffeb5 100644 --- a/src/ir/ir.cc +++ b/src/ir/ir.cc @@ -30,7 +30,8 @@ auto compile(const sourcemeta::core::JSON &input, sourcemeta::core::AlterSchemaMode::Canonicalizer); [[maybe_unused]] const auto canonicalized{canonicalizer.apply( schema, walker, resolver, - [](const auto &, const auto, const auto, const auto &) { assert(false); }, + [](const auto &, const auto, const auto, const auto &, + [[maybe_unused]] const auto applied) { assert(applied); }, default_dialect, default_id)}; assert(canonicalized.first); 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 1f968fc..e2da442 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 @@ -744,15 +744,12 @@ template class GenericPointer { 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()); + (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()( @@ -760,6 +757,18 @@ template class GenericPointer { &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 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 e4ab00a..69d6399 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 63fac3e..40b351c 100644 --- a/vendor/core/src/core/jsonschema/frame.cc +++ b/vendor/core/src/core/jsonschema/frame.cc @@ -444,9 +444,14 @@ auto SchemaFrame::analyse(const JSON &root, const SchemaWalker &walker, 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 @@ -1035,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> @@ -1113,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 & { @@ -1392,6 +1400,7 @@ auto SchemaFrame::reset() -> void { this->root_.clear(); this->locations_.clear(); this->references_.clear(); + this->standalone_ = false; } auto SchemaFrame::populate_pointer_to_location() const -> void { @@ -1474,6 +1483,22 @@ auto SchemaFrame::populate_reachability(const SchemaWalker &walker, // (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()); @@ -1484,21 +1509,25 @@ auto SchemaFrame::populate_reachability(const SchemaWalker &walker, continue; } - const WeakPointer *destination_pointer{nullptr}; - const auto destination_location{this->locations_.find( - {SchemaReferenceType::Static, reference.second.destination})}; - if (destination_location != this->locations_.cend()) { - destination_pointer = &destination_location->second.pointer; - } else { - const auto dynamic_destination{this->locations_.find( - {SchemaReferenceType::Dynamic, reference.second.destination})}; - if (dynamic_destination != this->locations_.cend()) { - destination_pointer = &dynamic_destination->second.pointer; + 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; } - if (destination_pointer != nullptr) { - reference_destinations.emplace_back(&source_pointer, destination_pointer); + 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); } } 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 a18eead..5400511 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 1061f87..522dbfa 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 @@ -163,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 &; @@ -268,6 +268,7 @@ class SOURCEMETA_CORE_JSONSCHEMA_EXPORT SchemaFrame { 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 55ff68d..f2b9f70 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 @@ -253,9 +253,10 @@ class SOURCEMETA_CORE_JSONSCHEMA_EXPORT SchemaTransformer { /// - The name of the rule /// - The message of the rule /// - The rule evaluation result - using Callback = std::function; + /// - Whether the rule is mutable (on check) or was mutated (on apply) + using Callback = std::function; /// Apply the bundle of rules to a schema [[nodiscard]] auto diff --git a/vendor/core/src/core/jsonschema/jsonschema.cc b/vendor/core/src/core/jsonschema/jsonschema.cc index aad3309..408f911 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 2ff1010..e7ba51f 100644 --- a/vendor/core/src/core/jsonschema/transformer.cc +++ b/vendor/core/src/core/jsonschema/transformer.cc @@ -87,7 +87,8 @@ auto check_rules( exclude_keyword)}; if (outcome.applies) { subschema_failed = true; - callback(entry_pointer, rule->name(), rule->message(), outcome); + callback(entry_pointer, rule->name(), rule->message(), outcome, + mutates); } } @@ -273,6 +274,7 @@ auto SchemaTransformer::apply(JSON &schema, const SchemaWalker &walker, } rule->transform(current, outcome); + callback(entry_pointer, rule->name(), rule->message(), outcome, true); applied = true;