#pragma once #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 Object = decltype(std::declval().as_object()); using MakeConstraint = std::function const &)>; using VersionedMakeConstraint = std::map>; private: using Self = ConstraintFactory; private: std::unordered_map 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 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: 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; } 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(detail::ParserContext const & context) { std::cerr << "Unimplemented constraint " << context.where << "\n"; return nullptr; } static pConstraint fatalUnimplemented(detail::ParserContext const & context) { JVALIDATE_THROW(std::runtime_error, "Unimplemented constraint " << context.where); } 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(); } if (then_ == else_) { return nullptr; } 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.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.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()); } // SECTION: Array Constraints static auto contains(detail::ParserContext const & context) { if (context.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.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.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 && (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.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); } }; }