Refactor memory management for schemas and constraints to improve API and allow for future improvements

This commit is contained in:
Tristan Penman
2015-12-23 14:34:04 +11:00
parent 015d463d0c
commit b1755309d2
12 changed files with 743 additions and 523 deletions

View File

@@ -74,6 +74,7 @@ using std::endl;
using valijson::Schema; using valijson::Schema;
using valijson::SchemaParser; using valijson::SchemaParser;
using valijson::Subschema;
using valijson::Validator; using valijson::Validator;
using valijson::ValidationResults; using valijson::ValidationResults;
using valijson::adapters::RapidJsonAdapter; using valijson::adapters::RapidJsonAdapter;
@@ -90,49 +91,64 @@ void addPropertiesConstraint(Schema &schema)
{ {
PropertiesConstraint::PropertySchemaMap propertySchemaMap; PropertiesConstraint::PropertySchemaMap propertySchemaMap;
PropertiesConstraint::PropertySchemaMap patternPropertiesSchemaMap;
{ {
// Create a child schema for the 'category' property that requires one // Prepare an enum constraint requires a document to be equal to at
// of several possible values. // least one of a set of possible values
Schema &propertySchema = propertySchemaMap["category"];
EnumConstraint::Values enumConstraintValues; EnumConstraint::Values enumConstraintValues;
enumConstraintValues.push_back(new RapidJsonFrozenValue("album")); enumConstraintValues.push_back(new RapidJsonFrozenValue("album"));
enumConstraintValues.push_back(new RapidJsonFrozenValue("book")); enumConstraintValues.push_back(new RapidJsonFrozenValue("book"));
enumConstraintValues.push_back(new RapidJsonFrozenValue("other")); enumConstraintValues.push_back(new RapidJsonFrozenValue("other"));
enumConstraintValues.push_back(new RapidJsonFrozenValue("video")); 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 // Create a child schema for the 'description' property that requires
// a string, but does not enforce any length constraints. // a string, but does not enforce any length constraints.
Schema &propertySchema = propertySchemaMap["description"]; const Subschema *subschema = schema.createSubschema();
propertySchema.addConstraint(new TypeConstraint(TypeConstraint::kString)); 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 // Create a child schema for the 'price' property, that requires a
// number with a value greater than zero. // number with a value greater than zero.
Schema &propertySchema = propertySchemaMap["price"]; const Subschema *subschema = schema.createSubschema();
propertySchema.addConstraint(new MinimumConstraint(0.0, true)); schema.addConstraintToSubschema(MinimumConstraint(0.0, true), subschema);
propertySchema.addConstraint(new TypeConstraint(TypeConstraint::kNumber)); 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 // Create a child schema for the 'title' property that requires a string
// that is between 1 and 200 characters in length. // that is between 1 and 200 characters in length.
Schema &propertySchema = propertySchemaMap["title"]; const Subschema *subschema = schema.createSubschema();
propertySchema.addConstraint(new MaxLengthConstraint(200)); schema.addConstraintToSubschema(MaxLengthConstraint(200), subschema);
propertySchema.addConstraint(new MinLengthConstraint(1)); schema.addConstraintToSubschema(MinLengthConstraint(1), subschema);
propertySchema.addConstraint(new TypeConstraint(TypeConstraint::kString)); schema.addConstraintToSubschema(TypeConstraint(TypeConstraint::kString),
subschema);
// Include subschema in properties constraint
propertySchemaMap["title"] = subschema;
} }
// Add a PropertiesConstraint to the schema, with the properties defined // Add a PropertiesConstraint to the schema, with the properties defined
// above, no pattern properties, and with additional property schemas // above, no pattern properties or additional property schemas
// prohibited. schema.addConstraint(PropertiesConstraint(propertySchemaMap));
schema.addConstraint(new PropertiesConstraint(
propertySchemaMap, patternPropertiesSchemaMap));
} }
void addRequiredConstraint(Schema &schema) void addRequiredConstraint(Schema &schema)
@@ -176,10 +192,10 @@ int main(int argc, char *argv[])
addTypeConstraint(schema); addTypeConstraint(schema);
// Perform validation // Perform validation
Validator validator(schema); Validator validator;
ValidationResults results; ValidationResults results;
RapidJsonAdapter targetDocumentAdapter(targetDocument); RapidJsonAdapter targetDocumentAdapter(targetDocument);
if (!validator.validate(targetDocumentAdapter, &results)) { if (!validator.validate(schema, targetDocumentAdapter, &results)) {
std::cerr << "Validation failed." << endl; std::cerr << "Validation failed." << endl;
ValidationResults::Error error; ValidationResults::Error error;
unsigned int errorNum = 1; unsigned int errorNum = 1;

View File

@@ -60,11 +60,10 @@ int main(int argc, char *argv[])
} }
// Perform validation // Perform validation
Validator validator(schema); Validator validator(Validator::kWeakTypes);
validator.setStrict(false);
ValidationResults results; ValidationResults results;
RapidJsonAdapter targetDocumentAdapter(targetDocument); RapidJsonAdapter targetDocumentAdapter(targetDocument);
if (!validator.validate(targetDocumentAdapter, &results)) { if (!validator.validate(schema, targetDocumentAdapter, &results)) {
std::cerr << "Validation failed." << endl; std::cerr << "Validation failed." << endl;
ValidationResults::Error error; ValidationResults::Error error;
unsigned int errorNum = 1; unsigned int errorNum = 1;

View File

@@ -16,14 +16,14 @@
#ifndef __VALIJSON_CONSTRAINTS_CONCRETE_CONSTRAINTS_HPP #ifndef __VALIJSON_CONSTRAINTS_CONCRETE_CONSTRAINTS_HPP
#define __VALIJSON_CONSTRAINTS_CONCRETE_CONSTRAINTS_HPP #define __VALIJSON_CONSTRAINTS_CONCRETE_CONSTRAINTS_HPP
#include <boost/ptr_container/ptr_vector.hpp>
#include <boost/variant.hpp>
#include <limits> #include <limits>
#include <map>
#include <set> #include <set>
#include <string> #include <string>
#include <vector>
#include <boost/ptr_container/ptr_map.hpp>
#include <boost/ptr_container/ptr_vector.hpp>
#include <boost/scoped_ptr.hpp>
#include <boost/shared_ptr.hpp>
#include <valijson/adapters/frozen_value.hpp> #include <valijson/adapters/frozen_value.hpp>
#include <valijson/constraints/basic_constraint.hpp> #include <valijson/constraints/basic_constraint.hpp>
@@ -44,7 +44,7 @@ namespace constraints {
*/ */
struct AllOfConstraint: BasicConstraint<AllOfConstraint> struct AllOfConstraint: BasicConstraint<AllOfConstraint>
{ {
typedef boost::ptr_vector<Schema> Schemas; typedef std::vector<const Subschema *> Schemas;
AllOfConstraint(const Schemas &schemas) AllOfConstraint(const Schemas &schemas)
: schemas(schemas) { } : schemas(schemas) { }
@@ -62,7 +62,7 @@ struct AllOfConstraint: BasicConstraint<AllOfConstraint>
*/ */
struct AnyOfConstraint: BasicConstraint<AnyOfConstraint> struct AnyOfConstraint: BasicConstraint<AnyOfConstraint>
{ {
typedef boost::ptr_vector<Schema> Schemas; typedef std::vector<const Subschema *> Schemas;
AnyOfConstraint(const Schemas &schemas) AnyOfConstraint(const Schemas &schemas)
: schemas(schemas) { } : schemas(schemas) { }
@@ -84,7 +84,8 @@ struct DependenciesConstraint: BasicConstraint<DependenciesConstraint>
typedef std::map<std::string, Dependencies> PropertyDependenciesMap; typedef std::map<std::string, Dependencies> PropertyDependenciesMap;
// A mapping from property names to dependent schemas // A mapping from property names to dependent schemas
typedef boost::ptr_map<std::string, Schema> PropertyDependentSchemasMap; typedef std::map<std::string, const Subschema *>
PropertyDependentSchemasMap;
DependenciesConstraint(const PropertyDependenciesMap &dependencies, DependenciesConstraint(const PropertyDependenciesMap &dependencies,
const PropertyDependentSchemasMap &dependentSchemas) const PropertyDependentSchemasMap &dependentSchemas)
@@ -117,7 +118,7 @@ struct EnumConstraint: BasicConstraint<EnumConstraint>
*/ */
struct ItemsConstraint: BasicConstraint<ItemsConstraint> struct ItemsConstraint: BasicConstraint<ItemsConstraint>
{ {
typedef boost::ptr_vector<Schema> Schemas; typedef std::vector<const Subschema *> Schemas;
/** /**
* @brief Construct a singular item constraint that allows no additional * @brief Construct a singular item constraint that allows no additional
@@ -125,8 +126,9 @@ struct ItemsConstraint: BasicConstraint<ItemsConstraint>
* *
* @param itemSchema * @param itemSchema
*/ */
ItemsConstraint(const Schema &itemSchema) ItemsConstraint(const Subschema *itemSchema)
: itemSchema(new Schema(itemSchema)) { } : itemSchema(itemSchema),
additionalItemsSchema(NULL) { }
/** /**
* @brief Construct a singular item schema that allows additional items * @brief Construct a singular item schema that allows additional items
@@ -134,10 +136,10 @@ struct ItemsConstraint: BasicConstraint<ItemsConstraint>
* @param itemSchema * @param itemSchema
* @param additionalItemsSchema * @param additionalItemsSchema
*/ */
ItemsConstraint(const Schema &itemSchema, ItemsConstraint(const Subschema *itemSchema,
const Schema &additionalItemsSchema) const Subschema *additionalItemsSchema)
: itemSchema(new Schema(itemSchema)), : itemSchema(itemSchema),
additionalItemsSchema(new Schema(additionalItemsSchema)) { } additionalItemsSchema(additionalItemsSchema) { }
/** /**
* @brief Construct a plural items constraint that does not allow for * @brief Construct a plural items constraint that does not allow for
@@ -146,7 +148,9 @@ struct ItemsConstraint: BasicConstraint<ItemsConstraint>
* @param itemSchemas collection of item schemas * @param itemSchemas collection of item schemas
*/ */
ItemsConstraint(const Schemas &itemSchemas) ItemsConstraint(const Schemas &itemSchemas)
: itemSchemas(new Schemas(itemSchemas)) { } : itemSchema(NULL),
itemSchemas(itemSchemas),
additionalItemsSchema(NULL) { }
/** /**
* @brief Construct a plural items constraint that allows additional items * @brief Construct a plural items constraint that allows additional items
@@ -155,21 +159,22 @@ struct ItemsConstraint: BasicConstraint<ItemsConstraint>
* @param additionalItemsSchema * @param additionalItemsSchema
*/ */
ItemsConstraint(const Schemas &itemSchemas, ItemsConstraint(const Schemas &itemSchemas,
const Schema &additionalItemsSchema) const Subschema *additionalItemsSchema)
: itemSchemas(new Schemas(itemSchemas)), : itemSchema(NULL),
additionalItemsSchema(new Schema(additionalItemsSchema)) { } itemSchemas(itemSchemas),
additionalItemsSchema(additionalItemsSchema) { }
/** /**
* @brief Copy constructor * @brief Copy constructor
*/ */
ItemsConstraint(const ItemsConstraint &other) ItemsConstraint(const ItemsConstraint &other)
: itemSchema(other.itemSchema ? new Schema(*other.itemSchema.get()) : NULL), : itemSchema(other.itemSchema),
itemSchemas(other.itemSchemas ? new Schemas(*other.itemSchemas.get()) : NULL), itemSchemas(other.itemSchemas),
additionalItemsSchema(other.additionalItemsSchema ? new Schema(*other.additionalItemsSchema.get()) : NULL) { } additionalItemsSchema(other.additionalItemsSchema) { }
const boost::scoped_ptr<const Schema> itemSchema; const Subschema* itemSchema;
const boost::scoped_ptr<const Schemas> itemSchemas; const Schemas itemSchemas;
const boost::scoped_ptr<const Schema> additionalItemsSchema; const Subschema* additionalItemsSchema;
}; };
/** /**
@@ -265,25 +270,17 @@ struct MinPropertiesConstraint: BasicConstraint<MinPropertiesConstraint>
}; };
/** /**
* @brief Represents a 'multipleOf' or 'divisibleBy' constraint for decimals * @brief Represents a 'multipleOf' or 'divisibleBy' constraint
*/ */
struct MultipleOfDecimalConstraint: BasicConstraint<MultipleOfDecimalConstraint> struct MultipleOfConstraint: BasicConstraint<MultipleOfConstraint>
{ {
MultipleOfDecimalConstraint(double multipleOf) explicit MultipleOfConstraint(int64_t value)
: multipleOf(multipleOf) { } : value(value) { }
const double multipleOf; explicit MultipleOfConstraint(double value)
}; : value(value) { }
/** const boost::variant<double, int64_t> value;
* @brief Represents a 'multipleOf' or 'divisibleBy' constraint for int64_t
*/
struct MultipleOfIntegerConstraint: BasicConstraint<MultipleOfIntegerConstraint>
{
MultipleOfIntegerConstraint(int64_t multipleOf)
: multipleOf(multipleOf) { }
const int64_t multipleOf;
}; };
/** /**
@@ -291,13 +288,13 @@ struct MultipleOfIntegerConstraint: BasicConstraint<MultipleOfIntegerConstraint>
*/ */
struct NotConstraint: BasicConstraint<NotConstraint> struct NotConstraint: BasicConstraint<NotConstraint>
{ {
NotConstraint(const Schema &schema) NotConstraint(const Subschema *schema)
: schema(new Schema(schema)) { } : schema(schema) { }
NotConstraint(const NotConstraint &other) NotConstraint(const NotConstraint &other)
: schema(other.schema ? new Schema(*other.schema) : NULL) { } : schema(other.schema) { }
const boost::scoped_ptr<const Schema> schema; const Subschema *schema;
}; };
/** /**
@@ -305,7 +302,7 @@ struct NotConstraint: BasicConstraint<NotConstraint>
*/ */
struct OneOfConstraint: BasicConstraint<OneOfConstraint> struct OneOfConstraint: BasicConstraint<OneOfConstraint>
{ {
typedef boost::ptr_vector<Schema> Schemas; typedef std::vector<const Subschema *> Schemas;
OneOfConstraint(const Schemas &schemas) OneOfConstraint(const Schemas &schemas)
: schemas(schemas) { } : schemas(schemas) { }
@@ -331,29 +328,33 @@ struct PatternConstraint: BasicConstraint<PatternConstraint>
*/ */
struct PropertiesConstraint: BasicConstraint<PropertiesConstraint> { struct PropertiesConstraint: BasicConstraint<PropertiesConstraint> {
typedef boost::ptr_map<std::string, Schema> PropertySchemaMap; typedef std::map<std::string, const Subschema *> PropertySchemaMap;
PropertiesConstraint(const PropertySchemaMap &properties)
: properties(properties),
additionalProperties(NULL) { }
PropertiesConstraint(const PropertySchemaMap &properties, PropertiesConstraint(const PropertySchemaMap &properties,
const PropertySchemaMap &patternProperties) const PropertySchemaMap &patternProperties)
: properties(properties), : properties(properties),
patternProperties(patternProperties) { } patternProperties(patternProperties),
additionalProperties(NULL) { }
PropertiesConstraint(const PropertySchemaMap &properties, PropertiesConstraint(const PropertySchemaMap &properties,
const PropertySchemaMap &patternProperties, const PropertySchemaMap &patternProperties,
const Schema &additionalProperties) const Subschema *additionalProperties)
: properties(properties), : properties(properties),
patternProperties(patternProperties), patternProperties(patternProperties),
additionalProperties(new Schema(additionalProperties)) { } additionalProperties(additionalProperties) { }
PropertiesConstraint(const PropertiesConstraint &other) PropertiesConstraint(const PropertiesConstraint &other)
: properties(other.properties), : properties(other.properties),
patternProperties(other.patternProperties), patternProperties(other.patternProperties),
additionalProperties(other.additionalProperties ? additionalProperties(other.additionalProperties) {}
new Schema(*other.additionalProperties.get()) : NULL) {}
const PropertySchemaMap properties; const PropertySchemaMap properties;
const PropertySchemaMap patternProperties; const PropertySchemaMap patternProperties;
const boost::scoped_ptr<const Schema> additionalProperties; const Subschema *additionalProperties;
}; };
@@ -391,15 +392,15 @@ struct TypeConstraint: BasicConstraint<TypeConstraint>
typedef std::set<JsonType> JsonTypes; typedef std::set<JsonType> JsonTypes;
typedef boost::ptr_vector<Schema> Schemas; typedef std::vector<const Subschema *> Schemas;
TypeConstraint(const JsonType jsonType) TypeConstraint(const JsonType jsonType)
: jsonTypes(makeJsonTypes(jsonType)) { } : jsonTypes(makeJsonTypes(jsonType)) { }
TypeConstraint(const JsonTypes jsonTypes) TypeConstraint(const JsonTypes &jsonTypes)
: jsonTypes(jsonTypes) { } : jsonTypes(jsonTypes) { }
TypeConstraint(const JsonTypes jsonTypes, TypeConstraint(const JsonTypes &jsonTypes,
const Schemas &schemas) const Schemas &schemas)
: jsonTypes(jsonTypes), : jsonTypes(jsonTypes),
schemas(schemas) { } schemas(schemas) { }

View File

@@ -19,8 +19,7 @@ struct MinimumConstraint;
struct MinItemsConstraint; struct MinItemsConstraint;
struct MinLengthConstraint; struct MinLengthConstraint;
struct MinPropertiesConstraint; struct MinPropertiesConstraint;
struct MultipleOfDecimalConstraint; struct MultipleOfConstraint;
struct MultipleOfIntegerConstraint;
struct NotConstraint; struct NotConstraint;
struct OneOfConstraint; struct OneOfConstraint;
struct PatternConstraint; struct PatternConstraint;
@@ -48,8 +47,7 @@ protected:
typedef constraints::MinItemsConstraint MinItemsConstraint; typedef constraints::MinItemsConstraint MinItemsConstraint;
typedef constraints::MinLengthConstraint MinLengthConstraint; typedef constraints::MinLengthConstraint MinLengthConstraint;
typedef constraints::MinPropertiesConstraint MinPropertiesConstraint; typedef constraints::MinPropertiesConstraint MinPropertiesConstraint;
typedef constraints::MultipleOfDecimalConstraint MultipleOfDecimalConstraint; typedef constraints::MultipleOfConstraint MultipleOfConstraint;
typedef constraints::MultipleOfIntegerConstraint MultipleOfIntegerConstraint;
typedef constraints::NotConstraint NotConstraint; typedef constraints::NotConstraint NotConstraint;
typedef constraints::OneOfConstraint OneOfConstraint; typedef constraints::OneOfConstraint OneOfConstraint;
typedef constraints::PatternConstraint PatternConstraint; typedef constraints::PatternConstraint PatternConstraint;
@@ -73,8 +71,7 @@ public:
virtual bool visit(const MinItemsConstraint &) = 0; virtual bool visit(const MinItemsConstraint &) = 0;
virtual bool visit(const MinLengthConstraint &) = 0; virtual bool visit(const MinLengthConstraint &) = 0;
virtual bool visit(const MinPropertiesConstraint &) = 0; virtual bool visit(const MinPropertiesConstraint &) = 0;
virtual bool visit(const MultipleOfDecimalConstraint &) = 0; virtual bool visit(const MultipleOfConstraint &) = 0;
virtual bool visit(const MultipleOfIntegerConstraint &) = 0;
virtual bool visit(const NotConstraint &) = 0; virtual bool visit(const NotConstraint &) = 0;
virtual bool visit(const OneOfConstraint &) = 0; virtual bool visit(const OneOfConstraint &) = 0;
virtual bool visit(const PatternConstraint &) = 0; virtual bool visit(const PatternConstraint &) = 0;

View File

@@ -21,7 +21,8 @@ public:
/** /**
* @brief Construct a new Schema instance with no constraints * @brief Construct a new Schema instance with no constraints
*/ */
Schema() {} Schema()
: sharedEmptySubschema(newSubschema()) { }
/** /**
* @brief Clean up and free all memory managed by the Schema * @brief Clean up and free all memory managed by the Schema
@@ -31,11 +32,14 @@ public:
*/ */
virtual ~Schema() virtual ~Schema()
{ {
sharedEmptySubschema->~Subschema();
::operator delete((void*)(sharedEmptySubschema));
sharedEmptySubschema = NULL;
try { try {
while (!subschemaSet.empty()) { for (std::set<Subschema *>::iterator itr = subschemaSet.begin();
std::set<Subschema*>::iterator itr = subschemaSet.begin(); itr != subschemaSet.end(); ++itr) {
Subschema *subschema = *itr; Subschema *subschema = *itr;
subschemaSet.erase(itr);
subschema->~Subschema(); subschema->~Subschema();
// TODO: Replace with custom free function // TODO: Replace with custom free function
::operator delete(subschema); ::operator delete(subschema);
@@ -60,20 +64,9 @@ public:
void addConstraintToSubschema(const Constraint &constraint, void addConstraintToSubschema(const Constraint &constraint,
const Subschema *subschema) const Subschema *subschema)
{ {
if (subschema == this) {
addConstraint(constraint);
return;
}
Subschema *noConst = const_cast<Subschema*>(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... // TODO: Check heirarchy for subschemas that do not belong...
noConst->addConstraint(constraint); mutableSubschema(subschema)->addConstraint(constraint);
} }
/** /**
@@ -83,25 +76,7 @@ public:
*/ */
const Subschema * createSubschema() const Subschema * createSubschema()
{ {
// TODO: Replace with custom malloc function Subschema *subschema = newSubschema();
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;
}
try { try {
if (!subschemaSet.insert(subschema).second) { if (!subschemaSet.insert(subschema).second) {
@@ -111,13 +86,21 @@ public:
} catch (...) { } catch (...) {
subschema->~Subschema(); subschema->~Subschema();
// TODO: Replace with custom free function // TODO: Replace with custom free function
::operator delete(ptr); ::operator delete(subschema);
throw; throw;
} }
return subschema; 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 * @brief Get a pointer to the root sub-schema of this Schema instance
*/ */
@@ -126,10 +109,102 @@ public:
return this; 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: 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*>(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 /// Set of Subschema instances owned by this schema
std::set<Subschema*> subschemaSet; std::set<Subschema*> subschemaSet;
/// Empty schema that can be reused by multiple constraints
const Subschema *sharedEmptySubschema;
}; };
} // namespace valijson } // namespace valijson

File diff suppressed because it is too large Load Diff

View File

@@ -2,10 +2,11 @@
#ifndef __VALIJSON_SUBSCHEMA_HPP #ifndef __VALIJSON_SUBSCHEMA_HPP
#define __VALIJSON_SUBSCHEMA_HPP #define __VALIJSON_SUBSCHEMA_HPP
#include <vector>
#include <boost/foreach.hpp> #include <boost/foreach.hpp>
#include <boost/function.hpp> #include <boost/function.hpp>
#include <boost/optional.hpp> #include <boost/optional.hpp>
#include <boost/ptr_container/ptr_vector.hpp>
#include <valijson/constraints/constraint.hpp> #include <valijson/constraints/constraint.hpp>
@@ -51,6 +52,24 @@ public:
: constraints(subschema.constraints), : constraints(subschema.constraints),
title(subschema.title) { } title(subschema.title) { }
/**
* @brief Clean up and free all memory managed by the Subschema
*/
virtual ~Subschema()
{
try {
for (std::vector<const Constraint *>::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 * @brief Add a constraint to this sub-schema
* *
@@ -94,8 +113,8 @@ public:
bool apply(ApplyFunction &applyFunction) const bool apply(ApplyFunction &applyFunction) const
{ {
bool allTrue = true; bool allTrue = true;
BOOST_FOREACH( const Constraint &constraint, constraints ) { BOOST_FOREACH( const Constraint *constraint, constraints ) {
allTrue = allTrue && applyFunction(constraint); allTrue = allTrue && applyFunction(*constraint);
} }
return allTrue; return allTrue;
@@ -113,8 +132,8 @@ public:
*/ */
bool applyStrict(ApplyFunction &applyFunction) const bool applyStrict(ApplyFunction &applyFunction) const
{ {
BOOST_FOREACH( const Constraint &constraint, constraints ) { BOOST_FOREACH( const Constraint *constraint, constraints ) {
if (!applyFunction(constraint)) { if (!applyFunction(*constraint)) {
return false; return false;
} }
} }
@@ -238,7 +257,7 @@ public:
private: private:
/// List of pointers to constraints that apply to this schema. /// List of pointers to constraints that apply to this schema.
boost::ptr_vector<Constraint> constraints; std::vector<const Constraint *> constraints;
/// Schema description (optional) /// Schema description (optional)
boost::optional<std::string> description; boost::optional<std::string> description;

View File

@@ -6,6 +6,7 @@
#include <boost/lexical_cast.hpp> #include <boost/lexical_cast.hpp>
#include <boost/regex.hpp> #include <boost/regex.hpp>
#include <boost/variant/get.hpp>
#include <valijson/constraints/concrete_constraints.hpp> #include <valijson/constraints/concrete_constraints.hpp>
#include <valijson/constraints/constraint_visitor.hpp> #include <valijson/constraints/constraint_visitor.hpp>
@@ -64,18 +65,18 @@ public:
* *
* @return true if validation passes, false otherwise * @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 // Wrap the validationCallback() function below so that it will be
// passed a reference to a constraint (_1), and a reference to the // passed a reference to a constraint (_1), and a reference to the
// visitor (*this). // 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 // Perform validation against each constraint defined in the schema
if (results == NULL) { if (results == NULL) {
// The applyStrict() function will return immediately if the // The applyStrict() function will return immediately if the
// callback function returns false // callback function returns false
if (!schema.applyStrict(fn)) { if (!subschema.applyStrict(fn)) {
return false; return false;
} }
} else { } else {
@@ -83,7 +84,7 @@ public:
// schema, even if the callback function returns false. Once // schema, even if the callback function returns false. Once
// iteration is complete, the apply() function will return true // iteration is complete, the apply() function will return true
// only if all invokations of the callback function returned true. // only if all invokations of the callback function returned true.
if (!schema.apply(fn)) { if (!subschema.apply(fn)) {
return false; return false;
} }
} }
@@ -116,10 +117,10 @@ public:
// Validate against each child schema // Validate against each child schema
unsigned int index = 0; 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 // Ensure that the target validates against child schema
if (!validateSchema(schema)) { if (!validateSchema(*subschema)) {
if (results) { if (results) {
validated = false; validated = false;
results->pushError(context, results->pushError(context,
@@ -161,8 +162,8 @@ public:
// visitor (*this). // visitor (*this).
Schema::ApplyFunction fn(boost::bind(validationCallback, _1, *this)); Schema::ApplyFunction fn(boost::bind(validationCallback, _1, *this));
BOOST_FOREACH( const Schema &schema, constraint.schemas ) { BOOST_FOREACH( const Subschema *subschema, constraint.schemas ) {
if (schema.apply(fn)) { if (subschema->apply(fn)) {
return true; return true;
} }
} }
@@ -231,8 +232,8 @@ public:
// dependent schema. // dependent schema.
PDSM::const_iterator depSchemasItr = depSchemas.find(m.first); PDSM::const_iterator depSchemasItr = depSchemas.find(m.first);
if (depSchemasItr != depSchemas.end()) { if (depSchemasItr != depSchemas.end()) {
const Schema *schema = depSchemasItr->second; const Subschema *subschema = depSchemasItr->second;
if (!validateSchema(*schema)) { if (!validateSchema(*subschema)) {
if (results) { if (results) {
results->pushError(context, "Failed to validate against dependent schema."); results->pushError(context, "Failed to validate against dependent schema.");
validated = false; validated = false;
@@ -326,14 +327,14 @@ public:
++index; ++index;
} }
} else if (constraint.itemSchemas) { } else if (!constraint.itemSchemas.empty()) {
// Get access to the target as an object // Get access to the target as an object
const typename AdapterType::Array arr = target.asArray(); const typename AdapterType::Array arr = target.asArray();
if (!constraint.additionalItemsSchema) { if (!constraint.additionalItemsSchema) {
// Check that the array length is <= length of the itemsSchema list // 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) { if (results) {
results->pushError(context, "Array contains more items than allowed by items constraint."); results->pushError(context, "Array contains more items than allowed by items constraint.");
validated = false; validated = false;
@@ -351,7 +352,7 @@ public:
newContext.push_back("[" + boost::lexical_cast<std::string>(index) + "]"); newContext.push_back("[" + boost::lexical_cast<std::string>(index) + "]");
ValidationVisitor<AdapterType> v(arrayItem, ValidationVisitor<AdapterType> v(arrayItem,
newContext, strictTypes, results); newContext, strictTypes, results);
if (index >= constraint.itemSchemas->size()) { if (index >= constraint.itemSchemas.size()) {
if (constraint.additionalItemsSchema) { if (constraint.additionalItemsSchema) {
if (!v.validateSchema(*constraint.additionalItemsSchema)) { if (!v.validateSchema(*constraint.additionalItemsSchema)) {
if (results) { if (results) {
@@ -367,7 +368,7 @@ public:
boost::lexical_cast<std::string>(index) + " in array due to missing schema."); boost::lexical_cast<std::string>(index) + " in array due to missing schema.");
validated = false; validated = false;
} }
} else if (!v.validateSchema(constraint.itemSchemas->at(index))) { } else if (!v.validateSchema(*constraint.itemSchemas.at(index))) {
if (results) { if (results) {
results->pushError(context, "Failed to validate item #" + results->pushError(context, "Failed to validate item #" +
boost::lexical_cast<std::string>(index) + " against corresponding item schema."); boost::lexical_cast<std::string>(index) + " against corresponding item schema.");
@@ -653,106 +654,99 @@ public:
/** /**
* @brief Validate against the multipleOf or divisibleBy constraints * @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. * @param constraint Constraint that the target must validate against.
* *
* @return true if the constraint is satisfied, false otherwise. * @return true if the constraint is satisfied, false otherwise.
*/ */
virtual bool visit(const MultipleOfDecimalConstraint &constraint) virtual bool visit(const MultipleOfConstraint &constraint)
{ {
double d = 0.; const int64_t *multipleOfInteger = boost::get<int64_t>(&constraint.value);
if (multipleOfInteger) {
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<std::string>(
constraint.multipleOf));
}
return false;
}
} else if (target.maybeInteger()) {
int64_t i = 0; 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<int64_t>(d);
} else {
return true;
}
if (i == 0) {
return true;
}
if (i % *multipleOfInteger != 0) {
if (results) { if (results) {
results->pushError(context, "Value could not be converted " results->pushError(context, "Value should be a multiple of " +
"to a number to check if it is a multiple of " + boost::lexical_cast<std::string>(*multipleOfInteger));
boost::lexical_cast<std::string>(
constraint.multipleOf));
} }
return false; return false;
} }
d = static_cast<double>(i);
} else {
return true; return true;
} }
if (d == 0) { const double *multipleOfDouble = boost::get<double>(&constraint.value);
return true; if (multipleOfDouble) {
} double d = 0.;
if (target.maybeDouble()) {
const double r = remainder(d, constraint.multipleOf); if (!target.asDouble(d)) {
if (results) {
if (fabs(r) > std::numeric_limits<double>::epsilon()) { results->pushError(context, "Value could not be converted "
if (results) { "to a number to check if it is a multiple of " +
results->pushError(context, "Value should be a multiple of " + boost::lexical_cast<std::string>(*multipleOfDouble));
boost::lexical_cast<std::string>(constraint.multipleOf)); }
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<std::string>(*multipleOfDouble));
}
return false;
}
d = static_cast<double>(i);
} else {
return true;
} }
return false;
}
return true; if (d == 0) {
} return true;
}
/** const double r = remainder(d, *multipleOfDouble);
* @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;
if (target.maybeInteger()) { if (fabs(r) > std::numeric_limits<double>::epsilon()) {
if (!target.asInteger(i)) {
if (results) { if (results) {
results->pushError(context, "Value could not be converted " results->pushError(context, "Value should be a multiple of " +
"to an integer for multipleOf check"); boost::lexical_cast<std::string>(*multipleOfDouble));
} }
return false; 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<int64_t>(d);
} else {
return true; return true;
} }
if (i == 0) { return false;
return true;
}
if (i % constraint.multipleOf != 0) {
if (results) {
results->pushError(context, "Value should be a multiple of " +
boost::lexical_cast<std::string>(constraint.multipleOf));
}
return false;
}
return true;
} }
/** /**
@@ -791,9 +785,9 @@ public:
ValidationResults newResults; ValidationResults newResults;
ValidationResults *childResults = (results) ? &newResults : NULL; ValidationResults *childResults = (results) ? &newResults : NULL;
BOOST_FOREACH( const Schema &schema, constraint.schemas ) { BOOST_FOREACH( const Subschema *subschema, constraint.schemas ) {
ValidationVisitor<AdapterType> v(target, context, strictTypes, childResults); ValidationVisitor<AdapterType> v(target, context, strictTypes, childResults);
if (v.validateSchema(schema)) { if (v.validateSchema(*subschema)) {
numValidated++; numValidated++;
} }
} }
@@ -1033,8 +1027,8 @@ public:
} }
} }
BOOST_FOREACH( const Schema &schema, constraint.schemas ) { BOOST_FOREACH( const Subschema *subschema, constraint.schemas ) {
if (validateSchema(schema)) { if (validateSchema(*subschema)) {
return true; return true;
} }
} }

View File

@@ -14,38 +14,30 @@ class Schema;
class ValidationResults; class ValidationResults;
/** /**
* @brief Class that wraps a schema and provides validation functionality. * @brief Class that provides validation functionality.
*
* This class wraps a Schema object, and encapsulates the logic required to
* validate rapidjson values aginst the schema.
*/ */
class Validator class Validator
{ {
public: public:
enum TypeCheckingMode
/**
* @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)
{ {
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. * @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 * will only continue for as long as the constraints are validated
* successfully. * 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 * @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<typename AdapterType> template<typename AdapterType>
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 // Construct a ValidationVisitor to perform validation at the root level
ValidationVisitor<AdapterType> v(target, std::vector<std::string>(1, "<root>"), ValidationVisitor<AdapterType> v(target,
strictTypes, results); std::vector<std::string>(1, "<root>"), strictTypes, results);
return v.validateSchema(*schema); return v.validateSchema(schema);
} }
private: private:
/// Pointer to an internal copy of a schema to use for validation
boost::scoped_ptr<const Schema> schema;
/// Flag indicating that strict type comparisons should be used /// Flag indicating that strict type comparisons should be used
bool strictTypes; const bool strictTypes;
}; };

View File

@@ -70,12 +70,12 @@ TEST_F(TestFetchDocumentCallback, Basics)
rapidjson::Document validDocument; rapidjson::Document validDocument;
validDocument.SetObject(); validDocument.SetObject();
validDocument.AddMember("test", "valid", allocator); validDocument.AddMember("test", "valid", allocator);
Validator validator(schema); Validator validator;
EXPECT_TRUE(validator.validate(RapidJsonAdapter(validDocument), NULL)); EXPECT_TRUE(validator.validate(schema, RapidJsonAdapter(validDocument), NULL));
// Test resulting schema with an invalid document // Test resulting schema with an invalid document
rapidjson::Document invalidDocument; rapidjson::Document invalidDocument;
invalidDocument.SetObject(); invalidDocument.SetObject();
invalidDocument.AddMember("test", 123, allocator); invalidDocument.AddMember("test", 123, allocator);
EXPECT_FALSE(validator.validate(RapidJsonAdapter(invalidDocument), NULL)); EXPECT_FALSE(validator.validate(schema, RapidJsonAdapter(invalidDocument), NULL));
} }

View File

@@ -46,9 +46,9 @@ TEST_F(TestValidationErrors, AllOfConstraintFailure)
ASSERT_TRUE( loadDocument(TEST_DATA_DIR "/documents/array_doubles_1_2_3.json", testDocument) ); ASSERT_TRUE( loadDocument(TEST_DATA_DIR "/documents/array_doubles_1_2_3.json", testDocument) );
RapidJsonAdapter testAdapter(testDocument); RapidJsonAdapter testAdapter(testDocument);
Validator validator(schema); Validator validator;
ValidationResults results; ValidationResults results;
EXPECT_FALSE( validator.validate(testAdapter, &results) ); EXPECT_FALSE( validator.validate(schema, testAdapter, &results) );
ValidationResults::Error error; ValidationResults::Error error;

View File

@@ -96,10 +96,10 @@ protected:
testItr = testObject.find("data"); testItr = testObject.find("data");
ASSERT_NE( testObject.end(), testItr ); ASSERT_NE( testObject.end(), testItr );
Validator validator(schema); Validator validator(strict ?
validator.setStrict(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 '" << "Failed while testing validate() function in '"
<< currentTest << "' of test case '" << currentTest << "' of test case '"
<< currentTestCase << "' with adapter '" << currentTestCase << "' with adapter '"