#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 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: 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 const & context) { std::cerr << "Unimplemented constraint " << context.where << "\n"; return nullptr; } static pConstraint fatalUnimplemented(ParserContext const & context) { JVALIDATE_THROW(std::runtime_error, "Unimplemented constraint " << context.where); } static auto type(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 auto ifThenElse(ParserContext const & context) { return std::make_unique( context.node(), context.neighbor("then").node(), context.neighbor("else").node()); } static auto isInEnumuration(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(ParserContext const & context) { return std::make_unique(context.schema.freeze()); } static auto allOf(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(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(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(ParserContext const & context) { return std::make_unique(context.node()); } // SECTION: Numeric Constraints static auto minimum(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(ParserContext const & context) { double value = context.schema.as_number(); return std::make_unique(value, true); } static auto maximum(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(ParserContext const & context) { double value = context.schema.as_number(); return std::make_unique(value, true); } static auto multipleOf(ParserContext const & context) { int64_t value = context.schema.as_integer(); return std::make_unique(value); } // SECTION: String Constraints static auto minLength(ParserContext const & context) { EXPECT(context.schema.type() == adapter::Type::Integer); return std::make_unique(context.schema.as_integer()); } static auto maxLength(ParserContext const & context) { EXPECT(context.schema.type() == adapter::Type::Integer); return std::make_unique(context.schema.as_integer()); } static auto pattern(ParserContext const & context) { return std::make_unique(context.schema.as_string()); } // SECTION: Array Constraints static auto contains(ParserContext const & context) { if (context.version < schema::Version::Draft2019_09) { return std::make_unique(context.schema.freeze()); } 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.schema.freeze(), minimum, maximum); } static auto minItems(ParserContext const & context) { EXPECT(context.schema.type() == adapter::Type::Integer); return std::make_unique(context.schema.as_integer()); } static auto maxItems(ParserContext const & context) { EXPECT(context.schema.type() == adapter::Type::Integer); return std::make_unique(context.schema.as_integer()); } static auto prefixItems(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 pConstraint additionalItems(ParserContext 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(schema, start_after); } static pConstraint itemsTupleOrVector(ParserContext const & context) { if (context.schema.type() == adapter::Type::Array) { return prefixItems(context); } return additionalItems(context); } static auto unevaluatedItems(ParserContext const & context) { return std::make_unique(context.node()); } static pConstraint uniqueItems(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(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(ParserContext const & context) { EXPECT(context.schema.type() == adapter::Type::Integer); return std::make_unique(context.schema.as_integer()); } static auto maxProperties(ParserContext const & context) { EXPECT(context.schema.type() == adapter::Type::Integer); return std::make_unique(context.schema.as_integer()); } static auto patternProperties(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(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(ParserContext const & context) { return std::make_unique(context.node()); } static auto unevaluatedProperties(ParserContext const & context) { return std::make_unique(context.node()); } static auto additionalProperties(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; return std::make_unique(context.node(), properties, patterns); } static auto dependencies(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::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(schemas, required); } static auto dependentSchemas(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(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].push_back(key.as_string()); } } return std::make_unique(rval); } }; }