mirror of
https://github.com/tristanpenman/valijson.git
synced 2024-12-13 10:32:58 +01:00
Refactor memory management for schemas and constraints to improve API and allow for future improvements
This commit is contained in:
parent
015d463d0c
commit
b1755309d2
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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) { }
|
||||
|
@ -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;
|
||||
|
@ -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
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
};
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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 '"
|
||||
|
Loading…
Reference in New Issue
Block a user