|
|
@@ -0,0 +1,453 @@
|
|
|
+#pragma once
|
|
|
+
|
|
|
+#include <functional>
|
|
|
+#include <map>
|
|
|
+#include <memory>
|
|
|
+#include <set>
|
|
|
+#include <string_view>
|
|
|
+#include <unordered_map>
|
|
|
+#include <unordered_set>
|
|
|
+
|
|
|
+#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/detail/expect.h>
|
|
|
+#include <jvalidate/detail/reference.h>
|
|
|
+#include <jvalidate/enum.h>
|
|
|
+#include <jvalidate/forward.h>
|
|
|
+#include <jvalidate/parser_context.h>
|
|
|
+
|
|
|
+namespace jvalidate {
|
|
|
+template <Adapter A> class ConstraintFactory {
|
|
|
+public:
|
|
|
+ using pConstraint = std::unique_ptr<constraint::Constraint>;
|
|
|
+ using Object = decltype(std::declval<A>().as_object());
|
|
|
+
|
|
|
+ using MakeConstraint = std::function<pConstraint(ParserContext<A> const &)>;
|
|
|
+ using VersionedMakeConstraint = std::map<schema::Version, MakeConstraint, std::greater<>>;
|
|
|
+
|
|
|
+private:
|
|
|
+ using Self = ConstraintFactory<A>;
|
|
|
+
|
|
|
+private:
|
|
|
+ std::unordered_map<std::string_view, MakeConstraint> constraints_{
|
|
|
+ {"additionalProperties", &Self::additionalProperties},
|
|
|
+ {"enum", &Self::isInEnumuration},
|
|
|
+ {"maxItems", &Self::maxItems},
|
|
|
+ {"maxLength", &Self::maxLength},
|
|
|
+ {"maximum", &Self::maximum},
|
|
|
+ {"minItems", &Self::minItems},
|
|
|
+ {"minLength", &Self::minLength},
|
|
|
+ {"minimum", &Self::minimum},
|
|
|
+ {"pattern", &Self::pattern},
|
|
|
+ {"patternProperties", &Self::patternProperties},
|
|
|
+ {"properties", &Self::properties},
|
|
|
+ {"type", &Self::type},
|
|
|
+ {"uniqueItems", &Self::uniqueItems},
|
|
|
+ };
|
|
|
+
|
|
|
+ std::unordered_map<std::string_view, VersionedMakeConstraint> 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}, {schema::Version::Draft2019_09, nullptr}}},
|
|
|
+ {"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::warnUnimplemented},
|
|
|
+ {schema::Version::Draft2020_12, nullptr}}},
|
|
|
+ {"format-assertion", {{schema::Version::Draft2020_12, &Self::fatalUnimplemented}}},
|
|
|
+ {"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}}},
|
|
|
+ };
|
|
|
+
|
|
|
+public:
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return nullptr;
|
|
|
+ }
|
|
|
+
|
|
|
+ // SECTION: Untyped Constraints
|
|
|
+
|
|
|
+ static pConstraint warnUnimplemented(ParserContext<A> const & context) {
|
|
|
+ std::cerr << "Unimplemented constraint " << context.where << "\n";
|
|
|
+ return nullptr;
|
|
|
+ }
|
|
|
+
|
|
|
+ static pConstraint fatalUnimplemented(ParserContext<A> const & context) {
|
|
|
+ JVALIDATE_THROW(std::runtime_error, "Unimplemented constraint " << context.where);
|
|
|
+ }
|
|
|
+
|
|
|
+ static auto type(ParserContext<A> const & context) {
|
|
|
+ static std::unordered_map<std::string_view, adapter::Type> const s_type_names{
|
|
|
+ {"null", adapter::Type::Null}, {"boolean", adapter::Type::Boolean},
|
|
|
+ {"integer", adapter::Type::Integer}, {"number", adapter::Type::Number},
|
|
|
+ {"string", adapter::Type::String}, {"array", adapter::Type::Array},
|
|
|
+ {"object", adapter::Type::Object},
|
|
|
+ };
|
|
|
+
|
|
|
+ auto to_type = [](std::string_view type) {
|
|
|
+ EXPECT_M(s_type_names.contains(type), "Unknown type " << type);
|
|
|
+ return s_type_names.at(type);
|
|
|
+ };
|
|
|
+
|
|
|
+ adapter::Type const type = context.schema.type();
|
|
|
+ if (type == adapter::Type::String) {
|
|
|
+ return std::make_unique<constraint::TypeConstraint>(to_type(context.schema.as_string()));
|
|
|
+ }
|
|
|
+
|
|
|
+ EXPECT(type == adapter::Type::Array);
|
|
|
+ std::set<adapter::Type> types;
|
|
|
+ for (auto subschema : context.schema.as_array()) {
|
|
|
+ types.insert(to_type(subschema.as_string()));
|
|
|
+ }
|
|
|
+ return std::make_unique<constraint::TypeConstraint>(types);
|
|
|
+ }
|
|
|
+
|
|
|
+ static auto ifThenElse(ParserContext<A> const & context) {
|
|
|
+ return std::make_unique<constraint::ConditionalConstraint>(
|
|
|
+ context.node(), context.neighbor("then").node(), context.neighbor("else").node());
|
|
|
+ }
|
|
|
+
|
|
|
+ static auto isInEnumuration(ParserContext<A> const & context) {
|
|
|
+ EXPECT(context.schema.type() == adapter::Type::Array);
|
|
|
+
|
|
|
+ std::vector<std::unique_ptr<adapter::Const const>> rval;
|
|
|
+ for (auto subschema : context.schema.as_array()) {
|
|
|
+ rval.push_back(subschema.freeze());
|
|
|
+ }
|
|
|
+
|
|
|
+ return std::make_unique<constraint::EnumConstraint>(std::move(rval));
|
|
|
+ }
|
|
|
+
|
|
|
+ static auto isConstant(ParserContext<A> const & context) {
|
|
|
+ return std::make_unique<constraint::EnumConstraint>(context.schema.freeze());
|
|
|
+ }
|
|
|
+
|
|
|
+ static auto allOf(ParserContext<A> const & context) {
|
|
|
+ EXPECT(context.schema.type() == adapter::Type::Array);
|
|
|
+
|
|
|
+ std::vector<schema::Node const *> rval;
|
|
|
+ size_t index = 0;
|
|
|
+ for (auto subschema : context.schema.as_array()) {
|
|
|
+ rval.push_back(context.child(subschema, index).node());
|
|
|
+ ++index;
|
|
|
+ }
|
|
|
+
|
|
|
+ return std::make_unique<constraint::AllOfConstraint>(rval);
|
|
|
+ }
|
|
|
+
|
|
|
+ static auto anyOf(ParserContext<A> const & context) {
|
|
|
+ EXPECT(context.schema.type() == adapter::Type::Array);
|
|
|
+
|
|
|
+ std::vector<schema::Node const *> rval;
|
|
|
+ size_t index = 0;
|
|
|
+ for (auto subschema : context.schema.as_array()) {
|
|
|
+ rval.push_back(context.child(subschema, index).node());
|
|
|
+ ++index;
|
|
|
+ }
|
|
|
+
|
|
|
+ return std::make_unique<constraint::AnyOfConstraint>(rval);
|
|
|
+ }
|
|
|
+
|
|
|
+ static auto oneOf(ParserContext<A> const & context) {
|
|
|
+ EXPECT(context.schema.type() == adapter::Type::Array);
|
|
|
+
|
|
|
+ std::vector<schema::Node const *> rval;
|
|
|
+ size_t index = 0;
|
|
|
+ for (auto subschema : context.schema.as_array()) {
|
|
|
+ rval.push_back(context.child(subschema, index).node());
|
|
|
+ ++index;
|
|
|
+ }
|
|
|
+
|
|
|
+ return std::make_unique<constraint::OneOfConstraint>(rval);
|
|
|
+ }
|
|
|
+
|
|
|
+ static auto isNot(ParserContext<A> const & context) {
|
|
|
+ return std::make_unique<constraint::NotConstraint>(context.node());
|
|
|
+ }
|
|
|
+
|
|
|
+ // SECTION: Numeric Constraints
|
|
|
+
|
|
|
+ static auto minimum(ParserContext<A> const & context) {
|
|
|
+ double value = context.schema.as_number();
|
|
|
+ if (context.version < schema::Version::Draft06 &&
|
|
|
+ context.parent->contains("exclusiveMinimum")) {
|
|
|
+ auto exclusive = (*context.parent)["exclusiveMinimum"];
|
|
|
+ EXPECT(exclusive.type() == adapter::Type::Boolean);
|
|
|
+ return std::make_unique<constraint::MinimumConstraint>(value, exclusive.as_boolean());
|
|
|
+ }
|
|
|
+ return std::make_unique<constraint::MinimumConstraint>(value, false);
|
|
|
+ }
|
|
|
+
|
|
|
+ static pConstraint exclusiveMinimum(ParserContext<A> const & context) {
|
|
|
+ double value = context.schema.as_number();
|
|
|
+ return std::make_unique<constraint::MinimumConstraint>(value, true);
|
|
|
+ }
|
|
|
+
|
|
|
+ static auto maximum(ParserContext<A> const & context) {
|
|
|
+ double value = context.schema.as_number();
|
|
|
+ if (context.version < schema::Version::Draft06 &&
|
|
|
+ context.parent->contains("exclusiveMaximum")) {
|
|
|
+ auto exclusive = (*context.parent)["exclusiveMaximum"];
|
|
|
+ EXPECT(exclusive.type() == adapter::Type::Boolean);
|
|
|
+ return std::make_unique<constraint::MaximumConstraint>(value, exclusive.as_boolean());
|
|
|
+ }
|
|
|
+ return std::make_unique<constraint::MaximumConstraint>(value, false);
|
|
|
+ }
|
|
|
+
|
|
|
+ static pConstraint exclusiveMaximum(ParserContext<A> const & context) {
|
|
|
+ double value = context.schema.as_number();
|
|
|
+ return std::make_unique<constraint::MaximumConstraint>(value, true);
|
|
|
+ }
|
|
|
+
|
|
|
+ static auto multipleOf(ParserContext<A> const & context) {
|
|
|
+ int64_t value = context.schema.as_integer();
|
|
|
+ return std::make_unique<constraint::MultipleOfConstraint>(value);
|
|
|
+ }
|
|
|
+
|
|
|
+ // SECTION: String Constraints
|
|
|
+
|
|
|
+ static auto minLength(ParserContext<A> const & context) {
|
|
|
+ EXPECT(context.schema.type() == adapter::Type::Integer);
|
|
|
+ return std::make_unique<constraint::MinLengthConstraint>(context.schema.as_integer());
|
|
|
+ }
|
|
|
+
|
|
|
+ static auto maxLength(ParserContext<A> const & context) {
|
|
|
+ EXPECT(context.schema.type() == adapter::Type::Integer);
|
|
|
+ return std::make_unique<constraint::MaxLengthConstraint>(context.schema.as_integer());
|
|
|
+ }
|
|
|
+
|
|
|
+ static auto pattern(ParserContext<A> const & context) {
|
|
|
+ return std::make_unique<constraint::PatternConstraint>(context.schema.as_string());
|
|
|
+ }
|
|
|
+
|
|
|
+ // SECTION: Array Constraints
|
|
|
+
|
|
|
+ static auto contains(ParserContext<A> const & context) {
|
|
|
+ if (context.version < schema::Version::Draft2019_09) {
|
|
|
+ return std::make_unique<constraint::ContainsConstraint>(context.schema.freeze());
|
|
|
+ }
|
|
|
+
|
|
|
+ std::optional<size_t> maximum;
|
|
|
+ std::optional<size_t> minimum;
|
|
|
+ if (context.parent->contains("maxContains")) {
|
|
|
+ maximum = (*context.parent)["maxContains"].as_integer();
|
|
|
+ }
|
|
|
+ if (context.parent->contains("minContains")) {
|
|
|
+ minimum = (*context.parent)["minContains"].as_integer();
|
|
|
+ }
|
|
|
+
|
|
|
+ return std::make_unique<constraint::ContainsConstraint>(context.schema.freeze(), minimum,
|
|
|
+ maximum);
|
|
|
+ }
|
|
|
+
|
|
|
+ static auto minItems(ParserContext<A> const & context) {
|
|
|
+ EXPECT(context.schema.type() == adapter::Type::Integer);
|
|
|
+ return std::make_unique<constraint::MinItemsConstraint>(context.schema.as_integer());
|
|
|
+ }
|
|
|
+
|
|
|
+ static auto maxItems(ParserContext<A> const & context) {
|
|
|
+ EXPECT(context.schema.type() == adapter::Type::Integer);
|
|
|
+ return std::make_unique<constraint::MaxItemsConstraint>(context.schema.as_integer());
|
|
|
+ }
|
|
|
+
|
|
|
+ static auto prefixItems(ParserContext<A> const & context) {
|
|
|
+ EXPECT(context.schema.type() == adapter::Type::Array);
|
|
|
+
|
|
|
+ std::vector<schema::Node const *> rval;
|
|
|
+ size_t index = 0;
|
|
|
+ for (auto subschema : context.schema.as_array()) {
|
|
|
+ rval.push_back(context.child(subschema, index).node());
|
|
|
+ ++index;
|
|
|
+ }
|
|
|
+
|
|
|
+ return std::make_unique<constraint::TupleConstraint>(rval);
|
|
|
+ }
|
|
|
+
|
|
|
+ static pConstraint additionalItems(ParserContext<A> const & context) {
|
|
|
+ std::string const prefix =
|
|
|
+ context.version >= schema::Version::Draft2020_12 ? "prefixItems" : "items";
|
|
|
+
|
|
|
+ Object const & parent = *context.parent;
|
|
|
+ size_t start_after = 0;
|
|
|
+ if (not prefix.empty() && parent.contains(prefix)) {
|
|
|
+ start_after = parent[prefix].as_integer();
|
|
|
+ }
|
|
|
+
|
|
|
+ schema::Node const * schema = context.node();
|
|
|
+ return std::make_unique<constraint::AdditionalItemsConstraint>(schema, start_after);
|
|
|
+ }
|
|
|
+
|
|
|
+ static pConstraint itemsTupleOrVector(ParserContext<A> const & context) {
|
|
|
+ if (context.schema.type() == adapter::Type::Array) {
|
|
|
+ return prefixItems(context);
|
|
|
+ }
|
|
|
+
|
|
|
+ return additionalItems(context);
|
|
|
+ }
|
|
|
+
|
|
|
+ static auto unevaluatedItems(ParserContext<A> const & context) {
|
|
|
+ return std::make_unique<constraint::UnevaluatedItemsConstraint>(context.node());
|
|
|
+ }
|
|
|
+
|
|
|
+ static pConstraint uniqueItems(ParserContext<A> const & context) {
|
|
|
+ EXPECT(context.schema.type() == adapter::Type::Boolean);
|
|
|
+ if (not context.schema.as_boolean()) {
|
|
|
+ return nullptr;
|
|
|
+ }
|
|
|
+
|
|
|
+ return std::make_unique<constraint::UniqueItemsConstraint>();
|
|
|
+ }
|
|
|
+
|
|
|
+ // SECTION: Object Constraints
|
|
|
+
|
|
|
+ static auto required(ParserContext<A> const & context) {
|
|
|
+ EXPECT(context.schema.type() == adapter::Type::Array);
|
|
|
+
|
|
|
+ std::unordered_set<std::string> rval;
|
|
|
+ for (auto subschema : context.schema.as_array()) {
|
|
|
+ EXPECT(subschema.type() == adapter::Type::String);
|
|
|
+ rval.insert(subschema.as_string());
|
|
|
+ }
|
|
|
+
|
|
|
+ return std::make_unique<constraint::RequiredConstraint>(rval);
|
|
|
+ }
|
|
|
+
|
|
|
+ static auto minProperties(ParserContext<A> const & context) {
|
|
|
+ EXPECT(context.schema.type() == adapter::Type::Integer);
|
|
|
+ return std::make_unique<constraint::MinPropertiesConstraint>(context.schema.as_integer());
|
|
|
+ }
|
|
|
+
|
|
|
+ static auto maxProperties(ParserContext<A> const & context) {
|
|
|
+ EXPECT(context.schema.type() == adapter::Type::Integer);
|
|
|
+ return std::make_unique<constraint::MaxPropertiesConstraint>(context.schema.as_integer());
|
|
|
+ }
|
|
|
+
|
|
|
+ static auto patternProperties(ParserContext<A> const & context) {
|
|
|
+ EXPECT(context.schema.type() == adapter::Type::Object);
|
|
|
+
|
|
|
+ std::vector<std::pair<std::string, schema::Node const *>> rval;
|
|
|
+ for (auto [prop, subschema] : context.schema.as_object()) {
|
|
|
+ rval.emplace_back(prop, context.child(subschema, prop).node());
|
|
|
+ }
|
|
|
+
|
|
|
+ return std::make_unique<constraint::PatternPropertiesConstraint>(rval);
|
|
|
+ }
|
|
|
+
|
|
|
+ static auto properties(ParserContext<A> const & context) {
|
|
|
+ EXPECT(context.schema.type() == adapter::Type::Object);
|
|
|
+
|
|
|
+ std::map<std::string, schema::Node const *> rval;
|
|
|
+ for (auto [prop, subschema] : context.schema.as_object()) {
|
|
|
+ rval.emplace(prop, context.child(subschema, prop).node());
|
|
|
+ }
|
|
|
+
|
|
|
+ return std::make_unique<constraint::PropertiesConstraint>(rval);
|
|
|
+ }
|
|
|
+
|
|
|
+ static auto propertyNames(ParserContext<A> const & context) {
|
|
|
+ return std::make_unique<constraint::PropertyNamesConstraint>(context.node());
|
|
|
+ }
|
|
|
+
|
|
|
+ static auto unevaluatedProperties(ParserContext<A> const & context) {
|
|
|
+ return std::make_unique<constraint::UnevaluatedPropertiesConstraint>(context.node());
|
|
|
+ }
|
|
|
+
|
|
|
+ static auto additionalProperties(ParserContext<A> const & context) {
|
|
|
+ std::unordered_set<std::string> properties;
|
|
|
+ std::vector<std::string> patterns;
|
|
|
+
|
|
|
+ Object const & parent = *context.parent;
|
|
|
+ if (parent.contains("properties")) {
|
|
|
+ for (auto [key, _] : parent["properties"].as_object()) {
|
|
|
+ properties.insert(key);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (parent.contains("patternProperties")) {
|
|
|
+ for (auto [key, _] : parent["patternProperties"].as_object()) {
|
|
|
+ patterns.push_back(key);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ using C = constraint::AdditionalPropertiesConstraint;
|
|
|
+ return std::make_unique<C>(context.node(), properties, patterns);
|
|
|
+ }
|
|
|
+
|
|
|
+ static auto dependencies(ParserContext<A> const & context) {
|
|
|
+ EXPECT(context.schema.type() == adapter::Type::Object);
|
|
|
+
|
|
|
+ std::map<std::string, schema::Node const *> schemas;
|
|
|
+ std::map<std::string, std::vector<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());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return std::make_unique<constraint::DependenciesConstraint>(schemas, required);
|
|
|
+ }
|
|
|
+
|
|
|
+ static auto dependentSchemas(ParserContext<A> const & context) {
|
|
|
+ EXPECT(context.schema.type() == adapter::Type::Object);
|
|
|
+
|
|
|
+ std::map<std::string, schema::Node const *> rval;
|
|
|
+ for (auto [prop, subschema] : context.schema.as_object()) {
|
|
|
+ rval.emplace(prop, context.child(subschema, prop).node());
|
|
|
+ }
|
|
|
+
|
|
|
+ return std::make_unique<constraint::DependenciesConstraint>(rval);
|
|
|
+ }
|
|
|
+
|
|
|
+ static auto dependentRequired(ParserContext<A> const & context) {
|
|
|
+ EXPECT(context.schema.type() == adapter::Type::Object);
|
|
|
+
|
|
|
+ std::map<std::string, std::vector<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());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return std::make_unique<constraint::DependenciesConstraint>(rval);
|
|
|
+ }
|
|
|
+};
|
|
|
+}
|