Selaa lähdekoodia

fix: handling for unimplemented* constraints

Sam Jaffe 1 vuosi sitten
vanhempi
commit
bed99df899
2 muutettua tiedostoa jossa 52 lisäystä ja 32 poistoa
  1. 0 3
      include/jvalidate/constraint.h
  2. 52 29
      include/jvalidate/validation_visitor.h

+ 0 - 3
include/jvalidate/constraint.h

@@ -197,9 +197,6 @@ public:
       else_ = context.neighbor("else").node();
     }
 
-    if (then_ == else_) {
-      return nullptr;
-    }
     return std::make_unique<constraint::ConditionalConstraint>(context.node(), then_, else_);
   }
 

+ 52 - 29
include/jvalidate/validation_visitor.h

@@ -1,5 +1,6 @@
 #pragma once
 
+#include <tuple>
 #include <unordered_map>
 
 #include <jvalidate/constraint/array_constraint.h>
@@ -17,7 +18,7 @@
 #include <jvalidate/validation_config.h>
 #include <jvalidate/validation_result.h>
 
-#define VISITED(type) std::get<std::unordered_set<type>>(visited_)
+#define VISITED(type) std::get<std::unordered_set<type>>(*visited_)
 
 #define NOOP_UNLESS_TYPE(etype) RETURN_UNLESS(adapter::Type::etype & document_.type(), Status::Noop)
 
@@ -31,24 +32,27 @@
 namespace jvalidate {
 template <Adapter A, RegexEngine RE>
 class ValidationVisitor : public constraint::ConstraintVisitor {
+private:
+  using VisitedAnnotation = std::tuple<std::unordered_set<size_t>, std::unordered_set<std::string>>;
+
 private:
   A document_;
   detail::Pointer where_;
   detail::Pointer schema_path_;
 
-  schema::Node const & schema_;
+  schema::Node const * schema_;
 
   ValidationResult * result_;
 
   ValidationConfig const & cfg_;
   std::unordered_map<std::string, RE> & regex_cache_;
 
-  mutable std::tuple<std::unordered_set<size_t>, std::unordered_set<std::string>> visited_;
+  mutable VisitedAnnotation * visited_ = nullptr;
 
 public:
   ValidationVisitor(A const & json, schema::Node const & schema, ValidationConfig const & cfg,
                     std::unordered_map<std::string, RE> & regex_cache, ValidationResult * result)
-      : ValidationVisitor(json, schema, cfg, regex_cache, {}, {}, result) {}
+      : document_(json), schema_(&schema), result_(result), cfg_(cfg), regex_cache_(regex_cache) {}
 
   Status visit(constraint::TypeConstraint const & cons) const {
     adapter::Type const type = document_.type();
@@ -120,7 +124,11 @@ public:
   }
 
   Status visit(constraint::NotConstraint const & cons) const {
-    return validate_subschema(cons.child, "") == Status::Reject;
+    VisitedAnnotation * suppress = nullptr;
+    std::swap(suppress, visited_);
+    auto rval = validate_subschema(cons.child, "") == Status::Reject;
+    std::swap(suppress, visited_);
+    return rval;
   }
 
   Status visit(constraint::ConditionalConstraint const & cons) const {
@@ -395,6 +403,9 @@ public:
 
   Status visit(constraint::UnevaluatedItemsConstraint const & cons) const {
     NOOP_UNLESS_TYPE(Array);
+    if (not visited_) {
+      return Status::Reject;
+    }
 
     Status rval = Status::Accept;
     auto array = document_.as_array();
@@ -410,6 +421,9 @@ public:
 
   Status visit(constraint::UnevaluatedPropertiesConstraint const & cons) const {
     NOOP_UNLESS_TYPE(Object);
+    if (not visited_) {
+      return Status::Reject;
+    }
 
     Status rval = Status::Accept;
     for (auto const & [key, elem] : document_.as_object()) {
@@ -423,29 +437,34 @@ public:
   }
 
   Status validate() {
-    if (auto const & reject = schema_.rejects_all()) {
+    if (auto const & reject = schema_->rejects_all()) {
       add_error(*reject);
       return Status::Reject;
     }
 
-    if (schema_.accepts_all()) {
+    if (schema_->accepts_all()) {
       // An accept-all schema is not No-Op for the purpose of unevaluated*
       return Status::Accept;
     }
 
+    VisitedAnnotation annotate;
+    if (schema_->requires_result_context() and not visited_) {
+      visited_ = &annotate;
+    }
+
     Status rval = Status::Noop;
-    if (auto ref = schema_.reference_schema()) {
+    if (auto ref = schema_->reference_schema()) {
       rval = validate_subschema(*ref, "$ref");
     }
 
     detail::Pointer const current_schema = schema_path_;
-    for (auto const & [key, p_constraint] : schema_.constraints()) {
+    for (auto const & [key, p_constraint] : schema_->constraints()) {
       BREAK_EARLY_IF_NO_RESULT_TREE();
       schema_path_ = current_schema / key;
       rval &= p_constraint->accept(*this);
     }
 
-    for (auto const & [key, p_constraint] : schema_.post_constraints()) {
+    for (auto const & [key, p_constraint] : schema_->post_constraints()) {
       BREAK_EARLY_IF_NO_RESULT_TREE();
       schema_path_ = current_schema / key;
       rval &= p_constraint->accept(*this);
@@ -469,38 +488,42 @@ private:
     to.insert(from.begin(), from.end());
   }
 
-  ValidationVisitor(A const & json, schema::Node const & schema, ValidationConfig const & cfg,
-                    std::unordered_map<std::string, RE> & regex_cache,
-                    detail::Pointer const & where, detail::Pointer const & schema_path,
-                    ValidationResult * result)
-      : document_(json), where_(where), schema_path_(schema_path), schema_(schema), cfg_(cfg),
-        regex_cache_(regex_cache), result_(result) {}
-
   template <typename K>
   Status validate_subschema(schema::Node const * subschema, K const & key) const {
-    EXPECT(subschema != &schema_); // TODO(samjaffe) - Figure out what's causing this infinite loop
-    ValidationVisitor next(document_, *subschema, cfg_, regex_cache_, where_, schema_path_ / key,
-                           result_);
+    EXPECT(subschema != schema_); // TODO(samjaffe) - Figure out what's causing this infinite loop
+
+    VisitedAnnotation annotate;
+
+    ValidationVisitor next = *this;
+    next.schema_path_ /= key;
+    std::tie(next.schema_, next.visited_) =
+        std::forward_as_tuple(subschema, visited_ ? &annotate : nullptr);
+
     Status rval = next.validate();
-    merge_visited(std::get<0>(visited_), std::get<0>(next.visited_));
-    merge_visited(std::get<1>(visited_), std::get<1>(next.visited_));
+
+    if (rval == Status::Accept and visited_) {
+      merge_visited(std::get<0>(*visited_), std::get<0>(annotate));
+      merge_visited(std::get<1>(*visited_), std::get<1>(annotate));
+    }
     return rval;
   }
 
   template <typename K>
   Status validate_subschema_on(schema::Node const * subschema, A const & document,
                                K const & key) const {
-    ValidationResult next;
-    ValidationResult * pnext = result_ ? &next : nullptr;
+    ValidationResult result;
+
+    ValidationVisitor next = *this;
+    next.where_ /= key;
+    std::tie(next.document_, next.schema_, next.result_, next.visited_) =
+        std::forward_as_tuple(document, subschema, result_ ? &result : nullptr, nullptr);
 
-    auto status = ValidationVisitor(document, *subschema, cfg_, regex_cache_, where_ / key,
-                                    schema_path_, pnext)
-                      .validate();
-    if (status != Status::Noop) {
+    auto status = next.validate();
+    if (status == Status::Accept and visited_) {
       VISITED(K).insert(key);
     }
     if (status == Status::Reject and result_) {
-      result_->add_error(std::move(next));
+      result_->add_error(std::move(result));
     }
     return status;
   }