瀏覽代碼

refactor: move the management of Reference <-> Anchor to a helper class

Sam Jaffe 1 年之前
父節點
當前提交
76611faa33
共有 3 個文件被更改,包括 117 次插入78 次删除
  1. 1 1
      Makefile
  2. 77 0
      include/jvalidate/detail/reference_cache.h
  3. 39 77
      include/jvalidate/reference_handler.h

+ 1 - 1
Makefile

@@ -26,7 +26,7 @@ TEST_BINARIES := .build/bin/selfvalidate
 
 EXCLUDED_TESTS := format* content non_bmp_regex ecmascript_regex \
 		  bignum float_overflow zeroTerminatedFloats
-EXCLUDED_TESTS := $(shell printf ":*optional_%s" $(EXCLUDED_TESTS) | cut -c2-):Draft2019_09/JsonSchema.TestSuite/recursiveRef
+EXCLUDED_TESTS := $(shell printf ":*optional_%s" $(EXCLUDED_TESTS) | cut -c2-)
 
 all: run-test
 

+ 77 - 0
include/jvalidate/detail/reference_cache.h

@@ -0,0 +1,77 @@
+#pragma once
+
+#include <functional>
+#include <map>
+#include <optional>
+
+#include <jvalidate/detail/pointer.h>
+#include <jvalidate/detail/reference.h>
+
+namespace jvalidate::detail {
+class ReferenceCache {
+private:
+  std::map<Reference, RootReference, std::greater<>> to_anchor_;
+  std::map<RootReference, Reference, std::greater<>> to_absolute_;
+
+public:
+  void emplace(URI const & root) { to_absolute_.emplace(root, root); }
+
+  Reference emplace(Reference const & absolute, RootReference const & canonical) {
+    for (Reference where = absolute; not where.pointer().empty();) {
+      // Recursively add all possible alternative paths that are equivalent to
+      // this absolute path as valid mappings to this canonical one.
+      auto it = to_absolute_.lower_bound(where.root());
+      if (it == to_absolute_.end() || where.root() == it->second.root()) {
+        break;
+      }
+
+      if (auto ptr = it->second.pointer(); where.pointer().starts_with(ptr)) {
+        where = it->second / where.pointer().relative_to(ptr);
+        to_anchor_.emplace(where, canonical);
+      } else {
+        break;
+      }
+    }
+
+    to_anchor_.emplace(absolute, canonical);
+    // TODO: Validate uniqueness?
+    to_absolute_.emplace(canonical, absolute);
+
+    return Reference(canonical);
+  }
+
+  Reference relative_to_nearest_anchor(Reference const & ref,
+                                       bool for_parent_reference = false) const {
+    if (to_absolute_.contains(ref.root())) {
+      return ref;
+    }
+
+    auto it = for_parent_reference ? to_anchor_.upper_bound(ref) : to_anchor_.lower_bound(ref);
+    if (it == to_anchor_.end()) {
+      return ref;
+    }
+
+    auto const & [absolute, anchor] = *it;
+    if (not ref.pointer().starts_with(absolute.pointer())) {
+      return ref;
+    }
+
+    return Reference(anchor, ref.pointer().relative_to(absolute.pointer()));
+  }
+
+  URI actual_parent_uri(detail::Reference const & parent) const {
+    // TODO(samjaffe): Think about this some more - there's something awkward here
+    URI uri = relative_to_nearest_anchor(parent, true).uri();
+
+    if (not uri.empty() || not parent.uri().empty()) {
+      return uri;
+    }
+
+    if (auto it = to_anchor_.find(Reference()); it != to_anchor_.end()) {
+      return it->second.uri();
+    }
+
+    return uri;
+  }
+};
+}

+ 39 - 77
include/jvalidate/reference_handler.h

@@ -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);
+    }
   }
 };
 }