|
|
@@ -8,6 +8,7 @@
|
|
|
#include <jvalidate/detail/anchor.h>
|
|
|
#include <jvalidate/detail/pointer.h>
|
|
|
#include <jvalidate/detail/reference.h>
|
|
|
+#include <jvalidate/detail/reference_cache.h>
|
|
|
#include <jvalidate/enum.h>
|
|
|
#include <jvalidate/forward.h>
|
|
|
#include <jvalidate/uri.h>
|
|
|
@@ -20,8 +21,10 @@ public:
|
|
|
private:
|
|
|
std::map<detail::RootReference, A> roots_;
|
|
|
|
|
|
- std::map<detail::Reference, detail::RootReference, std::greater<>> absolute_to_canonical_;
|
|
|
- std::map<detail::RootReference, detail::Reference, std::greater<>> canonical_to_absolute_;
|
|
|
+ detail::ReferenceCache references_;
|
|
|
+
|
|
|
+ std::multimap<URI, detail::Anchor> dynamic_anchors_;
|
|
|
+ std::map<detail::Anchor, detail::Reference> active_dynamic_anchors_;
|
|
|
|
|
|
public:
|
|
|
ReferenceManager(A const & root, schema::Version version, Keywords const & keywords)
|
|
|
@@ -36,54 +39,30 @@ public:
|
|
|
return std::nullopt;
|
|
|
}
|
|
|
|
|
|
- detail::Reference canonicalize(detail::Reference const & ref, bool allow_equality = false) const {
|
|
|
- if (canonical_to_absolute_.contains(ref.root())) {
|
|
|
- return ref;
|
|
|
- }
|
|
|
-
|
|
|
- auto it = allow_equality ? absolute_to_canonical_.lower_bound(ref)
|
|
|
- : absolute_to_canonical_.upper_bound(ref);
|
|
|
- if (it == absolute_to_canonical_.end()) {
|
|
|
- return ref;
|
|
|
- }
|
|
|
-
|
|
|
- auto const & [absolute, anchor] = *it;
|
|
|
- if (not ref.pointer().starts_with(absolute.pointer())) {
|
|
|
- return ref;
|
|
|
- }
|
|
|
-
|
|
|
- return detail::Reference(anchor, ref.pointer().relative_to(absolute.pointer()));
|
|
|
- }
|
|
|
-
|
|
|
detail::Reference canonicalize(detail::Reference const & ref,
|
|
|
detail::Reference const & parent) const {
|
|
|
// Relative URI, not in the HEREDOC (or we set an $id)
|
|
|
if (ref.uri().empty() and ref.anchor().empty()) {
|
|
|
- return detail::Reference(canonicalize(parent, true).root(), ref.pointer());
|
|
|
+ return detail::Reference(references_.relative_to_nearest_anchor(parent).root(),
|
|
|
+ ref.pointer());
|
|
|
}
|
|
|
|
|
|
// TODO(samjaffe): Clean this clause up
|
|
|
- URI uri = ref.uri().empty() ? parent.root().uri() : ref.uri();
|
|
|
- if (uri.empty()) {
|
|
|
- auto parent_uri = canonicalize(parent).uri();
|
|
|
- if (parent_uri == parent.uri() && parent.uri().empty()) {
|
|
|
- auto it = absolute_to_canonical_.find(detail::Reference());
|
|
|
- if (it != absolute_to_canonical_.end()) {
|
|
|
- uri = it->second.uri();
|
|
|
- }
|
|
|
+ URI uri = [this, &ref, &parent]() {
|
|
|
+ if (ref.uri().empty() && parent.uri().empty()) {
|
|
|
+ return references_.actual_parent_uri(parent);
|
|
|
}
|
|
|
- } else if (uri.is_rootless()) {
|
|
|
- auto parent_uri = canonicalize(parent).uri();
|
|
|
- if (parent_uri == parent.uri() && parent.uri().empty()) {
|
|
|
- auto it = absolute_to_canonical_.find(detail::Reference());
|
|
|
- if (it != absolute_to_canonical_.end()) {
|
|
|
- parent_uri = it->second.uri();
|
|
|
- }
|
|
|
+
|
|
|
+ URI uri = ref.uri().empty() ? parent.uri() : ref.uri();
|
|
|
+ if (not uri.is_rootless()) {
|
|
|
+ return uri;
|
|
|
}
|
|
|
- EXPECT_M(parent_uri.resource().rfind('/') != std::string::npos,
|
|
|
- "Unable to deduce root for relative uri " << uri << " (" << parent_uri << ")");
|
|
|
- uri = (uri.is_relative() ? parent_uri.parent() : parent_uri.root()) / uri;
|
|
|
- }
|
|
|
+
|
|
|
+ URI base = references_.actual_parent_uri(parent);
|
|
|
+ EXPECT_M(base.resource().rfind('/') != std::string::npos,
|
|
|
+ "Unable to deduce root for relative uri " << uri << " (" << base << ")");
|
|
|
+ return (uri.is_relative() ? base.parent() : base.root()) / uri;
|
|
|
+ }();
|
|
|
|
|
|
detail::Reference rval(uri, ref.anchor(), ref.pointer());
|
|
|
|
|
|
@@ -94,7 +73,7 @@ public:
|
|
|
|
|
|
void prime(Adapter auto const & json, URI const & where, schema::Version version,
|
|
|
Keywords const & keywords) {
|
|
|
- canonical_to_absolute_.emplace(where, detail::Reference(where));
|
|
|
+ references_.emplace(where);
|
|
|
prime_impl(json, detail::Reference(where), version, keywords);
|
|
|
}
|
|
|
|
|
|
@@ -147,50 +126,33 @@ private:
|
|
|
root = detail::RootReference(where.uri().root() / root.uri(), root.anchor());
|
|
|
}
|
|
|
|
|
|
- cache(root, where, json);
|
|
|
+ roots_.emplace(root, json);
|
|
|
+ where = references_.emplace(where, root);
|
|
|
}
|
|
|
|
|
|
- if (not schema.contains("$anchor") || version < schema::Version::Draft2019_09) {
|
|
|
+ // $anchor and its related keywords were introduced in Draft 2019-09
|
|
|
+ if (version < schema::Version::Draft2019_09) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- root = detail::RootReference(root.uri(), detail::Anchor(schema["$anchor"].as_string()));
|
|
|
-
|
|
|
- cache(root, where, json);
|
|
|
- }
|
|
|
-
|
|
|
- template <size_t I>
|
|
|
- bool extract_relative(detail::Reference const & where, auto const & tup,
|
|
|
- detail::Pointer & out) const {
|
|
|
- auto const & ptr = std::get<I>(tup).pointer();
|
|
|
- if (where.pointer().starts_with(ptr)) {
|
|
|
- out = where.pointer().relative_to(ptr);
|
|
|
- return true;
|
|
|
+ if (schema.contains("$anchor")) {
|
|
|
+ root = detail::RootReference(root.uri(), detail::Anchor(schema["$anchor"].as_string()));
|
|
|
+ roots_.emplace(root, json);
|
|
|
+ where = references_.emplace(where, root);
|
|
|
}
|
|
|
- return false;
|
|
|
- }
|
|
|
|
|
|
- void recursive_cache(detail::RootReference const & root, detail::Reference const & where) {
|
|
|
- if (where.pointer().empty()) {
|
|
|
- return;
|
|
|
- }
|
|
|
- detail::Pointer relative;
|
|
|
- if (auto it = canonical_to_absolute_.lower_bound(where.root());
|
|
|
- it != canonical_to_absolute_.end() && where.root() != it->second.root() &&
|
|
|
- extract_relative<1>(where, *it, relative)) {
|
|
|
- absolute_to_canonical_.emplace(it->second / relative, root);
|
|
|
- recursive_cache(root, it->second / relative);
|
|
|
- }
|
|
|
- }
|
|
|
+ std::string const dyn_anchor =
|
|
|
+ version > schema::Version::Draft2019_09 ? "$dynamicAnchor" : "$recursiveAnchor";
|
|
|
|
|
|
- void cache(detail::RootReference const & root, detail::Reference & where,
|
|
|
- Adapter auto const & json) {
|
|
|
- recursive_cache(root, where);
|
|
|
- absolute_to_canonical_.emplace(where, root);
|
|
|
- canonical_to_absolute_.emplace(root, where);
|
|
|
+ if (schema.contains(dyn_anchor) && version == schema::Version::Draft2019_09) {
|
|
|
+ detail::Anchor anchor(schema[dyn_anchor].as_string());
|
|
|
+ root = detail::RootReference(root.uri(), anchor);
|
|
|
|
|
|
- roots_.emplace(root, json);
|
|
|
- where = detail::Reference(root);
|
|
|
+ roots_.emplace(root, json);
|
|
|
+ where = references_.emplace(where, root);
|
|
|
+ dynamic_anchors_.emplace(root.uri(), anchor);
|
|
|
+ active_dynamic_anchors_.emplace(anchor, root);
|
|
|
+ }
|
|
|
}
|
|
|
};
|
|
|
}
|