Prechádzať zdrojové kódy

refactor: move construction of FormatValidator into Validator, allowing the user to provide user-defined-format codes

Sam Jaffe 2 týždňov pred
rodič
commit
436f35a923

+ 9 - 3
include/jvalidate/format.h

@@ -1,5 +1,6 @@
 #pragma once
 #include <cstdio>
+#include <functional>
 #include <jvalidate/_macro.h>
 
 #include <cctype>
@@ -703,7 +704,9 @@ template <auto Predicate> bool utf32(std::string_view str) {
 namespace jvalidate {
 class FormatValidator {
 public:
-  using Predicate = bool (*)(std::string_view);
+  using StatelessPredicate = bool (*)(std::string_view);
+  using Predicate = std::function<bool(std::string_view)>;
+  using UserDefinedFormats = std::unordered_map<std::string, Predicate>;
   enum class Status { Unknown, Unimplemented, Valid, Invalid };
 
 private:
@@ -712,7 +715,7 @@ private:
   // we're putting it here. It simply reduces the number of LoC when setting up.
   std::unordered_map<std::string, Predicate> formats_{{"regex", nullptr}};
 
-  std::unordered_map<std::string, Predicate> builtin_formats_{
+  std::unordered_map<std::string, StatelessPredicate> builtin_formats_{
       {"date", &format::date},
       {"date-time", &format::date_time},
       {"duration", &format::duration},
@@ -733,7 +736,7 @@ private:
       {"uuid", &format::uuid},
   };
 
-  std::unordered_map<std::string, Predicate> draft03_formats_{
+  std::unordered_map<std::string, StatelessPredicate> draft03_formats_{
       {"date", &format::date},
       // One of the weird things about draft03 - date-time allows for timezone
       // and fraction-of-second in the argument, but time only allows hh:mm:ss.
@@ -753,6 +756,9 @@ private:
 public:
   FormatValidator() = default;
   FormatValidator(Predicate is_regex) { formats_.insert_or_assign("regex", is_regex); }
+  FormatValidator(UserDefinedFormats const & formats, Predicate is_regex) : formats_(formats) {
+    formats_.insert_or_assign("regex", is_regex);
+  }
 
   Status operator()(std::string const & format, schema::Version for_version,
                     std::string_view text) const {

+ 5 - 3
include/jvalidate/validation_visitor.h

@@ -63,6 +63,7 @@ private:
   ValidationConfig const & cfg_;
   ExtensionVisitor extension_;
   RE & regex_;
+  FormatValidator & format_;
 
   mutable VisitedAnnotation * visited_ = nullptr;
   mutable StoreResults tracking_ = StoreResults::ForInvalid;
@@ -80,8 +81,9 @@ public:
    * receive a detailed summary of why a document is supported/unsupported.
    */
   ValidationVisitor(schema::Node const & schema, ValidationConfig const & cfg, RE & regex,
-                    ExtensionVisitor extension, ValidationResult * result)
-      : schema_(&schema), result_(result), cfg_(cfg), extension_(extension), regex_(regex) {}
+                    FormatValidator & format, ExtensionVisitor extension, ValidationResult * result)
+      : schema_(&schema), result_(result), cfg_(cfg), extension_(extension), regex_(regex),
+        format_(format) {}
 
   Status visit(constraint::ExtensionConstraint const & cons, Adapter auto const & document) const {
     // Because we don't provide any contract constraint on our ExtensionVisitor,
@@ -320,7 +322,7 @@ public:
       return true; // TODO: I think this can be made into Noop
     }
 
-    switch (FormatValidator(&RE::is_regex)(cons.format, cons.for_version, document.as_string())) {
+    switch (format_(cons.format, cons.for_version, document.as_string())) {
     case FormatValidator::Status::Unimplemented:
       return result(Status::Reject, "unimplemented format '", cons.format, "'");
     case FormatValidator::Status::Invalid:

+ 55 - 5
include/jvalidate/validator.h

@@ -30,6 +30,7 @@ private:
   ValidationConfig cfg_;
   ExtensionVisitor extension_;
   RE regex_;
+  FormatValidator format_{RE::is_regex};
 
 public:
   /**
@@ -40,12 +41,61 @@ public:
    * @param cfg Any special (runtime) configuration rules being applied to the
    * validator.
    */
-  Validator(schema::Node const & schema, ExtensionVisitor extension = {},
+  Validator(schema::Node const & schema, ValidationConfig const & cfg = {})
+      : schema_(schema), cfg_(cfg) {}
+
+  /**
+   * @brief Construct a Validator
+   *
+   * @param schema The root schema being validated against. Must outlive this.
+   *
+   * @param extension An extension visitor for processing user-defined
+   * constraints
+   *
+   * @param cfg Any special (runtime) configuration rules being applied to the
+   * validator.
+   */
+  Validator(schema::Node const & schema, ExtensionVisitor extension,
             ValidationConfig const & cfg = {})
       : schema_(schema), cfg_(cfg), extension_(extension) {}
 
-  Validator(schema::Node const & schema, ValidationConfig const & cfg)
-      : schema_(schema), cfg_(cfg) {}
+  /**
+   * @brief Construct a Validator
+   *
+   * @param schema The root schema being validated against. Must outlive this.
+   *
+   * @param user_defined_formats A map of format-name to string validator for
+   * user-defined format tools.
+   *
+   * @param cfg Any special (runtime) configuration rules being applied to the
+   * validator. Because user_defined_formats is provided by this constructor,
+   * we default to validate_format:=true.
+   */
+  Validator(schema::Node const & schema,
+            FormatValidator::UserDefinedFormats const & user_defined_formats,
+            ValidationConfig const & cfg = {.validate_format = true})
+      : schema_(schema), cfg_(cfg), format_(user_defined_formats, RE::is_regex) {}
+
+  /**
+   * @brief Construct a Validator
+   *
+   * @param schema The root schema being validated against. Must outlive this.
+   *
+   * @param extension An extension visitor for processing user-defined
+   * constraints
+   *
+   * @param user_defined_formats A map of format-name to string validator for
+   * user-defined format tools.
+   *
+   * @param cfg Any special (runtime) configuration rules being applied to the
+   * validator. Because user_defined_formats is provided by this constructor,
+   * we default to validate_format:=true.
+   */
+  Validator(schema::Node const & schema, ExtensionVisitor extension,
+            FormatValidator::UserDefinedFormats const & user_defined_formats,
+            ValidationConfig const & cfg = {.validate_format = true})
+      : schema_(schema), cfg_(cfg), extension_(extension),
+        format_(user_defined_formats, RE::is_regex) {}
 
   template <typename... Args> Validator(schema::Node &&, Args &&...) = delete;
 
@@ -69,7 +119,7 @@ public:
              "Cannot perform mutations on an immutable JSON Adapter");
     detail::OnBlockExit _ = [&result, this]() { post_process(result); };
     return static_cast<bool>(
-        ValidationVisitor(schema_, cfg_, regex_, extension_, result).validate(json));
+        ValidationVisitor(schema_, cfg_, regex_, format_, extension_, result).validate(json));
   }
 
   /**
@@ -89,7 +139,7 @@ public:
   template <MutableAdapter A> bool validate(A const & json, ValidationResult * result = nullptr) {
     detail::OnBlockExit _ = [&result, this]() { post_process(result); };
     return static_cast<bool>(
-        ValidationVisitor(schema_, cfg_, regex_, extension_, result).validate(json));
+        ValidationVisitor(schema_, cfg_, regex_, format_, extension_, result).validate(json));
   }
 
   /**