#pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace jvalidate { template class ConstraintFactory { public: using pConstraint = std::unique_ptr; using MakeConstraint = typename detail::Vocabulary::MakeConstraint; using Object = decltype(std::declval().as_object()); enum KeywordType { Keyword, Removed }; struct Make { Make(KeywordType t) : is_keyword(t == Keyword) {} template Make(F make) : make(make), is_keyword(true) {} explicit operator bool() const { return make || is_keyword; } operator MakeConstraint() const { return make; } MakeConstraint make = nullptr; bool is_keyword = false; }; struct Versioned { template Versioned(M make) : data{{schema::Version::Earliest, make}} {} template Versioned(schema::Version version, M make) : data{{version, make}} {} Versioned(std::initializer_list> init) : data(init) {} std::map> data; }; using Store = std::unordered_map; private: using Self = ConstraintFactory; private: std::unordered_map 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}, }; public: ConstraintFactory() = default; ConstraintFactory(std::initializer_list> init) { constraints_.insert(init.begin(), init.end()); } ConstraintFactory && with_user_keyword(std::string_view word, Versioned make) && { constraints_.insert(word, std::move(make)); return *this; } ConstraintFactory && override_keyword(std::string_view word, Versioned make) && { constraints_[word] = std::move(make); return *this; } detail::Vocabulary keywords(schema::Version version) const { std::unordered_map 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 detail::Vocabulary(version, std::move(rval)); } // SECTION: Untyped Constraints static auto type(detail::ParserContext const & context) { static std::unordered_map 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(to_type(context.schema.as_string())); } EXPECT(type == adapter::Type::Array); std::set types; for (auto subschema : context.schema.as_array()) { types.insert(to_type(subschema.as_string())); } return std::make_unique(types); } static pConstraint ifThenElse(detail::ParserContext const & context) { schema::Node const * then_ = context.fixed_schema(true); if (context.parent->contains("then")) { then_ = context.neighbor("then").node(); } schema::Node const * else_ = context.fixed_schema(true); if (context.parent->contains("else")) { else_ = context.neighbor("else").node(); } return std::make_unique(context.node(), then_, else_); } static auto isInEnumuration(detail::ParserContext const & context) { EXPECT(context.schema.type() == adapter::Type::Array); std::vector> rval; for (auto subschema : context.schema.as_array()) { rval.push_back(subschema.freeze()); } return std::make_unique(std::move(rval)); } static auto isConstant(detail::ParserContext const & context) { return std::make_unique(context.schema.freeze()); } static auto allOf(detail::ParserContext const & context) { EXPECT(context.schema.type() == adapter::Type::Array); std::vector 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(rval); } static auto anyOf(detail::ParserContext const & context) { EXPECT(context.schema.type() == adapter::Type::Array); std::vector 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(rval); } static auto oneOf(detail::ParserContext const & context) { EXPECT(context.schema.type() == adapter::Type::Array); std::vector 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(rval); } static auto isNot(detail::ParserContext const & context) { return std::make_unique(context.node()); } // SECTION: Numeric Constraints static auto minimum(detail::ParserContext const & context) { double value = context.schema.as_number(); if (context.vocab->version() < schema::Version::Draft06 && context.parent->contains("exclusiveMinimum")) { auto exclusive = (*context.parent)["exclusiveMinimum"]; EXPECT(exclusive.type() == adapter::Type::Boolean); return std::make_unique(value, exclusive.as_boolean()); } return std::make_unique(value, false); } static pConstraint exclusiveMinimum(detail::ParserContext const & context) { double value = context.schema.as_number(); return std::make_unique(value, true); } static auto maximum(detail::ParserContext const & context) { double value = context.schema.as_number(); if (context.vocab->version() < schema::Version::Draft06 && context.parent->contains("exclusiveMaximum")) { auto exclusive = (*context.parent)["exclusiveMaximum"]; EXPECT(exclusive.type() == adapter::Type::Boolean); return std::make_unique(value, exclusive.as_boolean()); } return std::make_unique(value, false); } static pConstraint exclusiveMaximum(detail::ParserContext const & context) { double value = context.schema.as_number(); return std::make_unique(value, true); } static auto multipleOf(detail::ParserContext const & context) { double value = context.schema.as_number(); return std::make_unique(value); } // SECTION: String Constraints static auto minLength(detail::ParserContext const & context) { EXPECT(context.schema.type() == adapter::Type::Integer || context.schema.type() == adapter::Type::Number); return std::make_unique(context.schema.as_integer()); } static auto maxLength(detail::ParserContext const & context) { EXPECT(context.schema.type() == adapter::Type::Integer || context.schema.type() == adapter::Type::Number); return std::make_unique(context.schema.as_integer()); } static auto pattern(detail::ParserContext const & context) { return std::make_unique(context.schema.as_string()); } static auto format(detail::ParserContext const & context) { return std::make_unique(context.schema.as_string()); } // SECTION: Array Constraints static auto contains(detail::ParserContext const & context) { if (context.vocab->version() < schema::Version::Draft2019_09) { return std::make_unique(context.node()); } std::optional maximum; std::optional 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(context.node(), minimum, maximum); } static auto minItems(detail::ParserContext const & context) { EXPECT(context.schema.type() == adapter::Type::Integer || context.schema.type() == adapter::Type::Number); return std::make_unique(context.schema.as_integer()); } static auto maxItems(detail::ParserContext const & context) { EXPECT(context.schema.type() == adapter::Type::Integer || context.schema.type() == adapter::Type::Number); return std::make_unique(context.schema.as_integer()); } static auto prefixItems(detail::ParserContext const & context) { EXPECT(context.schema.type() == adapter::Type::Array); std::vector 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(rval); } static auto additionalItemsAfter(detail::ParserContext const & context, size_t n) { using C = constraint::AdditionalItemsConstraint; if (context.vocab->version() < schema::Version::Draft06 && context.schema.type() == adapter::Type::Boolean) { return std::make_unique(context.always(), n); } return std::make_unique(context.node(), n); } static pConstraint additionalItems(detail::ParserContext const & context) { std::string const prefix = 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.vocab->version() < schema::Version::Draft2020_12 && (not parent.contains(prefix) || parent[prefix].type() == adapter::Type::Object)) { return nullptr; } return additionalItemsAfter(context, parent[prefix].array_size()); } static pConstraint itemsTupleOrVector(detail::ParserContext const & context) { if (context.schema.type() == adapter::Type::Array) { return prefixItems(context); } return additionalItemsAfter(context, 0); } static auto unevaluatedItems(detail::ParserContext const & context) { return std::make_unique(context.node()); } static pConstraint uniqueItems(detail::ParserContext const & context) { EXPECT(context.schema.type() == adapter::Type::Boolean); if (not context.schema.as_boolean()) { return nullptr; } return std::make_unique(); } // SECTION: Object Constraints static auto required(detail::ParserContext const & context) { EXPECT(context.schema.type() == adapter::Type::Array); std::unordered_set rval; for (auto subschema : context.schema.as_array()) { EXPECT(subschema.type() == adapter::Type::String); rval.insert(subschema.as_string()); } return std::make_unique(rval); } static auto minProperties(detail::ParserContext const & context) { EXPECT(context.schema.type() == adapter::Type::Integer || context.schema.type() == adapter::Type::Number); return std::make_unique(context.schema.as_integer()); } static auto maxProperties(detail::ParserContext const & context) { EXPECT(context.schema.type() == adapter::Type::Integer || context.schema.type() == adapter::Type::Number); return std::make_unique(context.schema.as_integer()); } static auto patternProperties(detail::ParserContext const & context) { EXPECT(context.schema.type() == adapter::Type::Object); std::vector> rval; for (auto [prop, subschema] : context.schema.as_object()) { rval.emplace_back(prop, context.child(subschema, prop).node()); } return std::make_unique(rval); } static auto properties(detail::ParserContext const & context) { EXPECT(context.schema.type() == adapter::Type::Object); std::map rval; for (auto [prop, subschema] : context.schema.as_object()) { rval.emplace(prop, context.child(subschema, prop).node()); } return std::make_unique(rval); } static auto propertyNames(detail::ParserContext const & context) { return std::make_unique(context.node()); } static auto unevaluatedProperties(detail::ParserContext const & context) { return std::make_unique(context.node()); } static auto additionalProperties(detail::ParserContext const & context) { std::unordered_set properties; std::vector 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; if (context.vocab->version() < schema::Version::Draft06 && context.schema.type() == adapter::Type::Boolean) { return std::make_unique(context.always(), properties, patterns); } return std::make_unique(context.node(), properties, patterns); } static auto dependencies(detail::ParserContext const & context) { EXPECT(context.schema.type() == adapter::Type::Object); std::map schemas; std::map> required; for (auto [prop, subschema] : context.schema.as_object()) { if (subschema.type() == adapter::Type::Array) { for (auto key : subschema.as_array()) { EXPECT(key.type() == adapter::Type::String); required[prop].insert(key.as_string()); } } else { schemas.emplace(prop, context.child(subschema, prop).node()); } } return std::make_unique(schemas, required); } static auto dependentSchemas(detail::ParserContext const & context) { EXPECT(context.schema.type() == adapter::Type::Object); std::map rval; for (auto [prop, subschema] : context.schema.as_object()) { rval.emplace(prop, context.child(subschema, prop).node()); } return std::make_unique(rval); } static auto dependentRequired(detail::ParserContext const & context) { EXPECT(context.schema.type() == adapter::Type::Object); std::map> 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].insert(key.as_string()); } } return std::make_unique(rval); } }; }