瀏覽代碼

docs: add more comments

Sam Jaffe 3 周之前
父節點
當前提交
6d9d68d873

+ 1 - 1
include/jvalidate/compat/compare.h

@@ -1,6 +1,6 @@
 #pragma once
 
-#include <compare>
+#include <compare> // IWYU pragma: keep
 
 // Apple Clang does not properly support <=> in the STL - so we need to force it
 #if __cpp_lib_three_way_comparison < 201907L

+ 56 - 0
include/jvalidate/constraint/array_constraint.h

@@ -7,32 +7,88 @@
 #include <jvalidate/forward.h>
 
 namespace jvalidate::constraint {
+/**
+ * @brief A constraint on the Array type.
+ * Given an argument array, every element after applies_after_nth is
+ * validated against subschema. applies_after_nth is made to be used in
+ * conjunction with {@see TupleConstraint} and cannot be manually specified.
+ *
+ * https://json-schema.org/draft/2020-12/json-schema-core#section-10.3.1.2
+ */
 struct AdditionalItemsConstraint {
   schema::Node const * subschema;
   size_t applies_after_nth;
 };
 
+/**
+ * @brief A constraint on the Array type, that counts the number of elements
+ * in the input array that match subschema and compares that number against
+ * minimum and maximum. If minimum is unset, then it is equivalent to a minimum
+ * of 1. If maximum is unset, then it is equivalent to a maximum of INF. As an
+ * optimization, validation can be stopped after minimum matches if maximum is
+ * unset.
+ *
+ * https://json-schema.org/draft/2020-12/json-schema-core#section-10.3.1.3
+ * https://json-schema.org/draft/2020-12/json-schema-validation#section-6.4.4
+ * https://json-schema.org/draft/2020-12/json-schema-validation#section-6.4.5
+ */
 struct ContainsConstraint {
   schema::Node const * subschema;
   std::optional<size_t> minimum = std::nullopt;
   std::optional<size_t> maximum = std::nullopt;
 };
 
+/**
+ * @brief A constraint on the Array type with the following characteristic(s):
+ * @code{.cpp}
+ * arg.size() <= value
+ * @endcode
+ *
+ * https://json-schema.org/draft/2020-12/json-schema-validation#section-6.4.1
+ */
 struct MaxItemsConstraint {
   int64_t value;
 };
 
+/**
+ * @brief A constraint on the Array type with the following characteristic(s):
+ * @code{.cpp}
+ * arg.size() >= value
+ * @endcode
+ *
+ * https://json-schema.org/draft/2020-12/json-schema-validation#section-6.4.2
+ */
 struct MinItemsConstraint {
   int64_t value;
 };
 
+/**
+ * @brief A constraint on the Array type.
+ * Given an argument array, the Nth element of that Array is validated against
+ * the Nth element of items.
+ *
+ * https://json-schema.org/draft/2020-12/json-schema-core#section-10.3.1.1
+ */
 struct TupleConstraint {
   std::vector<schema::Node const *> items;
 };
 
+/**
+ * @brief A constraint on the Array type.
+ * Given an argument array, every element which is not evaluated by another
+ * schema {@see jvalidate::Status::Noop} will be validated against subschema.
+ *
+ * https://json-schema.org/draft/2020-12/json-schema-core#section-11.2
+ */
 struct UnevaluatedItemsConstraint {
   schema::Node const * subschema;
 };
 
+/**
+ * @brief A constraint on the Array type that affirms that each element
+ * of the array is unique.
+ *
+ * https://json-schema.org/draft/2020-12/json-schema-validation#section-6.4.3
+ */
 struct UniqueItemsConstraint {};
 }

+ 25 - 0
include/jvalidate/constraint/extension_constraint.h

@@ -6,6 +6,18 @@
 #include <jvalidate/status.h>
 
 namespace jvalidate::constraint {
+/**
+ * @brief A plugin class that allows the for Domain-Specific Grammers to be
+ * implemented for a json-schema.
+ *
+ * These custom Constraints are implemented by creating two extension classes:
+ * - A constraint inheriting from {@see jvalidate::ConstraintBase}, which in
+ *   turn inherits from ExtensionConstraint::Impl. This acts as a simple data
+ *   wrapper for describing the Constraint.
+ * - A visitor inheriting from {@see jvalidate::extension::Visitor}.
+ *
+ * https://json-schema.org/draft/2020-12/json-schema-core#section-6.5
+ */
 struct ExtensionConstraint {
 public:
   struct Impl {
@@ -14,6 +26,19 @@ public:
   };
 
 public:
+  /**
+   * @brief Convenience function for constructing this ExtensionConstraint from
+   * the concrete underlying extension in the format that is required for use
+   * with {@see jvalidate::ConstraintFactory}.
+   *
+   * @tparam T A concrete type subtyping ExtensionConstraint::Impl
+   * @tparam Args The argument types for initializing the object
+   *
+   * @params args... The constructor arguments for T
+   *
+   * @return A unique_ptr to a new ExtensionConstraint of underlying type T,
+   * wrapped in the Constraint variant type.
+   */
   template <typename T, typename... Args> static std::unique_ptr<Constraint> make(Args &&... args) {
     return std::make_unique<Constraint>(
         ExtensionConstraint{std::make_unique<T>(std::forward<Args>(args)...)});

+ 70 - 0
include/jvalidate/constraint/general_constraint.h

@@ -8,36 +8,106 @@
 #include <jvalidate/status.h>
 
 namespace jvalidate::constraint {
+/**
+ * @brief A constraint on any JSON document.
+ * Validates that the given document is validated by the EVERY one of the
+ * child schemas.
+ * @code{.py}
+ * all(c.validate(arg) for c in children)
+ * @endcode
+ *
+ * https://json-schema.org/draft/2020-12/json-schema-core#section-10.2.1.1
+ */
 struct AllOfConstraint {
   std::vector<SubConstraint> children;
 };
 
+/**
+ * @brief A constraint on any JSON document.
+ * Validates that the given document is validated by the AT LEAST one of the
+ * child schemas.
+ * @code{.py}
+ * any(c.validate(arg) for c in children)
+ * @endcode
+ *
+ * https://json-schema.org/draft/2020-12/json-schema-core#section-10.2.1.2
+ */
 struct AnyOfConstraint {
   std::vector<SubConstraint> children;
 };
 
+/**
+ * @brief A constraint on any JSON document.
+ * Validates that the input json is exactly the single stored value.
+ * Stored values can be simple scalars (bool, number, string, null), or
+ * complicated documents.
+ * @code{.py}
+ * arg == value
+ * @endcode
+ *
+ * https://json-schema.org/draft/2020-12/json-schema-validation#section-6.1.3
+ */
 struct ConstConstraint {
   std::unique_ptr<adapter::Const const> value;
 };
 
+/**
+ * @brief A constraint on any JSON document.
+ * Validates that the input json is one of the stored values.
+ * Stored values can be simple scalars (bool, number, string, null), or
+ * complicated documents.
+ * @code{.py}
+ * arg in enumeration
+ * @endcode
+ *
+ * https://json-schema.org/draft/2020-12/json-schema-validation#section-6.1.2
+ */
 struct EnumConstraint {
   std::vector<std::unique_ptr<adapter::Const const>> enumeration;
 };
 
+/**
+ * @brief A constraint on any JSON document.
+ * Validates that the given document is validated by the EXACTLY one of the
+ * child schemas.
+ *
+ * https://json-schema.org/draft/2020-12/json-schema-core#section-10.2.1.3
+ */
 struct OneOfConstraint {
   std::vector<schema::Node const *> children;
 };
 
+/**
+ * @brief A constraint on any JSON document.
+ * Validates the document against the if_constraint. If the constraint returns
+ * Success or Noop, then the document will be validated against the
+ * then_constraint. Otherwise, it will be validated against the else_constraint.
+ *
+ * https://json-schema.org/draft/2020-12/json-schema-core#section-10.2.2
+ */
 struct ConditionalConstraint {
   schema::Node const * if_constraint;
   schema::Node const * then_constraint;
   schema::Node const * else_constraint;
 };
 
+/**
+ * @brief A constraint on any JSON document.
+ * Validates that the given document is NOT validated by the child schema.
+ * A result of {@see jvalidate::Status::Noop} is kept as a Noop.
+ *
+ * https://json-schema.org/draft/2020-12/json-schema-core#section-10.2.1.4
+ */
 struct NotConstraint {
   SubConstraint child;
 };
 
+/**
+ * @brief A constraint on any JSON document.
+ * Validates that the type of the document is one of the given types.
+ *
+ * https://json-schema.org/draft/2020-12/json-schema-validation#section-6.1.1
+ */
 struct TypeConstraint {
   std::set<adapter::Type> types;
 };

+ 28 - 0
include/jvalidate/constraint/number_constraint.h

@@ -7,6 +7,16 @@
 #include <jvalidate/forward.h>
 
 namespace jvalidate::constraint {
+/**
+ * @brief A constraint on the Int and Double types, asserting that the given
+ * arg is less than the stored value. Equality is accepted or rejected based
+ * on exclusive being true or not.
+ * Unlike other constraint types, numeric constraints are trivial and thus
+ * are implemented directly in the constraint object.
+ *
+ * https://json-schema.org/draft/2020-12/json-schema-validation#section-6.2.2
+ * https://json-schema.org/draft/2020-12/json-schema-validation#section-6.2.3
+ */
 struct MaximumConstraint {
   double value;
   bool exclusive;
@@ -14,6 +24,16 @@ struct MaximumConstraint {
   bool operator()(double arg) const { return exclusive ? arg < value : arg <= value; }
 };
 
+/**
+ * @brief A constraint on the Int and Double types, asserting that the given
+ * arg is greater than the stored value. Equality is accepted or rejected based
+ * on exclusive being true or not.
+ * Unlike other constraint types, numeric constraints are trivial and thus
+ * are implemented directly in the constraint object.
+ *
+ * https://json-schema.org/draft/2020-12/json-schema-validation#section-6.2.4
+ * https://json-schema.org/draft/2020-12/json-schema-validation#section-6.2.5
+ */
 struct MinimumConstraint {
   double value;
   bool exclusive;
@@ -21,6 +41,14 @@ struct MinimumConstraint {
   bool operator()(double arg) const { return exclusive ? arg > value : arg >= value; }
 };
 
+/**
+ * @brief A constraint on the Int and Double types, asserting that the given
+ * arg is a multiple of the stored value.
+ * Unlike other constraint types, numeric constraints are trivial and thus
+ * are implemented directly in the constraint object.
+ *
+ * https://json-schema.org/draft/2020-12/json-schema-validation#section-6.2.1
+ */
 struct MultipleOfConstraint {
   double value;
 

+ 80 - 0
include/jvalidate/constraint/object_constraint.h

@@ -9,41 +9,121 @@
 #include <jvalidate/forward.h>
 
 namespace jvalidate::constraint {
+/**
+ * @brief A constraint on the Object type.
+ * Given an argument object, every element that is not subject to a
+ * {@see PropertiesConstraint} or {@see PatternPropertiesConstraint} in the
+ * same level of the Schema as this constraint is validated against subschema.
+ *
+ * https://json-schema.org/draft/2020-12/json-schema-core#section-10.3.2.3
+ */
 struct AdditionalPropertiesConstraint {
   schema::Node const * subschema;
   std::unordered_set<std::string> properties;
   std::vector<std::string> patterns;
 };
 
+/**
+ * @brief A constraint on the Object type representing two different
+ * constraints:
+ * - If a given property is present in the Object, then validate the overall
+ *   object against the subschema (dependentSchemas).
+ * - If a given property is present in the Object, then require that the
+ *   overall object also contains a list of properties (dependentRequired).
+ * Because the explicit separation into different keywords is part of
+ * Draft2020-12, the behaviors are merged together for legacy reasons.
+ *
+ * https://json-schema.org/draft/2020-12/json-schema-core#section-10.2.2.4
+ * https://json-schema.org/draft/2020-12/json-schema-validation#section-6.5.4
+ */
 struct DependenciesConstraint {
   std::map<std::string, schema::Node const *> subschemas;
   std::map<std::string, std::unordered_set<std::string>> required;
 };
 
+/**
+ * @brief A constraint on the Object type with the following characteristic(s):
+ * @code{.cpp}
+ * arg.size() <= value
+ * @endcode
+ *
+ * https://json-schema.org/draft/2020-12/json-schema-validation#section-6.5.1
+ */
 struct MaxPropertiesConstraint {
   int64_t value;
 };
 
+/**
+ * @brief A constraint on the Object type with the following characteristic(s):
+ * @code{.cpp}
+ * arg.size() >= value
+ * @endcode
+ *
+ * https://json-schema.org/draft/2020-12/json-schema-validation#section-6.5.2
+ */
 struct MinPropertiesConstraint {
   int64_t value;
 };
 
+/**
+ * @brief A constraint on the Object type.
+ * Validates properties whose names match a given regex against the subschema.
+ * Multiple patterns allowed to match to the same property, meaning that a
+ * property can be validated against more than one subschema.
+ *
+ * https://json-schema.org/draft/2020-12/json-schema-core#section-10.3.2.2
+ */
 struct PatternPropertiesConstraint {
   std::vector<std::pair<std::string, schema::Node const *>> properties;
 };
 
+/**
+ * @brief A constraint on the Object type.
+ * Validates named properties against their matching subschemas. Does not
+ * intrinsically require that the property be present in the Object without
+ * use of {@see RequiredConstraint}.
+ *
+ * https://json-schema.org/draft/2020-12/json-schema-core#section-10.3.2.1
+ */
 struct PropertiesConstraint {
   std::map<std::string, schema::Node const *> properties;
 };
 
+/**
+ * @brief A constraint on the individual keys of an Object type. Since each key
+ * is a string, key_schema should be a schema which validates against the
+ * String type.
+ * @code{.py}
+ * for key in arg.keys():
+ *     key_schema.validate(key)
+ * @endcode
+ *
+ * https://json-schema.org/draft/2020-12/json-schema-core#section-10.3.2.4
+ */
 struct PropertyNamesConstraint {
   schema::Node const * key_schema;
 };
 
+/**
+ * @brief A constraint on the Object type.
+ * Validates that the object contains all of the stored properties.
+ * @code{.py}
+ * all(p in arg for p in properties)
+ * @endcode
+ *
+ * https://json-schema.org/draft/2020-12/json-schema-validation#section-6.5.3
+ */
 struct RequiredConstraint {
   std::unordered_set<std::string> properties;
 };
 
+/**
+ * @brief A constraint on the Object type.
+ * Given an argument object, every property which is not evaluated by another
+ * schema {@see jvalidate::Status::Noop} will be validated against subschema.
+ *
+ * https://json-schema.org/draft/2020-12/json-schema-core#section-11.3
+ */
 struct UnevaluatedPropertiesConstraint {
   schema::Node const * subschema;
 };

+ 33 - 0
include/jvalidate/constraint/string_constraint.h

@@ -6,18 +6,51 @@
 #include <jvalidate/forward.h>
 
 namespace jvalidate::constraint {
+/**
+ * @brief A constraint on the String type with the following characteristic(s):
+ * @code{.cpp}
+ * arg.size() >= value
+ * @endcode
+ *
+ * https://json-schema.org/draft/2020-12/json-schema-validation#section-6.3.2
+ */
 struct MinLengthConstraint {
   int64_t value;
 };
 
+/**
+ * @brief A constraint on the String type with the following characteristic(s):
+ * @code{.cpp}
+ * arg.size() <= value
+ * @endcode
+ *
+ * https://json-schema.org/draft/2020-12/json-schema-validation#section-6.3.1
+ */
 struct MaxLengthConstraint {
   int64_t value;
 };
 
+/**
+ * @brief A constraint on the String type with the following characteristic(s):
+ * @code{.py}
+ * re.match(regex, arg)
+ * @endcode
+ *
+ * https://json-schema.org/draft/2020-12/json-schema-validation#section-6.3.3
+ */
 struct PatternConstraint {
   std::string regex;
 };
 
+/**
+ * @brief A constraint on the String type that describes a string format using
+ * a human friendly name. The implementation of the regular expression, ABNF
+ * grammer, or state-machine parser that checks the validity of the string
+ * (for the standard list of formats respected by the JSON schema RPC) is
+ * implemented in {@see include/jvalidate/format.h}.
+ *
+ * https://json-schema.org/draft/2020-12/json-schema-validation#section-7
+ */
 struct FormatConstraint {
   std::string format;
   bool is_assertion;

+ 33 - 1
include/jvalidate/detail/parser_context.h

@@ -1,6 +1,5 @@
 #pragma once
 
-#include <functional>
 #include <optional>
 
 #include <jvalidate/detail/reference.h>
@@ -22,19 +21,52 @@ template <Adapter A> struct ParserContext {
   Reference where = {};
   Reference dynamic_where = {};
 
+  /**
+   * @brief Obtain the ParserContext for an arbitrary schema location,
+   * preserving the general context members of the root Schema, Vocabulary,
+   * and ReferenceManager.
+   *
+   * @param new_schema The new schema JSON adapter
+   * @param new_loc The json pointer to this new_schema relative to its document
+   * root.
+   * @param new_dyn The json pointer to this new_schema using dynamic reference
+   * rules.
+   * @param parent The parent of this schema, if that parent is an Object.
+   */
   ParserContext rebind(A const & new_schema, Reference const & new_loc, Reference const & new_dyn,
                        std::optional<Object> parent = std::nullopt) const {
     return {root, new_schema, vocab, ref, parent, new_loc, new_dyn};
   }
 
+  /**
+   * @brief Obtain the ParserContext for the child schema of the current
+   * location. Will set the parent context, which is used by some constraints
+   * like "contains" or "if".
+   *
+   * @param child The new child schema
+   * @param key The object-key to the new schema
+   */
   ParserContext child(A const & child, std::string const & key) const {
     return rebind(child, where / key, dynamic_where / key, schema.as_object());
   }
 
+  /**
+   * @brief Obtain the ParserContext for the child schema of the current
+   * location. Will clear the parent schema context.
+   *
+   * @param child The new child schema
+   * @param key The array-index to the new schema
+   */
   ParserContext child(A const & child, size_t index) const {
     return rebind(child, where / index, dynamic_where / index);
   }
 
+  /**
+   * @brief Obtain the ParserContext for the a child schema with the same parent
+   * as this context. Called when generating {@see ConditionalConstraint}.
+   *
+   * @param key The object-key to the new schema
+   */
   ParserContext neighbor(std::string const & key) const {
     return rebind((*parent)[key], where.parent() / key, dynamic_where.parent() / key, parent);
   }

+ 31 - 2
include/jvalidate/detail/relative_pointer.h

@@ -10,14 +10,29 @@
 #include <jvalidate/forward.h>
 
 namespace jvalidate::detail {
+/**
+ * @brief A special form of json pointer that is allowed to specify moving up
+ * into the parent scope of the current location.
+ * A relative pointer has two forms:
+ * - Nth parent-key, which takes the ABNF form:
+ *   non-negative-integer "#"
+ * - Nth parent followed by a JSON-Pointer as specified by RFC6901
+ *
+ * This does not comply with array neighbor offsets as described in
+ * Section 4 Paragraph 3 (the "index-manipulation" ABNF).
+ *
+ * https://json-schema.org/draft/2020-12/relative-json-pointer
+ * https://datatracker.ietf.org/doc/html/rfc6901
+ */
 class RelativePointer {
 public:
   RelativePointer(std::string_view path) {
-    if (path == "0") {
+    if (path == "0") { // A literal RelativePointer of "0" simply means "here"
       return;
     }
 
-    if (auto pos = path.find('/'); pos != path.npos) {
+    if (size_t pos = path.find('/'); pos != path.npos) {
+      // Handle the JSON-Pointer version
       pointer_ = Pointer(path.substr(pos));
       path.remove_suffix(path.size() - pos);
     } else if (path.ends_with('#')) {
@@ -26,9 +41,23 @@ public:
     }
 
     EXPECT_M(path == "0" || not path.starts_with("0"), "Cannot zero-prefix a relative pointer");
+    // Will throw an exception if path contains any non-numeric characters, or
+    // if path represents a number that is out of the bounds of a size_t.
     parent_steps_ = from_str<size_t>(path);
   }
 
+  /**
+   * @brief Acquire either the key of the nth parent or a JSON value at some "cousin"
+   * location in the overall document.
+   *
+   * @tparam A JSON Adapter type
+   *
+   * @param where The pointer representing the current location.
+   * @param root The root document location (accessed by where == Pointer())
+   *
+   * @return The evaluation result as described by
+   * https://json-schema.org/draft/2020-12/relative-json-pointer#rfc.section.4
+   */
   template <Adapter A>
   std::variant<std::string, A> inspect(Pointer const & where, A const & root) const {
     if (requests_key_) {

+ 66 - 0
include/jvalidate/extension.h

@@ -7,6 +7,10 @@
 #include <jvalidate/status.h>
 
 namespace jvalidate::extension {
+/**
+ * @brief A stub visitor type for building the ExtensionConstraint processing
+ * hierarchy. Exposed for use with {@see ConstraintBase::visit}.
+ */
 struct VisitorBase {
   virtual ~VisitorBase() = default;
 };
@@ -15,10 +19,44 @@ template <typename E>
 concept Constraint = std::is_base_of_v<constraint::ExtensionConstraint::Impl, E>;
 
 namespace detail {
+/**
+ * @brief A stub visitor type for proxying the ExtensionConstraint concrete
+ * implementation type into a visitor.
+ * Exists as a base class so that {@see jvalidate::extension::ConstraintBase}
+ * can cast a VisitorBase to this.
+ *
+ * The call graph (including abstract -> virtual resolution) is as follows:
+ * ExtensionConstraint::Impl::visit(VisitorBase const &)
+ * -> ConstraintBase<E>::visit(VisitorBase const &)
+ * ---> TypedVisitor<E>::visit(E const &)
+ * -----> TypedVisitorImpl<E, V>::visit(E const &)
+ * -------> Visitor::Impl::dispatch(E const &)
+ * ---------> Visitor::visit<Adapter, ValidationVisitor>(E const &, ...)
+ *
+ * @tparam E The ExtensionConstraint type that this visitor applies to
+ */
 template <Constraint E> struct TypedVisitor : VisitorBase {
   virtual Status visit(E const & cons) const = 0;
 };
 
+/**
+ * @brief The cleverness of this virtual class hierarchy.
+ * Connects {@see jvalidate::extension::Visitor::Impl} to one of the extension
+ * constraint types provided in its type signature.
+ *
+ * The call graph (including abstract -> virtual resolution) is as follows:
+ * ExtensionConstraint::Impl::visit(VisitorBase const &)
+ * -> ConstraintBase<E>::visit(VisitorBase const &)
+ * ---> TypedVisitor<E>::visit(E const &)
+ * -----> TypedVisitorImpl<E, V>::visit(E const &)
+ * -------> Visitor::Impl::dispatch(E const &)
+ * ---------> Visitor::visit<Adapter, ValidationVisitor>(E const &, ...)
+ *
+ * @tparam E The ExtensionConstraint type that this visitor applies to
+ * @tparam CRTP A "Curiously Recurring Template Pattern" class (the actually
+ * concrete class implementation of this Visitor).
+ * Must implement a function "dispatch" which can accept at least E.
+ */
 template <Constraint E, typename CRTP> struct TypedVisitorImpl : TypedVisitor<E> {
   Status visit(E const & cons) const final {
     return static_cast<CRTP const *>(this)->dispatch(cons);
@@ -26,6 +64,22 @@ template <Constraint E, typename CRTP> struct TypedVisitorImpl : TypedVisitor<E>
 };
 }
 
+/**
+ * @brief The preferred base class of all user-defined constraints. Using the
+ * "Curiously Recurring Template Pattern", it is able to unwrap RTTI (Run Time
+ * Type Information) through {@see TypedVisitor}.
+ *
+ * The call graph (including abstract -> virtual resolution) is as follows:
+ * ExtensionConstraint::Impl::visit(VisitorBase const &)
+ * -> ConstraintBase<E>::visit(VisitorBase const &)
+ * ---> TypedVisitor<E>::visit(E const &)
+ * -----> TypedVisitorImpl<E, V>::visit(E const &)
+ * -------> Visitor::Impl::dispatch(E const &)
+ * ---------> Visitor::visit<Adapter, ValidationVisitor>(E const &, ...)
+ *
+ * @tparam CRTP A "Curiously Recurring Template Pattern" class (the actually
+ * concrete class implementation of this ExtensionConstraint).
+ */
 template <typename CRTP> struct ConstraintBase : constraint::ExtensionConstraint::Impl {
   Status visit(VisitorBase const & visitor) const final {
     return dynamic_cast<detail::TypedVisitor<CRTP> const &>(visitor).visit(
@@ -33,6 +87,18 @@ template <typename CRTP> struct ConstraintBase : constraint::ExtensionConstraint
   }
 };
 
+/**
+ * @brief The visitor class responsible for performing validation on some list
+ * of ExtensionConstraints as desired by the user.
+ *
+ * @tparam CRTP A "Curiously Recurring Template Pattern" class (the actually
+ * concrete class implementation of this Visitor).
+ * This class must implement the visit function for each extension constraint
+ * in the Es... type list, as described by Visitor::Impl::dispatch.
+ *
+ * @tparam Es... A number of ExtensionConstraint implementations. Each of these
+ * must fulfill the {@see jvalidate::extension::Constraint} contract.
+ */
 template <typename CRTP, typename... Es> class Visitor {
 private:
   template <Adapter A, typename V> class Impl : public detail::TypedVisitorImpl<Es, Impl<A, V>>... {

+ 6 - 0
include/jvalidate/status.h

@@ -3,5 +3,11 @@
 #include <jvalidate/detail/tribool.h>
 
 namespace jvalidate {
+/**
+ * @brief A tribool enumeration representing validation results.
+ * For the sake of running "unevaluatedProperties" and
+ * "unevaluatedItems" schemas, we need to be able to track which
+ * properties/items are not interacted with by other schemas rules.
+ */
 JVALIDATE_TRIBOOL_TYPE(Status, Accept, Reject, Noop);
 }

+ 56 - 19
include/jvalidate/validation_visitor.h

@@ -1,5 +1,6 @@
 #pragma once
 
+#include <algorithm>
 #include <tuple>
 #include <type_traits>
 #include <vector>
@@ -82,6 +83,11 @@ public:
       : schema_(&schema), result_(result), cfg_(cfg), extension_(extension), regex_(regex) {}
 
   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
+    // validated given the input document.
+    // This covers a case where we write the extension around a specific adapter
+    // instead of generically.
     if constexpr (std::is_invocable_r_v<Status, ExtensionVisitor, decltype(cons),
                                         decltype(document), ValidationVisitor const &>) {
       return extension_(cons, document, *this);
@@ -94,14 +100,19 @@ public:
     adapter::Type const type = document.type();
 
     for (adapter::Type const accept : cons.types) {
-      if (type == accept) {
+      if (type == accept) { // Simple case, types are equal
         return result(Status::Accept, type, " is in types [", cons.types, "]");
       }
       if (accept == adapter::Type::Number && type == adapter::Type::Integer) {
+        // Number is a super-type of Integer, therefore all Integer values are
+        // accepted by a `"type": "number"` schema.
         return result(Status::Accept, type, " is in types [", cons.types, "]");
       }
       if (accept == adapter::Type::Integer && type == adapter::Type::Number &&
           detail::is_json_integer(document.as_number())) {
+        // Since the JSON specification does not distinguish between Number
+        // and Integer, but JSON Schema does, we need to check that the number
+        // is a whole integer that is representable within the system (64-bit).
         return result(Status::Accept, type, " is in types [", cons.types, "]");
       }
     }
@@ -152,6 +163,9 @@ public:
     std::optional<size_t> first_validated;
     for (auto const & [index, subschema] : detail::enumerate(cons.children)) {
       if (validate_subschema(subschema, document, index)) {
+        // This technically will produce different results when we're tracking
+        // visited nodes, but in practice it doesn't actually matter which
+        // subschema index we record in the annotation.
         first_validated = index;
       }
       if (not visited_ && first_validated.has_value()) {
@@ -283,12 +297,15 @@ public:
   }
 
   Status visit(constraint::FormatConstraint const & cons, Adapter auto const & document) const {
-    // https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01#name-defined-formats
+    // https://json-schema.org/draft/2020-12/json-schema-validation#name-defined-formats
     NOOP_UNLESS_TYPE(String);
 
     annotate(cons.format);
     if (not cfg_.validate_format && not cons.is_assertion) {
-      return true;
+      // Don't both validating formats if we're not in assertion mode
+      // Assertion mode is specified either by using the appropriate "$vocab"
+      // meta-schema or by requesting it in the ValidationConfig.
+      return true; // TODO: I think this can be made into Noop
     }
 
     return result(Status::Reject, " is unimplemented");
@@ -374,6 +391,8 @@ public:
     NOOP_UNLESS_TYPE(Array);
 
     if constexpr (std::totally_ordered<A>) {
+      // If the adapter defines comparison operators, then it becomes possible
+      // to compute uniqueness in O(n*log(n)) checks.
       std::map<A, size_t> cache;
       for (auto const & [index, elem] : detail::enumerate(document.as_array())) {
         if (auto [it, created] = cache.emplace(elem, index); not created) {
@@ -381,6 +400,9 @@ public:
         }
       }
     } else {
+      // Otherwise, we need to run an O(n^2) triangular array search comparing
+      // equality for each element. This still guarantees that each element is
+      // compared against each other element no more than once.
       auto array = document.as_array();
       for (size_t i = 0; i < array.size(); ++i) {
         for (size_t j = i + 1; j < array.size(); ++j) {
@@ -399,12 +421,9 @@ public:
     NOOP_UNLESS_TYPE(Object);
 
     auto matches_any_pattern = [this, &cons](std::string const & key) {
-      for (auto & pattern : cons.patterns) {
-        if (regex_.search(pattern, key)) {
-          return true;
-        }
-      }
-      return false;
+      return std::ranges::any_of(cons.patterns, [this, &key](auto const & pattern) {
+        return regex_.search(pattern, key);
+      });
     };
 
     Status rval = Status::Accept;
@@ -499,6 +518,15 @@ public:
     auto object = document.as_object();
 
     if constexpr (MutableAdapter<A>) {
+      // Special Rule - if the adapter is of a mutable json document (wraps a
+      // non-const reference and exposes the assign function) we will process
+      // the "default" annotation will be applied.
+      // https://json-schema.org/draft/2020-12/json-schema-validation#section-9.2
+      //
+      // Although the JSON Schema draft only says the the default value ought be
+      // valid against the schema, this implementation will assure that it is
+      // valid against this PropertiesConstraint, and any other constraints that
+      // are run after this one.
       for (auto const & [key, subschema] : cons.properties) {
         auto const * default_value = subschema->default_value();
         if (default_value && not object.contains(key)) {
@@ -525,7 +553,6 @@ public:
 
     Status rval = Status::Accept;
     for (auto const & [key, _] : document.as_object()) {
-      // TODO(samjaffe): Should we prefer a std::string adapter like valijson?
       rval &=
           validate_subschema_on(cons.key_schema, detail::StringAdapter(key), std::string("$$key"));
     }
@@ -625,11 +652,8 @@ public:
     // constraints. This is enforced in the parsing of the schema, rather than
     // during validation {@see jvalidate::schema::Node::construct}.
     if (std::optional<schema::Node const *> ref = schema_->reference_schema()) {
-      if (schema_path_.empty() || schema_path_.back() != "$ref") {
-        rval = validate_subschema(*ref, document, "$ref");
-      } else {
-        rval = validate_subschema(*ref, document);
-      }
+      // TODO: Investigate why this seems to produce .../$ref/$ref pointers
+      rval = validate_subschema(*ref, document, "$ref");
     }
 
     if (result_ && !schema_->description().empty()) {
@@ -660,10 +684,13 @@ public:
 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 str;
   }
 
+  // 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<<;
@@ -671,6 +698,8 @@ private:
     return ss.str();
   }
 
+  // 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) {
@@ -757,6 +786,8 @@ private:
 
     Status rval = next.validate(document);
 
+    // Only update the visited annotation of the current context if the
+    // subschema validates as Accepted.
     if (rval == Status::Accept and visited_) {
       std::get<0>(*visited_).merge(std::get<0>(annotate));
       std::get<1>(*visited_).merge(std::get<1>(annotate));
@@ -771,6 +802,8 @@ private:
    * @param subschema The subschema being validated.
    * @param document The child document being evaluated.
    * @param key The path to this document instance.
+   * @param schema_keys... The path to this subschema, relative to the current
+   * schema evaluation.
    *
    * @return The status of validating the current instance against the
    * subschema.
@@ -786,14 +819,18 @@ private:
     std::tie(next.schema_, next.result_, next.visited_) =
         std::forward_as_tuple(subschema, result_ ? &result : nullptr, nullptr);
 
-    auto status = next.validate(document);
-    if (status == Status::Accept and visited_) {
+    Status rval = next.validate(document);
+    // Only update the visited annotation of the current context if the
+    // subschema validates as Accepted.
+    if (rval == Status::Accept and visited_) {
       VISITED(K).insert(key);
     }
-    if ((status == Status::Reject or tracking_ == StoreResults::ForAnything) and result_) {
+    // Update the annotation/error content only if a failure is being reported,
+    // or if we are in an "if" schema.
+    if ((rval == Status::Reject or tracking_ == StoreResults::ForAnything) and result_) {
       result_->merge(std::move(result));
     }
-    return status;
+    return rval;
   }
 };
 }