Ver Fonte

refactor: put the handling of the dynamic scope stack into a helper

Sam Jaffe há 1 ano atrás
pai
commit
d589c359a0

+ 50 - 0
include/jvalidate/detail/context_stack.h

@@ -0,0 +1,50 @@
+#pragma once
+
+#include <deque>
+#include <map>
+#include <optional>
+
+#include <jvalidate/detail/on_block_exit.h>
+
+namespace jvalidate::detail {
+template <typename Key, typename Value> class ContextStack {
+private:
+  std::map<Key, std::deque<std::optional<Value>>> data_;
+
+public:
+  OnBlockExit scope(std::map<Key, Value> const & frame) {
+    if (frame.empty() && data_.empty()) {
+      return nullptr;
+    }
+
+    for (auto const & [k, v] : frame) {
+      data_[k].push_back(data_[k].empty() ? v : data_[k].front());
+    }
+
+    for (auto & [k, stack] : data_) {
+      if (not frame.contains(k)) {
+        stack.push_back(std::nullopt);
+      }
+    }
+
+    return [this]() {
+      for (auto it = data_.begin(); it != data_.end();) {
+        if (it->second.size() == 1) {
+          it = data_.erase(it);
+        } else {
+          it->second.pop_back();
+        }
+      }
+    };
+  }
+
+  std::optional<Value> lookup(Key const & key) const {
+    if (auto it = data_.find(key); it != data_.end()) {
+      return it->second.back();
+    }
+    return std::nullopt;
+  }
+
+  bool empty() const { return data_.empty(); }
+};
+}

+ 13 - 43
include/jvalidate/reference_handler.h

@@ -6,6 +6,7 @@
 #include <unordered_map>
 
 #include <jvalidate/detail/anchor.h>
+#include <jvalidate/detail/context_stack.h>
 #include <jvalidate/detail/on_block_exit.h>
 #include <jvalidate/detail/out.h>
 #include <jvalidate/detail/parser_context.h>
@@ -28,7 +29,7 @@ private:
 
   std::map<detail::RootReference, A> roots_;
 
-  std::map<detail::Anchor, detail::Reference> active_dynamic_anchors_;
+  detail::ContextStack<detail::Anchor, detail::Reference> active_dynamic_anchors_;
   std::map<URI, std::map<detail::Anchor, detail::Reference>> dynamic_anchors_;
 
 public:
@@ -38,36 +39,20 @@ public:
     prime(root, {}, version, keywords);
   }
 
-  detail::OnBlockExit suppress(detail::Anchor const & anchor) {
-    if (auto it = active_dynamic_anchors_.find(anchor); it != active_dynamic_anchors_.end()) {
-      detail::Reference where = it->second;
-      active_dynamic_anchors_.erase(it);
-      return [this, anchor, where]() { active_dynamic_anchors_[anchor] = where; };
-    }
-    return nullptr;
-  }
-
-  auto scoped_activate(detail::Reference ref) {
-    URI const uri = references_.relative_to_nearest_anchor(ref).uri();
-    activate_dynamic(uri);
-    return [this, uri]() { deactivate_dynamic(uri); };
+  auto dynamic_scope(detail::Reference const & ref) {
+    URI const uri =
+        ref.pointer().empty() ? ref.uri() : references_.relative_to_nearest_anchor(ref).uri();
+    return active_dynamic_anchors_.scope(dynamic_anchors_[uri]);
   }
 
-  auto load(detail::Reference const & ref, detail::ParserContext<A> const & context)
-      -> std::pair<std::optional<A>, detail::OnBlockExit> {
-    auto scoped_schema = [this, &ref](A const & schema) {
-      activate_dynamic(ref.uri());
-      return std::make_pair(ref.pointer().walk(schema),
-                            [this, uri = ref.uri()] { deactivate_dynamic(uri); });
-    };
-
+  std::optional<A> load(detail::Reference const & ref, detail::ParserContext<A> const & context) {
     if (auto it = roots_.find(ref.root()); it != roots_.end()) {
-      return scoped_schema(it->second);
+      return ref.pointer().walk(it->second);
     }
 
     std::optional<A> external = external_.try_load(ref.uri());
     if (not external) {
-      return std::make_pair(std::nullopt, nullptr);
+      return std::nullopt;
     }
 
     // TODO(samjaffe): Change Versions if needed...
@@ -76,18 +61,18 @@ public:
 
     // May have a sub-id that we map to
     if (auto it = roots_.find(ref.root()); it != roots_.end()) {
-      return scoped_schema(it->second);
+      return ref.pointer().walk(it->second);
     }
 
     // Will get called if the external schema does not declare a root document id?
-    return scoped_schema(*external);
+    return ref.pointer().walk(*external);
   }
 
   detail::Reference canonicalize(detail::Reference const & ref, detail::Reference const & parent,
                                  detail::inout<bool> dynamic_reference) const {
-    if (auto it = active_dynamic_anchors_.find(ref.anchor()); it != active_dynamic_anchors_.end()) {
+    if (std::optional dynamic = active_dynamic_anchors_.lookup(ref.anchor())) {
       if (dynamic_reference) {
-        return it->second;
+        return *dynamic;
       }
       dynamic_reference = ref.uri().empty() && ref.pointer().empty();
     }
@@ -133,21 +118,6 @@ public:
   }
 
 private:
-  void activate_dynamic(URI const & uri) {
-    active_dynamic_anchors_.insert(dynamic_anchors_[uri].begin(), dynamic_anchors_[uri].end());
-  }
-
-  void deactivate_dynamic(URI const & uri) {
-    std::map<detail::Anchor, detail::Reference> const & candidates = dynamic_anchors_[uri];
-    for (auto it = active_dynamic_anchors_.begin(); it != active_dynamic_anchors_.end();) {
-      if (auto cit = candidates.find(it->first); it->second == cit->second) {
-        it = active_dynamic_anchors_.erase(it);
-      } else {
-        ++it;
-      }
-    }
-  }
-
   void prime(Adapter auto const & json, detail::Reference where, schema::Version version,
              Keywords const & keywords) {
     if (json.type() != adapter::Type::Object) {

+ 3 - 18
include/jvalidate/schema.h

@@ -238,8 +238,7 @@ private:
       return *cached;
     }
 
-    // Special case if the root-level document does not have an $id property
-    if (auto [root, scope] = context.ref.load(lexical, context); root.has_value()) {
+    if (std::optional root = context.ref.load(lexical, context)) {
       return fetch_schema(context.rebind(*root, lexical, dynamic));
     }
 
@@ -293,25 +292,11 @@ template <Adapter A>
 detail::OnBlockExit Node::resolve_anchor(detail::ParserContext<A> const & context) {
   auto const schema = context.schema.as_object();
 
-  if (schema.contains("$anchor") || context.version < schema::Version::Draft2019_09) {
+  if (context.version < schema::Version::Draft2019_09 || not schema.contains("$id")) {
     return nullptr;
   }
 
-  if (context.version > schema::Version::Draft2019_09) {
-    return context.ref.scoped_activate(context.where);
-  }
-
-  if (context.version != schema::Version::Draft2019_09) {
-    return nullptr;
-  }
-  if (schema.contains("$recursiveAnchor") && schema["$recursiveAnchor"].as_boolean()) {
-    return context.ref.scoped_activate(context.where);
-  }
-  if (not schema.contains("$id") && not schema.contains("$recursiveAnchor")) {
-    return nullptr;
-  }
-
-  return context.ref.suppress(detail::Anchor());
+  return context.ref.dynamic_scope(context.where);
 }
 
 template <Adapter A> bool Node::resolve_reference(detail::ParserContext<A> const & context) {