Improve support for boolean subschemas

This commit is contained in:
Tristan Penman 2019-08-31 21:22:00 +10:00
parent c89d1d89cd
commit 866f5761bf
6 changed files with 78 additions and 80 deletions

View File

@ -40,13 +40,11 @@ class AllOfConstraint: public BasicConstraint<AllOfConstraint>
{ {
public: public:
AllOfConstraint() AllOfConstraint()
: subschemas(Allocator::rebind<const Subschema *>::other(allocator)), : subschemas(Allocator::rebind<const Subschema *>::other(allocator)) { }
alwaysInvalid(false) { }
AllOfConstraint(CustomAlloc allocFn, CustomFree freeFn) AllOfConstraint(CustomAlloc allocFn, CustomFree freeFn)
: BasicConstraint(allocFn, freeFn), : BasicConstraint(allocFn, freeFn),
subschemas(Allocator::rebind<const Subschema *>::other(allocator)), subschemas(Allocator::rebind<const Subschema *>::other(allocator)) { }
alwaysInvalid(false) { }
void addSubschema(const Subschema *subschema) void addSubschema(const Subschema *subschema)
{ {
@ -66,24 +64,12 @@ public:
} }
} }
bool getAlwaysInvalid() const
{
return alwaysInvalid;
}
void setAlwaysInvalid()
{
alwaysInvalid = true;
}
private: private:
typedef std::vector<const Subschema *, typedef std::vector<const Subschema *,
internal::CustomAllocator<const Subschema *> > Subschemas; internal::CustomAllocator<const Subschema *> > Subschemas;
/// Collection of sub-schemas, all of which must be satisfied /// Collection of sub-schemas, all of which must be satisfied
Subschemas subschemas; Subschemas subschemas;
bool alwaysInvalid;
}; };
/** /**
@ -97,8 +83,7 @@ class AnyOfConstraint: public BasicConstraint<AnyOfConstraint>
{ {
public: public:
AnyOfConstraint() AnyOfConstraint()
: subschemas(Allocator::rebind<const Subschema *>::other(allocator)), : subschemas(Allocator::rebind<const Subschema *>::other(allocator)) { }
alwaysValid(false) { }
AnyOfConstraint(CustomAlloc allocFn, CustomFree freeFn) AnyOfConstraint(CustomAlloc allocFn, CustomFree freeFn)
: BasicConstraint(allocFn, freeFn), : BasicConstraint(allocFn, freeFn),
@ -122,24 +107,12 @@ public:
} }
} }
bool getAlwaysValid() const
{
return alwaysValid;
}
void setAlwaysValid()
{
alwaysValid = true;
}
private: private:
typedef std::vector<const Subschema *, typedef std::vector<const Subschema *,
internal::CustomAllocator<const Subschema *> > Subschemas; internal::CustomAllocator<const Subschema *> > Subschemas;
/// Collection of sub-schemas, at least one of which must be satisfied /// Collection of sub-schemas, at least one of which must be satisfied
Subschemas subschemas; Subschemas subschemas;
bool alwaysValid;
}; };
class ConditionalConstraint: public BasicConstraint<ConditionalConstraint> class ConditionalConstraint: public BasicConstraint<ConditionalConstraint>

View File

@ -118,6 +118,11 @@ public:
return this; return this;
} }
void setAlwaysInvalid(const Subschema *subschema, bool value)
{
mutableSubschema(subschema)->setAlwaysInvalid(value);
}
/** /**
* @brief Update the description for one of the sub-schemas owned by this * @brief Update the description for one of the sub-schemas owned by this
* Schema instance * Schema instance

View File

@ -594,12 +594,24 @@ private:
"appropriate Adapter implementation"); "appropriate Adapter implementation");
if (!node.isObject()) { if (!node.isObject()) {
std::string s; if (version == kDraft7 && node.maybeBool()) {
s += "Expected node at "; // Boolean schema
s += nodePath; if (!node.asBool()) {
s += " to contain schema object; actual node type is: "; rootSchema.setAlwaysInvalid(&subschema, true);
s += internal::nodeTypeAsString(node); }
throw std::runtime_error(s); return;
} else {
std::string s;
s += "Expected node at ";
s += nodePath;
if (version == kDraft7) {
s += " to contain schema object or boolean value; actual node type is: ";
} else {
s += " to contain schema object; actual node type is: ";
}
s += internal::nodeTypeAsString(node);
throw std::runtime_error(s);
}
} }
const typename AdapterType::Object object = node.asObject(); const typename AdapterType::Object object = node.asObject();
@ -1095,26 +1107,16 @@ private:
int index = 0; int index = 0;
for (const AdapterType schemaNode : node.asArray()) { for (const AdapterType schemaNode : node.asArray()) {
if (schemaNode.maybeObject()) { if (schemaNode.maybeObject() || (version == kDraft7 && schemaNode.isBool())) {
const std::string childPath = nodePath + "/" + std::to_string(index); const std::string childPath = nodePath + "/" + std::to_string(index);
const Subschema *subschema = makeOrReuseSchema<AdapterType>( const Subschema *subschema = makeOrReuseSchema<AdapterType>(
rootSchema, rootNode, schemaNode, currentScope, rootSchema, rootNode, schemaNode, currentScope,
childPath, fetchDoc, NULL, NULL, docCache, schemaCache); childPath, fetchDoc, NULL, NULL, docCache, schemaCache);
constraint.addSubschema(subschema); constraint.addSubschema(subschema);
index++; index++;
} else if (version == kDraft7) {
if (schemaNode.maybeBool()) {
if (!schemaNode.asBool()) {
// Schema that always fails
constraint.setAlwaysInvalid();
}
} else {
throw std::runtime_error(
"Expected element to be an object or boolean value in 'allOf' constraint");
}
} else { } else {
throw std::runtime_error( throw std::runtime_error(
"Expected element to be an object value in 'allOf' constraint."); "Expected element to be a valid schema in 'allOf' constraint.");
} }
} }
@ -1159,25 +1161,16 @@ private:
int index = 0; int index = 0;
for (const AdapterType schemaNode : node.asArray()) { for (const AdapterType schemaNode : node.asArray()) {
if (schemaNode.maybeObject()) { if (schemaNode.maybeObject() || (version == kDraft7 && schemaNode.isBool())) {
const std::string childPath = nodePath + "/" + std::to_string(index); const std::string childPath = nodePath + "/" + std::to_string(index);
const Subschema *subschema = makeOrReuseSchema<AdapterType>( const Subschema *subschema = makeOrReuseSchema<AdapterType>(
rootSchema, rootNode, schemaNode, currentScope, rootSchema, rootNode, schemaNode, currentScope,
childPath, fetchDoc, NULL, NULL, docCache, schemaCache); childPath, fetchDoc, NULL, NULL, docCache, schemaCache);
constraint.addSubschema(subschema); constraint.addSubschema(subschema);
index++; index++;
} else if (version == kDraft7) {
if (schemaNode.maybeBool()) {
if (schemaNode.asBool()) {
constraint.setAlwaysValid();
}
} else {
throw std::runtime_error(
"Expected element to be an object or boolean value in 'allOf' constraint");
}
} else { } else {
throw std::runtime_error( throw std::runtime_error(
"Expected array element to be an object value in 'anyOf' constraint."); "Expected array element to be a valid schema in 'anyOf' constraint.");
} }
} }
@ -1270,7 +1263,7 @@ private:
SchemaCache &schemaCache) SchemaCache &schemaCache)
{ {
if (!node.maybeObject()) { if (!node.maybeObject()) {
throw std::runtime_error("Expected object value for 'dependencies' constraint."); throw std::runtime_error("Expected valid subschema for 'dependencies' constraint.");
} }
constraints::DependenciesConstraint dependenciesConstraint; constraints::DependenciesConstraint dependenciesConstraint;
@ -1508,7 +1501,7 @@ private:
// array is provided, or a single Schema object, in an object value is // array is provided, or a single Schema object, in an object value is
// provided. If the items constraint is not provided, then array items // provided. If the items constraint is not provided, then array items
// will be validated against the additionalItems schema. // will be validated against the additionalItems schema.
if (items.isObject()) { if (items.isObject() || (version == kDraft7 && items.maybeBool())) {
// If the items constraint contains an object value, then it // If the items constraint contains an object value, then it
// should contain a Schema that will be used to validate all // should contain a Schema that will be used to validate all
// items in a target array. Any schema defined by the // items in a target array. Any schema defined by the
@ -1526,8 +1519,7 @@ private:
} else { } else {
// All other formats will result in an exception being thrown. // All other formats will result in an exception being thrown.
throw std::runtime_error( throw std::runtime_error(
"Expected object value for singular 'items' " "Expected valid schema for singular 'items' constraint.");
"constraint.");
} }
return constraint; return constraint;
@ -1883,7 +1875,7 @@ private:
typename DocumentCache<AdapterType>::Type &docCache, typename DocumentCache<AdapterType>::Type &docCache,
SchemaCache &schemaCache) SchemaCache &schemaCache)
{ {
if (node.maybeObject()) { if (node.maybeObject() || (version == kDraft7 && node.maybeBool())) {
const Subschema *subschema = makeOrReuseSchema<AdapterType>( const Subschema *subschema = makeOrReuseSchema<AdapterType>(
rootSchema, rootNode, node, currentScope, nodePath, rootSchema, rootNode, node, currentScope, nodePath,
fetchDoc, NULL, NULL, docCache, schemaCache); fetchDoc, NULL, NULL, docCache, schemaCache);

View File

@ -40,7 +40,8 @@ public:
*/ */
Subschema() Subschema()
: allocFn(::operator new) : allocFn(::operator new)
, freeFn(::operator delete) { } , freeFn(::operator delete)
, alwaysInvalid(false) { }
/** /**
* @brief Construct a new Subschema using custom memory management * @brief Construct a new Subschema using custom memory management
@ -53,7 +54,8 @@ public:
*/ */
Subschema(CustomAlloc allocFn, CustomFree freeFn) Subschema(CustomAlloc allocFn, CustomFree freeFn)
: allocFn(allocFn) : allocFn(allocFn)
, freeFn(freeFn) { } , freeFn(freeFn)
, alwaysInvalid(false) { }
/** /**
* @brief Clean up and free all memory managed by the Subschema * @brief Clean up and free all memory managed by the Subschema
@ -139,6 +141,11 @@ public:
return true; return true;
} }
bool getAlwaysInvalid() const
{
return alwaysInvalid;
}
/** /**
* @brief Get the description associated with this sub-schema * @brief Get the description associated with this sub-schema
* *
@ -217,6 +224,11 @@ public:
return static_cast<bool>(title); return static_cast<bool>(title);
} }
void setAlwaysInvalid(bool value)
{
alwaysInvalid = value;
}
/** /**
* @brief Set the description for this sub-schema * @brief Set the description for this sub-schema
* *
@ -266,6 +278,8 @@ private:
// Disable copy assignment // Disable copy assignment
Subschema & operator=(const Subschema &); Subschema & operator=(const Subschema &);
bool alwaysInvalid;
/// List of pointers to constraints that apply to this schema. /// List of pointers to constraints that apply to this schema.
std::vector<const Constraint *> constraints; std::vector<const Constraint *> constraints;

View File

@ -63,6 +63,10 @@ public:
*/ */
bool validateSchema(const Subschema &subschema) bool validateSchema(const Subschema &subschema)
{ {
if (subschema.getAlwaysInvalid()) {
return false;
}
// Wrap the validationCallback() function below so that it will be // Wrap the validationCallback() function below so that it will be
// passed a reference to a constraint (_1), and a reference to the // passed a reference to a constraint (_1), and a reference to the
// visitor (*this). // visitor (*this).
@ -108,10 +112,6 @@ public:
*/ */
virtual bool visit(const AllOfConstraint &constraint) virtual bool visit(const AllOfConstraint &constraint)
{ {
if (constraint.getAlwaysInvalid()) {
return false;
}
bool validated = true; bool validated = true;
constraint.applyToSubschemas(ValidateSubschemas(target, context, constraint.applyToSubschemas(ValidateSubschemas(target, context,
true, false, *this, results, NULL, &validated)); true, false, *this, results, NULL, &validated));
@ -138,10 +138,6 @@ public:
*/ */
virtual bool visit(const AnyOfConstraint &constraint) virtual bool visit(const AnyOfConstraint &constraint)
{ {
if (constraint.getAlwaysValid()) {
return true;
}
unsigned int numValidated = 0; unsigned int numValidated = 0;
ValidationResults newResults; ValidationResults newResults;

View File

@ -446,7 +446,10 @@ TEST_F(TestValidator, Draft7_AnyOf)
processDraft7TestFile(TEST_SUITE_DIR "draft7/anyOf.json"); processDraft7TestFile(TEST_SUITE_DIR "draft7/anyOf.json");
} }
// TODO: untested boolean_schema TEST_F(TestValidator, Draft7_BooleanSchema)
{
processDraft7TestFile(TEST_SUITE_DIR "draft7/boolean_schema.json");
}
// TODO: untested const // TODO: untested const
@ -476,7 +479,10 @@ TEST_F(TestValidator, Draft7_IfThenElse)
processDraft7TestFile(TEST_SUITE_DIR "draft7/if-then-else.json"); processDraft7TestFile(TEST_SUITE_DIR "draft7/if-then-else.json");
} }
// TODO: broken items TEST_F(TestValidator, Draft7_Items)
{
processDraft7TestFile(TEST_SUITE_DIR "draft7/items.json");
}
TEST_F(TestValidator, Draft7_Maximum) TEST_F(TestValidator, Draft7_Maximum)
{ {
@ -513,18 +519,30 @@ TEST_F(TestValidator, Draft7_MultipleOf)
processDraft7TestFile(TEST_SUITE_DIR "draft7/multipleOf.json"); processDraft7TestFile(TEST_SUITE_DIR "draft7/multipleOf.json");
} }
// TODO: broken not TEST_F(TestValidator, Draft7_Not)
{
processDraft7TestFile(TEST_SUITE_DIR "draft7/not.json");
}
// TODO: broken oneOf TEST_F(TestValidator, Draft7_OneOf)
{
processDraft7TestFile(TEST_SUITE_DIR "draft7/oneOf.json");
}
TEST_F(TestValidator, Draft7_Pattern) TEST_F(TestValidator, Draft7_Pattern)
{ {
processDraft7TestFile(TEST_SUITE_DIR "draft7/pattern.json"); processDraft7TestFile(TEST_SUITE_DIR "draft7/pattern.json");
} }
// TODO: broken patternProperties TEST_F(TestValidator, Draft7_PatternProperties)
{
processDraft7TestFile(TEST_SUITE_DIR "draft7/patternProperties.json");
}
// TODO: broken properties TEST_F(TestValidator, Draft7_Properties)
{
processDraft7TestFile(TEST_SUITE_DIR "draft7/properties.json");
}
// TODO: broken ref // TODO: broken ref