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::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;

View File

@ -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;

View File

@ -16,14 +16,14 @@
#ifndef __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 <map>
#include <set>
#include <string>
#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 <vector>
#include <valijson/adapters/frozen_value.hpp>
#include <valijson/constraints/basic_constraint.hpp>
@ -44,7 +44,7 @@ namespace constraints {
*/
struct AllOfConstraint: BasicConstraint<AllOfConstraint>
{
typedef boost::ptr_vector<Schema> Schemas;
typedef std::vector<const Subschema *> Schemas;
AllOfConstraint(const Schemas &schemas)
: schemas(schemas) { }
@ -62,7 +62,7 @@ struct AllOfConstraint: BasicConstraint<AllOfConstraint>
*/
struct AnyOfConstraint: BasicConstraint<AnyOfConstraint>
{
typedef boost::ptr_vector<Schema> Schemas;
typedef std::vector<const Subschema *> Schemas;
AnyOfConstraint(const Schemas &schemas)
: schemas(schemas) { }
@ -84,7 +84,8 @@ struct DependenciesConstraint: BasicConstraint<DependenciesConstraint>
typedef std::map<std::string, Dependencies> PropertyDependenciesMap;
// 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,
const PropertyDependentSchemasMap &dependentSchemas)
@ -117,7 +118,7 @@ struct EnumConstraint: BasicConstraint<EnumConstraint>
*/
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
@ -125,8 +126,9 @@ struct ItemsConstraint: BasicConstraint<ItemsConstraint>
*
* @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<ItemsConstraint>
* @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<ItemsConstraint>
* @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<ItemsConstraint>
* @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<const Schema> itemSchema;
const boost::scoped_ptr<const Schemas> itemSchemas;
const boost::scoped_ptr<const Schema> additionalItemsSchema;
const Subschema* itemSchema;
const Schemas itemSchemas;
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)
: 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>
{
MultipleOfIntegerConstraint(int64_t multipleOf)
: multipleOf(multipleOf) { }
const int64_t multipleOf;
const boost::variant<double, int64_t> value;
};
/**
@ -291,13 +288,13 @@ struct MultipleOfIntegerConstraint: BasicConstraint<MultipleOfIntegerConstraint>
*/
struct NotConstraint: BasicConstraint<NotConstraint>
{
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<const Schema> schema;
const Subschema *schema;
};
/**
@ -305,7 +302,7 @@ struct NotConstraint: BasicConstraint<NotConstraint>
*/
struct OneOfConstraint: BasicConstraint<OneOfConstraint>
{
typedef boost::ptr_vector<Schema> Schemas;
typedef std::vector<const Subschema *> Schemas;
OneOfConstraint(const Schemas &schemas)
: schemas(schemas) { }
@ -331,29 +328,33 @@ struct PatternConstraint: BasicConstraint<PatternConstraint>
*/
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,
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<const Schema> additionalProperties;
const Subschema *additionalProperties;
};
@ -391,15 +392,15 @@ struct TypeConstraint: BasicConstraint<TypeConstraint>
typedef std::set<JsonType> JsonTypes;
typedef boost::ptr_vector<Schema> Schemas;
typedef std::vector<const Subschema *> 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) { }

View File

@ -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;

View File

@ -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<Subschema*>::iterator itr = subschemaSet.begin();
for (std::set<Subschema *>::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*>(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*>(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<Subschema*> subschemaSet;
/// Empty schema that can be reused by multiple constraints
const Subschema *sharedEmptySubschema;
};
} // namespace valijson

File diff suppressed because it is too large Load Diff

View File

@ -2,10 +2,11 @@
#ifndef __VALIJSON_SUBSCHEMA_HPP
#define __VALIJSON_SUBSCHEMA_HPP
#include <vector>
#include <boost/foreach.hpp>
#include <boost/function.hpp>
#include <boost/optional.hpp>
#include <boost/ptr_container/ptr_vector.hpp>
#include <valijson/constraints/constraint.hpp>
@ -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<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
*
@ -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<Constraint> constraints;
std::vector<const Constraint *> constraints;
/// Schema description (optional)
boost::optional<std::string> description;

View File

@ -6,6 +6,7 @@
#include <boost/lexical_cast.hpp>
#include <boost/regex.hpp>
#include <boost/variant/get.hpp>
#include <valijson/constraints/concrete_constraints.hpp>
#include <valijson/constraints/constraint_visitor.hpp>
@ -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<std::string>(index) + "]");
ValidationVisitor<AdapterType> 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<std::string>(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<std::string>(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<std::string>(
constraint.multipleOf));
}
return false;
}
} else if (target.maybeInteger()) {
const int64_t *multipleOfInteger = boost::get<int64_t>(&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<int64_t>(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<std::string>(
constraint.multipleOf));
results->pushError(context, "Value should be a multiple of " +
boost::lexical_cast<std::string>(*multipleOfInteger));
}
return false;
}
d = static_cast<double>(i);
} else {
return true;
}
if (d == 0) {
return true;
}
const double r = remainder(d, constraint.multipleOf);
if (fabs(r) > std::numeric_limits<double>::epsilon()) {
if (results) {
results->pushError(context, "Value should be a multiple of " +
boost::lexical_cast<std::string>(constraint.multipleOf));
const double *multipleOfDouble = boost::get<double>(&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<std::string>(*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<std::string>(*multipleOfDouble));
}
return false;
}
d = static_cast<double>(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<double>::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<std::string>(*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<int64_t>(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<std::string>(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<AdapterType> 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;
}
}

View File

@ -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<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
ValidationVisitor<AdapterType> v(target, std::vector<std::string>(1, "<root>"),
strictTypes, results);
ValidationVisitor<AdapterType> v(target,
std::vector<std::string>(1, "<root>"), 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<const Schema> schema;
/// 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;
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));
}

View File

@ -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;

View File

@ -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 '"