From b1755309d2629fbc36338e187af315efd0d2959e Mon Sep 17 00:00:00 2001 From: Tristan Penman Date: Wed, 23 Dec 2015 14:34:04 +1100 Subject: [PATCH] Refactor memory management for schemas and constraints to improve API and allow for future improvements --- examples/custom_schema.cpp | 56 +- examples/external_schema.cpp | 5 +- .../constraints/concrete_constraints.hpp | 111 +-- .../constraints/constraint_visitor.hpp | 9 +- include/valijson/schema.hpp | 147 +++- include/valijson/schema_parser.hpp | 644 +++++++++++------- include/valijson/subschema.hpp | 31 +- include/valijson/validation_visitor.hpp | 180 +++-- include/valijson/validator.hpp | 67 +- tests/test_fetch_document_callback.cpp | 6 +- tests/test_validation_errors.cpp | 4 +- tests/test_validator.cpp | 6 +- 12 files changed, 743 insertions(+), 523 deletions(-) diff --git a/examples/custom_schema.cpp b/examples/custom_schema.cpp index f627490..c35eadb 100644 --- a/examples/custom_schema.cpp +++ b/examples/custom_schema.cpp @@ -74,6 +74,7 @@ using std::endl; using valijson::Schema; using valijson::SchemaParser; +using valijson::Subschema; using valijson::Validator; using valijson::ValidationResults; using valijson::adapters::RapidJsonAdapter; @@ -90,49 +91,64 @@ void addPropertiesConstraint(Schema &schema) { PropertiesConstraint::PropertySchemaMap propertySchemaMap; - PropertiesConstraint::PropertySchemaMap patternPropertiesSchemaMap; { - // Create a child schema for the 'category' property that requires one - // of several possible values. - Schema &propertySchema = propertySchemaMap["category"]; + // Prepare an enum constraint requires a document to be equal to at + // least one of a set of possible values EnumConstraint::Values enumConstraintValues; enumConstraintValues.push_back(new RapidJsonFrozenValue("album")); enumConstraintValues.push_back(new RapidJsonFrozenValue("book")); enumConstraintValues.push_back(new RapidJsonFrozenValue("other")); enumConstraintValues.push_back(new RapidJsonFrozenValue("video")); - propertySchema.addConstraint(new EnumConstraint(enumConstraintValues)); + + // Create a subschema, owned by the root schema, with a constraint + const Subschema *subschema = schema.createSubschema(); + schema.addConstraintToSubschema(EnumConstraint(enumConstraintValues), + subschema); + + // Include subschema in properties constraint + propertySchemaMap["category"] = subschema; } { // Create a child schema for the 'description' property that requires // a string, but does not enforce any length constraints. - Schema &propertySchema = propertySchemaMap["description"]; - propertySchema.addConstraint(new TypeConstraint(TypeConstraint::kString)); + const Subschema *subschema = schema.createSubschema(); + schema.addConstraintToSubschema(TypeConstraint(TypeConstraint::kString), + subschema); + + // Include subschema in properties constraint + propertySchemaMap["description"] = subschema; } { // Create a child schema for the 'price' property, that requires a // number with a value greater than zero. - Schema &propertySchema = propertySchemaMap["price"]; - propertySchema.addConstraint(new MinimumConstraint(0.0, true)); - propertySchema.addConstraint(new TypeConstraint(TypeConstraint::kNumber)); + const Subschema *subschema = schema.createSubschema(); + schema.addConstraintToSubschema(MinimumConstraint(0.0, true), subschema); + schema.addConstraintToSubschema(TypeConstraint(TypeConstraint::kNumber), + subschema); + + // Include subschema in properties constraint + propertySchemaMap["price"] = subschema; } { // Create a child schema for the 'title' property that requires a string // that is between 1 and 200 characters in length. - Schema &propertySchema = propertySchemaMap["title"]; - propertySchema.addConstraint(new MaxLengthConstraint(200)); - propertySchema.addConstraint(new MinLengthConstraint(1)); - propertySchema.addConstraint(new TypeConstraint(TypeConstraint::kString)); + const Subschema *subschema = schema.createSubschema(); + schema.addConstraintToSubschema(MaxLengthConstraint(200), subschema); + schema.addConstraintToSubschema(MinLengthConstraint(1), subschema); + schema.addConstraintToSubschema(TypeConstraint(TypeConstraint::kString), + subschema); + + // Include subschema in properties constraint + propertySchemaMap["title"] = subschema; } // Add a PropertiesConstraint to the schema, with the properties defined - // above, no pattern properties, and with additional property schemas - // prohibited. - schema.addConstraint(new PropertiesConstraint( - propertySchemaMap, patternPropertiesSchemaMap)); + // above, no pattern properties or additional property schemas + schema.addConstraint(PropertiesConstraint(propertySchemaMap)); } void addRequiredConstraint(Schema &schema) @@ -176,10 +192,10 @@ int main(int argc, char *argv[]) addTypeConstraint(schema); // Perform validation - Validator validator(schema); + Validator validator; ValidationResults results; RapidJsonAdapter targetDocumentAdapter(targetDocument); - if (!validator.validate(targetDocumentAdapter, &results)) { + if (!validator.validate(schema, targetDocumentAdapter, &results)) { std::cerr << "Validation failed." << endl; ValidationResults::Error error; unsigned int errorNum = 1; diff --git a/examples/external_schema.cpp b/examples/external_schema.cpp index ab489b3..b0a5fcd 100644 --- a/examples/external_schema.cpp +++ b/examples/external_schema.cpp @@ -60,11 +60,10 @@ int main(int argc, char *argv[]) } // Perform validation - Validator validator(schema); - validator.setStrict(false); + Validator validator(Validator::kWeakTypes); ValidationResults results; RapidJsonAdapter targetDocumentAdapter(targetDocument); - if (!validator.validate(targetDocumentAdapter, &results)) { + if (!validator.validate(schema, targetDocumentAdapter, &results)) { std::cerr << "Validation failed." << endl; ValidationResults::Error error; unsigned int errorNum = 1; diff --git a/include/valijson/constraints/concrete_constraints.hpp b/include/valijson/constraints/concrete_constraints.hpp index 50b8b59..6fb824c 100644 --- a/include/valijson/constraints/concrete_constraints.hpp +++ b/include/valijson/constraints/concrete_constraints.hpp @@ -16,14 +16,14 @@ #ifndef __VALIJSON_CONSTRAINTS_CONCRETE_CONSTRAINTS_HPP #define __VALIJSON_CONSTRAINTS_CONCRETE_CONSTRAINTS_HPP +#include +#include + #include +#include #include #include - -#include -#include -#include -#include +#include #include #include @@ -44,7 +44,7 @@ namespace constraints { */ struct AllOfConstraint: BasicConstraint { - typedef boost::ptr_vector Schemas; + typedef std::vector Schemas; AllOfConstraint(const Schemas &schemas) : schemas(schemas) { } @@ -62,7 +62,7 @@ struct AllOfConstraint: BasicConstraint */ struct AnyOfConstraint: BasicConstraint { - typedef boost::ptr_vector Schemas; + typedef std::vector Schemas; AnyOfConstraint(const Schemas &schemas) : schemas(schemas) { } @@ -84,7 +84,8 @@ struct DependenciesConstraint: BasicConstraint typedef std::map PropertyDependenciesMap; // A mapping from property names to dependent schemas - typedef boost::ptr_map PropertyDependentSchemasMap; + typedef std::map + PropertyDependentSchemasMap; DependenciesConstraint(const PropertyDependenciesMap &dependencies, const PropertyDependentSchemasMap &dependentSchemas) @@ -117,7 +118,7 @@ struct EnumConstraint: BasicConstraint */ struct ItemsConstraint: BasicConstraint { - typedef boost::ptr_vector Schemas; + typedef std::vector Schemas; /** * @brief Construct a singular item constraint that allows no additional @@ -125,8 +126,9 @@ struct ItemsConstraint: BasicConstraint * * @param itemSchema */ - ItemsConstraint(const Schema &itemSchema) - : itemSchema(new Schema(itemSchema)) { } + ItemsConstraint(const Subschema *itemSchema) + : itemSchema(itemSchema), + additionalItemsSchema(NULL) { } /** * @brief Construct a singular item schema that allows additional items @@ -134,10 +136,10 @@ struct ItemsConstraint: BasicConstraint * @param itemSchema * @param additionalItemsSchema */ - ItemsConstraint(const Schema &itemSchema, - const Schema &additionalItemsSchema) - : itemSchema(new Schema(itemSchema)), - additionalItemsSchema(new Schema(additionalItemsSchema)) { } + ItemsConstraint(const Subschema *itemSchema, + const Subschema *additionalItemsSchema) + : itemSchema(itemSchema), + additionalItemsSchema(additionalItemsSchema) { } /** * @brief Construct a plural items constraint that does not allow for @@ -146,7 +148,9 @@ struct ItemsConstraint: BasicConstraint * @param itemSchemas collection of item schemas */ ItemsConstraint(const Schemas &itemSchemas) - : itemSchemas(new Schemas(itemSchemas)) { } + : itemSchema(NULL), + itemSchemas(itemSchemas), + additionalItemsSchema(NULL) { } /** * @brief Construct a plural items constraint that allows additional items @@ -155,21 +159,22 @@ struct ItemsConstraint: BasicConstraint * @param additionalItemsSchema */ ItemsConstraint(const Schemas &itemSchemas, - const Schema &additionalItemsSchema) - : itemSchemas(new Schemas(itemSchemas)), - additionalItemsSchema(new Schema(additionalItemsSchema)) { } + const Subschema *additionalItemsSchema) + : itemSchema(NULL), + itemSchemas(itemSchemas), + additionalItemsSchema(additionalItemsSchema) { } /** * @brief Copy constructor */ ItemsConstraint(const ItemsConstraint &other) - : itemSchema(other.itemSchema ? new Schema(*other.itemSchema.get()) : NULL), - itemSchemas(other.itemSchemas ? new Schemas(*other.itemSchemas.get()) : NULL), - additionalItemsSchema(other.additionalItemsSchema ? new Schema(*other.additionalItemsSchema.get()) : NULL) { } + : itemSchema(other.itemSchema), + itemSchemas(other.itemSchemas), + additionalItemsSchema(other.additionalItemsSchema) { } - const boost::scoped_ptr itemSchema; - const boost::scoped_ptr itemSchemas; - const boost::scoped_ptr additionalItemsSchema; + const Subschema* itemSchema; + const Schemas itemSchemas; + const Subschema* additionalItemsSchema; }; /** @@ -265,25 +270,17 @@ struct MinPropertiesConstraint: BasicConstraint }; /** - * @brief Represents a 'multipleOf' or 'divisibleBy' constraint for decimals + * @brief Represents a 'multipleOf' or 'divisibleBy' constraint */ -struct MultipleOfDecimalConstraint: BasicConstraint +struct MultipleOfConstraint: BasicConstraint { - MultipleOfDecimalConstraint(double multipleOf) - : multipleOf(multipleOf) { } + explicit MultipleOfConstraint(int64_t value) + : value(value) { } - const double multipleOf; -}; + explicit MultipleOfConstraint(double value) + : value(value) { } -/** - * @brief Represents a 'multipleOf' or 'divisibleBy' constraint for int64_t - */ -struct MultipleOfIntegerConstraint: BasicConstraint -{ - MultipleOfIntegerConstraint(int64_t multipleOf) - : multipleOf(multipleOf) { } - - const int64_t multipleOf; + const boost::variant value; }; /** @@ -291,13 +288,13 @@ struct MultipleOfIntegerConstraint: BasicConstraint */ struct NotConstraint: BasicConstraint { - NotConstraint(const Schema &schema) - : schema(new Schema(schema)) { } + NotConstraint(const Subschema *schema) + : schema(schema) { } NotConstraint(const NotConstraint &other) - : schema(other.schema ? new Schema(*other.schema) : NULL) { } + : schema(other.schema) { } - const boost::scoped_ptr schema; + const Subschema *schema; }; /** @@ -305,7 +302,7 @@ struct NotConstraint: BasicConstraint */ struct OneOfConstraint: BasicConstraint { - typedef boost::ptr_vector Schemas; + typedef std::vector Schemas; OneOfConstraint(const Schemas &schemas) : schemas(schemas) { } @@ -331,29 +328,33 @@ struct PatternConstraint: BasicConstraint */ struct PropertiesConstraint: BasicConstraint { - typedef boost::ptr_map PropertySchemaMap; + typedef std::map PropertySchemaMap; + + PropertiesConstraint(const PropertySchemaMap &properties) + : properties(properties), + additionalProperties(NULL) { } PropertiesConstraint(const PropertySchemaMap &properties, const PropertySchemaMap &patternProperties) : properties(properties), - patternProperties(patternProperties) { } + patternProperties(patternProperties), + additionalProperties(NULL) { } PropertiesConstraint(const PropertySchemaMap &properties, const PropertySchemaMap &patternProperties, - const Schema &additionalProperties) + const Subschema *additionalProperties) : properties(properties), patternProperties(patternProperties), - additionalProperties(new Schema(additionalProperties)) { } + additionalProperties(additionalProperties) { } PropertiesConstraint(const PropertiesConstraint &other) : properties(other.properties), patternProperties(other.patternProperties), - additionalProperties(other.additionalProperties ? - new Schema(*other.additionalProperties.get()) : NULL) {} + additionalProperties(other.additionalProperties) {} const PropertySchemaMap properties; const PropertySchemaMap patternProperties; - const boost::scoped_ptr additionalProperties; + const Subschema *additionalProperties; }; @@ -391,15 +392,15 @@ struct TypeConstraint: BasicConstraint typedef std::set JsonTypes; - typedef boost::ptr_vector Schemas; + typedef std::vector Schemas; TypeConstraint(const JsonType jsonType) : jsonTypes(makeJsonTypes(jsonType)) { } - TypeConstraint(const JsonTypes jsonTypes) + TypeConstraint(const JsonTypes &jsonTypes) : jsonTypes(jsonTypes) { } - TypeConstraint(const JsonTypes jsonTypes, + TypeConstraint(const JsonTypes &jsonTypes, const Schemas &schemas) : jsonTypes(jsonTypes), schemas(schemas) { } diff --git a/include/valijson/constraints/constraint_visitor.hpp b/include/valijson/constraints/constraint_visitor.hpp index d4533da..e75a3cc 100644 --- a/include/valijson/constraints/constraint_visitor.hpp +++ b/include/valijson/constraints/constraint_visitor.hpp @@ -19,8 +19,7 @@ struct MinimumConstraint; struct MinItemsConstraint; struct MinLengthConstraint; struct MinPropertiesConstraint; -struct MultipleOfDecimalConstraint; -struct MultipleOfIntegerConstraint; +struct MultipleOfConstraint; struct NotConstraint; struct OneOfConstraint; struct PatternConstraint; @@ -48,8 +47,7 @@ protected: typedef constraints::MinItemsConstraint MinItemsConstraint; typedef constraints::MinLengthConstraint MinLengthConstraint; typedef constraints::MinPropertiesConstraint MinPropertiesConstraint; - typedef constraints::MultipleOfDecimalConstraint MultipleOfDecimalConstraint; - typedef constraints::MultipleOfIntegerConstraint MultipleOfIntegerConstraint; + typedef constraints::MultipleOfConstraint MultipleOfConstraint; typedef constraints::NotConstraint NotConstraint; typedef constraints::OneOfConstraint OneOfConstraint; typedef constraints::PatternConstraint PatternConstraint; @@ -73,8 +71,7 @@ public: virtual bool visit(const MinItemsConstraint &) = 0; virtual bool visit(const MinLengthConstraint &) = 0; virtual bool visit(const MinPropertiesConstraint &) = 0; - virtual bool visit(const MultipleOfDecimalConstraint &) = 0; - virtual bool visit(const MultipleOfIntegerConstraint &) = 0; + virtual bool visit(const MultipleOfConstraint &) = 0; virtual bool visit(const NotConstraint &) = 0; virtual bool visit(const OneOfConstraint &) = 0; virtual bool visit(const PatternConstraint &) = 0; diff --git a/include/valijson/schema.hpp b/include/valijson/schema.hpp index 6cd8e51..4f220ae 100644 --- a/include/valijson/schema.hpp +++ b/include/valijson/schema.hpp @@ -21,7 +21,8 @@ public: /** * @brief Construct a new Schema instance with no constraints */ - Schema() {} + Schema() + : sharedEmptySubschema(newSubschema()) { } /** * @brief Clean up and free all memory managed by the Schema @@ -31,11 +32,14 @@ public: */ virtual ~Schema() { + sharedEmptySubschema->~Subschema(); + ::operator delete((void*)(sharedEmptySubschema)); + sharedEmptySubschema = NULL; + try { - while (!subschemaSet.empty()) { - std::set::iterator itr = subschemaSet.begin(); + for (std::set::iterator itr = subschemaSet.begin(); + itr != subschemaSet.end(); ++itr) { Subschema *subschema = *itr; - subschemaSet.erase(itr); subschema->~Subschema(); // TODO: Replace with custom free function ::operator delete(subschema); @@ -60,20 +64,9 @@ public: void addConstraintToSubschema(const Constraint &constraint, const Subschema *subschema) { - if (subschema == this) { - addConstraint(constraint); - return; - } - - Subschema *noConst = const_cast(subschema); - if (subschemaSet.find(noConst) == subschemaSet.end()) { - throw std::runtime_error( - "Subschema pointer is not owned by this Schema instance"); - } - // TODO: Check heirarchy for subschemas that do not belong... - noConst->addConstraint(constraint); + mutableSubschema(subschema)->addConstraint(constraint); } /** @@ -83,25 +76,7 @@ public: */ const Subschema * createSubschema() { - // TODO: Replace with custom malloc function - void *ptr = ::operator new(sizeof(Subschema)); - if (!ptr) { - throw std::runtime_error( - "Failed to allocate memory for sub-schema"); - } - - Subschema *subschema = NULL; - try { - subschema = new (ptr) Subschema(); - if (!subschema) { - throw std::runtime_error("Failed to construct sub-schema"); - } - ptr = NULL; - } catch (...) { - // TODO: Replace with custom free function - ::operator delete(ptr); - throw; - } + Subschema *subschema = newSubschema(); try { if (!subschemaSet.insert(subschema).second) { @@ -111,13 +86,21 @@ public: } catch (...) { subschema->~Subschema(); // TODO: Replace with custom free function - ::operator delete(ptr); + ::operator delete(subschema); throw; } return subschema; } + /** + * @brief Return a pointer to the shared empty schema + */ + const Subschema * emptySubschema() const + { + return sharedEmptySubschema; + } + /** * @brief Get a pointer to the root sub-schema of this Schema instance */ @@ -126,10 +109,102 @@ public: return this; } + /** + * @brief Update the description for one of the sub-schemas owned by this + * Schema instance + * + * @param subschema sub-schema to update + * @param description new description + */ + void setSubschemaDescription(const Subschema *subschema, + const std::string &description) + { + mutableSubschema(subschema)->setDescription(description); + } + + /** + * @brief Update the ID for one of the sub-schemas owned by this Schema + * instance + * + * @param subschema sub-schema to update + * @param id new ID + */ + void setSubschemaId(const Subschema *subschema, const std::string &id) + { + mutableSubschema(subschema)->setId(id); + } + + /** + * @brief Update the title for one of the sub-schemas owned by this Schema + * instance + * + * @param subschema sub-schema to update + * @param title new title + */ + void setSubschemaTitle(const Subschema *subschema, const std::string &title) + { + mutableSubschema(subschema)->setTitle(title); + } + private: + // Disable copy construction + Schema(const Schema &); + + // Disable copy assignment + Schema & operator=(const Schema &); + + static Subschema *newSubschema() + { + // TODO: Replace with custom alloc function + void *ptr = ::operator new(sizeof(Subschema)); + if (!ptr) { + throw std::runtime_error( + "Failed to allocate memory for shared empty sub-schema"); + } + + Subschema *subschema = NULL; + try { + subschema = new (ptr) Subschema(); + if (!subschema) { + throw std::runtime_error( + "Failed to construct shared empty sub-schema"); + } + ptr = NULL; + } catch (...) { + // TODO: Replace with custom free function + ::operator delete(ptr); + throw; + } + + return subschema; + } + + Subschema * mutableSubschema(const Subschema *subschema) + { + if (subschema == this) { + return this; + } + + if (subschema == sharedEmptySubschema) { + throw std::runtime_error( + "Cannot modify the shared empty sub-schema"); + } + + Subschema *noConst = const_cast(subschema); + if (subschemaSet.find(noConst) == subschemaSet.end()) { + throw std::runtime_error( + "Subschema pointer is not owned by this Schema instance"); + } + + return noConst; + } + /// Set of Subschema instances owned by this schema std::set subschemaSet; + + /// Empty schema that can be reused by multiple constraints + const Subschema *sharedEmptySubschema; }; } // namespace valijson diff --git a/include/valijson/schema_parser.hpp b/include/valijson/schema_parser.hpp index 475ba3c..2cd89f7 100644 --- a/include/valijson/schema_parser.hpp +++ b/include/valijson/schema_parser.hpp @@ -61,7 +61,7 @@ public: /** * @brief Populate a Schema object from JSON Schema document * - * When processing Draft 3 schemas, the parentSchema and ownName pointers + * When processing Draft 3 schemas, the parentSubschema and ownName pointers * should be set in contexts where a 'required' constraint would be valid. * These are used to add a RequiredConstraint object to the Schema that * contains the required property. @@ -77,7 +77,8 @@ public: boost::optional::Type> fetchDoc = boost::none) { - populateSchema(node, node, schema, boost::none, "", fetchDoc, NULL, NULL); + populateSchema(schema, node, node, schema, boost::none, "", + fetchDoc, NULL, NULL); } private: @@ -85,34 +86,38 @@ private: /** * @brief Populate a Schema object from JSON Schema document * - * When processing Draft 3 schemas, the parentSchema and ownName pointers + * When processing Draft 3 schemas, the parentSubschema and ownName pointers * should be set in contexts where a 'required' constraint would be valid. * These are used to add a RequiredConstraint object to the Schema that * contains the required property. * - * @param rootNode Reference to the node from which JSON References - * will be resolved when they refer to the current - * document - * @param node Reference to node to parse - * @param schema Reference to Schema to populate - * @param currentScope URI for current resolution scope - * @param nodePath JSON Pointer representing path to current node - * @param fetchDoc Function to fetch remote JSON documents (optional) - * @param parentSchema Optional pointer to the parent schema, used to - * support required keyword in Draft 3. - * @param ownName Optional pointer to a node name, used to support - * the 'required' keyword in Draft 3. + * @param rootSchema The Schema instance, and root subschema, through + * which other subschemas can be created and + * modified + * @param rootNode Reference to the node from which JSON References + * will be resolved when they refer to the current + * document + * @param node Reference to node to parse + * @param schema Reference to Schema to populate + * @param currentScope URI for current resolution scope + * @param nodePath JSON Pointer representing path to current node + * @param fetchDoc Optional function to fetch remote JSON documents + * @param parentSubschema Optional pointer to the parent schema, used to + * support required keyword in Draft 3 + * @param ownName Optional pointer to a node name, used to support + * the 'required' keyword in Draft 3 */ template void populateSchema( + Schema &rootSchema, const AdapterType &rootNode, const AdapterType &node, - Schema &schema, - boost::optional currentScope, + const Subschema &subschema, + const boost::optional currentScope, const std::string &nodePath, - boost::optional::Type> + const boost::optional::Type> fetchDoc = boost::none, - Schema *parentSchema = NULL, + const Subschema *parentSubschema = NULL, const std::string *ownName = NULL) { BOOST_STATIC_ASSERT_MSG((boost::is_convertiblesecond.asString(); - populateSchemaUsingJsonReference(jsonRef, rootNode, node, - schema, currentScope, nodePath, fetchDoc, - parentSchema, ownName); + populateSchemaUsingJsonReference(rootSchema, jsonRef, rootNode, + node, subschema, currentScope, nodePath, fetchDoc, + parentSubschema, ownName); return; } } @@ -143,29 +148,36 @@ private: if ((itr = object.find("id")) != object.end()) { if (itr->second.maybeString()) { - schema.setId(itr->second.asString()); + rootSchema.setSubschemaId(&subschema, itr->second.asString()); } } if ((itr = object.find("allOf")) != object.end()) { - schema.addConstraint(makeAllOfConstraint(rootNode, itr->second, - currentScope, nodePath + "/allOf", fetchDoc)); + rootSchema.addConstraintToSubschema( + makeAllOfConstraint(rootSchema, rootNode, itr->second, + currentScope, nodePath + "/allOf", fetchDoc), + &subschema); } if ((itr = object.find("anyOf")) != object.end()) { - schema.addConstraint(makeAnyOfConstraint(rootNode, itr->second, - currentScope, nodePath + "/anyOf", fetchDoc)); + rootSchema.addConstraintToSubschema( + makeAnyOfConstraint(rootSchema, rootNode, itr->second, + currentScope, nodePath + "/anyOf", fetchDoc), + &subschema); } if ((itr = object.find("dependencies")) != object.end()) { - schema.addConstraint(makeDependenciesConstraint(rootNode, - itr->second, currentScope, nodePath + "/dependencies", - fetchDoc)); + rootSchema.addConstraintToSubschema( + makeDependenciesConstraint(rootSchema, rootNode, + itr->second, currentScope, + nodePath + "/dependencies", fetchDoc), + &subschema); } if ((itr = object.find("description")) != object.end()) { if (itr->second.maybeString()) { - schema.setDescription(itr->second.asString()); + rootSchema.setSubschemaDescription(&subschema, + itr->second.asString()); } else { throw std::runtime_error( "'description' attribute should have a string value"); @@ -174,14 +186,16 @@ private: if ((itr = object.find("divisibleBy")) != object.end()) { if (version == kDraft3) { - schema.addConstraint(makeMultipleOfConstraint(itr->second)); + rootSchema.addConstraintToSubschema( + makeMultipleOfConstraint(itr->second), &subschema); } else { throw std::runtime_error("'divisibleBy' constraint not valid after draft 3"); } } if ((itr = object.find("enum")) != object.end()) { - schema.addConstraint(makeEnumConstraint(itr->second)); + rootSchema.addConstraintToSubschema(makeEnumConstraint(itr->second), + &subschema); } { @@ -192,80 +206,110 @@ private: additionalitemsItr = object.find("additionalItems"); if (object.end() != itemsItr || object.end() != additionalitemsItr) { - schema.addConstraint(makeItemsConstraint(rootNode, - itemsItr != object.end() ? &itemsItr->second : NULL, - additionalitemsItr != object.end() ? &additionalitemsItr->second : NULL, - currentScope, nodePath + "/items", - nodePath + "/additionalItems", fetchDoc)); + rootSchema.addConstraintToSubschema( + makeItemsConstraint(rootSchema, rootNode, + itemsItr != object.end() ? + &itemsItr->second : NULL, + additionalitemsItr != object.end() ? + &additionalitemsItr->second : NULL, + currentScope, nodePath + "/items", + nodePath + "/additionalItems", fetchDoc), + &subschema); } } if ((itr = object.find("maximum")) != object.end()) { typename AdapterType::Object::const_iterator exclusiveMaximumItr = object.find("exclusiveMaximum"); if (exclusiveMaximumItr == object.end()) { - schema.addConstraint(makeMaximumConstraint(itr->second, NULL)); + rootSchema.addConstraintToSubschema( + makeMaximumConstraint(itr->second, NULL), + &subschema); } else { - schema.addConstraint(makeMaximumConstraint(itr->second, &exclusiveMaximumItr->second)); + rootSchema.addConstraintToSubschema( + makeMaximumConstraint(itr->second, + &exclusiveMaximumItr->second), + &subschema); } } else if (object.find("exclusiveMaximum") != object.end()) { - // throw exception + throw std::runtime_error( + "'exclusiveMaximum' constraint only valid if a 'maximum' " + "constraint is also present"); } if ((itr = object.find("maxItems")) != object.end()) { - schema.addConstraint(makeMaxItemsConstraint(itr->second)); + rootSchema.addConstraintToSubschema( + makeMaxItemsConstraint(itr->second), &subschema); } if ((itr = object.find("maxLength")) != object.end()) { - schema.addConstraint(makeMaxLengthConstraint(itr->second)); + rootSchema.addConstraintToSubschema( + makeMaxLengthConstraint(itr->second), &subschema); } if ((itr = object.find("maxProperties")) != object.end()) { - schema.addConstraint(makeMaxPropertiesConstraint(itr->second)); + rootSchema.addConstraintToSubschema( + makeMaxPropertiesConstraint(itr->second), &subschema); } if ((itr = object.find("minimum")) != object.end()) { typename AdapterType::Object::const_iterator exclusiveMinimumItr = object.find("exclusiveMinimum"); if (exclusiveMinimumItr == object.end()) { - schema.addConstraint(makeMinimumConstraint(itr->second, NULL)); + rootSchema.addConstraintToSubschema( + makeMinimumConstraint(itr->second, NULL), + &subschema); } else { - schema.addConstraint(makeMinimumConstraint(itr->second, &exclusiveMinimumItr->second)); + rootSchema.addConstraintToSubschema( + makeMinimumConstraint(itr->second, + &exclusiveMinimumItr->second), + &subschema); } } else if (object.find("exclusiveMinimum") != object.end()) { - // throw exception + throw std::runtime_error( + "'exclusiveMinimum' constraint only valid if a 'minimum' " + "constraint is also present"); } if ((itr = object.find("minItems")) != object.end()) { - schema.addConstraint(makeMinItemsConstraint(itr->second)); + rootSchema.addConstraintToSubschema( + makeMinItemsConstraint(itr->second), &subschema); } if ((itr = object.find("minLength")) != object.end()) { - schema.addConstraint(makeMinLengthConstraint(itr->second)); + rootSchema.addConstraintToSubschema( + makeMinLengthConstraint(itr->second), &subschema); } if ((itr = object.find("minProperties")) != object.end()) { - schema.addConstraint(makeMinPropertiesConstraint(itr->second)); + rootSchema.addConstraintToSubschema( + makeMinPropertiesConstraint(itr->second), &subschema); } if ((itr = object.find("multipleOf")) != object.end()) { if (version == kDraft3) { throw std::runtime_error("'multipleOf' constraint not available in draft 3"); } else { - schema.addConstraint(makeMultipleOfConstraint(itr->second)); + rootSchema.addConstraintToSubschema( + makeMultipleOfConstraint(itr->second), &subschema); } } if ((itr = object.find("not")) != object.end()) { - schema.addConstraint(makeNotConstraint(rootNode, itr->second, - currentScope, nodePath + "/not", fetchDoc)); + rootSchema.addConstraintToSubschema( + makeNotConstraint(rootSchema, rootNode, itr->second, + currentScope, nodePath + "/not", fetchDoc), + &subschema); } if ((itr = object.find("oneOf")) != object.end()) { - schema.addConstraint(makeOneOfConstraint(rootNode, itr->second, - currentScope, nodePath + "/oneOf", fetchDoc)); + rootSchema.addConstraintToSubschema( + makeOneOfConstraint(rootSchema, rootNode, itr->second, + currentScope, nodePath + "/oneOf", fetchDoc), + &subschema); } if ((itr = object.find("pattern")) != object.end()) { - schema.addConstraint(makePatternConstraint(itr->second)); + rootSchema.addConstraintToSubschema( + makePatternConstraint(itr->second), &subschema); } { @@ -278,33 +322,45 @@ private: if (object.end() != propertiesItr || object.end() != patternPropertiesItr || object.end() != additionalPropertiesItr) { - schema.addConstraint(makePropertiesConstraint(rootNode, - propertiesItr != object.end() ? &propertiesItr->second : NULL, - patternPropertiesItr != object.end() ? &patternPropertiesItr->second : NULL, - additionalPropertiesItr != object.end() ? &additionalPropertiesItr->second : NULL, - currentScope, nodePath + "/properties", - nodePath + "/patternProperties", - nodePath + "/additionalProperties", fetchDoc, &schema)); + rootSchema.addConstraintToSubschema( + makePropertiesConstraint(rootSchema, rootNode, + propertiesItr != object.end() ? + &propertiesItr->second : NULL, + patternPropertiesItr != object.end() ? + &patternPropertiesItr->second : NULL, + additionalPropertiesItr != object.end() ? + &additionalPropertiesItr->second : NULL, + currentScope, nodePath + "/properties", + nodePath + "/patternProperties", + nodePath + "/additionalProperties", + fetchDoc, &subschema), + &subschema); } } if ((itr = object.find("required")) != object.end()) { if (version == kDraft3) { - if (parentSchema && ownName) { - if (constraints::Constraint *c = makeRequiredConstraintForSelf(itr->second, *ownName)) { - parentSchema->addConstraint(c); + if (parentSubschema && ownName) { + boost::optional + constraint = makeRequiredConstraintForSelf( + itr->second, *ownName); + if (constraint) { + rootSchema.addConstraintToSubschema(*constraint, + parentSubschema); } } else { throw std::runtime_error("'required' constraint not valid here"); } } else { - schema.addConstraint(makeRequiredConstraint(itr->second)); + rootSchema.addConstraintToSubschema( + makeRequiredConstraint(itr->second), &subschema); } } if ((itr = object.find("title")) != object.end()) { if (itr->second.maybeString()) { - schema.setTitle(itr->second.asString()); + rootSchema.setSubschemaTitle(&subschema, + itr->second.asString()); } else { throw std::runtime_error( "'title' attribute should have a string value"); @@ -312,14 +368,17 @@ private: } if ((itr = object.find("type")) != object.end()) { - schema.addConstraint(makeTypeConstraint(rootNode, itr->second, - currentScope, nodePath + "/type", fetchDoc)); + rootSchema.addConstraintToSubschema( + makeTypeConstraint(rootSchema, rootNode, itr->second, + currentScope, nodePath + "/type", fetchDoc), + &subschema); } if ((itr = object.find("uniqueItems")) != object.end()) { - constraints::Constraint *constraint = makeUniqueItemsConstraint(itr->second); + boost::optional constraint = + makeUniqueItemsConstraint(itr->second); if (constraint) { - schema.addConstraint(constraint); + rootSchema.addConstraintToSubschema(*constraint, &subschema); } } } @@ -330,31 +389,35 @@ private: * Allows JSON references to be used with minimal changes to the parser * helper functions. * - * @param jsonRef String containing JSON Reference value - * @param rootNode Reference to the node from which JSON References - * will be resolved when they refer to the current - * document; used for recursive parsing of schemas - * @param node Reference to node to parse - * @param schema Reference to Schema to populate - * @param currentScope URI for current resolution scope - * @param nodePath JSON Pointer representing path to current node - * @param fetchDoc Function to fetch remote JSON documents (optional) - * @param parentSchema Optional pointer to the parent schema, used to - * support required keyword in Draft 3. - * @param ownName Optional pointer to a node name, used to support - * the 'required' keyword in Draft 3. + * @param rootSchema The Schema instance, and root subschema, through + * which other subschemas can be created and + * modified + * @param jsonRef String containing JSON Reference value + * @param rootNode Reference to the node from which JSON References + * will be resolved when they refer to the current + * document; used for recursive parsing of schemas + * @param node Reference to node to parse + * @param schema Reference to Schema to populate + * @param currentScope URI for current resolution scope + * @param nodePath JSON Pointer representing path to current node + * @param fetchDoc Optional function to fetch remote JSON documents + * @param parentSubschema Optional pointer to the parent schema, used to + * support required keyword in Draft 3 + * @param ownName Optional pointer to a node name, used to support + * the 'required' keyword in Draft 3 */ template void populateSchemaUsingJsonReference( + Schema &rootSchema, const std::string &jsonRef, const AdapterType &rootNode, const AdapterType &node, - Schema &schema, - boost::optional currentScope, + const Subschema &subschema, + const boost::optional currentScope, const std::string &nodePath, - boost::optional::Type> + const boost::optional::Type> fetchDoc, - Schema *parentSchema = NULL, + const Subschema *parentSubschema = NULL, const std::string *ownName = NULL) { // Returns a document URI if the reference points somewhere @@ -389,22 +452,24 @@ private: *docPtr, jsonPointer); // Resolve reference against retrieved document - populateSchema(ref, ref, schema, currentScope, - nodePath, fetchDoc, parentSchema, ownName); + populateSchema(rootSchema, ref, ref, subschema, + currentScope, nodePath, fetchDoc, parentSubschema, ownName); } else { const AdapterType &ref = internal::json_pointer::resolveJsonPointer( rootNode, jsonPointer); // Resolve reference against current document - populateSchema(rootNode, ref, schema, currentScope, - nodePath, fetchDoc, parentSchema, ownName); + populateSchema(rootSchema, rootNode, ref, subschema, + currentScope, nodePath, fetchDoc, parentSubschema, ownName); } } /** * @brief Make a new AllOfConstraint object * + * @param rootSchema The Schema instance, and root subschema, through + * which other subschemas can be created and modified * @param rootNode Reference to the node from which JSON References * will be resolved when they refer to the current * document; used for recursive parsing of schemas @@ -417,15 +482,18 @@ private: * caller */ template - constraints::AllOfConstraint* makeAllOfConstraint( + constraints::AllOfConstraint makeAllOfConstraint( + Schema &rootSchema, const AdapterType &rootNode, const AdapterType &node, - boost::optional currentScope, + const boost::optional currentScope, const std::string &nodePath, - boost::optional::Type > fetchDoc) + const boost::optional< + typename FetchDocumentFunction::Type > fetchDoc) { if (!node.maybeArray()) { - throw std::runtime_error("Expected array value for 'allOf' constraint."); + throw std::runtime_error( + "Expected array value for 'allOf' constraint."); } constraints::AllOfConstraint::Schemas childSchemas; @@ -434,22 +502,26 @@ private: if (schemaNode.maybeObject()) { const std::string childPath = nodePath + "/" + boost::lexical_cast(index); - childSchemas.push_back(new Schema); - populateSchema(rootNode, schemaNode, - childSchemas.back(), currentScope, childPath, fetchDoc); + childSchemas.push_back(rootSchema.createSubschema()); + populateSchema(rootSchema, rootNode, schemaNode, + *childSchemas.back(), currentScope, childPath, + fetchDoc); index++; } else { - throw std::runtime_error("Expected array element to be an object value in 'allOf' constraint."); + throw std::runtime_error( + "Expected array element to be an object value in " + "'allOf' constraint."); } } - /// @todo: bypass deep copy of the child schemas - return new constraints::AllOfConstraint(childSchemas); + return constraints::AllOfConstraint(childSchemas); } /** * @brief Make a new AnyOfConstraint object * + * @param rootSchema The Schema instance, and root subschema, through + * which other subschemas can be created and modified * @param rootNode Reference to the node from which JSON References * will be resolved when they refer to the current * document; used for recursive parsing of schemas @@ -462,15 +534,18 @@ private: * caller */ template - constraints::AnyOfConstraint* makeAnyOfConstraint( + constraints::AnyOfConstraint makeAnyOfConstraint( + Schema &rootSchema, const AdapterType &rootNode, const AdapterType &node, - boost::optional currentScope, + const boost::optional currentScope, const std::string &nodePath, - boost::optional::Type > fetchDoc) + const boost::optional< + typename FetchDocumentFunction::Type > fetchDoc) { if (!node.maybeArray()) { - throw std::runtime_error("Expected array value for 'anyOf' constraint."); + throw std::runtime_error( + "Expected array value for 'anyOf' constraint."); } constraints::AnyOfConstraint::Schemas childSchemas; @@ -479,17 +554,19 @@ private: if (schemaNode.maybeObject()) { const std::string childPath = nodePath + "/" + boost::lexical_cast(index); - childSchemas.push_back(new Schema); - populateSchema(rootNode, schemaNode, - childSchemas.back(), currentScope, childPath, fetchDoc); + childSchemas.push_back(rootSchema.createSubschema()); + populateSchema(rootSchema, rootNode, schemaNode, + *childSchemas.back(), currentScope, childPath, + fetchDoc); index++; } else { - throw std::runtime_error("Expected array element to be an object value in 'anyOf' constraint."); + throw std::runtime_error( + "Expected array element to be an object value in " + "'anyOf' constraint."); } } - /// @todo: bypass deep copy of the child schemas - return new constraints::AnyOfConstraint(childSchemas); + return constraints::AnyOfConstraint(childSchemas); } /** @@ -512,6 +589,8 @@ private: * If the format of any part of the the dependency node does not match one * of these formats, an exception will be thrown. * + * @param rootSchema The Schema instance, and root subschema, through + * which other subschemas can be created and modified * @param rootNode Reference to the node from which JSON References * will be resolved when they refer to the current * document; used for recursive parsing of schemas @@ -525,13 +604,14 @@ private: * caller */ template - constraints::DependenciesConstraint* makeDependenciesConstraint( + constraints::DependenciesConstraint makeDependenciesConstraint( + Schema &rootSchema, const AdapterType &rootNode, const AdapterType &node, - boost::optional currentScope, + const boost::optional currentScope, const std::string &nodePath, - boost::optional::Type > - fetchDoc) + const boost::optional< + typename FetchDocumentFunction::Type > fetchDoc) { if (!node.maybeObject()) { throw std::runtime_error("Expected object value for 'dependencies' constraint."); @@ -570,9 +650,10 @@ private: // process it as a dependent schema. } else if (member.second.isObject()) { // Parse dependent subschema - Schema &childSchema = pdsm[member.first]; - populateSchema(rootNode, member.second, - childSchema, currentScope, nodePath, fetchDoc); + const Subschema *childSubschema = rootSchema.createSubschema(); + pdsm[member.first] = childSubschema; + populateSchema(rootSchema, rootNode, member.second, + *childSubschema, currentScope, nodePath, fetchDoc); // If we're supposed to be parsing a Draft3 schema, then the value // of the dependency mapping can also be a string containing the @@ -586,7 +667,7 @@ private: } } - return new constraints::DependenciesConstraint(pdm, pdsm); + return constraints::DependenciesConstraint(pdm, pdsm); } /** @@ -598,7 +679,7 @@ private: * @return pointer to a new EnumConstraint that belongs to the caller */ template - constraints::EnumConstraint* makeEnumConstraint( + constraints::EnumConstraint makeEnumConstraint( const AdapterType &node) { // Make a copy of each value in the enum array @@ -611,12 +692,15 @@ private: /// the EnumConstraint. Move semantics in C++11 should make it possible /// to avoid these copies without complicating the implementation of the /// EnumConstraint class. - return new constraints::EnumConstraint(values); + return constraints::EnumConstraint(values); } /** * @brief Make a new ItemsConstraint object. * + * @param rootSchema The Schema instance, and root subschema, + * through which other subschemas can be + * created and modified * @param rootNode Reference to the node from which JSON * References will be resolved when they refer * to the current document; used for recursive @@ -638,45 +722,47 @@ private: * @return pointer to a new ItemsConstraint that belongs to the caller */ template - constraints::ItemsConstraint* makeItemsConstraint( + constraints::ItemsConstraint makeItemsConstraint( + Schema &rootSchema, const AdapterType &rootNode, const AdapterType *items, const AdapterType *additionalItems, - boost::optional currentScope, + const boost::optional currentScope, const std::string &itemsPath, const std::string &additionalItemsPath, - boost::optional::Type > - fetchDoc) + const boost::optional< + typename FetchDocumentFunction::Type > fetchDoc) { // Construct a Schema object for the the additionalItems constraint, // if the additionalItems property is present - boost::scoped_ptr additionalItemsSchema; + const Subschema *additionalItemsSchema = NULL; if (additionalItems) { if (additionalItems->maybeBool()) { // If the value of the additionalItems property is a boolean // and is set to true, then additional array items do not need // to satisfy any constraints. if (additionalItems->asBool()) { - additionalItemsSchema.reset(new Schema()); + additionalItemsSchema = rootSchema.createSubschema(); } } else if (additionalItems->maybeObject()) { // If the value of the additionalItems property is an object, // then it should be parsed into a Schema object, which will be // used to validate additional array items. - additionalItemsSchema.reset(new Schema()); - populateSchema( - rootNode, *additionalItems, *additionalItemsSchema, - currentScope, additionalItemsPath, fetchDoc); + additionalItemsSchema = rootSchema.createSubschema(); + populateSchema(rootSchema, rootNode, + *additionalItems, *additionalItemsSchema, currentScope, + additionalItemsPath, fetchDoc); } else { // Any other format for the additionalItems property will result // in an exception being thrown. - throw std::runtime_error("Expected bool or object value for 'additionalItems'"); + throw std::runtime_error( + "Expected bool or object value for 'additionalItems'"); } } else { // The default value for the additionalItems property is an empty // object, which means that additional array items do not need to // satisfy any constraints. - additionalItemsSchema.reset(new Schema()); + additionalItemsSchema = rootSchema.createSubschema(); } // Construct a Schema object for each item in the items array, if an @@ -694,19 +780,20 @@ private: BOOST_FOREACH( const AdapterType v, items->getArray() ) { const std::string childPath = itemsPath + "/" + boost::lexical_cast(index); - itemSchemas.push_back(new Schema()); - Schema &childSchema = itemSchemas.back(); - populateSchema(rootNode, v, childSchema, - currentScope, childPath, fetchDoc); + itemSchemas.push_back(rootSchema.createSubschema()); + const Subschema &childSubschema = *itemSchemas.back(); + populateSchema(rootSchema, rootNode, v, + childSubschema, currentScope, childPath, fetchDoc); index++; } // Create an ItemsConstraint object using the appropriate // overloaded constructor. if (additionalItemsSchema) { - return new constraints::ItemsConstraint(itemSchemas, *additionalItemsSchema); + return constraints::ItemsConstraint(itemSchemas, + additionalItemsSchema); } else { - return new constraints::ItemsConstraint(itemSchemas); + return constraints::ItemsConstraint(itemSchemas); } } else if (items->isObject()) { @@ -714,42 +801,48 @@ private: // should contain a Schema that will be used to validate all // items in a target array. Any schema defined by the // additionalItems constraint will be ignored. - Schema childSchema; - populateSchema(rootNode, *items, childSchema, - currentScope, itemsPath, fetchDoc); + const Subschema *childSubschema = rootSchema.createSubschema(); + populateSchema(rootSchema, rootNode, *items, + *childSubschema, currentScope, itemsPath, fetchDoc); if (additionalItemsSchema) { - return new constraints::ItemsConstraint(childSchema, *additionalItemsSchema); + return constraints::ItemsConstraint(childSubschema, + additionalItemsSchema); } else { - return new constraints::ItemsConstraint(childSchema); + return constraints::ItemsConstraint(childSubschema); } } else if (items->maybeObject()) { // If a loosely-typed Adapter type is being used, then we'll // assume that an empty schema has been provided. - Schema childSchema; + const Subschema *childSubschema = rootSchema.createSubschema(); if (additionalItemsSchema) { - return new constraints::ItemsConstraint(childSchema, *additionalItemsSchema); + return constraints::ItemsConstraint(childSubschema, + additionalItemsSchema); } else { - return new constraints::ItemsConstraint(childSchema); + return constraints::ItemsConstraint(childSubschema); } } else { // All other formats will result in an exception being thrown. - throw std::runtime_error("Expected array or object value for 'items'."); + throw std::runtime_error( + "Expected array or object value for 'items'."); } } - Schema emptySchema; if (additionalItemsSchema) { - return new constraints::ItemsConstraint(emptySchema, *additionalItemsSchema); + return constraints::ItemsConstraint(rootSchema.emptySubschema(), + additionalItemsSchema); } - return new constraints::ItemsConstraint(emptySchema); + return constraints::ItemsConstraint(rootSchema.emptySubschema()); } /** * @brief Make a new MaximumConstraint object. * + * @param rootSchema The Schema instance, and root subschema, + * through which other subschemas can be + * created and modified * @param rootNode Reference to the node from which JSON * References will be resolved when they refer * to the current document; used for recursive @@ -762,7 +855,7 @@ private: * @return pointer to a new MaximumConstraint that belongs to the caller */ template - constraints::MaximumConstraint* makeMaximumConstraint( + constraints::MaximumConstraint makeMaximumConstraint( const AdapterType &node, const AdapterType *exclusiveMaximum) { @@ -771,16 +864,20 @@ private: if (exclusiveMaximum->maybeBool()) { exclusiveMaximumValue = exclusiveMaximum->asBool(); } else { - throw std::runtime_error("Expected boolean value for exclusiveMaximum constraint."); + throw std::runtime_error( + "Expected boolean value for exclusiveMaximum " + "constraint."); } } if (node.maybeDouble()) { double maximumValue = node.asDouble(); - return new constraints::MaximumConstraint(maximumValue, exclusiveMaximumValue); + return constraints::MaximumConstraint(maximumValue, + exclusiveMaximumValue); } - throw std::runtime_error("Expected numeric value for maximum constraint."); + throw std::runtime_error( + "Expected numeric value for maximum constraint."); } /** @@ -792,17 +889,18 @@ private: * @return pointer to a new MaxItemsConstraint that belongs to the caller. */ template - constraints::MaxItemsConstraint* makeMaxItemsConstraint( + constraints::MaxItemsConstraint makeMaxItemsConstraint( const AdapterType &node) { if (node.maybeInteger()) { int64_t value = node.asInteger(); if (value >= 0) { - return new constraints::MaxItemsConstraint(value); + return constraints::MaxItemsConstraint(value); } } - throw std::runtime_error("Expected positive integer value for maxItems constraint."); + throw std::runtime_error( + "Expected positive integer value for maxItems constraint."); } /** @@ -814,17 +912,18 @@ private: * @return pointer to a new MaxLengthConstraint that belongs to the caller */ template - constraints::MaxLengthConstraint* makeMaxLengthConstraint( + constraints::MaxLengthConstraint makeMaxLengthConstraint( const AdapterType &node) { if (node.maybeInteger()) { int64_t value = node.asInteger(); if (value >= 0) { - return new constraints::MaxLengthConstraint(value); + return constraints::MaxLengthConstraint(value); } } - throw std::runtime_error("Expected a positive integer value for maxLength constraint."); + throw std::runtime_error( + "Expected a positive integer value for maxLength constraint."); } /** @@ -838,17 +937,18 @@ private: * caller */ template - constraints::MaxPropertiesConstraint* makeMaxPropertiesConstraint( + constraints::MaxPropertiesConstraint makeMaxPropertiesConstraint( const AdapterType &node) { if (node.maybeInteger()) { int64_t value = node.asInteger(); if (value >= 0) { - return new constraints::MaxPropertiesConstraint(value); + return constraints::MaxPropertiesConstraint(value); } } - throw std::runtime_error("Expected a positive integer for 'maxProperties' constraint."); + throw std::runtime_error( + "Expected a positive integer for 'maxProperties' constraint."); } /** @@ -864,7 +964,7 @@ private: * @return pointer to a new MinimumConstraint that belongs to the caller */ template - constraints::MinimumConstraint* makeMinimumConstraint( + constraints::MinimumConstraint makeMinimumConstraint( const AdapterType &node, const AdapterType *exclusiveMinimum) { @@ -873,16 +973,20 @@ private: if (exclusiveMinimum->maybeBool()) { exclusiveMinimumValue = exclusiveMinimum->asBool(); } else { - throw std::runtime_error("Expected boolean value for 'exclusiveMinimum' constraint."); + throw std::runtime_error( + "Expected boolean value for 'exclusiveMinimum' " + "constraint."); } } if (node.maybeDouble()) { double minimumValue = node.asDouble(); - return new constraints::MinimumConstraint(minimumValue, exclusiveMinimumValue); + return constraints::MinimumConstraint(minimumValue, + exclusiveMinimumValue); } - throw std::runtime_error("Expected numeric value for 'minimum' constraint."); + throw std::runtime_error( + "Expected numeric value for 'minimum' constraint."); } /** @@ -894,17 +998,18 @@ private: * @return pointer to a new MinItemsConstraint that belongs to the caller */ template - constraints::MinItemsConstraint* makeMinItemsConstraint( + constraints::MinItemsConstraint makeMinItemsConstraint( const AdapterType &node) { if (node.maybeInteger()) { int64_t value = node.asInteger(); if (value >= 0) { - return new constraints::MinItemsConstraint(value); + return constraints::MinItemsConstraint(value); } } - throw std::runtime_error("Expected a positive integer value for 'minItems' constraint."); + throw std::runtime_error( + "Expected a positive integer value for 'minItems' constraint."); } /** @@ -916,17 +1021,19 @@ private: * @return pointer to a new MinLengthConstraint that belongs to the caller */ template - constraints::MinLengthConstraint* makeMinLengthConstraint( + constraints::MinLengthConstraint makeMinLengthConstraint( const AdapterType &node) { if (node.maybeInteger()) { int64_t value = node.asInteger(); if (value >= 0) { - return new constraints::MinLengthConstraint(value); + return constraints::MinLengthConstraint(value); } } - throw std::runtime_error("Expected a positive integer value for 'minLength' constraint."); + throw std::runtime_error( + "Expected a positive integer value for 'minLength' " + "constraint."); } @@ -941,17 +1048,18 @@ private: * caller */ template - constraints::MinPropertiesConstraint* makeMinPropertiesConstraint( + constraints::MinPropertiesConstraint makeMinPropertiesConstraint( const AdapterType &node) { if (node.maybeInteger()) { int64_t value = node.asInteger(); if (value >= 0) { - return new constraints::MinPropertiesConstraint(value); + return constraints::MinPropertiesConstraint(value); } } - throw std::runtime_error("Expected a positive integer for 'minProperties' constraint."); + throw std::runtime_error( + "Expected a positive integer for 'minProperties' constraint."); } /** @@ -964,24 +1072,25 @@ private: * caller */ template - constraints::Constraint* makeMultipleOfConstraint( + constraints::MultipleOfConstraint makeMultipleOfConstraint( const AdapterType &node) { // Allow both integral and double types to be provided if (node.maybeInteger()) { - return new constraints::MultipleOfIntegerConstraint( - node.asInteger()); + return constraints::MultipleOfConstraint(node.asInteger()); } else if (node.maybeDouble()) { - return new constraints::MultipleOfDecimalConstraint( - node.asDouble()); + return constraints::MultipleOfConstraint(node.asDouble()); } - throw std::runtime_error("Expected an numeric value for 'multipleOf' constraint."); + throw std::runtime_error( + "Expected an numeric value for 'multipleOf' constraint."); } /** * @brief Make a new NotConstraint object * + * @param rootSchema The Schema instance, and root subschema, through + * which other subschemas can be created and modified * @param rootNode Reference to the node from which JSON References * will be resolved when they refer to the current * document; used for recursive parsing of schemas @@ -993,18 +1102,20 @@ private: * @return pointer to a new NotConstraint object that belongs to the caller */ template - constraints::NotConstraint* makeNotConstraint( + constraints::NotConstraint makeNotConstraint( + Schema &rootSchema, const AdapterType &rootNode, const AdapterType &node, - boost::optional currentScope, + const boost::optional currentScope, const std::string &nodePath, - boost::optional::Type > fetchDoc) + const boost::optional< + typename FetchDocumentFunction::Type > fetchDoc) { if (node.maybeObject()) { - Schema childSchema; - populateSchema(rootNode, node, childSchema, - currentScope, nodePath, fetchDoc); - return new constraints::NotConstraint(childSchema); + const Subschema *childSubschema = rootSchema.createSubschema(); + populateSchema(rootSchema, rootNode, node, + *childSubschema, currentScope, nodePath, fetchDoc); + return constraints::NotConstraint(childSubschema); } throw std::runtime_error("Expected object value for 'not' constraint."); @@ -1013,6 +1124,8 @@ private: /** * @brief Make a new OneOfConstraint object * + * @param rootSchema The Schema instance, and root subschema, through + * which other subschemas can be created and modified * @param rootNode Reference to the node from which JSON References * will be resolved when they refer to the current * document; used for recursive parsing of schemas @@ -1024,27 +1137,27 @@ private: * @return pointer to a new OneOfConstraint that belongs to the caller */ template - constraints::OneOfConstraint* makeOneOfConstraint( + constraints::OneOfConstraint makeOneOfConstraint( + Schema &rootSchema, const AdapterType &rootNode, const AdapterType &node, - boost::optional currentScope, + const boost::optional currentScope, const std::string &nodePath, - boost::optional::Type > - fetchDoc) + const boost::optional< + typename FetchDocumentFunction::Type > fetchDoc) { constraints::OneOfConstraint::Schemas childSchemas; int index = 0; BOOST_FOREACH ( const AdapterType schemaNode, node.getArray() ) { const std::string childPath = nodePath + "/" + boost::lexical_cast(index); - childSchemas.push_back(new Schema); - populateSchema(rootNode, schemaNode, - childSchemas.back(), currentScope, childPath, fetchDoc); + childSchemas.push_back(rootSchema.createSubschema()); + populateSchema(rootSchema, rootNode, schemaNode, + *childSchemas.back(), currentScope, childPath, fetchDoc); index++; } - /// @todo: bypass deep copy of the child schemas - return new constraints::OneOfConstraint(childSchemas); + return constraints::OneOfConstraint(childSchemas); } /** @@ -1056,15 +1169,18 @@ private: * caller */ template - constraints::PatternConstraint* makePatternConstraint( + constraints::PatternConstraint makePatternConstraint( const AdapterType &node) { - return new constraints::PatternConstraint(node.getString()); + return constraints::PatternConstraint(node.getString()); } /** * @brief Make a new Properties object. * + * @param rootSchema The Schema instance, and root + * subschema, through which other + * subschemas can be created and modified * @param rootNode Reference to the node from which JSON * References will be resolved when they * refer to the current document; used @@ -1087,25 +1203,26 @@ private: * the 'additionalProperties' node * @param fetchDoc Function to fetch remote JSON * documents (optional) - * @param parentSchema Optional pointer to the Schema of the + * @param parentSubschema Optional pointer to the Schema of the * parent object, needed to support the - * 'required' keyword in Draft 3. + * 'required' keyword in Draft 3 * * @return pointer to a new Properties that belongs to the caller */ template - constraints::PropertiesConstraint* makePropertiesConstraint( + constraints::PropertiesConstraint makePropertiesConstraint( + Schema &rootSchema, const AdapterType &rootNode, const AdapterType *properties, const AdapterType *patternProperties, const AdapterType *additionalProperties, - boost::optional currentScope, + const boost::optional currentScope, const std::string &propertiesPath, const std::string &patternPropertiesPath, const std::string &additionalPropertiesPath, - boost::optional::Type > - fetchDoc, - Schema *parentSchema) + const boost::optional< + typename FetchDocumentFunction::Type > fetchDoc, + const Subschema *parentSubschema) { typedef typename AdapterType::ObjectMember Member; typedef constraints::PropertiesConstraint::PropertySchemaMap PSM; @@ -1118,10 +1235,11 @@ private: const std::string &propertyName = m.first; const std::string childPath = propertiesPath + "/" + propertyName; - Schema &childSchema = propertySchemas[propertyName]; - populateSchema( - rootNode, m.second, childSchema, currentScope, - childPath, fetchDoc, parentSchema, &propertyName); + const Subschema *childSubschema = rootSchema.createSubschema(); + propertySchemas[propertyName] = childSubschema; + populateSchema(rootSchema, rootNode, m.second, + *childSubschema, currentScope, childPath, fetchDoc, + parentSubschema, &propertyName); } } @@ -1133,15 +1251,16 @@ private: const std::string &propertyName = m.first; const std::string childPath = patternPropertiesPath + "/" + propertyName; - Schema &childSchema = patternPropertySchemas[propertyName]; - populateSchema( - rootNode, m.second, childSchema, currentScope, - childPath, fetchDoc, parentSchema, &propertyName); + const Subschema *childSubschema = rootSchema.createSubschema(); + patternPropertySchemas[propertyName] = childSubschema; + populateSchema(rootSchema, rootNode, m.second, + *childSubschema, currentScope, childPath, fetchDoc, + parentSubschema, &propertyName); } } // Populate an additionalItems schema if required - boost::scoped_ptr additionalPropertiesSchema; + const Subschema *additionalPropertiesSchema = NULL; if (additionalProperties) { // If additionalProperties has been set, check for a boolean value. // Setting 'additionalProperties' to true allows the values of @@ -1155,35 +1274,35 @@ private: // If it has a boolean value that is 'true', then an empty // schema should be used. if (additionalProperties->asBool()) { - additionalPropertiesSchema.reset(new Schema()); + additionalPropertiesSchema = rootSchema.createSubschema(); } } else if (additionalProperties->isObject()) { // If additionalProperties is an object, it should be used as // a child schema. - additionalPropertiesSchema.reset(new Schema()); - populateSchema(rootNode, *additionalProperties, - *additionalPropertiesSchema, currentScope, - additionalPropertiesPath, fetchDoc); + additionalPropertiesSchema = rootSchema.createSubschema(); + populateSchema(rootSchema, rootNode, + *additionalProperties, *additionalPropertiesSchema, + currentScope, additionalPropertiesPath, fetchDoc); } else { // All other types are invalid - throw std::runtime_error("Invalid type for 'additionalProperties' constraint."); + throw std::runtime_error( + "Invalid type for 'additionalProperties' constraint."); } } else { // If an additionalProperties constraint is not provided, then the // default value is an empty schema. - additionalPropertiesSchema.reset(new Schema()); + additionalPropertiesSchema = rootSchema.emptySubschema(); } if (additionalPropertiesSchema) { // If an additionalProperties schema has been created, construct a // new PropertiesConstraint object using that schema. - return new constraints::PropertiesConstraint( - propertySchemas, patternPropertySchemas, - *additionalPropertiesSchema); + return constraints::PropertiesConstraint(propertySchemas, + patternPropertySchemas, additionalPropertiesSchema); } - return new constraints::PropertiesConstraint( - propertySchemas, patternPropertySchemas); + return constraints::PropertiesConstraint(propertySchemas, + patternPropertySchemas); } /** @@ -1199,9 +1318,9 @@ private: * caller */ template - constraints::RequiredConstraint* makeRequiredConstraintForSelf( - const AdapterType &node, - const std::string &name) + boost::optional + makeRequiredConstraintForSelf(const AdapterType &node, + const std::string &name) { if (!node.maybeBool()) { throw std::runtime_error("Expected boolean value for 'required' attribute."); @@ -1210,10 +1329,10 @@ private: if (node.asBool()) { constraints::RequiredConstraint::RequiredProperties requiredProperties; requiredProperties.insert(name); - return new constraints::RequiredConstraint(requiredProperties); + return constraints::RequiredConstraint(requiredProperties); } - return NULL; + return boost::none; } /** @@ -1228,7 +1347,7 @@ private: * caller */ template - constraints::RequiredConstraint* makeRequiredConstraint( + constraints::RequiredConstraint makeRequiredConstraint( const AdapterType &node) { constraints::RequiredConstraint::RequiredProperties requiredProperties; @@ -1239,12 +1358,14 @@ private: requiredProperties.insert(v.getString()); } - return new constraints::RequiredConstraint(requiredProperties); + return constraints::RequiredConstraint(requiredProperties); } /** * @brief Make a new TypeConstraint object * + * @param rootSchema The Schema instance, and root subschema, through + * which other subschemas can be created and modified * @param rootNode Reference to the node from which JSON References * will be resolved when they refer to the current * document; used for recursive parsing of schemas @@ -1257,13 +1378,14 @@ private: * @return pointer to a new TypeConstraint object. */ template - constraints::TypeConstraint* makeTypeConstraint( + constraints::TypeConstraint makeTypeConstraint( + Schema &rootSchema, const AdapterType &rootNode, const AdapterType &node, - boost::optional currentScope, + const boost::optional currentScope, const std::string &nodePath, - boost::optional::Type > - fetchDoc) + const boost::optional< + typename FetchDocumentFunction::Type > fetchDoc) { typedef constraints::TypeConstraint TC; @@ -1271,9 +1393,11 @@ private: TC::Schemas schemas; if (node.isString()) { - const TC::JsonType jsonType = TC::jsonTypeFromString(node.getString()); + const TC::JsonType jsonType = + TC::jsonTypeFromString(node.getString()); if (jsonType == TC::kAny && version == kDraft4) { - throw std::runtime_error("'any' type is not supported in version 4 schemas."); + throw std::runtime_error( + "'any' type is not supported in version 4 schemas."); } jsonTypes.insert(jsonType); @@ -1281,31 +1405,34 @@ private: int index = 0; BOOST_FOREACH( const AdapterType v, node.getArray() ) { if (v.isString()) { - const TC::JsonType jsonType = TC::jsonTypeFromString(v.getString()); + const TC::JsonType jsonType = + TC::jsonTypeFromString(v.getString()); if (jsonType == TC::kAny && version == kDraft4) { - throw std::runtime_error("'any' type is not supported in version 4 schemas."); + throw std::runtime_error( + "'any' type is not supported in version 4 " + "schemas."); } jsonTypes.insert(jsonType); } else if (v.isObject() && version == kDraft3) { const std::string childPath = nodePath + "/" + boost::lexical_cast(index); - schemas.push_back(new Schema()); - populateSchema(rootNode, v, schemas.back(), - currentScope, childPath, fetchDoc); + schemas.push_back(rootSchema.createSubschema()); + populateSchema(rootSchema, rootNode, v, + *schemas.back(), currentScope, childPath, fetchDoc); } else { throw std::runtime_error("Type name should be a string."); } index++; } } else if (node.isObject() && version == kDraft3) { - schemas.push_back(new Schema()); - populateSchema(rootNode, node, schemas.back(), - currentScope, nodePath, fetchDoc); + schemas.push_back(rootSchema.createSubschema()); + populateSchema(rootSchema, rootNode, node, + *schemas.back(), currentScope, nodePath, fetchDoc); } else { throw std::runtime_error("Type name should be a string."); } - return new constraints::TypeConstraint(jsonTypes, schemas); + return constraints::TypeConstraint(jsonTypes, schemas); } /** @@ -1317,21 +1444,22 @@ private: * the caller, or NULL if the boolean value is false. */ template - constraints::UniqueItemsConstraint* makeUniqueItemsConstraint( - const AdapterType &node) + boost::optional + makeUniqueItemsConstraint(const AdapterType &node) { if (node.isBool() || node.maybeBool()) { // If the boolean value is true, this function will return a pointer // to a new UniqueItemsConstraint object. If it is value, then the // constraint is redundant, so NULL is returned instead. if (node.asBool()) { - return new constraints::UniqueItemsConstraint(); + return constraints::UniqueItemsConstraint(); } else { - return NULL; + return boost::none; } } - throw std::runtime_error("Expected boolean value for 'uniqueItems' constraint."); + throw std::runtime_error( + "Expected boolean value for 'uniqueItems' constraint."); } }; diff --git a/include/valijson/subschema.hpp b/include/valijson/subschema.hpp index 4f2f229..698e11e 100644 --- a/include/valijson/subschema.hpp +++ b/include/valijson/subschema.hpp @@ -2,10 +2,11 @@ #ifndef __VALIJSON_SUBSCHEMA_HPP #define __VALIJSON_SUBSCHEMA_HPP +#include + #include #include #include -#include #include @@ -51,6 +52,24 @@ public: : constraints(subschema.constraints), title(subschema.title) { } + /** + * @brief Clean up and free all memory managed by the Subschema + */ + virtual ~Subschema() + { + try { + for (std::vector::iterator itr = + constraints.begin(); itr != constraints.end(); ++itr) { + const Constraint *constraint = *itr; + delete constraint; + } + constraints.clear(); + } catch (const std::exception &e) { + fprintf(stderr, "Caught an exception in Subschema destructor: %s", + e.what()); + } + } + /** * @brief Add a constraint to this sub-schema * @@ -94,8 +113,8 @@ public: bool apply(ApplyFunction &applyFunction) const { bool allTrue = true; - BOOST_FOREACH( const Constraint &constraint, constraints ) { - allTrue = allTrue && applyFunction(constraint); + BOOST_FOREACH( const Constraint *constraint, constraints ) { + allTrue = allTrue && applyFunction(*constraint); } return allTrue; @@ -113,8 +132,8 @@ public: */ bool applyStrict(ApplyFunction &applyFunction) const { - BOOST_FOREACH( const Constraint &constraint, constraints ) { - if (!applyFunction(constraint)) { + BOOST_FOREACH( const Constraint *constraint, constraints ) { + if (!applyFunction(*constraint)) { return false; } } @@ -238,7 +257,7 @@ public: private: /// List of pointers to constraints that apply to this schema. - boost::ptr_vector constraints; + std::vector constraints; /// Schema description (optional) boost::optional description; diff --git a/include/valijson/validation_visitor.hpp b/include/valijson/validation_visitor.hpp index 820656e..6606606 100644 --- a/include/valijson/validation_visitor.hpp +++ b/include/valijson/validation_visitor.hpp @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -64,18 +65,18 @@ public: * * @return true if validation passes, false otherwise */ - bool validateSchema(const Schema &schema) + bool validateSchema(const Subschema &subschema) { // Wrap the validationCallback() function below so that it will be // passed a reference to a constraint (_1), and a reference to the // visitor (*this). - Schema::ApplyFunction fn(boost::bind(validationCallback, _1, *this)); + Subschema::ApplyFunction fn(boost::bind(validationCallback, _1, *this)); // Perform validation against each constraint defined in the schema if (results == NULL) { // The applyStrict() function will return immediately if the // callback function returns false - if (!schema.applyStrict(fn)) { + if (!subschema.applyStrict(fn)) { return false; } } else { @@ -83,7 +84,7 @@ public: // schema, even if the callback function returns false. Once // iteration is complete, the apply() function will return true // only if all invokations of the callback function returned true. - if (!schema.apply(fn)) { + if (!subschema.apply(fn)) { return false; } } @@ -116,10 +117,10 @@ public: // Validate against each child schema unsigned int index = 0; - BOOST_FOREACH( const Schema &schema, constraint.schemas ) { + BOOST_FOREACH( const Subschema *subschema, constraint.schemas ) { // Ensure that the target validates against child schema - if (!validateSchema(schema)) { + if (!validateSchema(*subschema)) { if (results) { validated = false; results->pushError(context, @@ -161,8 +162,8 @@ public: // visitor (*this). Schema::ApplyFunction fn(boost::bind(validationCallback, _1, *this)); - BOOST_FOREACH( const Schema &schema, constraint.schemas ) { - if (schema.apply(fn)) { + BOOST_FOREACH( const Subschema *subschema, constraint.schemas ) { + if (subschema->apply(fn)) { return true; } } @@ -231,8 +232,8 @@ public: // dependent schema. PDSM::const_iterator depSchemasItr = depSchemas.find(m.first); if (depSchemasItr != depSchemas.end()) { - const Schema *schema = depSchemasItr->second; - if (!validateSchema(*schema)) { + const Subschema *subschema = depSchemasItr->second; + if (!validateSchema(*subschema)) { if (results) { results->pushError(context, "Failed to validate against dependent schema."); validated = false; @@ -326,14 +327,14 @@ public: ++index; } - } else if (constraint.itemSchemas) { + } else if (!constraint.itemSchemas.empty()) { // Get access to the target as an object const typename AdapterType::Array arr = target.asArray(); if (!constraint.additionalItemsSchema) { // Check that the array length is <= length of the itemsSchema list - if (arr.size() > constraint.itemSchemas->size()) { + if (arr.size() > constraint.itemSchemas.size()) { if (results) { results->pushError(context, "Array contains more items than allowed by items constraint."); validated = false; @@ -351,7 +352,7 @@ public: newContext.push_back("[" + boost::lexical_cast(index) + "]"); ValidationVisitor v(arrayItem, newContext, strictTypes, results); - if (index >= constraint.itemSchemas->size()) { + if (index >= constraint.itemSchemas.size()) { if (constraint.additionalItemsSchema) { if (!v.validateSchema(*constraint.additionalItemsSchema)) { if (results) { @@ -367,7 +368,7 @@ public: boost::lexical_cast(index) + " in array due to missing schema."); validated = false; } - } else if (!v.validateSchema(constraint.itemSchemas->at(index))) { + } else if (!v.validateSchema(*constraint.itemSchemas.at(index))) { if (results) { results->pushError(context, "Failed to validate item #" + boost::lexical_cast(index) + " against corresponding item schema."); @@ -653,106 +654,99 @@ public: /** * @brief Validate against the multipleOf or divisibleBy constraints - * represented by a MultipleOfDecimalConstraint object. + * represented by a MultipleOfConstraint object. * * @param constraint Constraint that the target must validate against. * * @return true if the constraint is satisfied, false otherwise. */ - virtual bool visit(const MultipleOfDecimalConstraint &constraint) + virtual bool visit(const MultipleOfConstraint &constraint) { - double d = 0.; - - if (target.maybeDouble()) { - if (!target.asDouble(d)) { - if (results) { - results->pushError(context, "Value could not be converted " - "to a number to check if it is a multiple of " + - boost::lexical_cast( - constraint.multipleOf)); - } - return false; - } - } else if (target.maybeInteger()) { + const int64_t *multipleOfInteger = boost::get(&constraint.value); + if (multipleOfInteger) { int64_t i = 0; - if (!target.asInteger(i)) { + if (target.maybeInteger()) { + if (!target.asInteger(i)) { + if (results) { + results->pushError(context, "Value could not be converted " + "to an integer for multipleOf check"); + } + return false; + } + } else if (target.maybeDouble()) { + double d; + if (!target.asDouble(d)) { + if (results) { + results->pushError(context, "Value could not be converted " + "to a double for multipleOf check"); + } + return false; + } + i = static_cast(d); + } else { + return true; + } + + if (i == 0) { + return true; + } + + if (i % *multipleOfInteger != 0) { if (results) { - results->pushError(context, "Value could not be converted " - "to a number to check if it is a multiple of " + - boost::lexical_cast( - constraint.multipleOf)); + results->pushError(context, "Value should be a multiple of " + + boost::lexical_cast(*multipleOfInteger)); } return false; } - d = static_cast(i); - } else { + return true; } - if (d == 0) { - return true; - } - - const double r = remainder(d, constraint.multipleOf); - - if (fabs(r) > std::numeric_limits::epsilon()) { - if (results) { - results->pushError(context, "Value should be a multiple of " + - boost::lexical_cast(constraint.multipleOf)); + const double *multipleOfDouble = boost::get(&constraint.value); + if (multipleOfDouble) { + double d = 0.; + if (target.maybeDouble()) { + if (!target.asDouble(d)) { + if (results) { + results->pushError(context, "Value could not be converted " + "to a number to check if it is a multiple of " + + boost::lexical_cast(*multipleOfDouble)); + } + return false; + } + } else if (target.maybeInteger()) { + int64_t i = 0; + if (!target.asInteger(i)) { + if (results) { + results->pushError(context, "Value could not be converted " + "to a number to check if it is a multiple of " + + boost::lexical_cast(*multipleOfDouble)); + } + return false; + } + d = static_cast(i); + } else { + return true; } - return false; - } - return true; - } + if (d == 0) { + return true; + } - /** - * @brief Validate against the multipleOf or divisibleBy constraints - * represented by a MultipleOfIntegerConstraint object. - * - * @param constraint Constraint that the target must validate against. - * - * @return true if the constraint is satisfied, false otherwise. - */ - virtual bool visit(const MultipleOfIntegerConstraint &constraint) - { - int64_t i = 0; + const double r = remainder(d, *multipleOfDouble); - if (target.maybeInteger()) { - if (!target.asInteger(i)) { + if (fabs(r) > std::numeric_limits::epsilon()) { if (results) { - results->pushError(context, "Value could not be converted " - "to an integer for multipleOf check"); + results->pushError(context, "Value should be a multiple of " + + boost::lexical_cast(*multipleOfDouble)); } return false; } - } else if (target.maybeDouble()) { - double d; - if (!target.asDouble(d)) { - if (results) { - results->pushError(context, "Value could not be converted " - "to a double for multipleOf check"); - } - return false; - } - i = static_cast(d); - } else { + return true; } - if (i == 0) { - return true; - } - - if (i % constraint.multipleOf != 0) { - if (results) { - results->pushError(context, "Value should be a multiple of " + - boost::lexical_cast(constraint.multipleOf)); - } - return false; - } - - return true; + return false; } /** @@ -791,9 +785,9 @@ public: ValidationResults newResults; ValidationResults *childResults = (results) ? &newResults : NULL; - BOOST_FOREACH( const Schema &schema, constraint.schemas ) { + BOOST_FOREACH( const Subschema *subschema, constraint.schemas ) { ValidationVisitor v(target, context, strictTypes, childResults); - if (v.validateSchema(schema)) { + if (v.validateSchema(*subschema)) { numValidated++; } } @@ -1033,8 +1027,8 @@ public: } } - BOOST_FOREACH( const Schema &schema, constraint.schemas ) { - if (validateSchema(schema)) { + BOOST_FOREACH( const Subschema *subschema, constraint.schemas ) { + if (validateSchema(*subschema)) { return true; } } diff --git a/include/valijson/validator.hpp b/include/valijson/validator.hpp index 2338281..8921ec4 100644 --- a/include/valijson/validator.hpp +++ b/include/valijson/validator.hpp @@ -14,38 +14,30 @@ class Schema; class ValidationResults; /** - * @brief Class that wraps a schema and provides validation functionality. - * - * This class wraps a Schema object, and encapsulates the logic required to - * validate rapidjson values aginst the schema. + * @brief Class that provides validation functionality. */ class Validator { public: - - /** - * @brief Construct a validator using the specified schema. - * - * The schema that is provided will be copied. - * - * @param schema A schema to use for validation - */ - Validator(const Schema &schema) - : schema(new Schema(schema)), - strictTypes(true) { } - - /** - * @brief Set flag to use strict comparison during validation. - * - * The default value is true, but this can be changed at any time. Future - * invokations of validate() will make use of the new value. - * - * @param strictTypes whether or not to use strict comparison - */ - void setStrict(bool strictTypes) + enum TypeCheckingMode { - this->strictTypes = strictTypes; - } + kStrongTypes, + kWeakTypes + }; + + /** + * @brief Construct a Validator that uses strong type checking by default + */ + Validator() + : strictTypes(true) { } + + /** + * @brief Construct a Validator using a specific type checking mode + * + * @param typeCheckingMode choice of strong or weak type checking + */ + Validator(TypeCheckingMode typeCheckingMode) + : strictTypes(typeCheckingMode == kStrongTypes) { } /** * @brief Validate a JSON document and optionally return the results. @@ -58,30 +50,29 @@ public: * will only continue for as long as the constraints are validated * successfully. * - * @param target A rapidjson::Value to be validated. + * @param schema The schema to validate against + * @param target A rapidjson::Value to be validated * * @param results An optional pointer to a ValidationResults instance that - * will be used to report validation errors. + * will be used to report validation errors * - * @returns true if validation succeeds, false otherwise. + * @returns true if validation succeeds, false otherwise */ template - bool validate(const AdapterType &target, ValidationResults *results) + bool validate(const Subschema &schema, const AdapterType &target, + ValidationResults *results) { // Construct a ValidationVisitor to perform validation at the root level - ValidationVisitor v(target, std::vector(1, ""), - strictTypes, results); + ValidationVisitor v(target, + std::vector(1, ""), strictTypes, results); - return v.validateSchema(*schema); + return v.validateSchema(schema); } private: - /// Pointer to an internal copy of a schema to use for validation - boost::scoped_ptr schema; - /// Flag indicating that strict type comparisons should be used - bool strictTypes; + const bool strictTypes; }; diff --git a/tests/test_fetch_document_callback.cpp b/tests/test_fetch_document_callback.cpp index e6b01ca..7b489f3 100644 --- a/tests/test_fetch_document_callback.cpp +++ b/tests/test_fetch_document_callback.cpp @@ -70,12 +70,12 @@ TEST_F(TestFetchDocumentCallback, Basics) rapidjson::Document validDocument; validDocument.SetObject(); validDocument.AddMember("test", "valid", allocator); - Validator validator(schema); - EXPECT_TRUE(validator.validate(RapidJsonAdapter(validDocument), NULL)); + Validator validator; + EXPECT_TRUE(validator.validate(schema, RapidJsonAdapter(validDocument), NULL)); // Test resulting schema with an invalid document rapidjson::Document invalidDocument; invalidDocument.SetObject(); invalidDocument.AddMember("test", 123, allocator); - EXPECT_FALSE(validator.validate(RapidJsonAdapter(invalidDocument), NULL)); + EXPECT_FALSE(validator.validate(schema, RapidJsonAdapter(invalidDocument), NULL)); } diff --git a/tests/test_validation_errors.cpp b/tests/test_validation_errors.cpp index fba6a26..c4b9d43 100644 --- a/tests/test_validation_errors.cpp +++ b/tests/test_validation_errors.cpp @@ -46,9 +46,9 @@ TEST_F(TestValidationErrors, AllOfConstraintFailure) ASSERT_TRUE( loadDocument(TEST_DATA_DIR "/documents/array_doubles_1_2_3.json", testDocument) ); RapidJsonAdapter testAdapter(testDocument); - Validator validator(schema); + Validator validator; ValidationResults results; - EXPECT_FALSE( validator.validate(testAdapter, &results) ); + EXPECT_FALSE( validator.validate(schema, testAdapter, &results) ); ValidationResults::Error error; diff --git a/tests/test_validator.cpp b/tests/test_validator.cpp index 8e4b80a..2b14e5a 100644 --- a/tests/test_validator.cpp +++ b/tests/test_validator.cpp @@ -96,10 +96,10 @@ protected: testItr = testObject.find("data"); ASSERT_NE( testObject.end(), testItr ); - Validator validator(schema); - validator.setStrict(strict); + Validator validator(strict ? + Validator::kStrongTypes : Validator::kWeakTypes); - EXPECT_EQ( shouldValidate, validator.validate(testItr->second, NULL) ) + EXPECT_EQ( shouldValidate, validator.validate(schema, testItr->second, NULL) ) << "Failed while testing validate() function in '" << currentTest << "' of test case '" << currentTestCase << "' with adapter '"