|
|
@@ -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);
|
|
|
}
|