瀏覽代碼

refactor: begin process of extracting Vocabulary class

Sam Jaffe 1 年之前
父節點
當前提交
4ac178d4ad

+ 79 - 95
include/jvalidate/constraint.h

@@ -16,6 +16,7 @@
 
 #include <jvalidate/detail/expect.h>
 #include <jvalidate/detail/parser_context.h>
+#include <jvalidate/detail/vocabulary.h>
 #include <jvalidate/enum.h>
 #include <jvalidate/forward.h>
 
@@ -23,127 +24,110 @@ namespace jvalidate {
 template <Adapter A> class ConstraintFactory {
 public:
   using pConstraint = std::unique_ptr<constraint::Constraint>;
+  using MakeConstraint = typename detail::Vocabulary<A>::MakeConstraint;
+
   using Object = decltype(std::declval<A>().as_object());
+  enum KeywordType { Keyword, Removed };
+
+  struct Make {
+    Make(KeywordType t) : is_keyword(t == Keyword) {}
+    template <typename F> Make(F make) : make(make), is_keyword(true) {}
 
-  using MakeConstraint = std::function<pConstraint(detail::ParserContext<A> const &)>;
-  template <typename V> using Versioned = std::map<schema::Version, V, std::greater<>>;
-  template <typename V> using Keywords = std::unordered_map<std::string_view, V>;
+    explicit operator bool() const { return make || is_keyword; }
+    operator MakeConstraint() const { return make; }
+
+    MakeConstraint make = nullptr;
+    bool is_keyword = false;
+  };
+
+  struct Versioned {
+    template <typename M> Versioned(M make) : data{{schema::Version::Earliest, make}} {}
+    template <typename M> Versioned(schema::Version version, M make) : data{{version, make}} {}
+    Versioned(std::initializer_list<std::pair<schema::Version const, Make>> init) : data(init) {}
+
+    std::map<schema::Version, Make, std::greater<>> data;
+  };
+  using Store = std::unordered_map<std::string_view, Versioned>;
 
 private:
   using Self = ConstraintFactory<A>;
 
 private:
-  std::unordered_map<std::string_view, MakeConstraint> constraints_{
+  std::unordered_map<std::string_view, Versioned> constraints_{
+      {"$defs", {schema::Version::Draft2019_09, Keyword}},
+      {"additionalItems",
+       {{schema::Version::Earliest, &Self::additionalItems},
+        {schema::Version::Draft2020_12, Removed}}},
       {"additionalProperties", &Self::additionalProperties},
+      {"allOf", {schema::Version::Draft04, &Self::allOf}},
+      {"anyOf", {schema::Version::Draft04, &Self::anyOf}},
+      {"const", {schema::Version::Draft06, &Self::isConstant}},
+      {"contains", {schema::Version::Draft06, &Self::contains}},
+      {"definitions", Keyword},
+      {"dependencies", &Self::dependencies},
+      {"dependentRequired", {schema::Version::Draft2019_09, &Self::dependentRequired}},
+      {"dependentSchemas", {schema::Version::Draft2019_09, &Self::dependentSchemas}},
+      {"divisibleBy",
+       {{schema::Version::Earliest, &Self::multipleOf}, {schema::Version::Draft04, Removed}}},
+      {"else", {{schema::Version::Draft07, Keyword}}},
       {"enum", &Self::isInEnumuration},
+      {"exclusiveMaximum", {schema::Version::Draft06, &Self::exclusiveMaximum}},
+      {"exclusiveMinimum", {schema::Version::Draft06, &Self::exclusiveMinimum}},
+      {"format", &Self::format},
+      {"if", {schema::Version::Draft07, &Self::ifThenElse}},
+      {"items",
+       {{schema::Version::Earliest, &Self::itemsTupleOrVector},
+        {schema::Version::Draft2020_12, &Self::additionalItems}}},
       {"maxItems", &Self::maxItems},
       {"maxLength", &Self::maxLength},
+      {"maxProperties", {schema::Version::Draft04, &Self::maxProperties}},
       {"maximum", &Self::maximum},
       {"minItems", &Self::minItems},
       {"minLength", &Self::minLength},
+      {"minProperties", {schema::Version::Draft04, &Self::minProperties}},
       {"minimum", &Self::minimum},
+      {"multipleOf", {schema::Version::Draft04, &Self::multipleOf}},
+      {"not", {schema::Version::Draft04, &Self::isNot}},
+      {"oneOf", {schema::Version::Draft04, &Self::oneOf}},
       {"pattern", &Self::pattern},
       {"patternProperties", &Self::patternProperties},
+      {"prefixItems", {schema::Version::Draft2020_12, &Self::prefixItems}},
       {"properties", &Self::properties},
+      {"propertyNames", {schema::Version::Draft06, &Self::propertyNames}},
+      {"required", {schema::Version::Draft04, &Self::required}},
+      {"then", {schema::Version::Draft07, Keyword}},
       {"type", &Self::type},
+      {"unevaluatedItems", {schema::Version::Draft2019_09, &Self::unevaluatedItems}},
+      {"unevaluatedProperties", {schema::Version::Draft2019_09, &Self::unevaluatedProperties}},
       {"uniqueItems", &Self::uniqueItems},
   };
 
-  std::unordered_map<std::string_view, Versioned<MakeConstraint>> versioned_constraints_{
-      {"additionalItems",
-       {{schema::Version::Draft04, &Self::additionalItems},
-        {schema::Version::Draft2020_12, nullptr}}},
-      {"allOf", {{schema::Version::Draft04, &Self::allOf}}},
-      {"anyOf", {{schema::Version::Draft04, &Self::anyOf}}},
-      {"const", {{schema::Version::Draft06, &Self::isConstant}}},
-      {"contains", {{schema::Version::Draft06, &Self::contains}}},
-      {"dependencies", {{schema::Version::Draft04, &Self::dependencies}}},
-      {"dependentRequired", {{schema::Version::Draft2019_09, &Self::dependentRequired}}},
-      {"dependentSchemas", {{schema::Version::Draft2019_09, &Self::dependentSchemas}}},
-      {"divisibleBy",
-       {{schema::Version::Draft04, &Self::multipleOf}, {schema::Version::Draft04, nullptr}}},
-      {"exclusiveMaximum", {{schema::Version::Draft06, &Self::exclusiveMaximum}}},
-      {"exclusiveMinimum", {{schema::Version::Draft06, &Self::exclusiveMinimum}}},
-      {"format",
-       {{schema::Version::Draft04, &Self::format}, {schema::Version::Draft2020_12, nullptr}}},
-      {"if", {{schema::Version::Draft07, &Self::ifThenElse}}},
-      {"items",
-       {{schema::Version::Draft04, &Self::itemsTupleOrVector},
-        {schema::Version::Draft2020_12, &Self::additionalItems}}},
-      {"maxProperties", {{schema::Version::Draft04, &Self::maxProperties}}},
-      {"minProperties", {{schema::Version::Draft04, &Self::minProperties}}},
-      {"multipleOf", {{schema::Version::Draft04, &Self::multipleOf}}},
-      {"not", {{schema::Version::Draft04, &Self::isNot}}},
-      {"oneOf", {{schema::Version::Draft04, &Self::oneOf}}},
-      {"prefixItems", {{schema::Version::Draft2020_12, &Self::prefixItems}}},
-      {"propertyNames", {{schema::Version::Draft06, &Self::propertyNames}}},
-      {"required", {{schema::Version::Draft04, &Self::required}}},
-      {"unevaluatedItems", {{schema::Version::Draft2019_09, &Self::unevaluatedItems}}},
-      {"unevaluatedProperties", {{schema::Version::Draft2019_09, &Self::unevaluatedProperties}}},
-  };
-
-  Keywords<Versioned<std::set<schema::Wraps>>> keywords_{
-      {"$defs", {{schema::Version::Draft2019_09, {schema::Wraps::Object}}}},
-      {"additionalItems",
-       {{schema::Version::Draft04, {schema::Wraps::Schema}}, {schema::Version::Draft2020_12, {}}}},
-      {"additionalProperties", {{schema::Version::Draft04, {schema::Wraps::Schema}}}},
-      {"allOf", {{schema::Version::Draft04, {schema::Wraps::Array}}}},
-      {"anyOf", {{schema::Version::Draft04, {schema::Wraps::Array}}}},
-      {"definitions",
-       {{schema::Version::Draft04, {schema::Wraps::Object}}, {schema::Version::Draft2019_09, {}}}},
-      {"dependencies",
-       {{schema::Version::Draft04, {schema::Wraps::Object}}, {schema::Version::Draft2019_09, {}}}},
-      {"dependentSchemas", {{schema::Version::Draft2019_09, {schema::Wraps::Object}}}},
-      {"else", {{schema::Version::Draft07, {schema::Wraps::Schema}}}},
-      {"if", {{schema::Version::Draft07, {schema::Wraps::Schema}}}},
-      {"items",
-       {{schema::Version::Draft04, {schema::Wraps::Array, schema::Wraps::Schema}},
-        {schema::Version::Draft2020_12, {schema::Wraps::Schema}}}},
-      {"not", {{schema::Version::Draft04, {schema::Wraps::Schema}}}},
-      {"oneOf", {{schema::Version::Draft04, {schema::Wraps::Array}}}},
-      {"patternProperties", {{schema::Version::Draft04, {schema::Wraps::Object}}}},
-      {"prefixItems", {{schema::Version::Draft2020_12, {schema::Wraps::Array}}}},
-      {"properties", {{schema::Version::Draft04, {schema::Wraps::Object}}}},
-      {"then", {{schema::Version::Draft07, {schema::Wraps::Schema}}}},
-      {"unevaluatedItems", {{schema::Version::Draft2020_12, {schema::Wraps::Schema}}}},
-      {"unevaluatedProperties", {{schema::Version::Draft2020_12, {schema::Wraps::Schema}}}},
-  };
-
 public:
   ConstraintFactory() = default;
 
-  explicit ConstraintFactory(
-      Keywords<MakeConstraint> const & user_keywords,
-      Keywords<Versioned<MakeConstraint>> const & user_versioned_keywords = {}) {
-    constraints_.insert(constraints_.end(), user_keywords.begin(), user_keywords.end());
-    versioned_constraints_.insert(versioned_constraints_.end(), user_versioned_keywords.begin(),
-                                  user_versioned_keywords.end());
+  ConstraintFactory(std::initializer_list<std::pair<std::string_view, Versioned>> init) {
+    constraints_.insert(init.begin(), init.end());
   }
 
-  bool is_post_constraint(std::string_view key) const {
-    return key == "unevaluatedItems" || key == "unevaluatedProperties";
+  ConstraintFactory<A> && with_user_keyword(std::string_view word, Versioned make) && {
+    constraints_.insert(word, std::move(make));
+    return *this;
   }
 
-  Keywords<std::set<schema::Wraps>> keywords(schema::Version version) const {
-    Keywords<std::set<schema::Wraps>> rval;
-    for (auto const & [key, versions] : keywords_) {
-      if (auto it = versions.lower_bound(version); it != versions.end()) {
-        rval.emplace(key, it->second);
-      }
-    }
-    return rval;
+  ConstraintFactory<A> && override_keyword(std::string_view word, Versioned make) && {
+    constraints_[word] = std::move(make);
+    return *this;
   }
 
-  MakeConstraint operator()(std::string_view key, schema::Version version) const {
-    if (auto it = constraints_.find(key); it != constraints_.end()) {
-      return it->second;
-    }
-    if (auto it = versioned_constraints_.find(key); it != versioned_constraints_.end()) {
-      if (auto vit = it->second.lower_bound(version); vit != it->second.end()) {
-        return vit->second;
+  detail::Vocabulary<A> keywords(schema::Version version) const {
+    std::unordered_map<std::string_view, MakeConstraint> rval;
+    for (auto const & [key, versions] : constraints_) {
+      if (auto it = versions.data.lower_bound(version); it != versions.data.end() && it->second) {
+        rval.emplace(key, it->second);
       }
     }
-    return nullptr;
+
+    return detail::Vocabulary<A>(version, std::move(rval));
   }
 
   // SECTION: Untyped Constraints
@@ -250,7 +234,7 @@ public:
 
   static auto minimum(detail::ParserContext<A> const & context) {
     double value = context.schema.as_number();
-    if (context.version < schema::Version::Draft06 &&
+    if (context.vocab->version() < schema::Version::Draft06 &&
         context.parent->contains("exclusiveMinimum")) {
       auto exclusive = (*context.parent)["exclusiveMinimum"];
       EXPECT(exclusive.type() == adapter::Type::Boolean);
@@ -266,7 +250,7 @@ public:
 
   static auto maximum(detail::ParserContext<A> const & context) {
     double value = context.schema.as_number();
-    if (context.version < schema::Version::Draft06 &&
+    if (context.vocab->version() < schema::Version::Draft06 &&
         context.parent->contains("exclusiveMaximum")) {
       auto exclusive = (*context.parent)["exclusiveMaximum"];
       EXPECT(exclusive.type() == adapter::Type::Boolean);
@@ -310,7 +294,7 @@ public:
   // SECTION: Array Constraints
 
   static auto contains(detail::ParserContext<A> const & context) {
-    if (context.version < schema::Version::Draft2019_09) {
+    if (context.vocab->version() < schema::Version::Draft2019_09) {
       return std::make_unique<constraint::ContainsConstraint>(context.node());
     }
 
@@ -353,7 +337,7 @@ public:
 
   static auto additionalItemsAfter(detail::ParserContext<A> const & context, size_t n) {
     using C = constraint::AdditionalItemsConstraint;
-    if (context.version < schema::Version::Draft06 &&
+    if (context.vocab->version() < schema::Version::Draft06 &&
         context.schema.type() == adapter::Type::Boolean) {
       return std::make_unique<C>(context.always(), n);
     }
@@ -363,14 +347,14 @@ public:
 
   static pConstraint additionalItems(detail::ParserContext<A> const & context) {
     std::string const prefix =
-        context.version >= schema::Version::Draft2020_12 ? "prefixItems" : "items";
+        context.vocab->version() >= schema::Version::Draft2020_12 ? "prefixItems" : "items";
 
     Object const & parent = *context.parent;
     // Before Draft 2020-12, the "items" could be either a subschema or a tuple.
     // When not provided, we therefore treat it as an "accept-all" schema, and
     // thus will never have additionalItems to process. Similarly - if it is an
     // Object, then it must act on all items.
-    if (context.version < schema::Version::Draft2020_12 &&
+    if (context.vocab->version() < schema::Version::Draft2020_12 &&
         (not parent.contains(prefix) || parent[prefix].type() == adapter::Type::Object)) {
       return nullptr;
     }
@@ -472,7 +456,7 @@ public:
     }
 
     using C = constraint::AdditionalPropertiesConstraint;
-    if (context.version < schema::Version::Draft06 &&
+    if (context.vocab->version() < schema::Version::Draft06 &&
         context.schema.type() == adapter::Type::Boolean) {
       return std::make_unique<C>(context.always(), properties, patterns);
     }

+ 4 - 3
include/jvalidate/detail/parser_context.h

@@ -1,8 +1,10 @@
 #pragma once
 
+#include <functional>
 #include <optional>
 
 #include <jvalidate/detail/reference.h>
+#include <jvalidate/detail/vocabulary.h>
 #include <jvalidate/forward.h>
 
 namespace jvalidate::detail {
@@ -13,8 +15,7 @@ template <Adapter A> struct ParserContext {
 
   A schema;
 
-  schema::Version version;
-  ConstraintFactory<A> const & factory;
+  Vocabulary<A> const * vocab;
   ReferenceManager<A> & ref;
 
   std::optional<Object> parent = std::nullopt;
@@ -23,7 +24,7 @@ template <Adapter A> struct ParserContext {
 
   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, version, factory, ref, parent, new_loc, new_dyn};
+    return {root, new_schema, vocab, ref, parent, new_loc, new_dyn};
   }
 
   ParserContext child(A const & child, std::string const & key) const {

+ 27 - 15
include/jvalidate/detail/reference_manager.h

@@ -1,6 +1,7 @@
 #pragma once
 
 #include <functional>
+#include <jvalidate/detail/vocabulary.h>
 #include <map>
 #include <set>
 #include <unordered_map>
@@ -28,6 +29,7 @@ private:
   DocumentCache<A> & external_;
 
   ReferenceCache references_;
+  std::map<schema::Version, Vocabulary<A>> vocabularies_;
   std::map<RootReference, A> roots_;
   std::map<URI, std::map<Anchor, Reference>> dynamic_anchors_;
 
@@ -37,7 +39,14 @@ public:
   ReferenceManager(DocumentCache<A> & external, A const & root, schema::Version version,
                    ConstraintFactory<A> const & constraints)
       : external_(external), constraints_(constraints), roots_{{{}, root}} {
-    prime(root, {}, version, constraints_.keywords(version));
+    prime(root, {}, vocab(version));
+  }
+
+  Vocabulary<A> const & vocab(schema::Version version) {
+    if (not vocabularies_.contains(version)) {
+      vocabularies_.emplace(version, constraints_.keywords(version));
+    }
+    return vocabularies_.at(version);
   }
 
   auto dynamic_scope(Reference const & ref) {
@@ -58,7 +67,7 @@ public:
 
     // TODO(samjaffe): Change Versions if needed...
     references_.emplace(ref.uri());
-    prime(*external, ref, version, constraints_.keywords(version));
+    prime(*external, ref, vocab(version));
 
     // May have a sub-id that we map to
     if (auto it = roots_.find(ref.root()); it != roots_.end()) {
@@ -133,33 +142,36 @@ private:
     return active_dynamic_anchors_.lookup(uri, ref.anchor());
   }
 
-  void prime(Adapter auto const & json, Reference where, schema::Version version,
-             Keywords const & keywords) {
+  void prime(Adapter auto const & json, Reference where, Vocabulary<A> const & vocab) {
     if (json.type() != adapter::Type::Object) {
       return;
     }
 
-    canonicalize(where, version, json);
+    canonicalize(where, vocab.version(), json);
 
     for (auto const & [key, value] : json.as_object()) {
-      auto vit = keywords.find(key);
-      if (vit == keywords.end()) {
+      if (not vocab.is_keyword(key)) {
         continue;
       }
-
-      if (vit->second.contains(schema::Wraps::Array) && value.type() == adapter::Type::Array) {
+      switch (value.type()) {
+      case adapter::Type::Array: {
         size_t index = 0;
         for (auto const & elem : value.as_array()) {
-          prime(elem, where / key / index, version, keywords);
+          prime(elem, where / key / index, vocab);
           ++index;
         }
-      } else if (vit->second.contains(schema::Wraps::Object) &&
-                 value.type() == adapter::Type::Object) {
+        break;
+      }
+      case adapter::Type::Object:
+        if (not vocab.is_property_keyword(key)) {
+          prime(value, where / key, vocab);
+          break;
+        }
         for (auto const & [prop, elem] : value.as_object()) {
-          prime(elem, where / key / prop, version, keywords);
+          prime(elem, where / key / prop, vocab);
         }
-      } else if (vit->second.contains(schema::Wraps::Schema)) {
-        prime(value, where / key, version, keywords);
+      default:
+        break;
       }
     }
   }

+ 78 - 0
include/jvalidate/detail/vocabulary.h

@@ -0,0 +1,78 @@
+#pragma once
+
+#include <string_view>
+#include <unordered_map>
+#include <unordered_set>
+
+#include <jvalidate/enum.h>
+#include <jvalidate/forward.h>
+
+namespace jvalidate::detail {
+template <Adapter A> struct ParserContext;
+template <Adapter A> class Vocabulary {
+public:
+  friend class ConstraintFactory<A>;
+  using pConstraint = std::unique_ptr<constraint::Constraint>;
+  using MakeConstraint = std::function<pConstraint(ParserContext<A> const &)>;
+
+private:
+  schema::Version version_;
+  std::unordered_map<std::string_view, MakeConstraint> make_;
+  std::unordered_set<std::string_view> special_keywords_;
+
+  // TODO(samjaffe): Migrate this back to constraintsfactory
+  std::unordered_set<std::string_view> keywords_{"$defs",
+                                                 "additionalItems",
+                                                 "additionalProperties",
+                                                 "allOf",
+                                                 "anyOf",
+                                                 "definitions",
+                                                 "dependencies",
+                                                 "dependentSchemas",
+                                                 "else",
+                                                 "if",
+                                                 "items",
+                                                 "not",
+                                                 "oneOf",
+                                                 "patternProperties",
+                                                 "prefixItems",
+                                                 "properties",
+                                                 "then",
+                                                 "unevaluatedItems",
+                                                 "unevaluatedProperties"};
+  std::unordered_set<std::string_view> property_keywords_{
+      "$defs",     "definitions", "dependencies", "dependentSchemas", "patternProperties",
+      "properties"};
+  std::unordered_set<std::string_view> post_constraints_{"unevaluatedItems",
+                                                         "unevaluatedProperties"};
+
+public:
+  Vocabulary(schema::Version version, std::unordered_map<std::string_view, MakeConstraint> make)
+      : version_(version), make_(std::move(make)) {}
+
+  schema::Version version() const { return version_; }
+
+  /**
+   * @brief Is the given "key"word actually a keyword? As in, would
+   * I expect to resolve a constraint out of it.
+   */
+  bool is_keyword(std::string_view word) const {
+    return make_.contains(word) && keywords_.contains(word);
+  }
+
+  /**
+   * @brief Does the given "key"word represent a property object - that is to
+   * say, an object containing some number of schemas mapped by arbitrary keys
+   */
+  bool is_property_keyword(std::string_view word) const {
+    return is_keyword(word) && property_keywords_.contains(word);
+  }
+
+  bool is_constraint(std::string_view word) const { return make_.contains(word) && make_.at(word); }
+
+  auto constraint(std::string_view word, ParserContext<A> const & context) const {
+    return std::make_pair(is_constraint(word) ? make_.at(word)(context) : nullptr,
+                          post_constraints_.contains(word));
+  }
+};
+}

+ 3 - 0
include/jvalidate/enum.h

@@ -57,6 +57,9 @@ enum class Version : int {
   // Split: format -> (format-annotation, format-assertion)
   // https://json-schema.org/draft/2020-12/schema
   Draft2020_12,
+
+  Earliest = Draft04,
+  Latest = Draft2020_12,
 };
 
 enum class Wraps : int8_t { Array, Object, Schema };

+ 11 - 12
include/jvalidate/schema.h

@@ -113,7 +113,7 @@ public:
     }
 
     detail::ReferenceManager<A> ref(external, json, version, factory);
-    detail::ParserContext<A> root{*this, json, version, factory, ref};
+    detail::ParserContext<A> root{*this, json, &ref.vocab(version), ref};
 
     root.where = root.dynamic_where = ref.canonicalize({}, {}, false);
     construct(root);
@@ -193,7 +193,7 @@ private:
       return *cached;
     }
 
-    if (std::optional root = context.ref.load(lexical, context.version)) {
+    if (std::optional root = context.ref.load(lexical, context.vocab->version())) {
       return fetch_schema(context.rebind(*root, lexical, dynamic));
     }
 
@@ -208,7 +208,7 @@ private:
     }
 
     adapter::Type const type = context.schema.type();
-    if (type == adapter::Type::Boolean && context.version >= schema::Version::Draft06) {
+    if (type == adapter::Type::Boolean && context.vocab->version() >= schema::Version::Draft06) {
       return alias(context.dynamic_where, context.schema.as_boolean() ? &accept_ : &reject_);
     }
 
@@ -247,7 +247,7 @@ template <Adapter A>
 detail::OnBlockExit Node::resolve_anchor(detail::ParserContext<A> const & context) {
   auto const schema = context.schema.as_object();
 
-  if (context.version < schema::Version::Draft2019_09 || not schema.contains("$id")) {
+  if (context.vocab->version() < schema::Version::Draft2019_09 || not schema.contains("$id")) {
     return nullptr;
   }
 
@@ -264,12 +264,12 @@ template <Adapter A> bool Node::resolve_reference(detail::ParserContext<A> const
     return true;
   }
 
-  if (context.version < Version::Draft2019_09) {
+  if (context.vocab->version() < Version::Draft2019_09) {
     return false;
   }
 
   std::string const dyn_ref =
-      context.version > schema::Version::Draft2019_09 ? "$dynamicRef" : "$recursiveRef";
+      context.vocab->version() > schema::Version::Draft2019_09 ? "$dynamicRef" : "$recursiveRef";
   if (schema.contains(dyn_ref)) {
     detail::Reference ref(schema[dyn_ref].as_string());
 
@@ -289,7 +289,7 @@ template <Adapter A> void Node::construct(detail::ParserContext<A> context) {
     // At any point in the schema, we're allowed to change versions
     // This means that we're not version-locked to the latest grammar
     // (which is especially important for some breaking changes)
-    context.version = detail::version(context.schema);
+    context.vocab = &context.ref.vocab(detail::version(context.schema));
   }
 
   auto _ = resolve_anchor(context);
@@ -305,15 +305,14 @@ template <Adapter A> void Node::construct(detail::ParserContext<A> context) {
 
   // Prior to Draft 2019-09, reference keywords take precedence over everything
   // else (instead of allowing direct extensions).
-  if (has_reference && context.version < Version::Draft2019_09) {
+  if (has_reference && context.vocab->version() < Version::Draft2019_09) {
     return;
   }
 
   for (auto const & [key, subschema] : schema) {
     // Using a constraint store allows overriding certain rules, or the creation
     // of user-defined extention vocabularies.
-    auto make_constraint = context.factory(key, context.version);
-    if (not make_constraint) {
+    if (not context.vocab->is_constraint(key)) {
       continue;
     }
     // A constraint may return null if it is not applicable - but otherwise
@@ -321,11 +320,11 @@ template <Adapter A> void Node::construct(detail::ParserContext<A> context) {
     // modifier property for "maximum", and not a unique constaint on its own.
     // Therefore, we parse it alongside parsing "maximum", and could return
     // nullptr when requesting a constraint pointer for "exclusiveMaximum".
-    auto constraint = make_constraint(context.child(subschema, key));
+    auto [constraint, post] = context.vocab->constraint(key, context.child(subschema, key));
     if (not constraint) {
       continue;
     }
-    if (context.factory.is_post_constraint(key)) {
+    if (post) {
       post_constraints_.emplace(key, std::move(constraint));
     } else {
       constraints_.emplace(key, std::move(constraint));