diff --git a/DEPENDENCIES b/DEPENDENCIES index 5ee663e..dd55017 100644 --- a/DEPENDENCIES +++ b/DEPENDENCIES @@ -1,5 +1,5 @@ vendorpull https://github.com/sourcemeta/vendorpull dea311b5bfb53b6926a4140267959ae334d3ecf4 -core https://github.com/sourcemeta/core 8b7c698d7e9df6638cda5e32fbcb01653dbe7c54 +core https://github.com/sourcemeta/core 918a17d1e4f82a12334bf99d67ffef4e77fd6722 hydra https://github.com/sourcemeta/hydra a67b879df800e834ed8a2d056f98398f7211d870 -jsonbinpack https://github.com/sourcemeta/jsonbinpack d92d791f2451c7a1598ff4e908cc0298faeeea5e -blaze https://github.com/sourcemeta/blaze d0aafed3d2c0d16edaccddcc41917940c6f6f2b5 +jsonbinpack https://github.com/sourcemeta/jsonbinpack c7bb7f4d903c3b6925b62ce2bb5b8d360e01ce84 +blaze https://github.com/sourcemeta/blaze bf456d47dfd7fc51f077da268412735b4c6f9fa7 diff --git a/src/command_inspect.cc b/src/command_inspect.cc index 6f0a068..1c7cfd5 100644 --- a/src/command_inspect.cc +++ b/src/command_inspect.cc @@ -38,7 +38,8 @@ auto sourcemeta::jsonschema::cli::inspect( const sourcemeta::core::JSON schema{ sourcemeta::jsonschema::cli::read_file(options.at("").front())}; - sourcemeta::core::SchemaFrame frame; + sourcemeta::core::SchemaFrame frame{ + sourcemeta::core::SchemaFrame::Mode::References}; frame.analyse(schema, sourcemeta::core::schema_official_walker, resolver(options)); diff --git a/vendor/blaze/src/compiler/compile.cc b/vendor/blaze/src/compiler/compile.cc index 74e2a08..2ed0e71 100644 --- a/vendor/blaze/src/compiler/compile.cc +++ b/vendor/blaze/src/compiler/compile.cc @@ -113,7 +113,8 @@ auto compile(const sourcemeta::core::JSON &schema, sourcemeta::core::bundle(schema, walker, resolver, default_dialect)}; // Perform framing to resolve references later on - sourcemeta::core::SchemaFrame frame; + sourcemeta::core::SchemaFrame frame{ + sourcemeta::core::SchemaFrame::Mode::References}; frame.analyse(result, walker, resolver, default_dialect); const std::string base{sourcemeta::core::URI{ diff --git a/vendor/blaze/src/compiler/compile_helpers.h b/vendor/blaze/src/compiler/compile_helpers.h index 9552b5d..427be93 100644 --- a/vendor/blaze/src/compiler/compile_helpers.h +++ b/vendor/blaze/src/compiler/compile_helpers.h @@ -124,7 +124,7 @@ unsigned_integer_property(const sourcemeta::core::JSON &document, inline auto static_frame_entry(const Context &context, const SchemaContext &schema_context) - -> const sourcemeta::core::SchemaFrame::LocationsEntry & { + -> const sourcemeta::core::SchemaFrame::Location & { const auto current{ to_uri(schema_context.relative_pointer, schema_context.base).recompose()}; const auto type{sourcemeta::core::SchemaReferenceType::Static}; diff --git a/vendor/core/src/core/jsonpointer/include/sourcemeta/core/jsonpointer_template.h b/vendor/core/src/core/jsonpointer/include/sourcemeta/core/jsonpointer_template.h index 5263536..9beab2f 100644 --- a/vendor/core/src/core/jsonpointer/include/sourcemeta/core/jsonpointer_template.h +++ b/vendor/core/src/core/jsonpointer/include/sourcemeta/core/jsonpointer_template.h @@ -31,6 +31,21 @@ template class GenericPointerTemplate { /// ``` GenericPointerTemplate() : data{} {} + /// This constructor is the preferred way of creating a pointer template. + /// For example: + /// + /// ```cpp + /// #include + /// #include + /// + /// const sourcemeta::core::PointerTemplate pointer{ + /// "foo", + /// sourcemeta::core::PointerTemplate::Wildcard::Property}; + /// ``` + GenericPointerTemplate( + std::initializer_list tokens) + : data{std::move(tokens)} {} + /// This constructor creates a JSON Pointer template from an existing JSON /// Pointer. For example: /// @@ -100,7 +115,7 @@ template class GenericPointerTemplate { /// ``` template auto emplace_back(Args &&...args) -> reference { // It is a logical error to push a token after a key wildcard - assert(this->data.empty() || + assert(this->empty() || !std::holds_alternative(this->data.back()) || std::get(this->data.back()) != Wildcard::Key); return this->data.emplace_back(args...); @@ -118,7 +133,7 @@ template class GenericPointerTemplate { /// ``` auto push_back(const PointerT &other) -> void { // It is a logical error to push a token after a key wildcard - assert(this->data.empty() || + assert(this->empty() || !std::holds_alternative(this->data.back()) || std::get(this->data.back()) != Wildcard::Key); this->data.reserve(this->data.size() + other.size()); @@ -135,7 +150,7 @@ template class GenericPointerTemplate { /// pointer.pop_back(); /// ``` auto pop_back() -> void { - assert(!this->data.empty()); + assert(!this->empty()); this->data.pop_back(); } @@ -167,6 +182,20 @@ template class GenericPointerTemplate { return result; } + /// Check if a JSON Pointer template is empty. + /// For example: + /// + /// ```cpp + /// #include + /// #include + /// + /// const sourcemeta::core::PointerTemplate empty_pointer; + /// assert(empty_pointer.empty()); + /// ``` + [[nodiscard]] auto empty() const noexcept -> bool { + return this->data.empty(); + } + /// Compare JSON Pointer template instances auto operator==(const GenericPointerTemplate &other) const noexcept -> bool { diff --git a/vendor/core/src/core/jsonschema/bundle.cc b/vendor/core/src/core/jsonschema/bundle.cc index 9db37f5..b21fb39 100644 --- a/vendor/core/src/core/jsonschema/bundle.cc +++ b/vendor/core/src/core/jsonschema/bundle.cc @@ -137,7 +137,8 @@ auto bundle(sourcemeta::core::JSON &schema, const SchemaWalker &walker, const std::optional &default_dialect) -> void { const auto vocabularies{ sourcemeta::core::vocabularies(schema, resolver, default_dialect)}; - sourcemeta::core::SchemaFrame frame; + sourcemeta::core::SchemaFrame frame{ + sourcemeta::core::SchemaFrame::Mode::References}; bundle_schema(schema, definitions_keyword(vocabularies), schema, frame, walker, resolver, default_dialect); } diff --git a/vendor/core/src/core/jsonschema/frame.cc b/vendor/core/src/core/jsonschema/frame.cc index 6e20098..cd307ec 100644 --- a/vendor/core/src/core/jsonschema/frame.cc +++ b/vendor/core/src/core/jsonschema/frame.cc @@ -172,52 +172,9 @@ static auto fragment_string(const sourcemeta::core::URI &uri) return std::nullopt; } -static auto has_equivalent_origin( - const sourcemeta::core::SchemaFrame::Locations &frame, - const std::vector> &destination_of, - const sourcemeta::core::SchemaFrame::Locations::value_type &entry) -> bool { - return std::any_of(destination_of.cbegin(), destination_of.cend(), - [&entry, &frame](const auto &destination) { - return destination.get() == entry.first || - frame.at(destination.get()).pointer == - entry.second.pointer; - }); -} - -static auto mark_reference_origins_from( - sourcemeta::core::SchemaFrame::Locations &frame, - const sourcemeta::core::SchemaFrame::References &references, - const sourcemeta::core::SchemaFrame::Locations::value_type &entry) -> void { - for (const auto &reference : references) { - assert(!reference.first.second.empty() && - reference.first.second.back().is_property()); - assert(reference.first.second.back().to_property() == "$schema" || - reference.first.second.back().to_property() == "$ref" || - reference.first.second.back().to_property() == "$recursiveRef" || - reference.first.second.back().to_property() == "$dynamicRef"); - if (reference.first.second.initial() != entry.second.pointer) { - continue; - } - - auto match{ - frame.find({reference.first.first, reference.second.destination})}; - if (match == frame.cend()) { - continue; - } - - for (auto &subentry : frame) { - if (subentry.second.pointer == match->second.pointer && - !has_equivalent_origin(frame, subentry.second.destination_of, - entry)) { - subentry.second.destination_of.emplace_back(entry.first); - } - } - } -} - static auto store(sourcemeta::core::SchemaFrame::Locations &frame, + sourcemeta::core::SchemaFrame::Instances &instances, const sourcemeta::core::SchemaReferenceType type, const sourcemeta::core::SchemaFrame::LocationType entry_type, const std::string &uri, const std::optional &root_id, @@ -226,28 +183,25 @@ store(sourcemeta::core::SchemaFrame::Locations &frame, const sourcemeta::core::Pointer &pointer_from_base, const std::string &dialect, const std::string &base_dialect, const std::vector &instance_locations, + const std::optional &parent, const bool ignore_if_present = false) -> void { assert(std::set( instance_locations.cbegin(), instance_locations.cend()) .size() == instance_locations.size()); const auto canonical{sourcemeta::core::URI{uri}.canonicalize().recompose()}; - const auto inserted{frame - .insert({{type, canonical}, - {entry_type, - root_id, - base_id, - pointer_from_root, - pointer_from_base, - dialect, - base_dialect, - instance_locations, - {}}}) - .second}; + const auto inserted{ + frame + .insert({{type, canonical}, + {parent, entry_type, root_id, base_id, pointer_from_root, + pointer_from_base, dialect, base_dialect}}) + .second}; if (!ignore_if_present && !inserted) { std::ostringstream error; error << "Schema identifier already exists: " << uri; throw sourcemeta::core::SchemaError(error.str()); } + + instances[pointer_from_root] = instance_locations; } struct InternalEntry { @@ -255,47 +209,102 @@ struct InternalEntry { const std::optional id; }; -static auto traverse_instance_locations( - const sourcemeta::core::SchemaFrame::Locations &frame, - const sourcemeta::core::SchemaFrame::LocationsEntry &entry, - const std::optional ¤t, - std::vector &output) -> void { - // We only care about subschemas - if (entry.type != sourcemeta::core::SchemaFrame::LocationType::Resource && - entry.type != sourcemeta::core::SchemaFrame::LocationType::Subschema) { - return; - } - - if (current.has_value() && std::find(output.cbegin(), output.cend(), - current.value()) == output.cend()) { - output.push_back(current.value()); +static auto traverse_origin_instance_locations( + const sourcemeta::core::SchemaFrame &frame, + const sourcemeta::core::SchemaFrame::Instances &instances, + const sourcemeta::core::Pointer ¤t, + const std::optional &accumulator, + sourcemeta::core::SchemaFrame::Instances::mapped_type &destination) + -> void { + if (accumulator.has_value() && + std::find(destination.cbegin(), destination.cend(), + accumulator.value()) == destination.cend()) { + destination.push_back(accumulator.value()); } - for (const auto &origin : entry.destination_of) { - const auto &subentry{frame.at(origin.get())}; + for (const auto &reference : frame.references_to(current)) { + const auto subschema_pointer{reference.get().first.second.initial()}; // Avoid recursing to itself, in the case of circular subschemas - if (subentry.pointer == entry.pointer) { + if (subschema_pointer == current) { continue; } - for (const auto &instance_location : subentry.instance_locations) { - traverse_instance_locations(frame, subentry, instance_location, output); + const auto match{instances.find(subschema_pointer)}; + if (match != instances.cend()) { + for (const auto &instance_location : match->second) { + traverse_origin_instance_locations(frame, instances, subschema_pointer, + instance_location, destination); + } } } } -auto internal_analyse(const sourcemeta::core::JSON &schema, - sourcemeta::core::SchemaFrame::Locations &frame, - sourcemeta::core::SchemaFrame::References &references, - const sourcemeta::core::SchemaWalker &walker, - const sourcemeta::core::SchemaResolver &resolver, - const std::optional &default_dialect, - const std::optional &default_id) -> void { - using namespace sourcemeta::core; +struct CacheSubschema { + const sourcemeta::core::PointerTemplate instance_location; + const sourcemeta::core::PointerTemplate relative_instance_location; + const bool orphan; + const std::optional parent; +}; + +static auto repopulate_instance_locations( + const sourcemeta::core::SchemaFrame &frame, + const sourcemeta::core::SchemaFrame::Instances &instances, + const std::map &cache, + const sourcemeta::core::Pointer &, const CacheSubschema &cache_entry, + sourcemeta::core::SchemaFrame::Instances::mapped_type &destination, + const std::optional &accumulator) + -> void { + if (cache_entry.orphan && cache_entry.instance_location.empty()) { + return; + } else if (cache_entry.parent.has_value() && + // Don't consider bases from the root subschema, as if that + // subschema has any instance location other than "", then it + // indicates a recursive reference + !cache_entry.parent.value().empty()) { + const auto match{instances.find(cache_entry.parent.value())}; + if (match == instances.cend()) { + return; + } + + for (const auto &parent_instance_location : match->second) { + // Guard against overly unrolling recursive schemas + if (parent_instance_location == cache_entry.instance_location) { + continue; + } + + auto new_accumulator = cache_entry.relative_instance_location; + if (accumulator.has_value()) { + for (const auto &token : accumulator.value()) { + new_accumulator.emplace_back(token); + } + } + + auto result = parent_instance_location; + for (const auto &token : new_accumulator) { + result.emplace_back(token); + } + if (std::find(destination.cbegin(), destination.cend(), result) == + destination.cend()) { + destination.push_back(result); + } + + repopulate_instance_locations( + frame, instances, cache, cache_entry.parent.value(), + cache.at(cache_entry.parent.value()), destination, new_accumulator); + } + } +} + +namespace sourcemeta::core { + +auto SchemaFrame::analyse(const JSON &schema, const SchemaWalker &walker, + const SchemaResolver &resolver, + const std::optional &default_dialect, + const std::optional &default_id) + -> void { std::vector subschema_entries; - std::map> - subschemas; + std::map subschemas; std::map> base_uris; std::map> base_dialects; @@ -322,11 +331,20 @@ auto internal_analyse(const sourcemeta::core::JSON &schema, default_id.has_value() && root_id.value() != default_id.value()}; if (has_explicit_different_id) { - store(frame, SchemaReferenceType::Static, - SchemaFrame::LocationType::Resource, default_id.value(), - root_id.value(), root_id.value(), sourcemeta::core::empty_pointer, - sourcemeta::core::empty_pointer, root_dialect.value(), - root_base_dialect.value(), {{}}); + if (this->mode_ == SchemaFrame::Mode::Instances) { + store(this->locations_, this->instances_, SchemaReferenceType::Static, + SchemaFrame::LocationType::Resource, default_id.value(), + root_id.value(), root_id.value(), sourcemeta::core::empty_pointer, + sourcemeta::core::empty_pointer, root_dialect.value(), + root_base_dialect.value(), {{}}, std::nullopt); + } else { + store(this->locations_, this->instances_, SchemaReferenceType::Static, + SchemaFrame::LocationType::Resource, default_id.value(), + root_id.value(), root_id.value(), sourcemeta::core::empty_pointer, + sourcemeta::core::empty_pointer, root_dialect.value(), + root_base_dialect.value(), {}, std::nullopt); + } + base_uris.insert({sourcemeta::core::empty_pointer, {default_id.value()}}); } @@ -347,8 +365,9 @@ auto internal_analyse(const sourcemeta::core::JSON &schema, // Store information subschema_entries.emplace_back(InternalEntry{entry, std::move(id)}); subschemas.emplace(entry.pointer, - std::pair{ - entry.instance_location, entry.orphan}); + CacheSubschema{entry.instance_location, + entry.relative_instance_location, + entry.orphan, entry.parent}); } for (const auto &entry : subschema_entries) { @@ -385,23 +404,34 @@ auto internal_analyse(const sourcemeta::core::JSON &schema, const std::string new_id{maybe_relative.recompose()}; if (!maybe_relative_is_absolute || - !frame.contains({SchemaReferenceType::Static, new_id})) { + !this->locations_.contains( + {SchemaReferenceType::Static, new_id})) { assert(entry.common.base_dialect.has_value()); if (entry.common.orphan) { - store(frame, SchemaReferenceType::Static, + store(this->locations_, this->instances_, + SchemaReferenceType::Static, SchemaFrame::LocationType::Resource, new_id, root_id, new_id, entry.common.pointer, sourcemeta::core::empty_pointer, entry.common.dialect.value(), - entry.common.base_dialect.value(), {}); - } else { - store(frame, SchemaReferenceType::Static, + entry.common.base_dialect.value(), {}, entry.common.parent); + } else if (this->mode_ == SchemaFrame::Mode::Instances) { + store(this->locations_, this->instances_, + SchemaReferenceType::Static, SchemaFrame::LocationType::Resource, new_id, root_id, new_id, entry.common.pointer, sourcemeta::core::empty_pointer, entry.common.dialect.value(), entry.common.base_dialect.value(), - {entry.common.instance_location}); + {entry.common.instance_location}, entry.common.parent); + } else { + store(this->locations_, this->instances_, + SchemaReferenceType::Static, + SchemaFrame::LocationType::Resource, new_id, root_id, + new_id, entry.common.pointer, + sourcemeta::core::empty_pointer, + entry.common.dialect.value(), + entry.common.base_dialect.value(), {}, entry.common.parent); } } @@ -414,26 +444,28 @@ auto internal_analyse(const sourcemeta::core::JSON &schema, } } - // Handle metaschema references - const auto maybe_metaschema{ - sourcemeta::core::dialect(entry.common.subschema.get())}; - if (maybe_metaschema.has_value()) { - sourcemeta::core::URI metaschema{maybe_metaschema.value()}; - const auto nearest_bases{ - find_nearest_bases(base_uris, entry.common.pointer, entry.id)}; - if (!nearest_bases.first.empty()) { - metaschema.try_resolve_from(nearest_bases.first.front()); - } + if (this->mode_ != SchemaFrame::Mode::Locations) { + // Handle metaschema references + const auto maybe_metaschema{ + sourcemeta::core::dialect(entry.common.subschema.get())}; + if (maybe_metaschema.has_value()) { + sourcemeta::core::URI metaschema{maybe_metaschema.value()}; + const auto nearest_bases{ + find_nearest_bases(base_uris, entry.common.pointer, entry.id)}; + if (!nearest_bases.first.empty()) { + metaschema.try_resolve_from(nearest_bases.first.front()); + } - metaschema.canonicalize(); - const std::string destination{metaschema.recompose()}; - assert(entry.common.subschema.get().defines("$schema")); - references.insert_or_assign( - {SchemaReferenceType::Static, - entry.common.pointer.concat({"$schema"})}, - SchemaFrame::ReferencesEntry{destination, - metaschema.recompose_without_fragment(), - fragment_string(metaschema)}); + metaschema.canonicalize(); + const std::string destination{metaschema.recompose()}; + assert(entry.common.subschema.get().defines("$schema")); + this->references_.insert_or_assign( + {SchemaReferenceType::Static, + entry.common.pointer.concat({"$schema"})}, + SchemaFrame::ReferencesEntry{ + destination, metaschema.recompose_without_fragment(), + fragment_string(metaschema)}); + } } // Handle schema anchors @@ -443,7 +475,7 @@ auto internal_analyse(const sourcemeta::core::JSON &schema, find_nearest_bases(base_uris, entry.common.pointer, entry.id)}; std::vector instance_locations; - if (!entry.common.orphan) { + if (!entry.common.orphan && this->mode_ == SchemaFrame::Mode::Instances) { instance_locations.push_back(entry.common.instance_location); } @@ -452,31 +484,32 @@ auto internal_analyse(const sourcemeta::core::JSON &schema, const auto relative_anchor_uri{anchor_uri.recompose()}; if (type == AnchorType::Static || type == AnchorType::All) { - store(frame, SchemaReferenceType::Static, + store(this->locations_, this->instances_, SchemaReferenceType::Static, SchemaFrame::LocationType::Anchor, relative_anchor_uri, root_id, "", entry.common.pointer, entry.common.pointer.resolve_from(bases.second), entry.common.dialect.value(), entry.common.base_dialect.value(), - instance_locations); + instance_locations, entry.common.parent); } if (type == AnchorType::Dynamic || type == AnchorType::All) { - store(frame, SchemaReferenceType::Dynamic, - SchemaFrame::LocationType::Anchor, relative_anchor_uri, root_id, - "", entry.common.pointer, + store(this->locations_, this->instances_, + SchemaReferenceType::Dynamic, SchemaFrame::LocationType::Anchor, + relative_anchor_uri, root_id, "", entry.common.pointer, entry.common.pointer.resolve_from(bases.second), entry.common.dialect.value(), entry.common.base_dialect.value(), - instance_locations); + instance_locations, entry.common.parent); // Register a dynamic anchor as a static anchor if possible too if (entry.common.vocabularies.contains( "https://json-schema.org/draft/2020-12/vocab/core")) { - store(frame, SchemaReferenceType::Static, - SchemaFrame::LocationType::Anchor, relative_anchor_uri, - root_id, "", entry.common.pointer, - entry.common.pointer.resolve_from(bases.second), - entry.common.dialect.value(), - entry.common.base_dialect.value(), instance_locations, true); + store( + this->locations_, this->instances_, SchemaReferenceType::Static, + SchemaFrame::LocationType::Anchor, relative_anchor_uri, root_id, + "", entry.common.pointer, + entry.common.pointer.resolve_from(bases.second), + entry.common.dialect.value(), entry.common.base_dialect.value(), + instance_locations, entry.common.parent, true); } } } else { @@ -494,38 +527,43 @@ auto internal_analyse(const sourcemeta::core::JSON &schema, .canonicalize() .recompose()}; - if (!is_first && - frame.contains({SchemaReferenceType::Static, anchor_uri})) { + if (!is_first && this->locations_.contains( + {SchemaReferenceType::Static, anchor_uri})) { continue; } if (type == AnchorType::Static || type == AnchorType::All) { - store(frame, sourcemeta::core::SchemaReferenceType::Static, + store(this->locations_, this->instances_, + sourcemeta::core::SchemaReferenceType::Static, SchemaFrame::LocationType::Anchor, anchor_uri, root_id, base_string, entry.common.pointer, entry.common.pointer.resolve_from(bases.second), entry.common.dialect.value(), - entry.common.base_dialect.value(), instance_locations); + entry.common.base_dialect.value(), instance_locations, + entry.common.parent); } if (type == AnchorType::Dynamic || type == AnchorType::All) { - store(frame, sourcemeta::core::SchemaReferenceType::Dynamic, + store(this->locations_, this->instances_, + sourcemeta::core::SchemaReferenceType::Dynamic, SchemaFrame::LocationType::Anchor, anchor_uri, root_id, base_string, entry.common.pointer, entry.common.pointer.resolve_from(bases.second), entry.common.dialect.value(), - entry.common.base_dialect.value(), instance_locations); + entry.common.base_dialect.value(), instance_locations, + entry.common.parent); // Register a dynamic anchor as a static anchor if possible too if (entry.common.vocabularies.contains( "https://json-schema.org/draft/2020-12/vocab/core")) { - store(frame, sourcemeta::core::SchemaReferenceType::Static, + store(this->locations_, this->instances_, + sourcemeta::core::SchemaReferenceType::Static, SchemaFrame::LocationType::Anchor, anchor_uri, root_id, base_string, entry.common.pointer, entry.common.pointer.resolve_from(bases.second), entry.common.dialect.value(), entry.common.base_dialect.value(), instance_locations, - true); + entry.common.parent, true); } } @@ -559,45 +597,62 @@ auto internal_analyse(const sourcemeta::core::JSON &schema, relative_pointer_uri.canonicalize(); const auto result{relative_pointer_uri.recompose()}; - if (!frame.contains({SchemaReferenceType::Static, result})) { + if (!this->locations_.contains({SchemaReferenceType::Static, result})) { const auto nearest_bases{ find_nearest_bases(base_uris, pointer, base.first)}; assert(!nearest_bases.first.empty()); const auto ¤t_base{nearest_bases.first.front()}; const auto maybe_base_entry{ - frame.find({SchemaReferenceType::Static, current_base})}; + this->locations_.find({SchemaReferenceType::Static, current_base})}; const auto current_base_dialect{ - maybe_base_entry == frame.cend() + maybe_base_entry == this->locations_.cend() ? root_base_dialect.value() : maybe_base_entry->second.base_dialect}; const auto subschema{subschemas.find(pointer)}; if (subschema != subschemas.cend()) { // Handle orphan schemas - if (subschema->second.second) { - store(frame, SchemaReferenceType::Static, + if (subschema->second.orphan) { + store(this->locations_, this->instances_, + SchemaReferenceType::Static, SchemaFrame::LocationType::Subschema, result, root_id, current_base, pointer, pointer.resolve_from(nearest_bases.second), - dialects.first.front(), current_base_dialect, {}); - } else { - store(frame, SchemaReferenceType::Static, + dialects.first.front(), current_base_dialect, {}, + subschema->second.parent); + } else if (this->mode_ == SchemaFrame::Mode::Instances) { + store(this->locations_, this->instances_, + SchemaReferenceType::Static, SchemaFrame::LocationType::Subschema, result, root_id, current_base, pointer, pointer.resolve_from(nearest_bases.second), dialects.first.front(), current_base_dialect, - {subschema->second.first}); + {subschema->second.instance_location}, + subschema->second.parent); + } else { + store(this->locations_, this->instances_, + SchemaReferenceType::Static, + SchemaFrame::LocationType::Subschema, result, root_id, + current_base, pointer, + pointer.resolve_from(nearest_bases.second), + dialects.first.front(), current_base_dialect, {}, + subschema->second.parent); } } else { - store(frame, SchemaReferenceType::Static, + store(this->locations_, this->instances_, SchemaReferenceType::Static, SchemaFrame::LocationType::Pointer, result, root_id, current_base, pointer, pointer.resolve_from(nearest_bases.second), - dialects.first.front(), current_base_dialect, {}); + dialects.first.front(), current_base_dialect, {}, + dialects.second); } } } } + if (this->mode_ == SchemaFrame::Mode::Locations) { + return; + } + // Resolve references after all framing was performed for (const auto &entry : subschema_entries) { if (entry.common.subschema.get().is_object()) { @@ -612,7 +667,7 @@ auto internal_analyse(const sourcemeta::core::JSON &schema, } ref.canonicalize(); - references.insert_or_assign( + this->references_.insert_or_assign( {SchemaReferenceType::Static, entry.common.pointer.concat({"$ref"})}, SchemaFrame::ReferencesEntry{ref.recompose(), @@ -639,13 +694,13 @@ auto internal_analyse(const sourcemeta::core::JSON &schema, auto anchor_uri_string{ nearest_bases.first.empty() ? "" : nearest_bases.first.front()}; - const auto recursive_anchor{ - frame.find({SchemaReferenceType::Dynamic, anchor_uri_string})}; - const auto reference_type{recursive_anchor == frame.end() + const auto recursive_anchor{this->locations_.find( + {SchemaReferenceType::Dynamic, anchor_uri_string})}; + const auto reference_type{recursive_anchor == this->locations_.end() ? SchemaReferenceType::Static : SchemaReferenceType::Dynamic}; const sourcemeta::core::URI anchor_uri{std::move(anchor_uri_string)}; - references.insert_or_assign( + this->references_.insert_or_assign( {reference_type, entry.common.pointer.concat({"$recursiveRef"})}, SchemaFrame::ReferencesEntry{ anchor_uri.recompose(), anchor_uri.recompose_without_fragment(), @@ -671,14 +726,14 @@ auto internal_analyse(const sourcemeta::core::JSON &schema, const auto has_fragment{ref.fragment().has_value()}; const auto maybe_static_frame{ - frame.find({SchemaReferenceType::Static, ref_string})}; + this->locations_.find({SchemaReferenceType::Static, ref_string})}; const auto maybe_dynamic_frame{ - frame.find({SchemaReferenceType::Dynamic, ref_string})}; - const auto behaves_as_static{!has_fragment || - (has_fragment && - maybe_static_frame != frame.end() && - maybe_dynamic_frame == frame.end())}; - references.insert_or_assign( + this->locations_.find({SchemaReferenceType::Dynamic, ref_string})}; + const auto behaves_as_static{ + !has_fragment || + (has_fragment && maybe_static_frame != this->locations_.end() && + maybe_dynamic_frame == this->locations_.end())}; + this->references_.insert_or_assign( {behaves_as_static ? SchemaReferenceType::Static : SchemaReferenceType::Dynamic, entry.common.pointer.concat({"$dynamicRef"})}, @@ -691,22 +746,23 @@ auto internal_analyse(const sourcemeta::core::JSON &schema, // A schema is standalone if all references can be resolved within itself const bool standalone{std::all_of( - references.cbegin(), references.cend(), [&frame](const auto &reference) { + this->references_.cbegin(), this->references_.cend(), + [&](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" || - frame.contains({SchemaReferenceType::Static, - reference.second.destination}) || - frame.contains({SchemaReferenceType::Dynamic, - reference.second.destination}); + this->locations_.contains({SchemaReferenceType::Static, + reference.second.destination}) || + this->locations_.contains({SchemaReferenceType::Dynamic, + reference.second.destination}); })}; if (standalone) { // Find all dynamic anchors std::map> dynamic_anchors; - for (const auto &entry : frame) { + for (const auto &entry : this->locations_) { if (entry.first.first != SchemaReferenceType::Dynamic || entry.second.type != SchemaFrame::LocationType::Anchor) { continue; @@ -726,7 +782,7 @@ auto internal_analyse(const sourcemeta::core::JSON &schema, // is a static reference in disguise std::vector to_delete; std::vector to_insert; - for (const auto &reference : references) { + for (const auto &reference : this->references_) { if (reference.first.first != SchemaReferenceType::Dynamic || !reference.second.fragment.has_value()) { continue; @@ -754,54 +810,187 @@ auto internal_analyse(const sourcemeta::core::JSON &schema, // Because we can't mutate a map as we are traversing it for (const auto &key : to_delete) { - references.erase(key); + this->references_.erase(key); } for (auto &&entry : to_insert) { - references.emplace(std::move(entry)); + this->references_.emplace(std::move(entry)); } } - // We only care about marking reference origins from/to resources and - // subschemas + if (this->mode_ == sourcemeta::core::SchemaFrame::Mode::Instances) { + // Calculate alternative unresolved instance locations + for (auto &entry : this->locations_) { + if (entry.second.type == SchemaFrame::LocationType::Pointer) { + continue; + } - for (const auto &entry : frame) { - if (entry.second.type != SchemaFrame::LocationType::Resource) { - continue; + traverse_origin_instance_locations( + *this, this->instances_, entry.second.pointer, std::nullopt, + this->instances_[entry.second.pointer]); } - mark_reference_origins_from(frame, references, entry); + // This is guaranteed to be top-down + for (auto &entry : this->locations_) { + if (entry.second.type == SchemaFrame::LocationType::Pointer) { + continue; + } + + const auto subschema{subschemas.find(entry.second.pointer)}; + repopulate_instance_locations(*this, this->instances_, subschemas, + subschema->first, subschema->second, + this->instances_[entry.second.pointer], + std::nullopt); + } } +} - for (const auto &entry : frame) { - if (entry.second.type != SchemaFrame::LocationType::Subschema) { - continue; +auto SchemaFrame::locations() const noexcept -> const Locations & { + return this->locations_; +} + +auto SchemaFrame::references() const noexcept -> const References & { + return this->references_; +} + +auto SchemaFrame::vocabularies(const Location &location, + const SchemaResolver &resolver) const + -> std::map { + return sourcemeta::core::vocabularies(resolver, location.base_dialect, + location.dialect); +} + +auto SchemaFrame::uri(const Location &location, + const Pointer &relative_schema_location) const + -> std::string { + return to_uri(location.relative_pointer.concat(relative_schema_location), + location.base) + .recompose(); +} + +auto SchemaFrame::traverse(const Location &location, + const Pointer &relative_schema_location) const + -> const Location & { + const auto new_uri{this->uri(location, relative_schema_location)}; + const auto static_match{ + this->locations_.find({SchemaReferenceType::Static, new_uri})}; + if (static_match != this->locations_.cend()) { + return static_match->second; + } + + const auto dynamic_match{ + this->locations_.find({SchemaReferenceType::Dynamic, new_uri})}; + assert(dynamic_match != this->locations_.cend()); + return dynamic_match->second; +} + +auto SchemaFrame::traverse(const std::string &uri) const + -> std::optional> { + const auto static_result{ + this->locations_.find({SchemaReferenceType::Static, uri})}; + if (static_result != this->locations_.cend()) { + return static_result->second; + } + + const auto dynamic_result{ + this->locations_.find({SchemaReferenceType::Dynamic, uri})}; + if (dynamic_result != this->locations_.cend()) { + return dynamic_result->second; + } + + return std::nullopt; +} + +auto SchemaFrame::dereference(const Location &location, + const Pointer &relative_schema_location) const + -> std::pair>> { + const auto effective_location{ + location.pointer.concat({relative_schema_location})}; + const auto maybe_reference_entry{this->references_.find( + {SchemaReferenceType::Static, effective_location})}; + if (maybe_reference_entry == this->references_.cend()) { + // If static dereferencing failed but we know the reference + // is dynamic, then report so, but without a location, as by + // definition we can't know the destination until at runtime + if (this->references_.contains( + {SchemaReferenceType::Dynamic, effective_location})) { + return {SchemaReferenceType::Dynamic, std::nullopt}; } - mark_reference_origins_from(frame, references, entry); + return {SchemaReferenceType::Static, std::nullopt}; + } + + const auto destination{ + this->locations_.find({SchemaReferenceType::Static, + maybe_reference_entry->second.destination})}; + assert(destination != this->locations_.cend()); + return {SchemaReferenceType::Static, destination->second}; +} + +auto SchemaFrame::instance_locations(const Location &location) const -> const + typename Instances::mapped_type & { + const auto match{this->instances_.find(location.pointer)}; + if (match == this->instances_.cend()) { + static const typename Instances::mapped_type fallback; + return fallback; } - // Calculate alternative unresolved instance locations - for (auto &entry : frame) { - traverse_instance_locations(frame, entry.second, std::nullopt, - entry.second.instance_locations); + return match->second; +} + +auto SchemaFrame::references_to(const Pointer &pointer) const -> std::vector< + std::reference_wrapper> { + std::vector> + result; + + // TODO: This is currently very slow, as we need to loop on every reference + // to brute force whether it points to the desired entry or not + for (const auto &reference : this->references_) { + assert(!reference.first.second.empty()); + assert(reference.first.second.back().is_property()); + + if (reference.first.first == SchemaReferenceType::Static) { + const auto match{this->locations_.find( + {reference.first.first, reference.second.destination})}; + if (match != this->locations_.cend() && + match->second.pointer == pointer) { + result.emplace_back(reference); + } + } else { + for (const auto &location : this->locations_) { + if (location.second.type == LocationType::Anchor && + location.first.first == SchemaReferenceType::Dynamic && + location.second.pointer == pointer) { + if (!reference.second.fragment.has_value() || + URI{location.first.second}.fragment().value_or("") == + reference.second.fragment.value()) { + result.emplace_back(reference); + } + } + } + } } + + return result; } +} // namespace sourcemeta::core + +// TODO: Rephrase all this logic on top of framing by keeping track of all +// keyword inter-dependencies? + namespace { using namespace sourcemeta::core; // TODO: Extract all of this into a public utility to traverse // adjacent subschemas -auto find_adjacent_dependencies(const JSON::String ¤t, const JSON &schema, - const SchemaFrame &frame, - const SchemaWalker &walker, - const SchemaResolver &resolver, - const std::set &keywords, - const SchemaFrame::LocationsEntry &root, - const SchemaFrame::LocationsEntry &entry, - const bool is_static, - SchemaUnevaluatedEntry &result) -> void { +auto find_adjacent_dependencies( + const JSON::String ¤t, const JSON &schema, const SchemaFrame &frame, + const SchemaWalker &walker, const SchemaResolver &resolver, + const std::set &keywords, const SchemaFrame::Location &root, + const SchemaFrame::Location &entry, const bool is_static, + SchemaUnevaluatedEntry &result) -> void { const auto &subschema{get(schema, entry.pointer)}; if (!subschema.is_object()) { return; @@ -918,98 +1107,6 @@ auto find_adjacent_dependencies(const JSON::String ¤t, const JSON &schema, namespace sourcemeta::core { -auto SchemaFrame::analyse(const JSON &schema, const SchemaWalker &walker, - const SchemaResolver &resolver, - const std::optional &default_dialect, - const std::optional &default_id) - -> void { - internal_analyse(schema, this->locations_, this->references_, walker, - resolver, default_dialect, default_id); -} - -auto SchemaFrame::locations() const noexcept -> const Locations & { - return this->locations_; -} - -auto SchemaFrame::references() const noexcept -> const References & { - return this->references_; -} - -auto SchemaFrame::vocabularies(const LocationsEntry &location, - const SchemaResolver &resolver) const - -> std::map { - return sourcemeta::core::vocabularies(resolver, location.base_dialect, - location.dialect); -} - -auto SchemaFrame::uri(const LocationsEntry &location, - const Pointer &relative_schema_location) const - -> std::string { - return to_uri(location.relative_pointer.concat(relative_schema_location), - location.base) - .recompose(); -} - -auto SchemaFrame::traverse(const LocationsEntry &location, - const Pointer &relative_schema_location) const - -> const LocationsEntry & { - const auto new_uri{this->uri(location, relative_schema_location)}; - const auto static_match{ - this->locations_.find({SchemaReferenceType::Static, new_uri})}; - if (static_match != this->locations_.cend()) { - return static_match->second; - } - - const auto dynamic_match{ - this->locations_.find({SchemaReferenceType::Dynamic, new_uri})}; - assert(dynamic_match != this->locations_.cend()); - return dynamic_match->second; -} - -auto SchemaFrame::traverse(const std::string &uri) const - -> std::optional> { - const auto static_result{ - this->locations_.find({SchemaReferenceType::Static, uri})}; - if (static_result != this->locations_.cend()) { - return static_result->second; - } - - const auto dynamic_result{ - this->locations_.find({SchemaReferenceType::Dynamic, uri})}; - if (dynamic_result != this->locations_.cend()) { - return dynamic_result->second; - } - - return std::nullopt; -} - -auto SchemaFrame::dereference(const LocationsEntry &location, - const Pointer &relative_schema_location) const - -> std::pair>> { - const auto effective_location{ - location.pointer.concat({relative_schema_location})}; - const auto maybe_reference_entry{this->references_.find( - {SchemaReferenceType::Static, effective_location})}; - if (maybe_reference_entry == this->references_.cend()) { - // If static dereferencing failed but we know the reference - // is dynamic, then report so, but without a location, as by - // definition we can't know the destination until at runtime - if (this->references_.contains( - {SchemaReferenceType::Dynamic, effective_location})) { - return {SchemaReferenceType::Dynamic, std::nullopt}; - } - - return {SchemaReferenceType::Static, std::nullopt}; - } - - const auto destination{ - this->locations_.find({SchemaReferenceType::Static, - maybe_reference_entry->second.destination})}; - assert(destination != this->locations_.cend()); - return {SchemaReferenceType::Static, destination->second}; -} - auto unevaluated(const JSON &schema, const SchemaFrame &frame, const SchemaWalker &walker, const SchemaResolver &resolver) -> SchemaUnevaluatedEntries { 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 b64782b..d1a24b0 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 @@ -47,7 +47,9 @@ namespace sourcemeta::core { /// } /// })JSON"); /// -/// sourcemeta::core::SchemaFrame frame; +/// sourcemeta::core::SchemaFrame +/// frame{sourcemeta::core::SchemaFrame::Mode::References}; +/// /// frame.analyse(document, /// sourcemeta::core::schema_official_walker, /// sourcemeta::core::schema_official_resolver); @@ -101,10 +103,18 @@ namespace sourcemeta::core { /// ``` class SOURCEMETA_CORE_JSONSCHEMA_EXPORT SchemaFrame { public: + /// The mode of framing. More extensive analysis can be compute and memory + /// intensive + enum class Mode { Locations, References, Instances }; + + SchemaFrame(const Mode mode) : mode_{mode} {} + /// A single entry in a JSON Schema reference map struct ReferencesEntry { std::string destination; + // TODO: This string can be a `string_view` over the `destination` std::optional base; + // TODO: This string can be a `string_view` over the `destination` std::optional fragment; }; @@ -129,6 +139,7 @@ class SOURCEMETA_CORE_JSONSCHEMA_EXPORT SchemaFrame { enum class LocationType : std::uint8_t { Resource, Anchor, + // TODO: Distinguish between a Pointer and a Keyword Pointer, Subschema }; @@ -136,29 +147,32 @@ class SOURCEMETA_CORE_JSONSCHEMA_EXPORT SchemaFrame { #pragma GCC diagnostic pop #endif - /// Determines a location entry - using LocationKey = std::pair; - - /// A single frame in a JSON Schema reference frame - struct LocationsEntry { + /// A location entry + struct Location { + // TODO: Turn this into a weak pointer + std::optional parent; LocationType type; std::optional root; std::string base; + // TODO: Turn this into a weak pointer Pointer pointer; + // TODO: Turn this into a weak pointer Pointer relative_pointer; std::string dialect; std::string base_dialect; - // TODO: Support only populating these when needed, given - // how expensive they can be, by taking more options on the - // analyse method - std::vector instance_locations; - std::vector> destination_of; }; + // TODO: Indexing locations by reference type is wrong. We can index by just + // URI, and introduce a new `DynamicAnchor` location type /// A JSON Schema reference frame is a mapping of URIs to schema identifiers, /// JSON Pointers within the schema, and subschemas dialects. We call it /// reference frame as this mapping is essential for resolving references. - using Locations = std::map; + using Locations = + std::map, Location>; + + // TODO: Turn the mapped value into a proper set + /// A set of unresolved instance locations + using Instances = std::map>; /// Analyse a given schema auto analyse(const JSON &schema, const SchemaWalker &walker, @@ -174,32 +188,41 @@ class SOURCEMETA_CORE_JSONSCHEMA_EXPORT SchemaFrame { auto references() const noexcept -> const References &; /// Get the vocabularies associated with a location entry - auto vocabularies(const LocationsEntry &location, + auto vocabularies(const Location &location, const SchemaResolver &resolver) const -> std::map; /// Get the URI associated with a location entry - auto uri(const LocationsEntry &location, + auto uri(const Location &location, const Pointer &relative_schema_location = empty_pointer) const -> std::string; /// Get the location associated by traversing a pointer from another location - auto traverse(const LocationsEntry &location, + auto traverse(const Location &location, const Pointer &relative_schema_location) const - -> const LocationsEntry &; + -> const Location &; /// Get the location associated with a given URI auto traverse(const std::string &uri) const - -> std::optional>; + -> std::optional>; /// Try to dereference a reference location into its destination location auto - dereference(const LocationsEntry &location, + dereference(const Location &location, const Pointer &relative_schema_location = empty_pointer) const -> std::pair>>; + std::optional>>; + + /// Get the unresolved instance locations associated with a location entry + auto instance_locations(const Location &location) const -> const + typename Instances::mapped_type &; + + /// Find all references to a given location pointer + auto references_to(const Pointer &pointer) const -> std::vector< + std::reference_wrapper>; private: + Mode mode_; // Exporting symbols that depends on the standard C++ library is considered // safe. // https://learn.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-2-c4275?view=msvc-170&redirectedfrom=MSDN @@ -208,6 +231,7 @@ class SOURCEMETA_CORE_JSONSCHEMA_EXPORT SchemaFrame { #endif Locations locations_; References references_; + Instances instances_; #if defined(_MSC_VER) #pragma warning(default : 4251 4275) #endif @@ -249,7 +273,9 @@ using SchemaUnevaluatedEntries = std::map; /// "unevaluatedProperties": false /// })JSON"); /// -/// sourcemeta::core::SchemaFrame frame; +/// sourcemeta::core::SchemaFrame +/// frame{sourcemeta::core::SchemaFrame::Mode::References}; +/// /// frame.analyse(document, /// sourcemeta::core::schema_official_walker, /// sourcemeta::core::schema_official_resolver); diff --git a/vendor/core/src/core/jsonschema/include/sourcemeta/core/jsonschema_types.h b/vendor/core/src/core/jsonschema/include/sourcemeta/core/jsonschema_types.h index bbf242f..1b29e03 100644 --- a/vendor/core/src/core/jsonschema/include/sourcemeta/core/jsonschema_types.h +++ b/vendor/core/src/core/jsonschema/include/sourcemeta/core/jsonschema_types.h @@ -189,12 +189,21 @@ using SchemaWalker = std::function parent; + // TODO: Turn this into a weak pointer Pointer pointer; std::optional dialect; std::map vocabularies; std::optional base_dialect; std::reference_wrapper subschema; + + // TODO: These two pointer templates contain some overlap. + // Instead, have a `base_instance_location` and a `relative_instance_location` + // that when concatenated, represent the full `instance_location` PointerTemplate instance_location; + PointerTemplate relative_instance_location; + bool orphan; }; diff --git a/vendor/core/src/core/jsonschema/jsonschema.cc b/vendor/core/src/core/jsonschema/jsonschema.cc index d54d4ae..171f5a8 100644 --- a/vendor/core/src/core/jsonschema/jsonschema.cc +++ b/vendor/core/src/core/jsonschema/jsonschema.cc @@ -566,7 +566,8 @@ auto sourcemeta::core::reference_visit( const sourcemeta::core::SchemaVisitorReference &callback, const std::optional &default_dialect, const std::optional &default_id) -> void { - sourcemeta::core::SchemaFrame frame; + sourcemeta::core::SchemaFrame frame{ + sourcemeta::core::SchemaFrame::Mode::Locations}; frame.analyse(schema, walker, resolver, default_dialect, default_id); for (const auto &entry : frame.locations()) { if (entry.second.type != @@ -644,7 +645,8 @@ auto sourcemeta::core::unidentify( const sourcemeta::core::SchemaResolver &resolver, const std::optional &default_dialect) -> void { // (1) Re-frame before changing anything - sourcemeta::core::SchemaFrame frame; + sourcemeta::core::SchemaFrame frame{ + sourcemeta::core::SchemaFrame::Mode::References}; frame.analyse(schema, walker, resolver, default_dialect); // (2) Remove all identifiers and anchors diff --git a/vendor/core/src/core/jsonschema/resolver.cc b/vendor/core/src/core/jsonschema/resolver.cc index 1ebc793..b29c97f 100644 --- a/vendor/core/src/core/jsonschema/resolver.cc +++ b/vendor/core/src/core/jsonschema/resolver.cc @@ -20,7 +20,7 @@ auto SchemaMapResolver::add(const JSON &schema, // Registering the top-level schema is not enough. We need to check // and register every embedded schema resource too - SchemaFrame frame; + SchemaFrame frame{SchemaFrame::Mode::References}; frame.analyse(schema, schema_official_walker, *this, default_dialect, default_id); diff --git a/vendor/core/src/core/jsonschema/walker.cc b/vendor/core/src/core/jsonschema/walker.cc index bfef93f..160d4f0 100644 --- a/vendor/core/src/core/jsonschema/walker.cc +++ b/vendor/core/src/core/jsonschema/walker.cc @@ -6,8 +6,10 @@ namespace { enum class SchemaWalkerType_t : std::uint8_t { Deep, Flat }; -auto walk(const sourcemeta::core::Pointer &pointer, +auto walk(const std::optional &parent, + const sourcemeta::core::Pointer &pointer, const sourcemeta::core::PointerTemplate &instance_location, + const sourcemeta::core::PointerTemplate &relative_instance_location, std::vector &subschemas, const sourcemeta::core::JSON &subschema, const sourcemeta::core::SchemaWalker &walker, @@ -35,8 +37,9 @@ auto walk(const sourcemeta::core::Pointer &pointer, if (type == SchemaWalkerType_t::Deep || level > 0) { sourcemeta::core::SchemaIteratorEntry entry{ - pointer, new_dialect, vocabularies, base_dialect, - subschema, instance_location, orphan}; + parent, pointer, new_dialect, vocabularies, + base_dialect, subschema, instance_location, relative_instance_location, + orphan}; subschemas.push_back(std::move(entry)); } @@ -55,8 +58,10 @@ auto walk(const sourcemeta::core::Pointer &pointer, auto new_instance_location{instance_location}; new_instance_location.emplace_back( sourcemeta::core::PointerTemplate::Wildcard::Property); - walk(new_pointer, new_instance_location, subschemas, pair.second, - walker, resolver, new_dialect, type, level + 1, orphan); + walk(pointer, new_pointer, new_instance_location, + {sourcemeta::core::PointerTemplate::Wildcard::Property}, + subschemas, pair.second, walker, resolver, new_dialect, type, + level + 1, orphan); } break; case sourcemeta::core::SchemaKeywordType:: @@ -66,8 +71,10 @@ auto walk(const sourcemeta::core::Pointer &pointer, auto new_instance_location{instance_location}; new_instance_location.emplace_back( sourcemeta::core::PointerTemplate::Wildcard::Key); - walk(new_pointer, new_instance_location, subschemas, pair.second, - walker, resolver, new_dialect, type, level + 1, orphan); + walk(pointer, new_pointer, new_instance_location, + {sourcemeta::core::PointerTemplate::Wildcard::Key}, subschemas, + pair.second, walker, resolver, new_dialect, type, level + 1, + orphan); } break; case sourcemeta::core::SchemaKeywordType:: @@ -77,8 +84,10 @@ auto walk(const sourcemeta::core::Pointer &pointer, auto new_instance_location{instance_location}; new_instance_location.emplace_back( sourcemeta::core::PointerTemplate::Wildcard::Item); - walk(new_pointer, new_instance_location, subschemas, pair.second, - walker, resolver, new_dialect, type, level + 1, orphan); + walk(pointer, new_pointer, new_instance_location, + {sourcemeta::core::PointerTemplate::Wildcard::Item}, subschemas, + pair.second, walker, resolver, new_dialect, type, level + 1, + orphan); } break; case sourcemeta::core::SchemaKeywordType::ApplicatorValueTraverseParent: { @@ -86,8 +95,9 @@ auto walk(const sourcemeta::core::Pointer &pointer, new_pointer.emplace_back(pair.first); auto new_instance_location{instance_location}; new_instance_location.pop_back(); - walk(new_pointer, new_instance_location, subschemas, pair.second, - walker, resolver, new_dialect, type, level + 1, orphan); + walk(pointer, new_pointer, new_instance_location, {}, subschemas, + pair.second, walker, resolver, new_dialect, type, level + 1, + orphan); } break; case sourcemeta::core::SchemaKeywordType::ApplicatorValueInPlaceOther: @@ -95,8 +105,9 @@ auto walk(const sourcemeta::core::Pointer &pointer, case sourcemeta::core::SchemaKeywordType::ApplicatorValueInPlace: { sourcemeta::core::Pointer new_pointer{pointer}; new_pointer.emplace_back(pair.first); - walk(new_pointer, instance_location, subschemas, pair.second, walker, - resolver, new_dialect, type, level + 1, orphan); + walk(pointer, new_pointer, instance_location, {}, subschemas, + pair.second, walker, resolver, new_dialect, type, level + 1, + orphan); } break; case sourcemeta::core::SchemaKeywordType::ApplicatorElementsTraverseItem: @@ -107,9 +118,9 @@ auto walk(const sourcemeta::core::Pointer &pointer, new_pointer.emplace_back(index); auto new_instance_location{instance_location}; new_instance_location.emplace_back(new_pointer.back()); - walk(new_pointer, new_instance_location, subschemas, - pair.second.at(index), walker, resolver, new_dialect, type, - level + 1, orphan); + walk(pointer, new_pointer, new_instance_location, + {new_pointer.back()}, subschemas, pair.second.at(index), + walker, resolver, new_dialect, type, level + 1, orphan); } } @@ -123,7 +134,7 @@ auto walk(const sourcemeta::core::Pointer &pointer, sourcemeta::core::Pointer new_pointer{pointer}; new_pointer.emplace_back(pair.first); new_pointer.emplace_back(index); - walk(new_pointer, instance_location, subschemas, + walk(pointer, new_pointer, instance_location, {}, subschemas, pair.second.at(index), walker, resolver, new_dialect, type, level + 1, orphan); } @@ -140,8 +151,9 @@ auto walk(const sourcemeta::core::Pointer &pointer, new_pointer.emplace_back(subpair.first); auto new_instance_location{instance_location}; new_instance_location.emplace_back(new_pointer.back()); - walk(new_pointer, new_instance_location, subschemas, subpair.second, - walker, resolver, new_dialect, type, level + 1, orphan); + walk(pointer, new_pointer, new_instance_location, + {new_pointer.back()}, subschemas, subpair.second, walker, + resolver, new_dialect, type, level + 1, orphan); } } @@ -156,8 +168,9 @@ auto walk(const sourcemeta::core::Pointer &pointer, new_pointer.emplace_back(subpair.first); auto new_instance_location{instance_location}; new_instance_location.emplace_back(subpair.first); - walk(new_pointer, new_instance_location, subschemas, subpair.second, - walker, resolver, new_dialect, type, level + 1, orphan); + walk(pointer, new_pointer, new_instance_location, {subpair.first}, + subschemas, subpair.second, walker, resolver, new_dialect, + type, level + 1, orphan); } } @@ -169,8 +182,9 @@ auto walk(const sourcemeta::core::Pointer &pointer, sourcemeta::core::Pointer new_pointer{pointer}; new_pointer.emplace_back(pair.first); new_pointer.emplace_back(subpair.first); - walk(new_pointer, instance_location, subschemas, subpair.second, - walker, resolver, new_dialect, type, level + 1, orphan); + walk(pointer, new_pointer, instance_location, {}, subschemas, + subpair.second, walker, resolver, new_dialect, type, level + 1, + orphan); } } @@ -182,8 +196,9 @@ auto walk(const sourcemeta::core::Pointer &pointer, sourcemeta::core::Pointer new_pointer{pointer}; new_pointer.emplace_back(pair.first); new_pointer.emplace_back(subpair.first); - walk(new_pointer, instance_location, subschemas, subpair.second, - walker, resolver, new_dialect, type, level + 1, true); + walk(pointer, new_pointer, instance_location, {}, subschemas, + subpair.second, walker, resolver, new_dialect, type, level + 1, + true); } } @@ -198,9 +213,9 @@ auto walk(const sourcemeta::core::Pointer &pointer, new_pointer.emplace_back(index); auto new_instance_location{instance_location}; new_instance_location.emplace_back(new_pointer.back()); - walk(new_pointer, new_instance_location, subschemas, - pair.second.at(index), walker, resolver, new_dialect, type, - level + 1, orphan); + walk(pointer, new_pointer, new_instance_location, + {new_pointer.back()}, subschemas, pair.second.at(index), + walker, resolver, new_dialect, type, level + 1, orphan); } } else { sourcemeta::core::Pointer new_pointer{pointer}; @@ -208,8 +223,10 @@ auto walk(const sourcemeta::core::Pointer &pointer, auto new_instance_location{instance_location}; new_instance_location.emplace_back( sourcemeta::core::PointerTemplate::Wildcard::Item); - walk(new_pointer, new_instance_location, subschemas, pair.second, - walker, resolver, new_dialect, type, level + 1, orphan); + walk(pointer, new_pointer, new_instance_location, + {sourcemeta::core::PointerTemplate::Wildcard::Item}, subschemas, + pair.second, walker, resolver, new_dialect, type, level + 1, + orphan); } break; @@ -221,15 +238,16 @@ auto walk(const sourcemeta::core::Pointer &pointer, sourcemeta::core::Pointer new_pointer{pointer}; new_pointer.emplace_back(pair.first); new_pointer.emplace_back(index); - walk(new_pointer, instance_location, subschemas, + walk(pointer, new_pointer, instance_location, {}, subschemas, pair.second.at(index), walker, resolver, new_dialect, type, level + 1, orphan); } } else { sourcemeta::core::Pointer new_pointer{pointer}; new_pointer.emplace_back(pair.first); - walk(new_pointer, instance_location, subschemas, pair.second, walker, - resolver, new_dialect, type, level + 1, orphan); + walk(pointer, new_pointer, instance_location, {}, subschemas, + pair.second, walker, resolver, new_dialect, type, level + 1, + orphan); } break; @@ -268,12 +286,13 @@ sourcemeta::core::SchemaIterator::SchemaIterator( // the current schema is a subschema, but cannot walk any further. if (!dialect.has_value()) { sourcemeta::core::SchemaIteratorEntry entry{ - pointer, std::nullopt, {}, std::nullopt, - schema, instance_location, false}; + std::nullopt, pointer, std::nullopt, {}, std::nullopt, + schema, instance_location, instance_location, false}; this->subschemas.push_back(std::move(entry)); } else { - walk(pointer, instance_location, this->subschemas, schema, walker, resolver, - dialect.value(), SchemaWalkerType_t::Deep, 0, false); + walk(std::nullopt, pointer, instance_location, instance_location, + this->subschemas, schema, walker, resolver, dialect.value(), + SchemaWalkerType_t::Deep, 0, false); } } @@ -287,8 +306,9 @@ sourcemeta::core::SchemaIteratorFlat::SchemaIteratorFlat( if (dialect.has_value()) { sourcemeta::core::Pointer pointer; sourcemeta::core::PointerTemplate instance_location; - walk(pointer, instance_location, this->subschemas, schema, walker, resolver, - dialect.value(), SchemaWalkerType_t::Flat, 0, false); + walk(std::nullopt, pointer, instance_location, instance_location, + this->subschemas, schema, walker, resolver, dialect.value(), + SchemaWalkerType_t::Flat, 0, false); } } @@ -315,8 +335,9 @@ sourcemeta::core::SchemaKeywordIterator::SchemaKeywordIterator( for (const auto &entry : schema.as_object()) { sourcemeta::core::SchemaIteratorEntry subschema_entry{ - {entry.first}, dialect, vocabularies, base_dialect, - entry.second, {}, false}; + std::nullopt, {entry.first}, dialect, vocabularies, + base_dialect, entry.second, {}, {}, + false}; this->entries.push_back(std::move(subschema_entry)); }