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

View File

@ -602,8 +602,9 @@ private:
/** /**
* @brief Represents a 'type' constraint. * @brief Represents a 'type' constraint.
*/ */
struct TypeConstraint: BasicConstraint<TypeConstraint> class TypeConstraint: public BasicConstraint<TypeConstraint>
{ {
public:
enum JsonType { enum JsonType {
kAny, kAny,
kArray, kArray,
@ -615,29 +616,51 @@ struct TypeConstraint: BasicConstraint<TypeConstraint>
kString 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) void addNamedType(JsonType type)
: 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)
{ {
JsonTypes jsonTypes; namedTypes.insert(type);
jsonTypes.insert(jsonType);
return jsonTypes;
} }
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) { if (typeName.compare("any") == 0) {
return kAny; return kAny;
@ -657,11 +680,21 @@ struct TypeConstraint: BasicConstraint<TypeConstraint>
return kString; 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; private:
const Schemas schemas; 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 PatternConstraint;
struct PropertiesConstraint; struct PropertiesConstraint;
struct RequiredConstraint; struct RequiredConstraint;
struct TypeConstraint;
struct UniqueItemsConstraint; struct UniqueItemsConstraint;
class AllOfConstraint; class AllOfConstraint;
@ -29,6 +28,7 @@ class DependenciesConstraint;
class LinearItemsConstraint; class LinearItemsConstraint;
class OneOfConstraint; class OneOfConstraint;
class SingularItemsConstraint; class SingularItemsConstraint;
class TypeConstraint;
/// Interface to allow usage of the visitor pattern with Constraints /// Interface to allow usage of the visitor pattern with Constraints
class ConstraintVisitor class ConstraintVisitor

View File

@ -1432,52 +1432,62 @@ private:
const boost::optional< const boost::optional<
typename FetchDocumentFunction<AdapterType>::Type > fetchDoc) typename FetchDocumentFunction<AdapterType>::Type > fetchDoc)
{ {
typedef constraints::TypeConstraint TC; typedef constraints::TypeConstraint TypeConstraint;
TC::JsonTypes jsonTypes; TypeConstraint constraint;
TC::Schemas schemas;
if (node.isString()) { if (node.isString()) {
const TC::JsonType jsonType = const TypeConstraint::JsonType type =
TC::jsonTypeFromString(node.getString()); TypeConstraint::jsonTypeFromString(node.getString());
if (jsonType == TC::kAny && version == kDraft4) {
if (type == TypeConstraint::kAny && version == kDraft4) {
throw std::runtime_error( throw std::runtime_error(
"'any' type is not supported in version 4 schemas."); "'any' type is not supported in version 4 schemas.");
} }
jsonTypes.insert(jsonType);
constraint.addNamedType(type);
} else if (node.isArray()) { } else if (node.isArray()) {
int index = 0; int index = 0;
BOOST_FOREACH( const AdapterType v, node.getArray() ) { BOOST_FOREACH( const AdapterType v, node.getArray() ) {
if (v.isString()) { if (v.isString()) {
const TC::JsonType jsonType = const TypeConstraint::JsonType type =
TC::jsonTypeFromString(v.getString()); TypeConstraint::jsonTypeFromString(v.getString());
if (jsonType == TC::kAny && version == kDraft4) {
if (type == TypeConstraint::kAny && version == kDraft4) {
throw std::runtime_error( throw std::runtime_error(
"'any' type is not supported in version 4 " "'any' type is not supported in version 4 "
"schemas."); "schemas.");
} }
jsonTypes.insert(jsonType);
constraint.addNamedType(type);
} else if (v.isObject() && version == kDraft3) { } else if (v.isObject() && version == kDraft3) {
const std::string childPath = nodePath + "/" + const std::string childPath = nodePath + "/" +
boost::lexical_cast<std::string>(index); boost::lexical_cast<std::string>(index);
schemas.push_back(rootSchema.createSubschema()); const Subschema *subschema = rootSchema.createSubschema();
constraint.addSchemaType(subschema);
populateSchema<AdapterType>(rootSchema, rootNode, v, populateSchema<AdapterType>(rootSchema, rootNode, v,
*schemas.back(), currentScope, childPath, fetchDoc); *subschema, currentScope, childPath, fetchDoc);
} else { } else {
throw std::runtime_error("Type name should be a string."); throw std::runtime_error("Type name should be a string.");
} }
index++; index++;
} }
} else if (node.isObject() && version == kDraft3) { } else if (node.isObject() && version == kDraft3) {
schemas.push_back(rootSchema.createSubschema()); const Subschema *subschema = rootSchema.createSubschema();
constraint.addSchemaType(subschema);
populateSchema<AdapterType>(rootSchema, rootNode, node, populateSchema<AdapterType>(rootSchema, rootNode, node,
*schemas.back(), currentScope, nodePath, fetchDoc); *subschema, currentScope, nodePath, fetchDoc);
} else { } else {
throw std::runtime_error("Type name should be a string."); 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 * @brief Validate a value against a TypeConstraint
* TypeConstraint object.
* *
* 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) virtual bool visit(const TypeConstraint &constraint)
{ {
// Try to match the type to one of the types in the jsonTypes array // Check named types
BOOST_FOREACH( const TypeConstraint::JsonType jsonType, constraint.jsonTypes ) { {
switch (jsonType) { // ValidateNamedTypes functor assumes target is invalid
case TypeConstraint::kAny: bool validated = false;
return true; constraint.applyToNamedTypes(ValidateNamedTypes(target, strictTypes,
case TypeConstraint::kArray: false, true, &validated));
if (target.isArray()) { if (validated) {
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)) {
return true; return true;
} }
} }
if (results) { // Check schema-based types
results->pushError(context, "Value type not permitted by 'type' constraint."); {
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; 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 * @brief Functor to validate schema-based dependencies
*/ */
@ -1306,8 +1345,8 @@ private:
if (results) { if (results) {
results->pushError(context, results->pushError(context,
"Failed to validate against child schema #" + "Failed to validate against child schema #" +
boost::lexical_cast<std::string>(index) + "."); boost::lexical_cast<std::string>(index) + ".");
} }
return continueOnFailure; return continueOnFailure;