From 00e5c051f1f5d3167fdf42d16212b3c073ef32c6 Mon Sep 17 00:00:00 2001 From: Tristan Penman Date: Tue, 29 Dec 2015 09:28:22 +1100 Subject: [PATCH] Update TypeConstraint to use custom allocator for internal list of sub-schemas --- examples/custom_schema.cpp | 19 ++- .../constraints/concrete_constraints.hpp | 77 ++++++--- .../constraints/constraint_visitor.hpp | 2 +- include/valijson/schema_parser.hpp | 42 +++-- include/valijson/validation_visitor.hpp | 147 +++++++++++------- 5 files changed, 187 insertions(+), 100 deletions(-) diff --git a/examples/custom_schema.cpp b/examples/custom_schema.cpp index 3b15138..555601a 100644 --- a/examples/custom_schema.cpp +++ b/examples/custom_schema.cpp @@ -114,8 +114,9 @@ void addPropertiesConstraint(Schema &schema) // Create a child schema for the 'description' property that requires // a string, but does not enforce any length constraints. const Subschema *subschema = schema.createSubschema(); - schema.addConstraintToSubschema(TypeConstraint(TypeConstraint::kString), - subschema); + TypeConstraint typeConstraint; + typeConstraint.addNamedType(TypeConstraint::kString); + schema.addConstraintToSubschema(typeConstraint, subschema); // Include subschema in properties constraint propertySchemaMap["description"] = subschema; @@ -126,8 +127,9 @@ void addPropertiesConstraint(Schema &schema) // number with a value greater than zero. const Subschema *subschema = schema.createSubschema(); schema.addConstraintToSubschema(MinimumConstraint(0.0, true), subschema); - schema.addConstraintToSubschema(TypeConstraint(TypeConstraint::kNumber), - subschema); + TypeConstraint typeConstraint; + typeConstraint.addNamedType(TypeConstraint::kNumber); + schema.addConstraintToSubschema(typeConstraint, subschema); // Include subschema in properties constraint propertySchemaMap["price"] = subschema; @@ -139,8 +141,9 @@ void addPropertiesConstraint(Schema &schema) const Subschema *subschema = schema.createSubschema(); schema.addConstraintToSubschema(MaxLengthConstraint(200), subschema); schema.addConstraintToSubschema(MinLengthConstraint(1), subschema); - schema.addConstraintToSubschema(TypeConstraint(TypeConstraint::kString), - subschema); + TypeConstraint typeConstraint; + typeConstraint.addNamedType(TypeConstraint::kString); + schema.addConstraintToSubschema(typeConstraint, subschema); // Include subschema in properties constraint propertySchemaMap["title"] = subschema; @@ -166,7 +169,9 @@ void addTypeConstraint(Schema &schema) { // Add a TypeConstraint to the schema, specifying that the root of the // document must be an object. - schema.addConstraint(TypeConstraint(TypeConstraint::kObject)); + TypeConstraint typeConstraint; + typeConstraint.addNamedType(TypeConstraint::kObject); + schema.addConstraint(typeConstraint); } int main(int argc, char *argv[]) diff --git a/include/valijson/constraints/concrete_constraints.hpp b/include/valijson/constraints/concrete_constraints.hpp index 52b347d..3c9b441 100644 --- a/include/valijson/constraints/concrete_constraints.hpp +++ b/include/valijson/constraints/concrete_constraints.hpp @@ -602,8 +602,9 @@ private: /** * @brief Represents a 'type' constraint. */ -struct TypeConstraint: BasicConstraint +class TypeConstraint: public BasicConstraint { +public: enum JsonType { kAny, kArray, @@ -615,29 +616,51 @@ struct TypeConstraint: BasicConstraint kString }; - typedef std::set JsonTypes; + TypeConstraint() + : namedTypes(std::less(), allocator), + schemaTypes(Allocator::rebind::other(allocator)) { } - typedef std::vector Schemas; + TypeConstraint(CustomAlloc allocFn, CustomFree freeFn) + : BasicConstraint(allocFn, freeFn), + namedTypes(std::less(), allocator), + schemaTypes(Allocator::rebind::other(allocator)) { } - TypeConstraint(const JsonType jsonType) - : jsonTypes(makeJsonTypes(jsonType)) { } - - TypeConstraint(const JsonTypes &jsonTypes) - : jsonTypes(jsonTypes) { } - - TypeConstraint(const JsonTypes &jsonTypes, - const Schemas &schemas) - : jsonTypes(jsonTypes), - schemas(schemas) { } - - static JsonTypes makeJsonTypes(const JsonType jsonType) + void addNamedType(JsonType type) { - JsonTypes jsonTypes; - jsonTypes.insert(jsonType); - return jsonTypes; + namedTypes.insert(type); } - static JsonType jsonTypeFromString(const std::string &typeName) + void addSchemaType(const Subschema *subschema) + { + schemaTypes.push_back(subschema); + } + + template + void applyToNamedTypes(const FunctorType &fn) const + { + BOOST_FOREACH( const JsonType namedType, namedTypes ) { + if (!fn(namedType)) { + return; + } + } + } + + template + void applyToSchemaTypes(const FunctorType &fn) const + { + unsigned int index = 0; + BOOST_FOREACH( const Subschema *subschema, schemaTypes ) { + if (!fn(index, subschema)) { + return; + } + + index++; + } + } + + template + static JsonType jsonTypeFromString(const std::basic_string, AllocatorType> &typeName) { if (typeName.compare("any") == 0) { return kAny; @@ -657,11 +680,21 @@ struct TypeConstraint: BasicConstraint return kString; } - throw std::runtime_error("Unrecognised JSON type name '" + typeName + "'"); + throw std::runtime_error("Unrecognised JSON type name '" + + std::string(typeName.c_str()) + "'"); } - const JsonTypes jsonTypes; - const Schemas schemas; +private: + typedef std::set, Allocator> NamedTypes; + + typedef std::vector::other> SchemaTypes; + + /// Set of named JSON types that serve as valid types + NamedTypes namedTypes; + + /// Set of sub-schemas that serve as valid types + SchemaTypes schemaTypes; }; /** diff --git a/include/valijson/constraints/constraint_visitor.hpp b/include/valijson/constraints/constraint_visitor.hpp index 6bd8932..6d7d0d4 100644 --- a/include/valijson/constraints/constraint_visitor.hpp +++ b/include/valijson/constraints/constraint_visitor.hpp @@ -20,7 +20,6 @@ struct NotConstraint; struct PatternConstraint; struct PropertiesConstraint; struct RequiredConstraint; -struct TypeConstraint; struct UniqueItemsConstraint; class AllOfConstraint; @@ -29,6 +28,7 @@ class DependenciesConstraint; class LinearItemsConstraint; class OneOfConstraint; class SingularItemsConstraint; +class TypeConstraint; /// Interface to allow usage of the visitor pattern with Constraints class ConstraintVisitor diff --git a/include/valijson/schema_parser.hpp b/include/valijson/schema_parser.hpp index 59cc052..9c01697 100644 --- a/include/valijson/schema_parser.hpp +++ b/include/valijson/schema_parser.hpp @@ -1432,52 +1432,62 @@ private: const boost::optional< typename FetchDocumentFunction::Type > fetchDoc) { - typedef constraints::TypeConstraint TC; + typedef constraints::TypeConstraint TypeConstraint; - TC::JsonTypes jsonTypes; - TC::Schemas schemas; + TypeConstraint constraint; if (node.isString()) { - const TC::JsonType jsonType = - TC::jsonTypeFromString(node.getString()); - if (jsonType == TC::kAny && version == kDraft4) { + const TypeConstraint::JsonType type = + TypeConstraint::jsonTypeFromString(node.getString()); + + if (type == TypeConstraint::kAny && version == kDraft4) { throw std::runtime_error( "'any' type is not supported in version 4 schemas."); } - jsonTypes.insert(jsonType); + + constraint.addNamedType(type); } else if (node.isArray()) { int index = 0; BOOST_FOREACH( const AdapterType v, node.getArray() ) { if (v.isString()) { - const TC::JsonType jsonType = - TC::jsonTypeFromString(v.getString()); - if (jsonType == TC::kAny && version == kDraft4) { + const TypeConstraint::JsonType type = + TypeConstraint::jsonTypeFromString(v.getString()); + + if (type == TypeConstraint::kAny && version == kDraft4) { throw std::runtime_error( "'any' type is not supported in version 4 " "schemas."); } - jsonTypes.insert(jsonType); + + constraint.addNamedType(type); + } else if (v.isObject() && version == kDraft3) { const std::string childPath = nodePath + "/" + boost::lexical_cast(index); - schemas.push_back(rootSchema.createSubschema()); + const Subschema *subschema = rootSchema.createSubschema(); + constraint.addSchemaType(subschema); populateSchema(rootSchema, rootNode, v, - *schemas.back(), currentScope, childPath, fetchDoc); + *subschema, currentScope, childPath, fetchDoc); + } else { throw std::runtime_error("Type name should be a string."); } + index++; } + } else if (node.isObject() && version == kDraft3) { - schemas.push_back(rootSchema.createSubschema()); + const Subschema *subschema = rootSchema.createSubschema(); + constraint.addSchemaType(subschema); populateSchema(rootSchema, rootNode, node, - *schemas.back(), currentScope, nodePath, fetchDoc); + *subschema, currentScope, nodePath, fetchDoc); + } else { throw std::runtime_error("Type name should be a string."); } - return constraints::TypeConstraint(jsonTypes, schemas); + return constraint; } /** diff --git a/include/valijson/validation_visitor.hpp b/include/valijson/validation_visitor.hpp index 7c83b45..9337f5e 100644 --- a/include/valijson/validation_visitor.hpp +++ b/include/valijson/validation_visitor.hpp @@ -968,68 +968,39 @@ public: } /** - * @brief Validate against the type constraint represented by a - * TypeConstraint object. + * @brief Validate a value against a TypeConstraint * - * Checks that the target is of the expected type. + * Checks that the target is one of the valid named types, or matches one + * of a set of valid sub-schemas. * - * @param constraint Constraint that the target must validate against + * @param constraint TypeConstraint to validate against * - * @return true if validation succeeds, false otherwise + * @return \c true if validation is successful; \c false otherwise */ virtual bool visit(const TypeConstraint &constraint) { - // Try to match the type to one of the types in the jsonTypes array - BOOST_FOREACH( const TypeConstraint::JsonType jsonType, constraint.jsonTypes ) { - switch (jsonType) { - case TypeConstraint::kAny: - return true; - case TypeConstraint::kArray: - if (target.isArray()) { - return true; - } - break; - case TypeConstraint::kBoolean: - if (target.isBool() || (!strictTypes && target.maybeBool())) { - return true; - } - break; - case TypeConstraint::kInteger: - if (target.isInteger() || (!strictTypes && target.maybeInteger())) { - return true; - } - break; - case TypeConstraint::kNull: - if (target.isNull() || (!strictTypes && target.maybeNull())) { - return true; - } - break; - case TypeConstraint::kNumber: - if (target.isNumber() || (!strictTypes && target.maybeDouble())) { - return true; - } - break; - case TypeConstraint::kObject: - if (target.isObject()) { - return true; - } - break; - case TypeConstraint::kString: - if (target.isString()) { - return true; - } - break; - } - } - - BOOST_FOREACH( const Subschema *subschema, constraint.schemas ) { - if (validateSchema(*subschema)) { + // Check named types + { + // ValidateNamedTypes functor assumes target is invalid + bool validated = false; + constraint.applyToNamedTypes(ValidateNamedTypes(target, strictTypes, + false, true, &validated)); + if (validated) { return true; } } - if (results) { - results->pushError(context, "Value type not permitted by 'type' constraint."); + // Check schema-based types + { + unsigned int numValidated = 0; + constraint.applyToSchemaTypes(ValidateSubschemas(target, context, + false, true, *this, NULL, &numValidated, NULL)); + if (numValidated > 0) { + return true; + } else if (results) { + results->pushError(context, + "Value type not permitted by 'type' constraint."); + } } return false; @@ -1205,6 +1176,74 @@ private: }; + /** + * @brief Functor to validate value against named JSON types + */ + struct ValidateNamedTypes + { + ValidateNamedTypes( + const AdapterType &target, + bool strict, + bool continueOnSuccess, + bool continueOnFailure, + bool *validated) + : target(target), + strict(strict), + continueOnSuccess(continueOnSuccess), + continueOnFailure(continueOnFailure), + validated(validated) { } + + bool operator()(constraints::TypeConstraint::JsonType jsonType) const + { + typedef constraints::TypeConstraint TypeConstraint; + + bool valid = false; + + switch (jsonType) { + case TypeConstraint::kAny: + valid = true; + break; + case TypeConstraint::kArray: + valid = target.isArray(); + break; + case TypeConstraint::kBoolean: + valid = target.isBool() || (!strict && target.maybeBool()); + break; + case TypeConstraint::kInteger: + valid = target.isInteger() || + (!strict && target.maybeInteger()); + break; + case TypeConstraint::kNull: + valid = target.isNull() || (!strict && target.maybeNull()); + break; + case TypeConstraint::kNumber: + valid = target.isNumber() || (!strict && target.maybeDouble()); + break; + case TypeConstraint::kObject: + valid = target.isObject(); + break; + case TypeConstraint::kString: + valid = target.isString(); + break; + default: + break; + } + + if (valid && validated) { + *validated = true; + } + + return (valid && continueOnSuccess) || continueOnFailure; + } + + private: + const AdapterType target; + const bool strict; + const bool continueOnSuccess; + const bool continueOnFailure; + bool * const validated; + }; + /** * @brief Functor to validate schema-based dependencies */ @@ -1306,8 +1345,8 @@ private: if (results) { results->pushError(context, - "Failed to validate against child schema #" + - boost::lexical_cast(index) + "."); + "Failed to validate against child schema #" + + boost::lexical_cast(index) + "."); } return continueOnFailure;