浏览代码

refactor: make less clever extension handling

Sam Jaffe 1 年之前
父节点
当前提交
4577447d89

+ 6 - 7
include/jvalidate/constraint/constraint.h

@@ -1,6 +1,7 @@
 #pragma once
 
 #include <jvalidate/constraint/visitor.h>
+#include <jvalidate/detail/pointer.h>
 #include <jvalidate/enum.h>
 #include <jvalidate/forward.h>
 
@@ -18,13 +19,11 @@ public:
   }
 };
 
-template <typename CRTP> class ExtensionConstraint : public Constraint {
+class ExtensionConstraint : public Constraint {
 public:
-  Status accept(ConstraintVisitor const & visitor) const final {
-    if (auto * ptr = dynamic_cast<ExtensionConstraintVisitor<CRTP> const *>(&visitor)) {
-      return ptr->visit(*static_cast<CRTP const *>(this));
-    }
-    return Status::Noop;
-  }
+  Status accept(ConstraintVisitor const & visitor) const final { return visitor.visit(*this); }
+
+  virtual Status validate(adapter::Adapter const & json, detail::Pointer const & where,
+                          Result * result) const = 0;
 };
 }

+ 2 - 0
include/jvalidate/constraint/visitor.h

@@ -6,6 +6,8 @@ namespace jvalidate::constraint {
 struct ConstraintVisitor {
   virtual ~ConstraintVisitor() = default;
 
+  virtual Status visit(ExtensionConstraint const &) const = 0;
+
   virtual Status visit(TypeConstraint const &) const = 0;
   virtual Status visit(EnumConstraint const &) const = 0;
   virtual Status visit(AllOfConstraint const &) const = 0;

+ 22 - 23
include/jvalidate/forward.h

@@ -27,40 +27,41 @@ namespace jvalidate::constraint {
 class ConstraintVisitor;
 class Constraint;
 template <typename> class SimpleConstraint;
-template <typename> class ExtensionConstraint;
+class ExtensionConstraint;
 
-template <typename T>
-concept Extension = std::is_base_of_v<ExtensionConstraint<T>, T>;
-
-class AdditionalItemsConstraint;
-class AdditionalPropertiesConstraint;
+class TypeConstraint;
+class EnumConstraint;
 class AllOfConstraint;
 class AnyOfConstraint;
+class OneOfConstraint;
+class NotConstraint;
 class ConditionalConstraint;
-class ContainsConstraint;
-class DependenciesConstraint;
-class EnumConstraint;
-class MaxItemsConstraint;
-class MaxLengthConstraint;
-class MaxPropertiesConstraint;
+
 class MaximumConstraint;
-class MinItemsConstraint;
-class MinLengthConstraint;
-class MinPropertiesConstraint;
 class MinimumConstraint;
 class MultipleOfConstraint;
-class NotConstraint;
-class OneOfConstraint;
+
+class MaxLengthConstraint;
+class MinLengthConstraint;
 class PatternConstraint;
+
+class AdditionalItemsConstraint;
+class ContainsConstraint;
+class MaxItemsConstraint;
+class MinItemsConstraint;
+class TupleConstraint;
+class UnevaluatedItemsConstraint;
+class UniqueItemsConstraint;
+
+class AdditionalPropertiesConstraint;
+class DependenciesConstraint;
+class MaxPropertiesConstraint;
+class MinPropertiesConstraint;
 class PatternPropertiesConstraint;
 class PropertiesConstraint;
 class PropertyNamesConstraint;
 class RequiredConstraint;
-class TupleConstraint;
-class TypeConstraint;
-class UnevaluatedItemsConstraint;
 class UnevaluatedPropertiesConstraint;
-class UniqueItemsConstraint;
 }
 
 namespace jvalidate::schema {
@@ -120,8 +121,6 @@ concept RegexEngine = std::constructible_from<std::string> && requires(R const r
 }
 
 namespace jvalidate {
-template <Adapter A, RegexEngine RE, typename CRTP, constraint::Extension... Extensions>
-class ValidationVisitorBase;
 template <Adapter A, RegexEngine RE> class ValidationVisitor;
 
 class Validator;

+ 36 - 28
include/jvalidate/validation_visitor.h

@@ -18,24 +18,28 @@
   RETURN_UNLESS(document_.type() == adapter::Type::etype, Status::Noop)
 
 namespace jvalidate {
-template <Adapter A, RegexEngine RE, typename CRTP, constraint::Extension... Extensions>
-class ValidationVisitorBase : public constraint::ConstraintVisitor,
-                              public constraint::ExtensionConstraintVisitor<Extensions>... {
+template <Adapter A, RegexEngine RE>
+class ValidationVisitor : public constraint::ConstraintVisitor {
 private:
   A document_;
+  detail::Pointer where_;
   schema::Node const & schema_;
   Result * result_;
   std::unordered_map<std::string, RE> & regex_cache_;
 
 public:
-  ValidationVisitorBase(A const & json, schema::Node const & schema,
-                        std::unordered_map<std::string, RE> & regex_cache, Result * result)
-      : document_(json), schema_(schema), result_(result) {}
+  ValidationVisitor(A const & json, schema::Node const & schema,
+                    std::unordered_map<std::string, RE> & regex_cache, Result * result)
+      : ValidationVisitor(json, schema, {}, regex_cache, result) {}
 
   Status visit(constraint::TypeConstraint const & cons) const {
     return cons.types.contains(document_.type());
   }
 
+  Status visit(constraint::ExtensionConstraint const & cons) const {
+    return cons.validate(document_, where_, result_);
+  }
+
   Status visit(constraint::EnumConstraint const & cons) const {
     for (auto const & option : cons.enumeration) {
       // TODO(samjaffe) Strictness
@@ -142,7 +146,7 @@ public:
 
     Status rval = Status::Accept;
     for (size_t i = cons.applies_after_nth; i < array.size(); ++i) {
-      rval &= validate_subschema_on(cons.subschema, array[i]);
+      rval &= validate_subschema_on(cons.subschema, array[i], i);
       if (!rval && result_ == nullptr) {
         break;
       }
@@ -158,8 +162,8 @@ public:
     size_t const minimum = cons.minimum.value_or(1);
     size_t const maximum = cons.maximum.value_or(array.size());
     size_t matches = 0;
-    for (A const & elem : array) {
-      if (validate_subschema_on(cons.subschema, elem)) {
+    for (size_t i = 0; i < array.size(); ++i) {
+      if (validate_subschema_on(cons.subschema, array[i], i)) {
         ++matches;
       }
     }
@@ -193,7 +197,7 @@ public:
 
     Status rval = Status::Accept;
     for (size_t i = 0; i < cons.items.size(); ++i) {
-      rval &= validate_subschema_on(cons.items[i], array[i]);
+      rval &= validate_subschema_on(cons.items[i], array[i], i);
       if (!rval && result_ == nullptr) {
         break;
       }
@@ -242,7 +246,7 @@ public:
     Status rval = Status::Accept;
     for (auto const & [key, elem] : document_.as_object()) {
       if (not cons.properties.contains(key) && not matches_any_pattern(key)) {
-        rval &= validate_subschema_on(cons.subschema, elem);
+        rval &= validate_subschema_on(cons.subschema, elem, key);
       }
       if (!rval && result_ == nullptr) {
         break;
@@ -304,7 +308,7 @@ public:
       RE const & regex = regex_cache_.try_emplace(pattern, pattern).first->second;
       for (auto const & [key, elem] : document_.as_object()) {
         if (regex.search(key)) {
-          rval &= validate_subschema_on(subschema, elem);
+          rval &= validate_subschema_on(subschema, elem, key);
         }
         if (!rval && result_ == nullptr) {
           break;
@@ -321,7 +325,7 @@ public:
     Status rval = Status::Accept;
     for (auto const & [key, elem] : document_.as_object()) {
       if (auto it = cons.properties.find(key); it != cons.properties.end()) {
-        rval &= validate_subschema_on(it->second, elem);
+        rval &= validate_subschema_on(it->second, elem, key);
       }
       if (!rval && result_ == nullptr) {
         break;
@@ -338,7 +342,7 @@ public:
     for (auto const & [key, _] : document_.as_object()) {
       // TODO(samjaffe): Should we prefer a std::string adapter like valijson?
       typename A::value_type key_json{key};
-      rval &= validate_subschema_on(cons.key_schema, A(key_json));
+      rval &= validate_subschema_on(cons.key_schema, A(key_json), "$$key");
     }
   }
 
@@ -360,7 +364,7 @@ public:
     auto array = document_.as_array();
     for (size_t i = 0; i < array.size(); ++i) {
       if (not result_->visited_items.contains(i)) {
-        rval &= validate_subschema_for(cons.subschema, array[i]);
+        rval &= validate_subschema_on(cons.subschema, array[i], i);
       }
       // TODO(samjaffe): Special Rule
       if (!rval && result_ == nullptr) {
@@ -375,7 +379,7 @@ public:
     Status rval = Status::Accept;
     for (auto const & [key, elem] : document_.as_object()) {
       if (not result_->visited_properties.contains(key)) {
-        rval &= validate_subschema_for(cons.subschema, elem);
+        rval &= validate_subschema_on(cons.subschema, elem, key);
       }
       // TODO(samjaffe): Special Rule
       if (!rval && result_ == nullptr) {
@@ -415,22 +419,26 @@ public:
   }
 
 private:
-  Status validate(A const & document) const { return validate_subschema_on(&schema_, document_); }
+  ValidationVisitor(A const & json, detail::Pointer const & where, schema::Node const & schema,
+                    std::unordered_map<std::string, RE> & regex_cache, Result * result)
+      : document_(json), where_(where), schema_(schema), regex_cache_(regex_cache),
+        result_(result) {}
 
-  Status validate_subschema(schema::Node const * subschema) const {
-    return validate_subschema_on(subschema, document_);
+  template <typename K> Status validate(A const & document, K const & key) const {
+    return validate_subschema_on(&schema_, document_, key);
   }
 
-  Status validate_subschema_on(schema::Node const * subschema, A const & document) const {
-    // TODO(samjaffe): Result implementation
-    Result * pnext = nullptr;
-    return CRTP(document, *subschema, regex_cache_, pnext).validate();
+  Status validate_subschema(schema::Node const * subschema) const {
+    // TODO(samjaffe): Propagation for Unevaluated*Constraint
+    return CRTP(document_, *subschema, where_, regex_cache_, result_).validate();
   }
-};
 
-template <Adapter A, RegexEngine RE>
-class ValidationVisitor final : public ValidationVisitorBase<A, RE, ValidationVisitor<A, RE>> {
-public:
-  using ValidationVisitor::ValidationVisitorBase::ValidationVisitorBase;
+  template <typename K>
+  Status validate_subschema_on(schema::Node const * subschema, A const & document,
+                               K const & key) const {
+    Result next;
+    Result * pnext = result_ ? &next : nullptr;
+    return CRTP(document, *subschema, where_ / key, regex_cache_, pnext).validate();
+  }
 };
 }