mirror of
https://github.com/tristanpenman/valijson.git
synced 2025-01-19 08:46:42 +01:00
Split ItemsConstraint into SingularItemsConstraint and LinearItemsConstraint, using CustomAllocator where necessary
This commit is contained in:
parent
a02048cfcf
commit
579b0708ee
@ -248,67 +248,70 @@ struct EnumConstraint: BasicConstraint<EnumConstraint>
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Represents a pair of 'items' and 'additionalItems' constraints.
|
||||
* @brief Represents non-singular 'items' and 'additionalItems' constraints
|
||||
*
|
||||
* Unlike the SingularItemsConstraint class, this class represents an 'items'
|
||||
* constraint that specifies an array of sub-schemas, which should be used to
|
||||
* validate each item in an array, in sequence. It also represents an optional
|
||||
* 'additionalItems' sub-schema that should be used when an array contains
|
||||
* more values than there are sub-schemas in the 'items' constraint.
|
||||
*
|
||||
* The prefix 'Linear' comes from the fact that this class contains a list of
|
||||
* sub-schemas that corresponding array items must be validated against, and
|
||||
* this validation is performed linearly (i.e. in sequence).
|
||||
*/
|
||||
struct ItemsConstraint: BasicConstraint<ItemsConstraint>
|
||||
class LinearItemsConstraint: public BasicConstraint<LinearItemsConstraint>
|
||||
{
|
||||
typedef std::vector<const Subschema *> Schemas;
|
||||
public:
|
||||
LinearItemsConstraint()
|
||||
: itemSubschemas(Allocator::rebind<const Subschema *>::other(allocator)),
|
||||
additionalItemsSubschema(NULL) { }
|
||||
|
||||
/**
|
||||
* @brief Construct a singular item constraint that allows no additional
|
||||
* items
|
||||
*
|
||||
* @param itemSchema
|
||||
*/
|
||||
ItemsConstraint(const Subschema *itemSchema)
|
||||
: itemSchema(itemSchema),
|
||||
additionalItemsSchema(NULL) { }
|
||||
LinearItemsConstraint(CustomAlloc allocFn, CustomFree freeFn)
|
||||
: BasicConstraint(allocFn, freeFn),
|
||||
itemSubschemas(Allocator::rebind<const Subschema *>::other(allocator)),
|
||||
additionalItemsSubschema(NULL) { }
|
||||
|
||||
/**
|
||||
* @brief Construct a singular item schema that allows additional items
|
||||
*
|
||||
* @param itemSchema
|
||||
* @param additionalItemsSchema
|
||||
*/
|
||||
ItemsConstraint(const Subschema *itemSchema,
|
||||
const Subschema *additionalItemsSchema)
|
||||
: itemSchema(itemSchema),
|
||||
additionalItemsSchema(additionalItemsSchema) { }
|
||||
void addItemSubschema(const Subschema *subschema)
|
||||
{
|
||||
itemSubschemas.push_back(subschema);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a plural items constraint that does not allow for
|
||||
* additional item schemas
|
||||
*
|
||||
* @param itemSchemas collection of item schemas
|
||||
*/
|
||||
ItemsConstraint(const Schemas &itemSchemas)
|
||||
: itemSchema(NULL),
|
||||
itemSchemas(itemSchemas),
|
||||
additionalItemsSchema(NULL) { }
|
||||
template<typename FunctorType>
|
||||
void applyToItemSubschemas(const FunctorType &fn) const
|
||||
{
|
||||
unsigned int index = 0;
|
||||
BOOST_FOREACH( const Subschema *subschema, itemSubschemas ) {
|
||||
if (!fn(index, subschema)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a plural items constraint that allows additional items
|
||||
*
|
||||
* @param itemSchemas
|
||||
* @param additionalItemsSchema
|
||||
*/
|
||||
ItemsConstraint(const Schemas &itemSchemas,
|
||||
const Subschema *additionalItemsSchema)
|
||||
: itemSchema(NULL),
|
||||
itemSchemas(itemSchemas),
|
||||
additionalItemsSchema(additionalItemsSchema) { }
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Copy constructor
|
||||
*/
|
||||
ItemsConstraint(const ItemsConstraint &other)
|
||||
: itemSchema(other.itemSchema),
|
||||
itemSchemas(other.itemSchemas),
|
||||
additionalItemsSchema(other.additionalItemsSchema) { }
|
||||
const Subschema * getAdditionalItemsSubschema() const
|
||||
{
|
||||
return additionalItemsSubschema;
|
||||
}
|
||||
|
||||
const Subschema* itemSchema;
|
||||
const Schemas itemSchemas;
|
||||
const Subschema* additionalItemsSchema;
|
||||
size_t getItemSubschemaCount() const
|
||||
{
|
||||
return itemSubschemas.size();
|
||||
}
|
||||
|
||||
void setAdditionalItemsSubschema(const Subschema *subschema)
|
||||
{
|
||||
additionalItemsSubschema = subschema;
|
||||
}
|
||||
|
||||
private:
|
||||
typedef std::vector<const Subschema *,
|
||||
internal::CustomAllocator<const Subschema *> > Subschemas;
|
||||
|
||||
Subschemas itemSubschemas;
|
||||
|
||||
const Subschema* additionalItemsSubschema;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -533,6 +536,36 @@ struct RequiredConstraint: BasicConstraint<RequiredConstraint>
|
||||
const RequiredProperties requiredProperties;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Represents an 'items' constraint that specifies one sub-schema
|
||||
*
|
||||
* A value is considered valid against this constraint if it is an array, and
|
||||
* each item in the array validates against the sub-schema specified by this
|
||||
* constraint.
|
||||
*
|
||||
* The prefix 'Singular' comes from the fact that array items must validate
|
||||
* against exactly one sub-schema.
|
||||
*/
|
||||
class SingularItemsConstraint: public BasicConstraint<SingularItemsConstraint>
|
||||
{
|
||||
public:
|
||||
SingularItemsConstraint()
|
||||
: itemsSubschema(NULL) { }
|
||||
|
||||
const Subschema * getItemsSubschema() const
|
||||
{
|
||||
return itemsSubschema;
|
||||
}
|
||||
|
||||
void setItemsSubschema(const Subschema *subschema)
|
||||
{
|
||||
itemsSubschema = subschema;
|
||||
}
|
||||
|
||||
private:
|
||||
const Subschema *itemsSubschema;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Represents a 'type' constraint.
|
||||
*/
|
||||
|
@ -6,7 +6,6 @@ namespace valijson {
|
||||
namespace constraints {
|
||||
|
||||
struct EnumConstraint;
|
||||
struct ItemsConstraint;
|
||||
struct FormatConstraint;
|
||||
struct MaximumConstraint;
|
||||
struct MaxItemsConstraint;
|
||||
@ -27,7 +26,9 @@ struct UniqueItemsConstraint;
|
||||
class AllOfConstraint;
|
||||
class AnyOfConstraint;
|
||||
class DependenciesConstraint;
|
||||
class LinearItemsConstraint;
|
||||
class OneOfConstraint;
|
||||
class SingularItemsConstraint;
|
||||
|
||||
/// Interface to allow usage of the visitor pattern with Constraints
|
||||
class ConstraintVisitor
|
||||
@ -39,7 +40,7 @@ protected:
|
||||
typedef constraints::AnyOfConstraint AnyOfConstraint;
|
||||
typedef constraints::DependenciesConstraint DependenciesConstraint;
|
||||
typedef constraints::EnumConstraint EnumConstraint;
|
||||
typedef constraints::ItemsConstraint ItemsConstraint;
|
||||
typedef constraints::LinearItemsConstraint LinearItemsConstraint;
|
||||
typedef constraints::MaximumConstraint MaximumConstraint;
|
||||
typedef constraints::MaxItemsConstraint MaxItemsConstraint;
|
||||
typedef constraints::MaxLengthConstraint MaxLengthConstraint;
|
||||
@ -54,6 +55,7 @@ protected:
|
||||
typedef constraints::PatternConstraint PatternConstraint;
|
||||
typedef constraints::PropertiesConstraint PropertiesConstraint;
|
||||
typedef constraints::RequiredConstraint RequiredConstraint;
|
||||
typedef constraints::SingularItemsConstraint SingularItemsConstraint;
|
||||
typedef constraints::TypeConstraint TypeConstraint;
|
||||
typedef constraints::UniqueItemsConstraint UniqueItemsConstraint;
|
||||
|
||||
@ -63,7 +65,7 @@ public:
|
||||
virtual bool visit(const AnyOfConstraint &) = 0;
|
||||
virtual bool visit(const DependenciesConstraint &) = 0;
|
||||
virtual bool visit(const EnumConstraint &) = 0;
|
||||
virtual bool visit(const ItemsConstraint &) = 0;
|
||||
virtual bool visit(const LinearItemsConstraint &) = 0;
|
||||
virtual bool visit(const MaximumConstraint &) = 0;
|
||||
virtual bool visit(const MaxItemsConstraint &) = 0;
|
||||
virtual bool visit(const MaxLengthConstraint &) = 0;
|
||||
@ -78,6 +80,7 @@ public:
|
||||
virtual bool visit(const PatternConstraint &) = 0;
|
||||
virtual bool visit(const PropertiesConstraint &) = 0;
|
||||
virtual bool visit(const RequiredConstraint &) = 0;
|
||||
virtual bool visit(const SingularItemsConstraint &) = 0;
|
||||
virtual bool visit(const TypeConstraint &) = 0;
|
||||
virtual bool visit(const UniqueItemsConstraint &) = 0;
|
||||
|
||||
|
@ -199,22 +199,30 @@ private:
|
||||
}
|
||||
|
||||
{
|
||||
// Check for schema keywords that require the creation of a
|
||||
// ItemsConstraint instance.
|
||||
const typename AdapterType::Object::const_iterator
|
||||
itemsItr = object.find("items"),
|
||||
additionalitemsItr = object.find("additionalItems");
|
||||
if (object.end() != itemsItr ||
|
||||
object.end() != additionalitemsItr) {
|
||||
rootSchema.addConstraintToSubschema(
|
||||
makeItemsConstraint(rootSchema, rootNode,
|
||||
itemsItr != object.end() ?
|
||||
&itemsItr->second : NULL,
|
||||
additionalitemsItr != object.end() ?
|
||||
&additionalitemsItr->second : NULL,
|
||||
currentScope, nodePath + "/items",
|
||||
nodePath + "/additionalItems", fetchDoc),
|
||||
&subschema);
|
||||
const typename AdapterType::Object::const_iterator itemsItr =
|
||||
object.find("items");
|
||||
|
||||
if (object.end() != itemsItr) {
|
||||
if (!itemsItr->second.isArray()) {
|
||||
rootSchema.addConstraintToSubschema(
|
||||
makeSingularItemsConstraint(rootSchema, rootNode,
|
||||
itemsItr->second, currentScope,
|
||||
nodePath + "/items", fetchDoc),
|
||||
&subschema);
|
||||
|
||||
} else {
|
||||
const typename AdapterType::Object::const_iterator
|
||||
additionalItemsItr = object.find("additionalItems");
|
||||
rootSchema.addConstraintToSubschema(
|
||||
makeLinearItemsConstraint(rootSchema, rootNode,
|
||||
itemsItr != object.end() ?
|
||||
&itemsItr->second : NULL,
|
||||
additionalItemsItr != object.end() ?
|
||||
&additionalItemsItr->second : NULL,
|
||||
currentScope, nodePath + "/items",
|
||||
nodePath + "/additionalItems", fetchDoc),
|
||||
&subschema);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -728,7 +736,7 @@ private:
|
||||
* @return pointer to a new ItemsConstraint that belongs to the caller
|
||||
*/
|
||||
template<typename AdapterType>
|
||||
constraints::ItemsConstraint makeItemsConstraint(
|
||||
constraints::LinearItemsConstraint makeLinearItemsConstraint(
|
||||
Schema &rootSchema,
|
||||
const AdapterType &rootNode,
|
||||
const AdapterType *items,
|
||||
@ -739,24 +747,27 @@ private:
|
||||
const boost::optional<
|
||||
typename FetchDocumentFunction<AdapterType>::Type > fetchDoc)
|
||||
{
|
||||
constraints::LinearItemsConstraint constraint;
|
||||
|
||||
// Construct a Schema object for the the additionalItems constraint,
|
||||
// if the additionalItems property is present
|
||||
const Subschema *additionalItemsSchema = NULL;
|
||||
if (additionalItems) {
|
||||
if (additionalItems->maybeBool()) {
|
||||
// If the value of the additionalItems property is a boolean
|
||||
// and is set to true, then additional array items do not need
|
||||
// to satisfy any constraints.
|
||||
if (additionalItems->asBool()) {
|
||||
additionalItemsSchema = rootSchema.createSubschema();
|
||||
constraint.setAdditionalItemsSubschema(
|
||||
rootSchema.emptySubschema());
|
||||
}
|
||||
} else if (additionalItems->maybeObject()) {
|
||||
// If the value of the additionalItems property is an object,
|
||||
// then it should be parsed into a Schema object, which will be
|
||||
// used to validate additional array items.
|
||||
additionalItemsSchema = rootSchema.createSubschema();
|
||||
const Subschema *subschema = rootSchema.createSubschema();
|
||||
constraint.setAdditionalItemsSubschema(subschema);
|
||||
populateSchema<AdapterType>(rootSchema, rootNode,
|
||||
*additionalItems, *additionalItemsSchema, currentScope,
|
||||
*additionalItems, *subschema, currentScope,
|
||||
additionalItemsPath, fetchDoc);
|
||||
} else {
|
||||
// Any other format for the additionalItems property will result
|
||||
@ -768,14 +779,12 @@ private:
|
||||
// The default value for the additionalItems property is an empty
|
||||
// object, which means that additional array items do not need to
|
||||
// satisfy any constraints.
|
||||
additionalItemsSchema = rootSchema.createSubschema();
|
||||
constraint.setAdditionalItemsSubschema(rootSchema.emptySubschema());
|
||||
}
|
||||
|
||||
// Construct a Schema object for each item in the items array, if an
|
||||
// array is provided, or a single Schema object, in an object value is
|
||||
// provided. If the items constraint is not provided, then array items
|
||||
// Construct a Schema object for each item in the items array.
|
||||
// If the items constraint is not provided, then array items
|
||||
// will be validated against the additionalItems schema.
|
||||
constraints::ItemsConstraint::Schemas itemSchemas;
|
||||
if (items) {
|
||||
if (items->isArray()) {
|
||||
// If the items constraint contains an array, then it should
|
||||
@ -786,61 +795,87 @@ private:
|
||||
BOOST_FOREACH( const AdapterType v, items->getArray() ) {
|
||||
const std::string childPath = itemsPath + "/" +
|
||||
boost::lexical_cast<std::string>(index);
|
||||
itemSchemas.push_back(rootSchema.createSubschema());
|
||||
const Subschema &childSubschema = *itemSchemas.back();
|
||||
const Subschema *subschema = rootSchema.createSubschema();
|
||||
constraint.addItemSubschema(subschema);
|
||||
populateSchema<AdapterType>(rootSchema, rootNode, v,
|
||||
childSubschema, currentScope, childPath, fetchDoc);
|
||||
*subschema, currentScope, childPath, fetchDoc);
|
||||
index++;
|
||||
}
|
||||
|
||||
// Create an ItemsConstraint object using the appropriate
|
||||
// overloaded constructor.
|
||||
if (additionalItemsSchema) {
|
||||
return constraints::ItemsConstraint(itemSchemas,
|
||||
additionalItemsSchema);
|
||||
} else {
|
||||
return constraints::ItemsConstraint(itemSchemas);
|
||||
}
|
||||
|
||||
} else if (items->isObject()) {
|
||||
// If the items constraint contains an object value, then it
|
||||
// should contain a Schema that will be used to validate all
|
||||
// items in a target array. Any schema defined by the
|
||||
// additionalItems constraint will be ignored.
|
||||
const Subschema *childSubschema = rootSchema.createSubschema();
|
||||
populateSchema<AdapterType>(rootSchema, rootNode, *items,
|
||||
*childSubschema, currentScope, itemsPath, fetchDoc);
|
||||
if (additionalItemsSchema) {
|
||||
return constraints::ItemsConstraint(childSubschema,
|
||||
additionalItemsSchema);
|
||||
} else {
|
||||
return constraints::ItemsConstraint(childSubschema);
|
||||
}
|
||||
|
||||
} else if (items->maybeObject()) {
|
||||
// If a loosely-typed Adapter type is being used, then we'll
|
||||
// assume that an empty schema has been provided.
|
||||
const Subschema *childSubschema = rootSchema.createSubschema();
|
||||
if (additionalItemsSchema) {
|
||||
return constraints::ItemsConstraint(childSubschema,
|
||||
additionalItemsSchema);
|
||||
} else {
|
||||
return constraints::ItemsConstraint(childSubschema);
|
||||
}
|
||||
|
||||
} else {
|
||||
// All other formats will result in an exception being thrown.
|
||||
throw std::runtime_error(
|
||||
"Expected array or object value for 'items'.");
|
||||
"Expected array value for non-singular 'items' "
|
||||
"constraint.");
|
||||
}
|
||||
}
|
||||
|
||||
if (additionalItemsSchema) {
|
||||
return constraints::ItemsConstraint(rootSchema.emptySubschema(),
|
||||
additionalItemsSchema);
|
||||
return constraint;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Make a new ItemsConstraint object.
|
||||
*
|
||||
* @param rootSchema The Schema instance, and root subschema,
|
||||
* through which other subschemas can be
|
||||
* created and modified
|
||||
* @param rootNode Reference to the node from which JSON
|
||||
* References will be resolved when they refer
|
||||
* to the current document; used for recursive
|
||||
* parsing of schemas
|
||||
* @param items Optional pointer to a JSON node containing
|
||||
* an object mapping property names to
|
||||
* schemas.
|
||||
* @param additionalItems Optional pointer to a JSON node containing
|
||||
* an additional properties schema or a
|
||||
* boolean value.
|
||||
* @param currentScope URI for current resolution scope
|
||||
* @param itemsPath JSON Pointer representing the path to
|
||||
* the 'items' node
|
||||
* @param additionalItemsPath JSON Pointer representing the path to
|
||||
* the 'additionalItems' node
|
||||
* @param fetchDoc Function to fetch remote JSON documents
|
||||
* (optional)
|
||||
*
|
||||
* @return pointer to a new ItemsConstraint that belongs to the caller
|
||||
*/
|
||||
template<typename AdapterType>
|
||||
constraints::SingularItemsConstraint makeSingularItemsConstraint(
|
||||
Schema &rootSchema,
|
||||
const AdapterType &rootNode,
|
||||
const AdapterType &items,
|
||||
const boost::optional<std::string> currentScope,
|
||||
const std::string &itemsPath,
|
||||
const boost::optional<
|
||||
typename FetchDocumentFunction<AdapterType>::Type > fetchDoc)
|
||||
{
|
||||
constraints::SingularItemsConstraint constraint;
|
||||
|
||||
// Construct a Schema object for each item in the items array, if an
|
||||
// array is provided, or a single Schema object, in an object value is
|
||||
// provided. If the items constraint is not provided, then array items
|
||||
// will be validated against the additionalItems schema.
|
||||
if (items.isObject()) {
|
||||
// If the items constraint contains an object value, then it
|
||||
// should contain a Schema that will be used to validate all
|
||||
// items in a target array. Any schema defined by the
|
||||
// additionalItems constraint will be ignored.
|
||||
const Subschema *subschema = rootSchema.createSubschema();
|
||||
constraint.setItemsSubschema(subschema);
|
||||
populateSchema<AdapterType>(rootSchema, rootNode, items,
|
||||
*subschema, currentScope, itemsPath, fetchDoc);
|
||||
|
||||
} else if (items.maybeObject()) {
|
||||
// If a loosely-typed Adapter type is being used, then we'll
|
||||
// assume that an empty schema has been provided.
|
||||
constraint.setItemsSubschema(rootSchema.emptySubschema());
|
||||
|
||||
} else {
|
||||
// All other formats will result in an exception being thrown.
|
||||
throw std::runtime_error(
|
||||
"Expected object value for singular 'items' "
|
||||
"constraint.");
|
||||
}
|
||||
|
||||
return constraints::ItemsConstraint(rootSchema.emptySubschema());
|
||||
return constraint;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -232,68 +232,52 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Validate against the items and additionalItems constraints
|
||||
* represented by an ItemsConstraint object.
|
||||
* @brief Validate a value against a LinearItemsConstraint
|
||||
|
||||
* A LinearItemsConstraint represents an 'items' constraint that specifies,
|
||||
* for each item in array, an individual sub-schema that the item must
|
||||
* validate against. The LinearItemsConstraint class also captures the
|
||||
* presence of an 'additionalItems' constraint, which specifies a default
|
||||
* sub-schema that should be used if an array contains more items than
|
||||
* there are sub-schemas in the 'items' constraint.
|
||||
*
|
||||
* An items constraint restricts the values in array to those that match a
|
||||
* given set of schemas. An item constraint can specify either an ordered
|
||||
* list of child schemas that will be used to validate the corresponding
|
||||
* value in the target array, or a single schema that will be used to
|
||||
* validate all items.
|
||||
* If the current value is not an array, validation always succeeds.
|
||||
*
|
||||
* If a list of child schemas is used, then the additionalItems constraint
|
||||
* will also be considered. If present, the schema derived from the
|
||||
* additionalItems constraint will be used to validate items that do not
|
||||
* have a corresponding child schema in the items constraint. If the
|
||||
* items constraint was not provided, then the additionalItems schema will
|
||||
* be used to validate all items in the array.
|
||||
* @param constraint SingularItemsConstraint to validate against
|
||||
*
|
||||
* @param constraint Constraint that the target must validate against.
|
||||
*
|
||||
* @return true if validatation succeeds, false otherwise.
|
||||
* @returns \c true if validation is successful; \c false otherwise
|
||||
*/
|
||||
virtual bool visit(const ItemsConstraint &constraint)
|
||||
virtual bool visit(const LinearItemsConstraint &constraint)
|
||||
{
|
||||
// Ignore values that are not arrays
|
||||
if ((strictTypes && !target.isArray()) || (!target.maybeArray())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Sub-schema to validate against when number of items in array exceeds
|
||||
// the number of sub-schemas provided by the 'items' constraint
|
||||
const Subschema * const additionalItemsSubschema =
|
||||
constraint.getAdditionalItemsSubschema();
|
||||
|
||||
// Track how many items are validated using 'items' constraint
|
||||
unsigned int numValidated = 0;
|
||||
|
||||
// Array to validate
|
||||
const typename AdapterType::Array arr = target.asArray();
|
||||
const size_t arrSize = arr.size();
|
||||
|
||||
// Track validation status
|
||||
bool validated = true;
|
||||
|
||||
if (constraint.itemSchema) {
|
||||
|
||||
// Get access to the target as an object
|
||||
const typename AdapterType::Array arr = target.asArray();
|
||||
|
||||
// Validate all items against single schema
|
||||
unsigned int index = 0;
|
||||
BOOST_FOREACH( const AdapterType arrayItem, arr ) {
|
||||
std::vector<std::string> newContext = context;
|
||||
newContext.push_back("[" + boost::lexical_cast<std::string>(index) + "]");
|
||||
ValidationVisitor<AdapterType> v(arrayItem,
|
||||
newContext, strictTypes, results);
|
||||
if (!v.validateSchema(*constraint.itemSchema)) {
|
||||
// Validate as many items as possible using 'items' sub-schemas
|
||||
const size_t itemSubschemaCount = constraint.getItemSubschemaCount();
|
||||
if (itemSubschemaCount > 0) {
|
||||
if (!additionalItemsSubschema) {
|
||||
if (arrSize > itemSubschemaCount) {
|
||||
if (results) {
|
||||
results->pushError(context, "Failed to validate item #" + boost::lexical_cast<std::string>(index) + " in array.");
|
||||
validated = false;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
++index;
|
||||
}
|
||||
|
||||
} 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 (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;
|
||||
} else {
|
||||
return false;
|
||||
@ -301,67 +285,59 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
// Validate items against the schema with the same index, or
|
||||
// additionalItems schema
|
||||
unsigned int index = 0;
|
||||
BOOST_FOREACH( const AdapterType arrayItem, arr ) {
|
||||
std::vector<std::string> newContext = context;
|
||||
newContext.push_back("[" + boost::lexical_cast<std::string>(index) + "]");
|
||||
ValidationVisitor<AdapterType> v(arrayItem,
|
||||
newContext, strictTypes, results);
|
||||
if (index >= constraint.itemSchemas.size()) {
|
||||
if (constraint.additionalItemsSchema) {
|
||||
if (!v.validateSchema(*constraint.additionalItemsSchema)) {
|
||||
if (results) {
|
||||
results->pushError(context, "Failed to validate item #" +
|
||||
boost::lexical_cast<std::string>(index) + " against additional items schema.");
|
||||
validated = false;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
constraint.applyToItemSubschemas(ValidateItems(arr, context,
|
||||
results != NULL, strictTypes, results, &numValidated,
|
||||
&validated));
|
||||
|
||||
if (!results && !validated) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Validate remaining items using 'additionalItems' sub-schema
|
||||
if (numValidated < arrSize) {
|
||||
if (additionalItemsSubschema) {
|
||||
// Begin validation from the first item not validated against
|
||||
// an sub-schema provided by the 'items' constraint
|
||||
unsigned int index = numValidated;
|
||||
typename AdapterType::Array::const_iterator begin = arr.begin();
|
||||
begin.advance(numValidated);
|
||||
for (typename AdapterType::Array::const_iterator itr = begin;
|
||||
itr != arr.end(); ++itr) {
|
||||
|
||||
// Update context for current array item
|
||||
std::vector<std::string> newContext = context;
|
||||
newContext.push_back("[" +
|
||||
boost::lexical_cast<std::string>(index) + "]");
|
||||
|
||||
ValidationVisitor<AdapterType> validator(*itr, newContext,
|
||||
strictTypes, results);
|
||||
|
||||
if (!validator.validateSchema(*additionalItemsSubschema)) {
|
||||
if (results) {
|
||||
results->pushError(context,
|
||||
"Failed to validate item #" +
|
||||
boost::lexical_cast<std::string>(index) +
|
||||
" against additional items schema.");
|
||||
validated = false;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
results->pushError(context, "Cannot validate item #" +
|
||||
boost::lexical_cast<std::string>(index) + " in array due to missing schema.");
|
||||
validated = false;
|
||||
}
|
||||
} 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.");
|
||||
validated = false;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
++index;
|
||||
|
||||
} else if (results) {
|
||||
results->pushError(context, "Cannot validate item #" +
|
||||
boost::lexical_cast<std::string>(numValidated) + " or "
|
||||
"greater using 'items' constraint or 'additionalItems' "
|
||||
"constraint.");
|
||||
validated = false;
|
||||
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
} else if (constraint.additionalItemsSchema) {
|
||||
|
||||
// Get access to the target as an object
|
||||
const typename AdapterType::Array arr = target.asArray();
|
||||
|
||||
// Validate each item against additional items schema
|
||||
unsigned int index = 0;
|
||||
BOOST_FOREACH( const AdapterType arrayItem, arr ) {
|
||||
std::vector<std::string> newContext = context;
|
||||
newContext.push_back("[" + boost::lexical_cast<std::string>(index) + "]");
|
||||
ValidationVisitor<AdapterType> v(arrayItem,
|
||||
newContext, strictTypes, results);
|
||||
if (!v.validateSchema(*constraint.additionalItemsSchema)) {
|
||||
if (results) {
|
||||
results->pushError(context, "Failed to validate item #" +
|
||||
boost::lexical_cast<std::string>(index) + " against additional items schema.");
|
||||
validated = false;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
++index;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return validated;
|
||||
@ -926,6 +902,65 @@ public:
|
||||
return validated;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Validate a value against a SingularItemsConstraint
|
||||
*
|
||||
* A SingularItemsConstraint represents an 'items' constraint that specifies
|
||||
* a sub-schema against which all items in an array must validate. If the
|
||||
* current value is not an array, validation always succeeds.
|
||||
*
|
||||
* @param constraint SingularItemsConstraint to validate against
|
||||
*
|
||||
* @returns \c true if validation is successful; \c false otherwise
|
||||
*/
|
||||
virtual bool visit(const SingularItemsConstraint &constraint)
|
||||
{
|
||||
// Ignore values that are not arrays
|
||||
if (!target.isArray()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Schema against which all items must validate
|
||||
const Subschema *itemsSubschema = constraint.getItemsSubschema();
|
||||
|
||||
// Default items sub-schema accepts all values
|
||||
if (!itemsSubschema) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Track whether validation has failed
|
||||
bool validated = true;
|
||||
|
||||
unsigned int index = 0;
|
||||
BOOST_FOREACH( const AdapterType &item, target.getArray() ) {
|
||||
// Update context for current array item
|
||||
std::vector<std::string> newContext = context;
|
||||
newContext.push_back("[" +
|
||||
boost::lexical_cast<std::string>(index) + "]");
|
||||
|
||||
// Create a validator for the current array item
|
||||
ValidationVisitor<AdapterType> validationVisitor(item,
|
||||
newContext, strictTypes, results);
|
||||
|
||||
// Perform validation
|
||||
if (!validationVisitor.validateSchema(*itemsSubschema)) {
|
||||
if (results) {
|
||||
results->pushError(context,
|
||||
"Failed to validate item #" +
|
||||
boost::lexical_cast<std::string>(index) +
|
||||
" in array.");
|
||||
validated = false;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
return validated;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Validate against the type constraint represented by a
|
||||
* TypeConstraint object.
|
||||
@ -1089,6 +1124,78 @@ private:
|
||||
bool * const validated;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Functor to validate against sub-schemas in 'items' constraint
|
||||
*/
|
||||
struct ValidateItems
|
||||
{
|
||||
ValidateItems(
|
||||
const typename AdapterType::Array &arr,
|
||||
const std::vector<std::string> &context,
|
||||
bool continueOnFailure,
|
||||
bool strictTypes,
|
||||
ValidationResults *results,
|
||||
unsigned int *numValidated,
|
||||
bool *validated)
|
||||
: arr(arr),
|
||||
context(context),
|
||||
continueOnFailure(continueOnFailure),
|
||||
strictTypes(strictTypes),
|
||||
results(results),
|
||||
numValidated(numValidated),
|
||||
validated(validated) { }
|
||||
|
||||
bool operator()(unsigned int index, const Subschema *subschema) const
|
||||
{
|
||||
// Check that there are more elements to validate
|
||||
if (index >= arr.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Update context
|
||||
std::vector<std::string> newContext = context;
|
||||
newContext.push_back(
|
||||
"[" + boost::lexical_cast<std::string>(index) + "]");
|
||||
|
||||
// Find array item
|
||||
typename AdapterType::Array::const_iterator itr = arr.begin();
|
||||
itr.advance(index);
|
||||
|
||||
// Validate current array item
|
||||
ValidationVisitor validator(*itr, newContext, strictTypes, results);
|
||||
if (validator.validateSchema(*subschema)) {
|
||||
if (numValidated) {
|
||||
(*numValidated)++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (validated) {
|
||||
*validated = false;
|
||||
}
|
||||
|
||||
if (results) {
|
||||
results->pushError(newContext,
|
||||
"Failed to validate item #" +
|
||||
boost::lexical_cast<std::string>(index) +
|
||||
" against corresponding item schema.");
|
||||
}
|
||||
|
||||
return continueOnFailure;
|
||||
}
|
||||
|
||||
private:
|
||||
const typename AdapterType::Array &arr;
|
||||
const std::vector<std::string> &context;
|
||||
bool continueOnFailure;
|
||||
bool strictTypes;
|
||||
ValidationResults * const results;
|
||||
unsigned int * const numValidated;
|
||||
bool * const validated;
|
||||
|
||||
};
|
||||
|
||||
struct ValidateSchemaDependencies
|
||||
{
|
||||
ValidateSchemaDependencies(
|
||||
|
Loading…
x
Reference in New Issue
Block a user