4 Commits 684ea3fd55 ... f050c8693c

Author SHA1 Message Date
  Sam Jaffe f050c8693c refactor: move formatting helpers into detail/iostream.h 2 weeks ago
  Sam Jaffe bd0bdbe8a5 refactor: remove friending of ExtensionVisitor in favor of a better API 2 weeks ago
  Sam Jaffe c41337805c refactor: store root document in ValidationVisitor, so that ExtensionVisitor no longer needs to capture state 2 weeks ago
  Sam Jaffe 5080d27ddf fix: add concepts to detail/number.h 2 weeks ago

+ 27 - 0
include/jvalidate/detail/iostream.h

@@ -2,6 +2,7 @@
 
 #include <iostream>
 #include <set>
+#include <sstream>
 #include <unordered_set>
 
 #include <jvalidate/enum.h>
@@ -83,4 +84,30 @@ inline std::ostream & operator<<(std::ostream & os, std::unordered_set<T> const
   }
   return os << ' ' << ']';
 }
+
+template <typename S>
+  requires(std::is_constructible_v<std::string, S>)
+// Optimization to avoid running string-like objects through a
+// std::stringstream in fmtlist.
+static std::string to_string(S const & str) {
+  return std::string(str);
+}
+
+// Format va_args into a single string to annotate or mark an error message
+static std::string to_string(auto const &... args) {
+  std::stringstream ss;
+  using ::jvalidate::operator<<;
+  [[maybe_unused]] int _[] = {(ss << args, 0)...};
+  return ss.str();
+}
+
+// Format an iterable argument into a vector of strings to annotate or mark
+// an error.
+static std::vector<std::string> to_string_list(auto const & arg) {
+  std::vector<std::string> strs;
+  for (auto const & elem : arg) {
+    strs.push_back(::jvalidate::to_string(elem));
+  }
+  return strs;
+}
 }

+ 1 - 0
include/jvalidate/detail/number.h

@@ -10,6 +10,7 @@
 
 #include <charconv>
 #include <cmath>
+#include <concepts>
 #include <limits>
 #include <stdexcept>
 #include <string_view>

+ 1 - 1
include/jvalidate/forward.h

@@ -190,7 +190,7 @@ namespace jvalidate {
 template <Adapter A> class ConstraintFactory;
 template <Adapter A> class DocumentCache;
 
-template <RegexEngine RE, typename ExtensionVisitor> class ValidationVisitor;
+template <Adapter Root, RegexEngine RE, typename ExtensionVisitor> class ValidationVisitor;
 
 template <RegexEngine RE, typename ExtensionVisitor> class Validator;
 

+ 1 - 1
include/jvalidate/validation_result.h

@@ -13,7 +13,7 @@ namespace jvalidate {
 class ValidationResult {
 public:
   // Only allow ValidationVisitor to construct the elements of a validation result
-  template <RegexEngine, typename> friend class ValidationVisitor;
+  template <Adapter, RegexEngine, typename> friend class ValidationVisitor;
 
   using DocPointer = detail::Pointer;
   using SchemaPointer = detail::Pointer;

+ 59 - 45
include/jvalidate/validation_visitor.h

@@ -44,18 +44,29 @@
     }                                                                                              \
   } while (false)
 
+#define ANNOTATION_HELPER(name, ADD, FMT)                                                          \
+  void name(auto const &... args) const {                                                          \
+    if (not result_) {                                                                             \
+      /* do nothing if there's no result object to append to */                                    \
+    } else if (schema_path_.empty()) {                                                             \
+      result_->ADD(where_, schema_path_, "", FMT(args...));                                        \
+    } else {                                                                                       \
+      result_->ADD(where_, schema_path_.parent(), schema_path_.back(), FMT(args...));              \
+    }                                                                                              \
+  }
+
 namespace jvalidate {
-template <RegexEngine RE, typename ExtensionVisitor> class ValidationVisitor {
+template <Adapter Root, RegexEngine RE, typename ExtensionVisitor> class ValidationVisitor {
 private:
   JVALIDATE_TRIBOOL_TYPE(StoreResults, ForValid, ForInvalid, ForAnything);
   using VisitedAnnotation = std::tuple<std::unordered_set<size_t>, std::unordered_set<std::string>>;
-  friend ExtensionVisitor;
 
 private:
   detail::Pointer where_;
   detail::Pointer schema_path_;
 
   schema::Node const * schema_;
+  Root const * root_;
 
   ValidationResult * result_;
 
@@ -78,10 +89,12 @@ public:
    * @param[optional] result A cache of result/annotation info for the user to
    * 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) {}
+  ValidationVisitor(schema::Node const & schema, Root const & root, ValidationConfig const & cfg,
+                    RE & regex, ExtensionVisitor extension, ValidationResult * result)
+      : schema_(&schema), root_(&root), result_(result), cfg_(cfg), extension_(extension),
+        regex_(regex) {}
 
+private:
   Status visit(constraint::ExtensionConstraint const & cons, Adapter auto const & document) const {
     // Because we don't provide any contract constraint on our ExtensionVisitor,
     // we instead defer it to here where we validate that the extension can be
@@ -558,8 +571,8 @@ public:
     return rval;
   }
 
-  template <Adapter A>
-  Status visit(constraint::PropertyNamesConstraint const & cons, A const & document) const {
+  Status visit(constraint::PropertyNamesConstraint const & cons,
+               Adapter auto const & document) const {
     NOOP_UNLESS_TYPE(Object);
 
     Status rval = Status::Accept;
@@ -625,9 +638,12 @@ public:
     return rval;
   }
 
+public:
   /**
    * @brief The main entry point into the validator. Validates the provided
-   * document according to the schema.
+   * document according to the schema. This function should only be called
+   * internally (validate_subschema/validate_subschema_on) or via the Validator
+   * class.
    */
   Status validate(Adapter auto const & document) {
     // Step 1) Check if this is an always-false schema. Sometimes, this will
@@ -692,33 +708,40 @@ public:
     return rval;
   }
 
-private:
-  template <typename S>
-    requires(std::is_constructible_v<std::string, S>)
-  // Optimization to avoid running string-like objects through a
-  // std::stringstream in fmtlist.
-  static std::string fmt(S const & str) {
-    return std::string(str);
-  }
+  // Functions to grant access to some, but not all of the internals of the
+  // ValidationVisitor for the purposes of implementing ExtensionVisitor
+  // validators.
+  detail::Pointer const & where() const { return where_; }
+  Root const & root() const { return *root_; }
+  ValidationConfig const & config() const { return cfg_; }
+  RE & regex() const { return regex_; }
 
-  // Format va_args into a single string to annotate or mark an error message
-  static std::string fmt(auto const &... args) {
-    std::stringstream ss;
-    using ::jvalidate::operator<<;
-    [[maybe_unused]] int _[] = {(ss << args, 0)...};
-    return ss.str();
+  /**
+   * @brief Allow ExtensionVisitor to enter a state similar to a NotConstraint
+   * when calling sub-requests.
+   *
+   * @return A ScopedState object that will restore the tracking mode once it
+   * is destroyed.
+   */
+  [[nodiscard]] detail::ScopedState invert_tracking() const {
+    return detail::ScopedState(tracking_, !tracking_);
   }
 
-  // Format an iterable argument into a vector of strings to annotate or mark
-  // an error.
-  static std::vector<std::string> fmtlist(auto const & arg) {
-    std::vector<std::string> strs;
-    for (auto const & elem : arg) {
-      strs.push_back(fmt(elem));
-    }
-    return strs;
+  /**
+   * @brief Allow ExtensionVisitor to enable tracking of all results in child
+   * constraints.
+   *
+   * @return A ScopedState object that will restore the tracking mode once it
+   * is destroyed.
+   */
+  [[nodiscard]] detail::ScopedState track_everything() const {
+    return detail::ScopedState(tracking_, StoreResults::ForAnything);
   }
 
+  ANNOTATION_HELPER(error, error, jvalidate::to_string)
+  ANNOTATION_HELPER(annotate, annotate, jvalidate::to_string)
+  ANNOTATION_HELPER(annotate_list, annotate, jvalidate::to_string_list)
+
   bool should_annotate(Status stat) const {
     if (not result_) {
       return false;
@@ -733,21 +756,6 @@ private:
     }
   }
 
-#define ANNOTATION_HELPER(name, ADD, FMT)                                                          \
-  void name(auto const &... args) const {                                                          \
-    if (not result_) {                                                                             \
-      /* do nothing if there's no result object to append to */                                    \
-    } else if (schema_path_.empty()) {                                                             \
-      result_->ADD(where_, schema_path_, "", FMT(args...));                                        \
-    } else {                                                                                       \
-      result_->ADD(where_, schema_path_.parent(), schema_path_.back(), FMT(args...));              \
-    }                                                                                              \
-  }
-
-  ANNOTATION_HELPER(error, error, fmt)
-  ANNOTATION_HELPER(annotate, annotate, fmt)
-  ANNOTATION_HELPER(annotate_list, annotate, fmtlist)
-
   Status result(Status stat, auto const &... args) const {
     return (should_annotate(stat) ? error(args...) : void(), stat);
   }
@@ -845,3 +853,9 @@ private:
   }
 };
 }
+
+#undef ANNOTATION_HELPER
+#undef BREAK_EARLY_IF_NO_RESULT_TREE
+#undef NOOP_UNLESS_TYPE
+#undef VALIDATE_SUBSCHEMA_AND_MARK_LOCAL_VISIT
+#undef VISITED

+ 2 - 2
include/jvalidate/validator.h

@@ -69,7 +69,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_, json, cfg_, regex_, extension_, result).validate(json));
   }
 
   /**
@@ -89,7 +89,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_, json, cfg_, regex_, extension_, result).validate(json));
   }
 
   /**

+ 3 - 9
tests/extension_test.cxx

@@ -28,24 +28,18 @@ struct IsKeyOfConstraint : jvalidate::extension::ConstraintBase<IsKeyOfConstrain
   jvalidate::detail::RelativePointer ptr;
 };
 
-template <jvalidate::Adapter A>
-class Visitor : public jvalidate::extension::Visitor<Visitor<A>, IsKeyOfConstraint> {
+class Visitor : public jvalidate::extension::Visitor<Visitor, IsKeyOfConstraint> {
 public:
-  Visitor(A const & root_document) : root_document_(root_document) {}
-
   template <jvalidate::Adapter A2>
   Status visit(IsKeyOfConstraint const & cons, A2 const & document, auto const & validator) const {
     validator.annotate(cons.ptr);
     auto const & object =
-        std::get<1>(cons.ptr.inspect(validator.where_, root_document_)).as_object();
+        std::get<1>(cons.ptr.inspect(validator.where(), validator.root())).as_object();
     if (object.find(document.as_string()) != object.end()) {
       return Status::Accept;
     }
     return Status::Reject;
   }
-
-private:
-  A root_document_;
 };
 
 auto validate(Json::Value const & schema_doc, Json::Value const & instance_doc,
@@ -58,7 +52,7 @@ auto validate(Json::Value const & schema_doc, Json::Value const & instance_doc,
   jvalidate::Schema const schema(schema_doc, version, factory);
 
   jvalidate::ValidationResult result;
-  (void)jvalidate::Validator(schema, Visitor(A(instance_doc))).validate(instance_doc, &result);
+  (void)jvalidate::Validator(schema, Visitor()).validate(instance_doc, &result);
 
   return result;
 }