浏览代码

fix: edge case for empty uri in dynanchor

Sam Jaffe 1 年之前
父节点
当前提交
786c262015
共有 2 个文件被更改,包括 38 次插入28 次删除
  1. 17 4
      include/jvalidate/detail/context_stack.h
  2. 21 24
      include/jvalidate/reference_handler.h

+ 17 - 4
include/jvalidate/detail/context_stack.h

@@ -7,16 +7,18 @@
 #include <jvalidate/detail/on_block_exit.h>
 
 namespace jvalidate::detail {
-template <typename Key, typename Value> class ContextStack {
+template <typename Source, typename Key, typename Value> class ContextStack {
 private:
+  std::deque<Source> sources_;
   std::map<Key, std::deque<std::optional<Value>>> data_;
 
 public:
-  OnBlockExit scope(std::map<Key, Value> const & frame) {
+  OnBlockExit scope(Source const & source, std::map<Key, Value> const & frame) {
     if (frame.empty() && data_.empty()) {
       return nullptr;
     }
 
+    sources_.push_back(source);
     for (auto const & [k, v] : frame) {
       data_[k].push_back(data_[k].empty() ? v : data_[k].front());
     }
@@ -28,23 +30,34 @@ public:
     }
 
     return [this]() {
+      sources_.pop_back();
       for (auto it = data_.begin(); it != data_.end();) {
         if (it->second.size() == 1) {
           it = data_.erase(it);
         } else {
           it->second.pop_back();
+          ++it;
         }
       }
     };
   }
 
-  std::optional<Value> lookup(Key const & key) const {
+  std::optional<Value> lookup(Source const & source, Key const & key) const {
     if (auto it = data_.find(key); it != data_.end()) {
-      return it->second.back();
+      return it->second.at(index_of(source));
     }
     return std::nullopt;
   }
 
+  size_t index_of(Source const & source) const {
+    for (size_t i = sources_.size(); i-- > 0;) {
+      if (sources_[i] == source) {
+        return i;
+      }
+    }
+    return sources_.size() - 1;
+  }
+
   bool empty() const { return data_.empty(); }
 };
 }

+ 21 - 24
include/jvalidate/reference_handler.h

@@ -29,7 +29,7 @@ private:
 
   std::map<detail::RootReference, A> roots_;
 
-  detail::ContextStack<detail::Anchor, detail::Reference> active_dynamic_anchors_;
+  detail::ContextStack<URI, detail::Anchor, detail::Reference> active_dynamic_anchors_;
   std::map<URI, std::map<detail::Anchor, detail::Reference>> dynamic_anchors_;
 
 public:
@@ -42,7 +42,7 @@ public:
   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]);
+    return active_dynamic_anchors_.scope(uri, dynamic_anchors_[uri]);
   }
 
   std::optional<A> load(detail::Reference const & ref, detail::ParserContext<A> const & context) {
@@ -70,24 +70,7 @@ public:
 
   detail::Reference canonicalize(detail::Reference const & ref, detail::Reference const & parent,
                                  detail::inout<bool> dynamic_reference) const {
-    if (std::optional dynamic = active_dynamic_anchors_.lookup(ref.anchor())) {
-      if (dynamic_reference) {
-        return *dynamic;
-      }
-      dynamic_reference = ref.uri().empty() && ref.pointer().empty();
-    }
-
-    if (active_dynamic_anchors_.empty()) {
-      dynamic_reference = true;
-    }
-
-    // Relative URI, not in the HEREDOC (or we set an $id)
-    if (ref.uri().empty() and ref.anchor().empty()) {
-      return detail::Reference(references_.relative_to_nearest_anchor(parent).root(),
-                               ref.pointer());
-    }
-
-    URI uri = [this, &ref, &parent]() {
+    URI const uri = [this, &ref, &parent]() {
       if (ref.uri().empty() && parent.uri().empty()) {
         return references_.actual_parent_uri(parent);
       }
@@ -110,11 +93,25 @@ public:
       return base.parent() / uri;
     }();
 
-    detail::Reference rval(uri, ref.anchor(), ref.pointer());
+    URI const dyn_uri = ref.uri().empty() ? ref.uri() : uri;
+    if (std::optional dynamic = active_dynamic_anchors_.lookup(dyn_uri, ref.anchor())) {
+      if (dynamic_reference) {
+        return *dynamic;
+      }
+      dynamic_reference = ref.uri().empty() && ref.pointer().empty();
+    }
+
+    if (active_dynamic_anchors_.empty()) {
+      dynamic_reference = true;
+    }
+
+    // Relative URI, not in the HEREDOC (or we set an $id)
+    if (ref.uri().empty() and ref.anchor().empty()) {
+      return detail::Reference(references_.relative_to_nearest_anchor(parent).root(),
+                               ref.pointer());
+    }
 
-    // Will now need to go make an external fetch...
-    // TODO(samjaffe): Make that process internal?
-    return rval;
+    return detail::Reference(uri, ref.anchor(), ref.pointer());
   }
 
 private: