Просмотр исходного кода

feat: implement (most of) the validation system

Sam Jaffe 1 год назад
Родитель
Сommit
d43ddfd53e

+ 5 - 4
include/jvalidate/adapter.h

@@ -9,6 +9,7 @@
 #include <jvalidate/detail/object_iterator.h>
 #include <jvalidate/enum.h>
 #include <jvalidate/forward.h>
+#include <jvalidate/status.h>
 
 namespace jvalidate::adapter {
 class Adapter {
@@ -23,14 +24,14 @@ public:
   virtual double as_number() const = 0;
   virtual std::string as_string() const = 0;
 
-  virtual bool apply_array(AdapterCallback const & cb) const = 0;
-  virtual bool apply_object(ObjectAdapterCallback const & cb) const = 0;
+  virtual Status apply_array(AdapterCallback const & cb) const = 0;
+  virtual Status apply_object(ObjectAdapterCallback const & cb) const = 0;
 };
 
 class Const {
 public:
   virtual ~Const() = default;
-  virtual bool apply(AdapterCallback const & cb) const = 0;
+  virtual Status apply(AdapterCallback const & cb) const = 0;
 };
 }
 
@@ -39,7 +40,7 @@ template <typename JSON> class GenericConst final : public Const {
 public:
   explicit GenericConst(JSON const & value) : value_(value) {}
 
-  bool apply(AdapterCallback const & cb) const {
+  Status apply(AdapterCallback const & cb) const {
     return cb(typename AdapterTraits<JSON>::ConstAdapter(value_));
   }
 

+ 10 - 7
include/jvalidate/constraint.h

@@ -86,6 +86,10 @@ private:
   };
 
 public:
+  bool is_post_constraint(std::string_view key) const {
+    return key == "unevaluatedItems" || key == "unevaluatedProperties";
+  }
+
   MakeConstraint operator()(std::string_view key, schema::Version version) const {
     if (auto it = constraints_.find(key); it != constraints_.end()) {
       return it->second;
@@ -257,7 +261,7 @@ public:
 
   static auto contains(ParserContext<A> const & context) {
     if (context.version < schema::Version::Draft2019_09) {
-      return std::make_unique<constraint::ContainsConstraint>(context.schema.freeze());
+      return std::make_unique<constraint::ContainsConstraint>(context.node());
     }
 
     std::optional<size_t> maximum;
@@ -269,8 +273,7 @@ public:
       minimum = (*context.parent)["minContains"].as_integer();
     }
 
-    return std::make_unique<constraint::ContainsConstraint>(context.schema.freeze(), minimum,
-                                                            maximum);
+    return std::make_unique<constraint::ContainsConstraint>(context.node(), minimum, maximum);
   }
 
   static auto minItems(ParserContext<A> const & context) {
@@ -409,14 +412,14 @@ public:
     EXPECT(context.schema.type() == adapter::Type::Object);
 
     std::map<std::string, schema::Node const *> schemas;
-    std::map<std::string, std::vector<std::string>> required;
+    std::map<std::string, std::unordered_set<std::string>> required;
     for (auto [prop, subschema] : context.schema.as_object()) {
       if (subschema.type() == adapter::Type::Object) {
         schemas.emplace(prop, context.child(subschema, prop).node());
       } else {
         for (auto key : subschema.as_array()) {
           EXPECT(key.type() == adapter::Type::String);
-          required[prop].push_back(key.as_string());
+          required[prop].insert(key.as_string());
         }
       }
     }
@@ -438,12 +441,12 @@ public:
   static auto dependentRequired(ParserContext<A> const & context) {
     EXPECT(context.schema.type() == adapter::Type::Object);
 
-    std::map<std::string, std::vector<std::string>> rval;
+    std::map<std::string, std::unordered_set<std::string>> rval;
     for (auto [prop, subschema] : context.schema.as_object()) {
       EXPECT(subschema.type() == adapter::Type::Array);
       for (auto key : subschema.as_array()) {
         EXPECT(key.type() == adapter::Type::String);
-        rval[prop].push_back(key.as_string());
+        rval[prop].insert(key.as_string());
       }
     }
 

+ 26 - 24
include/jvalidate/constraint/array_constraint.h

@@ -7,59 +7,61 @@
 #include <jvalidate/adapter.h>
 #include <jvalidate/constraint/constraint.h>
 #include <jvalidate/forward.h>
-#include <jvalidate/validator.h>
 
 namespace jvalidate::constraint {
-class AdditionalItemsConstraint : public Constraint {
-private:
-  schema::Node const * subschema_;
-  size_t applies_after_nth_;
+class AdditionalItemsConstraint : public SimpleConstraint<AdditionalItemsConstraint> {
+public:
+  schema::Node const * subschema;
+  size_t applies_after_nth;
 
 public:
   AdditionalItemsConstraint(schema::Node const * subschema, size_t applies_after_nth)
-      : subschema_(subschema), applies_after_nth_(applies_after_nth) {}
+      : subschema(subschema), applies_after_nth(applies_after_nth) {}
 };
 
-class ContainsConstraint : public Constraint {
-private:
-  std::unique_ptr<adapter::Const const> constant_;
-  std::optional<size_t> minimum_;
-  std::optional<size_t> maximum_;
+class ContainsConstraint : public SimpleConstraint<ContainsConstraint> {
+public:
+  schema::Node const * subschema;
+  std::optional<size_t> minimum;
+  std::optional<size_t> maximum;
 
 public:
-  ContainsConstraint(std::unique_ptr<adapter::Const const> && constant)
-      : constant_(std::move(constant)) {}
+  ContainsConstraint(schema::Node const * subschema) : subschema(subschema) {}
 
-  ContainsConstraint(std::unique_ptr<adapter::Const const> && constant,
-                     std::optional<size_t> minimum, std::optional<size_t> maximum)
-      : constant_(std::move(constant)), minimum_(minimum), maximum_(maximum) {}
+  ContainsConstraint(schema::Node const * subschema, std::optional<size_t> minimum,
+                     std::optional<size_t> maximum)
+      : subschema(subschema), minimum(minimum), maximum(maximum) {}
 };
 
-class MaxItemsConstraint : public Constraint {
+class MaxItemsConstraint : public SimpleConstraint<MaxItemsConstraint> {
 private:
   int64_t value_;
 
 public:
   MaxItemsConstraint(int64_t value) : value_(value) {}
+
+  bool operator()(ArrayAdapter auto const & arg) const { return arg.size() <= value_; }
 };
 
-class MinItemsConstraint : public Constraint {
+class MinItemsConstraint : public SimpleConstraint<MinItemsConstraint> {
 private:
   int64_t value_;
 
 public:
   MinItemsConstraint(int64_t value) : value_(value) {}
+
+  bool operator()(ArrayAdapter auto const & arg) const { return arg.size() >= value_; }
 };
 
-class TupleConstraint : public Constraint {
-private:
-  std::vector<schema::Node const *> items_;
+class TupleConstraint : public SimpleConstraint<TupleConstraint> {
+public:
+  std::vector<schema::Node const *> items;
 
 public:
-  TupleConstraint(std::vector<schema::Node const *> const & items) : items_(items) {}
+  TupleConstraint(std::vector<schema::Node const *> const & items) : items(items) {}
 };
 
-class UnevaluatedItemsConstraint : public Constraint {
+class UnevaluatedItemsConstraint : public SimpleConstraint<UnevaluatedItemsConstraint> {
 private:
   schema::Node const * subschema_;
 
@@ -67,7 +69,7 @@ public:
   UnevaluatedItemsConstraint(schema::Node const * subschema) : subschema_(subschema) {}
 };
 
-class UniqueItemsConstraint : public Constraint {
+class UniqueItemsConstraint : public SimpleConstraint<UniqueItemsConstraint> {
 public:
 };
 }

+ 20 - 0
include/jvalidate/constraint/constraint.h

@@ -1,10 +1,30 @@
 #pragma once
 
+#include <jvalidate/constraint/visitor.h>
+#include <jvalidate/enum.h>
 #include <jvalidate/forward.h>
 
 namespace jvalidate::constraint {
 class Constraint {
 public:
   virtual ~Constraint() = default;
+  virtual Status accept(ConstraintVisitor const & visitor) const = 0;
+};
+
+template <typename CRTP> class SimpleConstraint : public Constraint {
+public:
+  Status accept(ConstraintVisitor const & visitor) const final {
+    return visitor.visit(*static_cast<CRTP const *>(this));
+  }
+};
+
+template <typename CRTP> 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;
+  }
 };
 }

+ 33 - 33
include/jvalidate/constraint/general_constraint.h

@@ -8,70 +8,70 @@
 #include <jvalidate/forward.h>
 
 namespace jvalidate::constraint {
-class AllOfConstraint : public Constraint {
-private:
-  std::vector<schema::Node const *> children_;
+class AllOfConstraint : public SimpleConstraint<AllOfConstraint> {
+public:
+  std::vector<schema::Node const *> children;
 
 public:
-  AllOfConstraint(std::vector<schema::Node const *> const & children) : children_(children) {}
+  AllOfConstraint(std::vector<schema::Node const *> const & children) : children(children) {}
 };
 
-class AnyOfConstraint : public Constraint {
-private:
-  std::vector<schema::Node const *> children_;
+class AnyOfConstraint : public SimpleConstraint<AnyOfConstraint> {
+public:
+  std::vector<schema::Node const *> children;
 
 public:
-  AnyOfConstraint(std::vector<schema::Node const *> const & children) : children_(children) {}
+  AnyOfConstraint(std::vector<schema::Node const *> const & children) : children(children) {}
 };
 
-class EnumConstraint : public Constraint {
-private:
-  std::vector<std::unique_ptr<adapter::Const const>> enums_;
+class EnumConstraint : public SimpleConstraint<EnumConstraint> {
+public:
+  std::vector<std::unique_ptr<adapter::Const const>> enumeration;
 
 public:
   EnumConstraint(std::unique_ptr<adapter::Const const> && constant) {
-    enums_.push_back(std::move(constant));
+    enumeration.push_back(std::move(constant));
   }
 
   EnumConstraint(std::vector<std::unique_ptr<adapter::Const const>> && enums)
-      : enums_(std::move(enums)) {}
+      : enumeration(std::move(enums)) {}
 };
 
-class OneOfConstraint : public Constraint {
-private:
-  std::vector<schema::Node const *> children_;
+class OneOfConstraint : public SimpleConstraint<OneOfConstraint> {
+public:
+  std::vector<schema::Node const *> children;
 
 public:
-  OneOfConstraint(std::vector<schema::Node const *> const & children) : children_(children) {}
+  OneOfConstraint(std::vector<schema::Node const *> const & children) : children(children) {}
 };
 
-class ConditionalConstraint : public Constraint {
-private:
-  schema::Node const * if_constraint_;
-  schema::Node const * then_constraint_;
-  schema::Node const * else_constraint_;
+class ConditionalConstraint : public SimpleConstraint<ConditionalConstraint> {
+public:
+  schema::Node const * if_constraint;
+  schema::Node const * then_constraint;
+  schema::Node const * else_constraint;
 
 public:
   ConditionalConstraint(schema::Node const * if_constraint, schema::Node const * then_constraint,
                         schema::Node const * else_constraint)
-      : if_constraint_(if_constraint), then_constraint_(then_constraint),
-        else_constraint_(else_constraint) {}
+      : if_constraint(if_constraint), then_constraint(then_constraint),
+        else_constraint(else_constraint) {}
 };
 
-class NotConstraint : public Constraint {
-private:
-  schema::Node const * child_;
+class NotConstraint : public SimpleConstraint<NotConstraint> {
+public:
+  schema::Node const * child;
 
 public:
-  NotConstraint(schema::Node const * child) : child_(child) {}
+  NotConstraint(schema::Node const * child) : child(child) {}
 };
 
-class TypeConstraint : public Constraint {
-private:
-  std::set<adapter::Type> types_;
+class TypeConstraint : public SimpleConstraint<TypeConstraint> {
+public:
+  std::set<adapter::Type> types;
 
 public:
-  TypeConstraint(adapter::Type type) : types_{type} {}
-  TypeConstraint(std::set<adapter::Type> const & types) : types_(types) {}
+  TypeConstraint(adapter::Type type) : types{type} {}
+  TypeConstraint(std::set<adapter::Type> const & types) : types(types) {}
 };
 }

+ 9 - 3
include/jvalidate/constraint/number_constraint.h

@@ -4,29 +4,35 @@
 #include <jvalidate/forward.h>
 
 namespace jvalidate::constraint {
-class MaximumConstraint : public Constraint {
+class MaximumConstraint : public SimpleConstraint<MaximumConstraint> {
 private:
   double value_;
   bool exclusive_;
 
 public:
   MaximumConstraint(double value, bool exclusive) : value_(value), exclusive_(exclusive) {}
+
+  bool operator()(double arg) const { return exclusive_ ? arg < value_ : arg <= value_; }
 };
 
-class MinimumConstraint : public Constraint {
+class MinimumConstraint : public SimpleConstraint<MinimumConstraint> {
 private:
   double value_;
   bool exclusive_;
 
 public:
   MinimumConstraint(double value, bool exclusive) : value_(value), exclusive_(exclusive) {}
+
+  bool operator()(double arg) const { return exclusive_ ? arg > value_ : arg >= value_; }
 };
 
-class MultipleOfConstraint : public Constraint {
+class MultipleOfConstraint : public SimpleConstraint<MultipleOfConstraint> {
 private:
   int64_t value_;
 
 public:
   MultipleOfConstraint(int64_t value) : value_(value) {}
+
+  bool operator()(int64_t arg) const { return (arg % value_) == 0; }
 };
 }

+ 38 - 35
include/jvalidate/constraint/object_constraint.h

@@ -11,89 +11,92 @@
 #include <jvalidate/forward.h>
 
 namespace jvalidate::constraint {
-class AdditionalPropertiesConstraint : public Constraint {
-private:
-  schema::Node const * subschema_;
-  std::unordered_set<std::string> properties_;
-  std::vector<std::string> patterns_;
+class AdditionalPropertiesConstraint : public SimpleConstraint<AdditionalPropertiesConstraint> {
+public:
+  schema::Node const * subschema;
+  std::unordered_set<std::string> properties;
+  std::vector<std::string> patterns;
 
 public:
   AdditionalPropertiesConstraint(schema::Node const * subschema,
                                  std::unordered_set<std::string> const & properties,
                                  std::vector<std::string> const & patterns)
-      : subschema_(subschema), properties_(properties), patterns_(patterns) {}
+      : subschema(subschema), properties(properties), patterns(patterns) {}
 };
 
-class DependenciesConstraint : public Constraint {
-private:
-  std::map<std::string, schema::Node const *> subschemas_;
-  std::map<std::string, std::vector<std::string>> required_;
+class DependenciesConstraint : public SimpleConstraint<DependenciesConstraint> {
+public:
+  std::map<std::string, schema::Node const *> subschemas;
+  std::map<std::string, std::unordered_set<std::string>> required;
 
 public:
   DependenciesConstraint(std::map<std::string, schema::Node const *> const & subschemas)
-      : subschemas_(subschemas) {}
+      : subschemas(subschemas) {}
 
-  DependenciesConstraint(std::map<std::string, std::vector<std::string>> const & required)
-      : required_(required) {}
+  DependenciesConstraint(std::map<std::string, std::unordered_set<std::string>> const & required)
+      : required(required) {}
 
   DependenciesConstraint(std::map<std::string, schema::Node const *> const & subschemas,
-                         std::map<std::string, std::vector<std::string>> const & required)
-      : subschemas_(subschemas), required_(required) {}
+                         std::map<std::string, std::unordered_set<std::string>> const & required)
+      : subschemas(subschemas), required(required) {}
 };
 
-class MaxPropertiesConstraint : public Constraint {
+class MaxPropertiesConstraint : public SimpleConstraint<MaxPropertiesConstraint> {
 private:
   int64_t value_;
 
 public:
   MaxPropertiesConstraint(int64_t value) : value_(value) {}
+
+  bool operator()(ObjectAdapter auto const & arg) const { return arg.size() <= value_; }
 };
 
-class MinPropertiesConstraint : public Constraint {
+class MinPropertiesConstraint : public SimpleConstraint<MinPropertiesConstraint> {
 private:
   int64_t value_;
 
 public:
   MinPropertiesConstraint(int64_t value) : value_(value) {}
+
+  bool operator()(ObjectAdapter auto const & arg) const { return arg.size() >= value_; }
 };
 
-class PatternPropertiesConstraint : public Constraint {
-private:
-  std::vector<std::pair<std::string, schema::Node const *>> properties_;
+class PatternPropertiesConstraint : public SimpleConstraint<PatternPropertiesConstraint> {
+public:
+  std::vector<std::pair<std::string, schema::Node const *>> properties;
 
 public:
   PatternPropertiesConstraint(
       std::vector<std::pair<std::string, schema::Node const *>> const & properties)
-      : properties_(properties) {}
+      : properties(properties) {}
 };
 
-class PropertiesConstraint : public Constraint {
-private:
-  std::map<std::string, schema::Node const *> properties_;
+class PropertiesConstraint : public SimpleConstraint<PropertiesConstraint> {
+public:
+  std::map<std::string, schema::Node const *> properties;
 
 public:
   PropertiesConstraint(std::map<std::string, schema::Node const *> const & properties)
-      : properties_(properties) {}
+      : properties(properties) {}
 };
 
-class PropertyNamesConstraint : public Constraint {
-private:
-  schema::Node const * key_schema_;
+class PropertyNamesConstraint : public SimpleConstraint<PropertyNamesConstraint> {
+public:
+  schema::Node const * key_schema;
 
 public:
-  PropertyNamesConstraint(schema::Node const * key_schema) : key_schema_(key_schema) {}
+  PropertyNamesConstraint(schema::Node const * key_schema) : key_schema(key_schema) {}
 };
 
-class RequiredConstraint : public Constraint {
-private:
-  std::unordered_set<std::string> properties_;
+class RequiredConstraint : public SimpleConstraint<RequiredConstraint> {
+public:
+  std::unordered_set<std::string> properties;
 
 public:
-  RequiredConstraint(std::unordered_set<std::string> const & properties)
-      : properties_(properties) {}
+  RequiredConstraint(std::unordered_set<std::string> const & properties) : properties(properties) {}
 };
 
-class UnevaluatedPropertiesConstraint : public Constraint {
+class UnevaluatedPropertiesConstraint : public SimpleConstraint<UnevaluatedPropertiesConstraint> {
 private:
   schema::Node const * subschema_;
 

+ 10 - 6
include/jvalidate/constraint/string_constraint.h

@@ -6,27 +6,31 @@
 #include <jvalidate/forward.h>
 
 namespace jvalidate::constraint {
-class MinLengthConstraint : public Constraint {
+class MinLengthConstraint : public SimpleConstraint<MinLengthConstraint> {
 private:
   int64_t value_;
 
 public:
   MinLengthConstraint(int64_t value) : value_(value) {}
+
+  bool operator()(std::string_view arg) const { return arg.size() >= value_; }
 };
 
-class MaxLengthConstraint : public Constraint {
+class MaxLengthConstraint : public SimpleConstraint<MaxLengthConstraint> {
 private:
   int64_t value_;
 
 public:
   MaxLengthConstraint(int64_t value) : value_(value) {}
+
+  bool operator()(std::string_view arg) const { return arg.size() <= value_; }
 };
 
-class PatternConstraint : public Constraint {
-private:
-  std::string regex_;
+class PatternConstraint : public SimpleConstraint<PatternConstraint> {
+public:
+  std::string regex;
 
 public:
-  PatternConstraint(std::string const & regex) : regex_(regex) {}
+  PatternConstraint(std::string const & regex) : regex(regex) {}
 };
 }

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

@@ -0,0 +1,48 @@
+#pragma once
+
+#include <jvalidate/forward.h>
+
+namespace jvalidate::constraint {
+struct ConstraintVisitor {
+  virtual ~ConstraintVisitor() = default;
+
+  virtual Status visit(TypeConstraint const &) const = 0;
+  virtual Status visit(EnumConstraint const &) const = 0;
+  virtual Status visit(AllOfConstraint const &) const = 0;
+  virtual Status visit(AnyOfConstraint const &) const = 0;
+  virtual Status visit(OneOfConstraint const &) const = 0;
+  virtual Status visit(NotConstraint const &) const = 0;
+  virtual Status visit(ConditionalConstraint const &) const = 0;
+
+  virtual Status visit(MaximumConstraint const &) const = 0;
+  virtual Status visit(MinimumConstraint const &) const = 0;
+  virtual Status visit(MultipleOfConstraint const &) const = 0;
+
+  virtual Status visit(MaxLengthConstraint const &) const = 0;
+  virtual Status visit(MinLengthConstraint const &) const = 0;
+  virtual Status visit(PatternConstraint const &) const = 0;
+
+  virtual Status visit(AdditionalItemsConstraint const &) const = 0;
+  virtual Status visit(ContainsConstraint const &) const = 0;
+  virtual Status visit(MaxItemsConstraint const &) const = 0;
+  virtual Status visit(MinItemsConstraint const &) const = 0;
+  virtual Status visit(TupleConstraint const &) const = 0;
+  virtual Status visit(UniqueItemsConstraint const &) const = 0;
+
+  virtual Status visit(AdditionalPropertiesConstraint const &) const = 0;
+  virtual Status visit(DependenciesConstraint const &) const = 0;
+  virtual Status visit(MaxPropertiesConstraint const &) const = 0;
+  virtual Status visit(MinPropertiesConstraint const &) const = 0;
+  virtual Status visit(PatternPropertiesConstraint const &) const = 0;
+  virtual Status visit(PropertiesConstraint const &) const = 0;
+  virtual Status visit(PropertyNamesConstraint const &) const = 0;
+  virtual Status visit(RequiredConstraint const &) const = 0;
+
+  virtual Status visit(UnevaluatedItemsConstraint const &) const = 0;
+  virtual Status visit(UnevaluatedPropertiesConstraint const &) const = 0;
+};
+
+template <typename Cons> struct ExtensionConstraintVisitor {
+  virtual Status visit(Cons const &) const = 0;
+};
+}

+ 0 - 23
include/jvalidate/detail/regex_engine.h

@@ -1,23 +0,0 @@
-#pragma once
-
-#include <regex>
-#include <string>
-#include <unordered_map>
-
-namespace jvalidate::detail {
-class RegexEngine {
-public:
-  virtual ~RegexEngine() = default;
-  virtual bool operator()(std::string const & regex, std::string const & text) = 0;
-};
-
-class StdRegexEngine final : public RegexEngine {
-private:
-  std::unordered_map<std::string, std::regex> cache_;
-
-public:
-  bool operator()(std::string const & regex, std::string const & text) {
-    return std::regex_match(text, cache_.try_emplace(regex, regex).first->second);
-  }
-};
-}

+ 7 - 7
include/jvalidate/detail/simple_adapter.h

@@ -90,7 +90,7 @@ public:
   using value_type = std::remove_const_t<JSON>;
 
 public:
-  SimpleAdapter(JSON * value) : value_(value) {}
+  SimpleAdapter(JSON * value = nullptr) : value_(value) {}
   SimpleAdapter(JSON & value) : value_(&value) {}
 
   size_t array_size() const { return self().as_array().size(); }
@@ -99,10 +99,10 @@ public:
 
   detail::SimpleArrayAdapter<JSON> as_array() const { return value_; }
 
-  bool apply_array(AdapterCallback const & cb) const final {
-    bool result = true;
+  Status apply_array(AdapterCallback const & cb) const final {
+    Status result = Status::Noop;
     for (auto const & child : self().as_array()) {
-      result = cb(child) && result;
+      result = cb(child) & result;
     }
     return result;
   }
@@ -115,10 +115,10 @@ public:
 
   detail::SimpleObjectAdapter<JSON, CRTP> as_object() const { return value_; }
 
-  bool apply_object(ObjectAdapterCallback const & cb) const final {
-    bool result = true;
+  Status apply_object(ObjectAdapterCallback const & cb) const final {
+    Status result = Status::Noop;
     for (auto const & [key, child] : self().as_object()) {
-      result = cb(key, child) && result;
+      result = cb(key, child) & result;
     }
     return result;
   }

+ 61 - 12
include/jvalidate/forward.h

@@ -3,13 +3,19 @@
 #include <functional>
 #include <string>
 
+namespace jvalidate {
+class Result;
+class Schema;
+class Status;
+}
+
 namespace jvalidate::adapter {
 enum class Type : int8_t;
 class Adapter;
 class Const;
 
-using AdapterCallback = std::function<bool(adapter::Adapter const &)>;
-using ObjectAdapterCallback = std::function<bool(std::string const &, adapter::Adapter const &)>;
+using AdapterCallback = std::function<Status(adapter::Adapter const &)>;
+using ObjectAdapterCallback = std::function<Status(std::string const &, adapter::Adapter const &)>;
 
 template <typename> struct AdapterTraits;
 template <typename V> struct AdapterTraits<V const> : AdapterTraits<V> {};
@@ -20,6 +26,41 @@ template <typename JSON> using AdapterFor = typename AdapterTraits<JSON>::templa
 namespace jvalidate::constraint {
 class ConstraintVisitor;
 class Constraint;
+template <typename> class SimpleConstraint;
+template <typename> class ExtensionConstraint;
+
+template <typename T>
+concept Extension = std::is_base_of_v<ExtensionConstraint<T>, T>;
+
+class AdditionalItemsConstraint;
+class AdditionalPropertiesConstraint;
+class AllOfConstraint;
+class AnyOfConstraint;
+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 PatternConstraint;
+class PatternPropertiesConstraint;
+class PropertiesConstraint;
+class PropertyNamesConstraint;
+class RequiredConstraint;
+class TupleConstraint;
+class TypeConstraint;
+class UnevaluatedItemsConstraint;
+class UnevaluatedPropertiesConstraint;
+class UniqueItemsConstraint;
 }
 
 namespace jvalidate::schema {
@@ -28,14 +69,6 @@ class Node;
 }
 
 namespace jvalidate {
-template <typename A>
-concept ScalarAdapter = requires(A const a) {
-  a.operator bool();
-  a.operator int64_t();
-  a.operator double();
-  a.operator std::string();
-};
-
 template <typename It>
 concept ArrayIterator = std::forward_iterator<It> and std::is_default_constructible_v<It> and
     requires(It const it) {
@@ -68,12 +101,28 @@ concept ObjectAdapter = requires(A const a) {
 
 template <typename A>
 concept Adapter = std::is_base_of_v<adapter::Adapter, A> && requires(A a) {
+  { a.type() } -> std::same_as<adapter::Type>;
+  { a.as_boolean() } -> std::same_as<bool>;
+  { a.as_integer() } -> std::convertible_to<int64_t>;
+  { a.as_number() } -> std::convertible_to<double>;
+  { a.as_number() } -> std::floating_point;
   { a.as_object() } -> ObjectAdapter;
   { a.as_array() } -> ArrayAdapter;
+
+  { a.array_size() } -> std::convertible_to<size_t>;
+  { a.object_size() } -> std::convertible_to<size_t>;
+};
+
+template <typename R>
+concept RegexEngine = std::constructible_from<std::string> && requires(R const regex) {
+  { regex.search("") } -> std::same_as<bool>;
 };
 }
 
 namespace jvalidate {
-class Result;
-class Schema;
+template <Adapter A, RegexEngine RE, typename CRTP, constraint::Extension... Extensions>
+class ValidationVisitorBase;
+template <Adapter A, RegexEngine RE> class ValidationVisitor;
+
+class Validator;
 }

+ 7 - 0
include/jvalidate/result.h

@@ -0,0 +1,7 @@
+#pragma once
+
+#include <jvalidate/forward.h>
+
+namespace jvalidate {
+class Result {};
+}

+ 23 - 8
include/jvalidate/schema.h

@@ -21,8 +21,9 @@ private:
 
   detail::Reference uri_;
   bool rejects_all_{false};
-  schema::Node const * reference_{nullptr};
-  std::map<std::string, std::unique_ptr<constraint::Constraint>> constraints_{};
+  std::optional<schema::Node const *> reference_{};
+  std::unordered_map<std::string, std::unique_ptr<constraint::Constraint>> constraints_{};
+  std::unordered_map<std::string, std::unique_ptr<constraint::Constraint>> post_constraints_{};
 
 protected:
   static Version schema_version(std::string_view url);
@@ -33,7 +34,15 @@ public:
   Node(bool rejects_all = false) : rejects_all_(rejects_all) {}
   template <Adapter A> Node(ParserContext<A> context);
 
-  bool is_pure_reference() const { return reference_ && constraints_.empty() && not default_; }
+  bool is_pure_reference() const {
+    return reference_ && constraints_.empty() && post_constraints_.empty() && not default_;
+  }
+  bool rejects_all() const { return rejects_all_; }
+  std::optional<schema::Node const *> reference_schema() const { return reference_; }
+
+  bool requires_result_context() const { return not post_constraints_.empty(); }
+  auto const & constraints() const { return constraints_; }
+  auto const & post_constraints() const { return constraints_; }
 };
 
 inline Version Node::schema_version(std::string_view url) {
@@ -97,6 +106,10 @@ public:
   Schema(A const & json, schema::Version version)
       : schema::Node(ParserContext<A>{.root = *this, .schema = json, .version = version}) {}
 
+  template <typename JSON, typename... Args>
+  explicit Schema(JSON const & json, Args &&... args)
+      : Schema(adapter::AdapterFor<JSON const>(json), std::forward<Args>(args)...) {}
+
 private:
   void anchor(std::string anchor, detail::Reference const & from) {}
 
@@ -128,7 +141,7 @@ private:
   template <Adapter A> schema::Node const * fetch_schema(ParserContext<A> const & context) {
     adapter::Type const type = context.schema.type();
     if (type == adapter::Type::Boolean) {
-      // TODO: Legal Context...
+      // TODO(samjaffe): Legal Context...
       return alias(context.where, context.schema.as_boolean() ? &accept_ : &reject_);
     }
 
@@ -179,7 +192,7 @@ template <Adapter A> Node::Node(ParserContext<A> context) {
     context.root.alias(detail::Reference(schema["$id"].as_string(), false), this);
   }
 
-  // TODO(sjaffe): $recursiveAnchor, $dynamicAnchor, $recursiveRef, $dynamicRef
+  // TODO(samjaffe): $recursiveAnchor, $dynamicAnchor, $recursiveRef, $dynamicRef
   if (schema.contains("$anchor")) {
     // Create an anchor mapping using the current document and the anchor
     // string. There's no need for special validation/chaining here, because
@@ -203,11 +216,12 @@ template <Adapter A> Node::Node(ParserContext<A> context) {
     description_ = schema["description"].as_string();
   }
 
+  // TODO(samjaffe): Pass this around instead
+  ConstraintFactory<A> factory;
   for (auto [key, subschema] : schema) {
     // Using a constraint store allows overriding certain rules, or the creation
     // of user-defined extention vocabularies.
-    // TODO(sjaffe): Pass this around instead
-    if (auto make_constraint = ConstraintFactory<A>()(key, context.version)) {
+    if (auto make_constraint = factory(key, context.version)) {
       EXPECT_M(not has_reference || context.version >= Version::Draft2019_09,
                "Cannot directly extend $ref schemas before Draft2019-09");
       // A constraint may return null if it is not applicable - but otherwise
@@ -216,7 +230,8 @@ template <Adapter A> Node::Node(ParserContext<A> context) {
       // Therefore, we parse it alongside parsing "maximum", and could return
       // nullptr when requesting a constraint pointer for "exclusiveMaximum".
       if (auto constraint = make_constraint(context.child(subschema, key))) {
-        constraints_.emplace(key, std::move(constraint));
+        auto & into = factory.is_post_constraint(key) ? post_constraints_ : constraints_;
+        into.emplace(key, std::move(constraint));
       }
     }
   }

+ 47 - 0
include/jvalidate/status.h

@@ -0,0 +1,47 @@
+#pragma once
+
+namespace jvalidate {
+class Status {
+public:
+  enum Enum { Accept, Reject, Noop };
+
+private:
+  Enum state_;
+
+public:
+  Status(bool state) : state_(state ? Accept : Reject) {}
+  Status(Enum state) : state_(state) {}
+
+  operator bool() const { return state_ == Accept; }
+
+  friend Status operator!(Status val) {
+    if (val.state_ == Noop) {
+      return Status::Noop;
+    }
+    return val.state_ == Reject ? Accept : Reject;
+  }
+
+  friend Status operator|(Status lhs, Status rhs) {
+    if (lhs.state_ == Noop && rhs.state_ == Noop) {
+      return Noop;
+    }
+    if (lhs.state_ == Reject || rhs.state_ == Reject) {
+      return Reject;
+    }
+    return Accept;
+  }
+
+  friend Status operator&(Status lhs, Status rhs) {
+    if (lhs.state_ == Noop && rhs.state_ == Noop) {
+      return Noop;
+    }
+    if (lhs.state_ == Accept && rhs.state_ == Accept) {
+      return Accept;
+    }
+    return Reject;
+  }
+
+  Status & operator&=(Status rhs) { return *this = *this & rhs; }
+  Status & operator|=(Status rhs) { return *this = *this | rhs; }
+};
+}

+ 389 - 0
include/jvalidate/validation_visitor.h

@@ -0,0 +1,389 @@
+#pragma once
+
+#include <unordered_map>
+
+#include <jvalidate/constraint/array_constraint.h>
+#include <jvalidate/constraint/general_constraint.h>
+#include <jvalidate/constraint/number_constraint.h>
+#include <jvalidate/constraint/object_constraint.h>
+#include <jvalidate/constraint/string_constraint.h>
+#include <jvalidate/constraint/visitor.h>
+#include <jvalidate/detail/expect.h>
+#include <jvalidate/forward.h>
+#include <jvalidate/result.h>
+#include <jvalidate/schema.h>
+#include <jvalidate/status.h>
+
+#define NOOP_UNLESS_TYPE(etype)                                                                    \
+  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>... {
+private:
+  A document_;
+  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) {}
+
+  Status visit(constraint::TypeConstraint const & cons) const {
+    return cons.types.contains(document_.type());
+  }
+
+  Status visit(constraint::EnumConstraint const & cons) const {
+    // TODO(samjaffe) Implement
+    /* auto equals = [this](adapter::Adapter const & frozen) { return document_.equals(frozen); };
+     */
+    /* for (auto const & option : cons.enumeration) { */
+    /*   if (option->apply(equals)) { */
+    /*     return Status::Accept; */
+    /*   } */
+    /* } */
+    return Status::Reject;
+  }
+
+  Status visit(constraint::AllOfConstraint const & cons) const {
+    Status rval = Status::Accept;
+
+    for (schema::Node const * subschema : cons.children) {
+      rval &= validate_subschema(subschema);
+      if (!rval && result_ == nullptr) {
+        break;
+      }
+    }
+
+    return rval;
+  }
+
+  Status visit(constraint::AnyOfConstraint const & cons) const {
+    for (schema::Node const * subschema : cons.children) {
+      if (validate_subschema(subschema)) {
+        return Status::Accept;
+      }
+    }
+
+    return Status::Reject;
+  }
+
+  Status visit(constraint::OneOfConstraint const & cons) const {
+    size_t matches = 0;
+    for (schema::Node const * subschema : cons.children) {
+      if (validate_subschema(subschema)) {
+        ++matches;
+      }
+    }
+
+    return matches == 1 ? Status::Accept : Status::Reject;
+  }
+
+  Status visit(constraint::NotConstraint const & cons) const {
+    return !validate_subschema(cons.child);
+  }
+
+  Status visit(constraint::ConditionalConstraint const & cons) const {
+    if (validate_subschema(cons.if_constraint)) {
+      return validate_subschema(cons.then_constraint);
+    }
+    return validate_subschema(cons.else_constraint);
+  }
+
+  Status visit(constraint::MaximumConstraint const & cons) const {
+    switch (document_.type()) {
+    case adapter::Type::Integer:
+      return cons(document_.as_integer());
+    case adapter::Type::Number:
+      return cons(document_.as_number());
+    default:
+      return Status::Noop;
+    }
+  }
+
+  Status visit(constraint::MinimumConstraint const & cons) const {
+    switch (document_.type()) {
+    case adapter::Type::Integer:
+      return cons(document_.as_integer());
+    case adapter::Type::Number:
+      return cons(document_.as_number());
+    default:
+      return Status::Noop;
+    }
+  }
+
+  Status visit(constraint::MultipleOfConstraint const & cons) const {
+    NOOP_UNLESS_TYPE(Integer);
+    return cons(document_.as_integer());
+  }
+
+  Status visit(constraint::MaxLengthConstraint const & cons) const {
+    NOOP_UNLESS_TYPE(String);
+    return cons(document_.as_string());
+  }
+
+  Status visit(constraint::MinLengthConstraint const & cons) const {
+    NOOP_UNLESS_TYPE(String);
+    return cons(document_.as_string());
+  }
+
+  Status visit(constraint::PatternConstraint const & cons) const {
+    NOOP_UNLESS_TYPE(String);
+
+    RE const & regex = regex_cache_.try_emplace(cons.regex, cons.regex).first->second;
+    return regex.search(document_.as_string());
+  }
+
+  Status visit(constraint::AdditionalItemsConstraint const & cons) const {
+    NOOP_UNLESS_TYPE(Array);
+
+    auto array = document_.as_array();
+
+    Status rval = Status::Accept;
+    for (size_t i = cons.applies_after_nth; i < array.size(); ++i) {
+      rval &= validate_subschema_on(cons.subschema, array[i]);
+      if (!rval && result_ == nullptr) {
+        break;
+      }
+    }
+
+    return rval;
+  }
+
+  Status visit(constraint::ContainsConstraint const & cons) const {
+    NOOP_UNLESS_TYPE(Array);
+
+    auto array = document_.as_array();
+    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)) {
+        ++matches;
+      }
+    }
+
+    if (matches < minimum) {
+      return Status::Reject;
+    }
+    if (matches > maximum) {
+      return Status::Reject;
+    }
+    return Status::Accept;
+  }
+
+  Status visit(constraint::MaxItemsConstraint const & cons) const {
+    NOOP_UNLESS_TYPE(Array);
+    return cons(document_.as_array());
+  }
+
+  Status visit(constraint::MinItemsConstraint const & cons) const {
+    NOOP_UNLESS_TYPE(Array);
+    return cons(document_.as_array());
+  }
+
+  Status visit(constraint::TupleConstraint const & cons) const {
+    NOOP_UNLESS_TYPE(Array);
+
+    auto array = document_.as_array();
+    if (array.size() < cons.items.size()) {
+      return Status::Reject;
+    }
+
+    Status rval = Status::Accept;
+    for (size_t i = 0; i < cons.items.size(); ++i) {
+      rval &= validate_subschema_on(cons.items[i], array[i]);
+      if (!rval && result_ == nullptr) {
+        break;
+      }
+    }
+
+    return rval;
+  }
+
+  Status visit(constraint::UniqueItemsConstraint const & cons) const { throw; }
+
+  Status visit(constraint::AdditionalPropertiesConstraint const & cons) const {
+    NOOP_UNLESS_TYPE(Object);
+
+    auto matches_any_pattern = [this, &cons](std::string const & key) {
+      for (auto & pattern : cons.patterns) {
+        RE const & regex = regex_cache_.try_emplace(pattern, pattern).first->second;
+        if (regex.search(key)) {
+          return true;
+        }
+      }
+      return false;
+    };
+
+    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);
+      }
+      if (!rval && result_ == nullptr) {
+        break;
+      }
+    }
+
+    return rval;
+  }
+
+  Status visit(constraint::DependenciesConstraint const & cons) const {
+    NOOP_UNLESS_TYPE(Object);
+
+    auto object = document_.as_object();
+    Status rval = Status::Accept;
+    for (auto const & [key, subschema] : cons.subschemas) {
+      if (not object.contains(key)) {
+        continue;
+      }
+
+      rval &= validate_subschema(subschema);
+      if (!rval && result_ == nullptr) {
+        break;
+      }
+    }
+
+    for (auto [key, required] : cons.required) {
+      if (not object.contains(key)) {
+        continue;
+      }
+
+      for (auto const & [key, _] : object) {
+        required.erase(key);
+      }
+
+      rval &= required.empty();
+
+      if (!rval && result_ == nullptr) {
+        break;
+      }
+    }
+
+    return rval;
+  }
+
+  Status visit(constraint::MaxPropertiesConstraint const & cons) const {
+    NOOP_UNLESS_TYPE(Object);
+    return cons(document_.as_object());
+  }
+
+  Status visit(constraint::MinPropertiesConstraint const & cons) const {
+    NOOP_UNLESS_TYPE(Object);
+    return cons(document_.as_object());
+  }
+
+  Status visit(constraint::PatternPropertiesConstraint const & cons) const {
+    NOOP_UNLESS_TYPE(Object);
+
+    Status rval = Status::Accept;
+    for (auto const & [pattern, subschema] : cons.properties) {
+      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);
+        }
+        if (!rval && result_ == nullptr) {
+          break;
+        }
+      }
+    }
+
+    return rval;
+  }
+
+  Status visit(constraint::PropertiesConstraint const & cons) const {
+    NOOP_UNLESS_TYPE(Object);
+
+    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);
+      }
+      if (!rval && result_ == nullptr) {
+        break;
+      }
+    }
+
+    return rval;
+  }
+
+  Status visit(constraint::PropertyNamesConstraint const & cons) const {
+    NOOP_UNLESS_TYPE(Object);
+
+    Status rval = Status::Accept;
+    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));
+    }
+  }
+
+  Status visit(constraint::RequiredConstraint const & cons) const {
+    NOOP_UNLESS_TYPE(Object);
+
+    auto required = cons.properties;
+    for (auto const & [key, _] : document_.as_object()) {
+      required.erase(key);
+    }
+
+    return required.empty();
+  }
+
+  Status visit(constraint::UnevaluatedItemsConstraint const & cons) const { throw; }
+  Status visit(constraint::UnevaluatedPropertiesConstraint const & cons) const { throw; }
+
+  Status validate() const {
+    if (schema_.rejects_all()) {
+      return Status::Reject;
+    }
+
+    Status rval = Status::Noop;
+    if (schema_.requires_result_context()) {
+      // Ensure that we store results even if there aren't any...
+      // TODO(samjaffe) Implement this
+    }
+
+    if (auto ref = schema_.reference_schema()) {
+      rval = validate_subschema(*ref);
+    }
+
+    for (auto const & [key, p_constraint] : schema_.constraints()) {
+      if (rval || result_) {
+        rval &= p_constraint->accept(*this);
+      }
+    }
+
+    for (auto const & [key, p_constraint] : schema_.post_constraints()) {
+      if (rval || result_) {
+        rval &= p_constraint->accept(*this);
+      }
+    }
+
+    return rval;
+  }
+
+private:
+  Status validate(A const & document) const { return validate_subschema_on(&schema_, document_); }
+
+  Status validate_subschema(schema::Node const * subschema) const {
+    return validate_subschema_on(subschema, document_);
+  }
+
+  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();
+  }
+};
+
+template <Adapter A, RegexEngine RE>
+class ValidationVisitor final : public ValidationVisitorBase<A, RE, ValidationVisitor<A, RE>> {
+public:
+  using ValidationVisitor::ValidationVisitorBase::ValidationVisitorBase;
+};
+}

+ 41 - 0
include/jvalidate/validator.h

@@ -0,0 +1,41 @@
+#pragma once
+
+#include <regex>
+#include <unordered_map>
+
+#include <jvalidate/forward.h>
+#include <jvalidate/status.h>
+#include <jvalidate/validation_visitor.h>
+
+namespace jvalidate::detail {
+class StdRegexEngine {
+public:
+  std::regex regex_;
+
+public:
+  StdRegexEngine(std::string const & regex) : regex_(regex) {}
+  bool search(std::string const & text) const { return std::regex_search(text, regex_); }
+};
+}
+
+namespace jvalidate {
+template <template <Adapter, RegexEngine> class ValidationVisitor,
+          RegexEngine RE = detail::StdRegexEngine>
+class ExtendedValidator {
+private:
+  schema::Node const & schema_;
+  std::unordered_map<std::string, RE> regex_cache_;
+
+public:
+  ExtendedValidator(schema::Node const & schema) : schema_(schema) {}
+
+  template <Adapter A> Status validate(A const & json, Result const * result = nullptr) const {
+    return ValidationVisitor<A, RE>(json, schema_, regex_cache_, result).validate();
+  }
+};
+
+class Validator : public ExtendedValidator<ValidationVisitor> {
+public:
+  using Validator::ExtendedValidator::ExtendedValidator;
+};
+}