Przeglądaj źródła

fix: some issues w/ ValidationVisitor, add ValidationConfig

Sam Jaffe 1 rok temu
rodzic
commit
d383ec8eec

+ 5 - 1
include/jvalidate/forward.h

@@ -4,9 +4,10 @@
 #include <string>
 
 namespace jvalidate {
-class ValidationResult;
 class Schema;
 class Status;
+struct ValidationConfig;
+class ValidationResult;
 }
 
 namespace jvalidate::adapter {
@@ -114,6 +115,9 @@ concept Adapter = std::is_base_of_v<adapter::Adapter, A> && requires(A a) {
   { a.object_size() } -> std::convertible_to<size_t>;
 };
 
+template <typename A>
+concept MutableAdapter = Adapter<A> && false;
+
 template <typename R>
 concept RegexEngine = std::constructible_from<std::string> && requires(R const regex) {
   { regex.search("") } -> std::same_as<bool>;

+ 20 - 0
include/jvalidate/validation_config.h

@@ -0,0 +1,20 @@
+#pragma once
+
+#include <jvalidate/forward.h>
+
+namespace jvalidate {
+struct ValidationConfig {
+  // This property controls our willingness to engage in type coercion
+  // in order to label any two documents as equal to one another.
+  // If set to true, a best effort will be made to coerce the scalar type
+  // of documents to match the typing of their pairs.
+  // We'll also pretend that the empty array ([]) and empty object ({}) are
+  // equal to one another.
+  bool strict_equality = true;
+
+  // When enabled, if a property or item is "Missing", but the associated
+  // subschema in PropertiesConstraint or TupleConstraint has a default
+  // associated with it - then we construct that object into the document.
+  bool construct_default_values = false;
+};
+}

+ 22 - 11
include/jvalidate/validation_visitor.h

@@ -12,6 +12,7 @@
 #include <jvalidate/forward.h>
 #include <jvalidate/schema.h>
 #include <jvalidate/status.h>
+#include <jvalidate/validation_config.h>
 #include <jvalidate/validation_result.h>
 
 #define NOOP_UNLESS_TYPE(etype)                                                                    \
@@ -23,15 +24,19 @@ class ValidationVisitor : public constraint::ConstraintVisitor {
 private:
   A document_;
   detail::Pointer where_;
+
   schema::Node const & schema_;
+
   ValidationResult * result_;
   ValidationResult * local_result_;
+
+  ValidationConfig const & cfg_;
   std::unordered_map<std::string, RE> & regex_cache_;
 
 public:
-  ValidationVisitor(A const & json, schema::Node const & schema,
+  ValidationVisitor(A const & json, schema::Node const & schema, ValidationConfig const & cfg,
                     std::unordered_map<std::string, RE> & regex_cache, ValidationResult * result)
-      : ValidationVisitor(json, schema, {}, regex_cache, result) {}
+      : ValidationVisitor(json, schema, cfg, regex_cache, {}, result) {}
 
   Status visit(constraint::TypeConstraint const & cons) const {
     return cons.types.contains(document_.type());
@@ -42,9 +47,11 @@ public:
   }
 
   Status visit(constraint::EnumConstraint const & cons) const {
+    auto is_equal = [this](auto const & frozen) {
+      return document_.equals(frozen, cfg_.strict_equality);
+    };
     for (auto const & option : cons.enumeration) {
-      // TODO(samjaffe): Strictness
-      if (option->apply([this](auto const & frozen) { return document_.equals(frozen); })) {
+      if (option->apply(is_equal)) {
         return Status::Accept;
       }
     }
@@ -389,7 +396,7 @@ public:
     }
   }
 
-  Status validate() const {
+  Status validate() {
     if (schema_.rejects_all()) {
       return Status::Reject;
     }
@@ -421,14 +428,17 @@ public:
   }
 
 private:
-  ValidationVisitor(A const & json, detail::Pointer const & where, schema::Node const & schema,
-                    std::unordered_map<std::string, RE> & regex_cache, ValidationResult * result,
+  ValidationVisitor(A const & json, schema::Node const & schema, ValidationConfig const & cfg,
+                    std::unordered_map<std::string, RE> & regex_cache,
+                    detail::Pointer const & where, ValidationResult * result,
                     ValidationResult * local_result = nullptr)
-      : document_(json), where_(where), schema_(schema), regex_cache_(regex_cache), result_(result),
-        local_result_(local_result ?: result_) {}
+      : document_(json), where_(where), schema_(schema), cfg_(cfg), regex_cache_(regex_cache),
+        result_(result), local_result_(local_result ?: result_) {}
 
   Status validate_subschema(schema::Node const * subschema) const {
-    return CRTP(document_, *subschema, where_, regex_cache_, result_, local_result_).validate();
+    return ValidationVisitor(document_, *subschema, cfg_, regex_cache_, where_, result_,
+                             local_result_)
+        .validate();
   }
 
   template <typename K>
@@ -437,7 +447,8 @@ private:
     ValidationResult next;
     ValidationResult * pnext = result_ ? &next : nullptr;
 
-    auto status = CRTP(document, *subschema, where_ / key, regex_cache_, pnext).validate();
+    auto status =
+        ValidationVisitor(document, *subschema, cfg_, regex_cache_, where_ / key, pnext).validate();
     if (status != Status::Noop and local_result_) {
       local_result_->record(key);
     }

+ 13 - 3
include/jvalidate/validator.h

@@ -5,6 +5,7 @@
 
 #include <jvalidate/forward.h>
 #include <jvalidate/status.h>
+#include <jvalidate/validation_config.h>
 #include <jvalidate/validation_visitor.h>
 
 namespace jvalidate::detail {
@@ -22,14 +23,23 @@ namespace jvalidate {
 template <RegexEngine RE = detail::StdRegexEngine> class ValidatorT {
 private:
   schema::Node const & schema_;
+  ValidationConfig cfg_;
   std::unordered_map<std::string, RE> regex_cache_;
 
 public:
-  ValidatorT(schema::Node const & schema) : schema_(schema) {}
+  ValidatorT(schema::Node const & schema, ValidationConfig const & cfg = {})
+      : schema_(schema), cfg_(cfg) {}
 
   template <Adapter A>
-  Status validate(A const & json, ValidationResult const * result = nullptr) const {
-    return ValidationVisitor<A, RE>(json, schema_, regex_cache_, result).validate();
+  requires(not MutableAdapter<A>) Status
+      validate(A const & json, ValidationResult * result = nullptr) {
+    EXPECT_M(not cfg_.construct_default_values,
+             "Cannot perform mutations on an immutable JSON Adapter");
+    return ValidationVisitor<A, RE>(json, schema_, cfg_, regex_cache_, result).validate();
+  }
+
+  template <MutableAdapter A> Status validate(A const & json, ValidationResult * result = nullptr) {
+    return ValidationVisitor<A, RE>(json, schema_, cfg_, regex_cache_, result).validate();
   }
 };