Update TypeConstraint to use custom allocator for internal list of sub-schemas

This commit is contained in:
Tristan Penman 2015-12-29 09:28:22 +11:00
parent 000a1efc9a
commit 00e5c051f1
5 changed files with 187 additions and 100 deletions

View File

@ -114,8 +114,9 @@ void addPropertiesConstraint(Schema &schema)
// Create a child schema for the 'description' property that requires
// a string, but does not enforce any length constraints.
const Subschema *subschema = schema.createSubschema();
schema.addConstraintToSubschema(TypeConstraint(TypeConstraint::kString),
subschema);
TypeConstraint typeConstraint;
typeConstraint.addNamedType(TypeConstraint::kString);
schema.addConstraintToSubschema(typeConstraint, subschema);
// Include subschema in properties constraint
propertySchemaMap["description"] = subschema;
@ -126,8 +127,9 @@ void addPropertiesConstraint(Schema &schema)
// number with a value greater than zero.
const Subschema *subschema = schema.createSubschema();
schema.addConstraintToSubschema(MinimumConstraint(0.0, true), subschema);
schema.addConstraintToSubschema(TypeConstraint(TypeConstraint::kNumber),
subschema);
TypeConstraint typeConstraint;
typeConstraint.addNamedType(TypeConstraint::kNumber);
schema.addConstraintToSubschema(typeConstraint, subschema);
// Include subschema in properties constraint
propertySchemaMap["price"] = subschema;
@ -139,8 +141,9 @@ void addPropertiesConstraint(Schema &schema)
const Subschema *subschema = schema.createSubschema();
schema.addConstraintToSubschema(MaxLengthConstraint(200), subschema);
schema.addConstraintToSubschema(MinLengthConstraint(1), subschema);
schema.addConstraintToSubschema(TypeConstraint(TypeConstraint::kString),
subschema);
TypeConstraint typeConstraint;
typeConstraint.addNamedType(TypeConstraint::kString);
schema.addConstraintToSubschema(typeConstraint, subschema);
// Include subschema in properties constraint
propertySchemaMap["title"] = subschema;
@ -166,7 +169,9 @@ void addTypeConstraint(Schema &schema)
{
// Add a TypeConstraint to the schema, specifying that the root of the
// document must be an object.
schema.addConstraint(TypeConstraint(TypeConstraint::kObject));
TypeConstraint typeConstraint;
typeConstraint.addNamedType(TypeConstraint::kObject);
schema.addConstraint(typeConstraint);
}
int main(int argc, char *argv[])

View File

@ -602,8 +602,9 @@ private:
/**
* @brief Represents a 'type' constraint.
*/
struct TypeConstraint: BasicConstraint<TypeConstraint>
class TypeConstraint: public BasicConstraint<TypeConstraint>
{
public:
enum JsonType {
kAny,
kArray,
@ -615,29 +616,51 @@ struct TypeConstraint: BasicConstraint<TypeConstraint>
kString
};
typedef std::set<JsonType> JsonTypes;
TypeConstraint()
: namedTypes(std::less<JsonType>(), allocator),
schemaTypes(Allocator::rebind<const Subschema *>::other(allocator)) { }
typedef std::vector<const Subschema *> Schemas;
TypeConstraint(CustomAlloc allocFn, CustomFree freeFn)
: BasicConstraint(allocFn, freeFn),
namedTypes(std::less<JsonType>(), allocator),
schemaTypes(Allocator::rebind<const Subschema *>::other(allocator)) { }
TypeConstraint(const JsonType jsonType)
: jsonTypes(makeJsonTypes(jsonType)) { }
TypeConstraint(const JsonTypes &jsonTypes)
: jsonTypes(jsonTypes) { }
TypeConstraint(const JsonTypes &jsonTypes,
const Schemas &schemas)
: jsonTypes(jsonTypes),
schemas(schemas) { }
static JsonTypes makeJsonTypes(const JsonType jsonType)
void addNamedType(JsonType type)
{
JsonTypes jsonTypes;
jsonTypes.insert(jsonType);
return jsonTypes;
namedTypes.insert(type);
}
static JsonType jsonTypeFromString(const std::string &typeName)
void addSchemaType(const Subschema *subschema)
{
schemaTypes.push_back(subschema);
}
template<typename FunctorType>
void applyToNamedTypes(const FunctorType &fn) const
{
BOOST_FOREACH( const JsonType namedType, namedTypes ) {
if (!fn(namedType)) {
return;
}
}
}
template<typename FunctorType>
void applyToSchemaTypes(const FunctorType &fn) const
{
unsigned int index = 0;
BOOST_FOREACH( const Subschema *subschema, schemaTypes ) {
if (!fn(index, subschema)) {
return;
}
index++;
}
}
template<typename AllocatorType>
static JsonType jsonTypeFromString(const std::basic_string<char,
std::char_traits<char>, AllocatorType> &typeName)
{
if (typeName.compare("any") == 0) {
return kAny;
@ -657,11 +680,21 @@ struct TypeConstraint: BasicConstraint<TypeConstraint>
return kString;
}
throw std::runtime_error("Unrecognised JSON type name '" + typeName + "'");
throw std::runtime_error("Unrecognised JSON type name '" +
std::string(typeName.c_str()) + "'");
}
const JsonTypes jsonTypes;
const Schemas schemas;
private:
typedef std::set<JsonType, std::less<JsonType>, Allocator> NamedTypes;
typedef std::vector<const Subschema *,
Allocator::rebind<const Subschema *>::other> SchemaTypes;
/// Set of named JSON types that serve as valid types
NamedTypes namedTypes;
/// Set of sub-schemas that serve as valid types
SchemaTypes schemaTypes;
};
/**

View File

@ -20,7 +20,6 @@ struct NotConstraint;
struct PatternConstraint;
struct PropertiesConstraint;
struct RequiredConstraint;
struct TypeConstraint;
struct UniqueItemsConstraint;
class AllOfConstraint;
@ -29,6 +28,7 @@ class DependenciesConstraint;
class LinearItemsConstraint;
class OneOfConstraint;
class SingularItemsConstraint;
class TypeConstraint;
/// Interface to allow usage of the visitor pattern with Constraints
class ConstraintVisitor

View File

@ -1432,52 +1432,62 @@ private:
const boost::optional<
typename FetchDocumentFunction<AdapterType>::Type > fetchDoc)
{
typedef constraints::TypeConstraint TC;
typedef constraints::TypeConstraint TypeConstraint;
TC::JsonTypes jsonTypes;
TC::Schemas schemas;
TypeConstraint constraint;
if (node.isString()) {
const TC::JsonType jsonType =
TC::jsonTypeFromString(node.getString());
if (jsonType == TC::kAny && version == kDraft4) {
const TypeConstraint::JsonType type =
TypeConstraint::jsonTypeFromString(node.getString());
if (type == TypeConstraint::kAny && version == kDraft4) {
throw std::runtime_error(
"'any' type is not supported in version 4 schemas.");
}
jsonTypes.insert(jsonType);
constraint.addNamedType(type);
} else if (node.isArray()) {
int index = 0;
BOOST_FOREACH( const AdapterType v, node.getArray() ) {
if (v.isString()) {
const TC::JsonType jsonType =
TC::jsonTypeFromString(v.getString());
if (jsonType == TC::kAny && version == kDraft4) {
const TypeConstraint::JsonType type =
TypeConstraint::jsonTypeFromString(v.getString());
if (type == TypeConstraint::kAny && version == kDraft4) {
throw std::runtime_error(
"'any' type is not supported in version 4 "
"schemas.");
}
jsonTypes.insert(jsonType);
constraint.addNamedType(type);
} else if (v.isObject() && version == kDraft3) {
const std::string childPath = nodePath + "/" +
boost::lexical_cast<std::string>(index);
schemas.push_back(rootSchema.createSubschema());
const Subschema *subschema = rootSchema.createSubschema();
constraint.addSchemaType(subschema);
populateSchema<AdapterType>(rootSchema, rootNode, v,
*schemas.back(), currentScope, childPath, fetchDoc);
*subschema, currentScope, childPath, fetchDoc);
} else {
throw std::runtime_error("Type name should be a string.");
}
index++;
}
} else if (node.isObject() && version == kDraft3) {
schemas.push_back(rootSchema.createSubschema());
const Subschema *subschema = rootSchema.createSubschema();
constraint.addSchemaType(subschema);
populateSchema<AdapterType>(rootSchema, rootNode, node,
*schemas.back(), currentScope, nodePath, fetchDoc);
*subschema, currentScope, nodePath, fetchDoc);
} else {
throw std::runtime_error("Type name should be a string.");
}
return constraints::TypeConstraint(jsonTypes, schemas);
return constraint;
}
/**

View File

@ -968,68 +968,39 @@ public:
}
/**
* @brief Validate against the type constraint represented by a
* TypeConstraint object.
* @brief Validate a value against a TypeConstraint
*
* Checks that the target is of the expected type.
* Checks that the target is one of the valid named types, or matches one
* of a set of valid sub-schemas.
*
* @param constraint Constraint that the target must validate against
* @param constraint TypeConstraint to validate against
*
* @return true if validation succeeds, false otherwise
* @return \c true if validation is successful; \c false otherwise
*/
virtual bool visit(const TypeConstraint &constraint)
{
// Try to match the type to one of the types in the jsonTypes array
BOOST_FOREACH( const TypeConstraint::JsonType jsonType, constraint.jsonTypes ) {
switch (jsonType) {
case TypeConstraint::kAny:
return true;
case TypeConstraint::kArray:
if (target.isArray()) {
return true;
}
break;
case TypeConstraint::kBoolean:
if (target.isBool() || (!strictTypes && target.maybeBool())) {
return true;
}
break;
case TypeConstraint::kInteger:
if (target.isInteger() || (!strictTypes && target.maybeInteger())) {
return true;
}
break;
case TypeConstraint::kNull:
if (target.isNull() || (!strictTypes && target.maybeNull())) {
return true;
}
break;
case TypeConstraint::kNumber:
if (target.isNumber() || (!strictTypes && target.maybeDouble())) {
return true;
}
break;
case TypeConstraint::kObject:
if (target.isObject()) {
return true;
}
break;
case TypeConstraint::kString:
if (target.isString()) {
return true;
}
break;
}
}
BOOST_FOREACH( const Subschema *subschema, constraint.schemas ) {
if (validateSchema(*subschema)) {
// Check named types
{
// ValidateNamedTypes functor assumes target is invalid
bool validated = false;
constraint.applyToNamedTypes(ValidateNamedTypes(target, strictTypes,
false, true, &validated));
if (validated) {
return true;
}
}
if (results) {
results->pushError(context, "Value type not permitted by 'type' constraint.");
// Check schema-based types
{
unsigned int numValidated = 0;
constraint.applyToSchemaTypes(ValidateSubschemas(target, context,
false, true, *this, NULL, &numValidated, NULL));
if (numValidated > 0) {
return true;
} else if (results) {
results->pushError(context,
"Value type not permitted by 'type' constraint.");
}
}
return false;
@ -1205,6 +1176,74 @@ private:
};
/**
* @brief Functor to validate value against named JSON types
*/
struct ValidateNamedTypes
{
ValidateNamedTypes(
const AdapterType &target,
bool strict,
bool continueOnSuccess,
bool continueOnFailure,
bool *validated)
: target(target),
strict(strict),
continueOnSuccess(continueOnSuccess),
continueOnFailure(continueOnFailure),
validated(validated) { }
bool operator()(constraints::TypeConstraint::JsonType jsonType) const
{
typedef constraints::TypeConstraint TypeConstraint;
bool valid = false;
switch (jsonType) {
case TypeConstraint::kAny:
valid = true;
break;
case TypeConstraint::kArray:
valid = target.isArray();
break;
case TypeConstraint::kBoolean:
valid = target.isBool() || (!strict && target.maybeBool());
break;
case TypeConstraint::kInteger:
valid = target.isInteger() ||
(!strict && target.maybeInteger());
break;
case TypeConstraint::kNull:
valid = target.isNull() || (!strict && target.maybeNull());
break;
case TypeConstraint::kNumber:
valid = target.isNumber() || (!strict && target.maybeDouble());
break;
case TypeConstraint::kObject:
valid = target.isObject();
break;
case TypeConstraint::kString:
valid = target.isString();
break;
default:
break;
}
if (valid && validated) {
*validated = true;
}
return (valid && continueOnSuccess) || continueOnFailure;
}
private:
const AdapterType target;
const bool strict;
const bool continueOnSuccess;
const bool continueOnFailure;
bool * const validated;
};
/**
* @brief Functor to validate schema-based dependencies
*/
@ -1306,8 +1345,8 @@ private:
if (results) {
results->pushError(context,
"Failed to validate against child schema #" +
boost::lexical_cast<std::string>(index) + ".");
"Failed to validate against child schema #" +
boost::lexical_cast<std::string>(index) + ".");
}
return continueOnFailure;