Add support for 'contains' constraint

This commit is contained in:
Tristan Penman 2019-09-24 14:08:45 +10:00
parent ba44a8d641
commit f71355b90f
5 changed files with 173 additions and 4 deletions

View File

@ -165,6 +165,36 @@ private:
const Subschema *elseSubschema;
};
/**
* @brief Represents a 'contains' constraint
*
* An contains constraint provides a collection of values that must be present
* in an array.
*/
class ContainsConstraint: public BasicConstraint<ContainsConstraint>
{
public:
ContainsConstraint()
: subschema(nullptr) { }
ContainsConstraint(CustomAlloc allocFn, CustomFree freeFn)
: BasicConstraint(allocFn, freeFn),
subschema(nullptr) { }
const Subschema * getSubschema() const
{
return subschema;
}
void setSubschema(const Subschema *subschema)
{
this->subschema = subschema;
}
private:
const Subschema *subschema;
};
/**
* @brief Represents a 'dependencies' constraint.
*
@ -307,7 +337,6 @@ public:
throw;
}
}
} catch (...) {
// Delete values already added to constraint
for (const EnumValue *value : enumValues) {

View File

@ -6,6 +6,7 @@ namespace constraints {
class AllOfConstraint;
class AnyOfConstraint;
class ConditionalConstraint;
class ContainsConstraint;
class DependenciesConstraint;
class EnumConstraint;
class LinearItemsConstraint;
@ -39,6 +40,7 @@ protected:
typedef constraints::AllOfConstraint AllOfConstraint;
typedef constraints::AnyOfConstraint AnyOfConstraint;
typedef constraints::ConditionalConstraint ConditionalConstraint;
typedef constraints::ContainsConstraint ContainsConstraint;
typedef constraints::DependenciesConstraint DependenciesConstraint;
typedef constraints::EnumConstraint EnumConstraint;
typedef constraints::LinearItemsConstraint LinearItemsConstraint;
@ -67,6 +69,7 @@ public:
virtual bool visit(const AllOfConstraint &) = 0;
virtual bool visit(const AnyOfConstraint &) = 0;
virtual bool visit(const ConditionalConstraint &) = 0;
virtual bool visit(const ContainsConstraint &) = 0;
virtual bool visit(const DependenciesConstraint &) = 0;
virtual bool visit(const EnumConstraint &) = 0;
virtual bool visit(const LinearItemsConstraint &) = 0;

View File

@ -649,6 +649,13 @@ private:
&subschema);
}
if ((itr = object.find("contains")) != object.end()) {
rootSchema.addConstraintToSubschema(
makeContainsConstraint(rootSchema, rootNode, itr->second,
updatedScope, nodePath + "/contains", fetchDoc,
docCache, schemaCache), &subschema);
}
if ((itr = object.find("dependencies")) != object.end()) {
rootSchema.addConstraintToSubschema(
makeDependenciesConstraint(rootSchema, rootNode,
@ -689,8 +696,7 @@ private:
}
if ((itr = object.find("enum")) != object.end()) {
rootSchema.addConstraintToSubschema(makeEnumConstraint(itr->second),
&subschema);
rootSchema.addConstraintToSubschema(makeEnumConstraint(itr->second), &subschema);
}
{
@ -1177,6 +1183,35 @@ private:
return constraint;
}
/**
* @brief Make a new ConditionalConstraint 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 ifNode Schema that will be used to evaluate the
* conditional.
* @param thenNode Optional pointer to a JSON node containing
* a schema that will be used when the conditional
* evaluates to true.
* @param elseNode Optional pointer to a JSON node containing
* a schema that will be used when the conditional
* evaluates to false.
* @param currentScope URI for current resolution scope
* @param containsPath JSON Pointer representing the path to
* the 'contains' node
* @param fetchDoc Function to fetch remote JSON documents
* (optional)
* @param docCache Cache of resolved and fetched remote
* documents
* @param schemaCache Cache of populated schemas
*
* @return pointer to a new ContainsConstraint that belongs to the caller
*/
template<typename AdapterType>
constraints::ConditionalConstraint makeConditionalConstraint(
Schema &rootSchema,
@ -1215,6 +1250,63 @@ private:
return constraint;
}
/**
* @brief Make a new ContainsConstraint 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 contains Optional pointer to a JSON node containing
* an object mapping property names to
* schemas.
* @param currentScope URI for current resolution scope
* @param containsPath JSON Pointer representing the path to
* the 'contains' node
* @param fetchDoc Function to fetch remote JSON documents
* (optional)
* @param docCache Cache of resolved and fetched remote
* documents
* @param schemaCache Cache of populated schemas
*
* @return pointer to a new ContainsConstraint that belongs to the caller
*/
template<typename AdapterType>
constraints::ContainsConstraint makeContainsConstraint(
Schema &rootSchema,
const AdapterType &rootNode,
const AdapterType &contains,
const opt::optional<std::string> currentScope,
const std::string &containsPath,
const typename FunctionPtrs<AdapterType>::FetchDoc fetchDoc,
typename DocumentCache<AdapterType>::Type &docCache,
SchemaCache &schemaCache)
{
constraints::ContainsConstraint constraint;
if (contains.isObject() || (version == kDraft7 && contains.maybeBool())) {
const Subschema *subschema = makeOrReuseSchema<AdapterType>(
rootSchema, rootNode, contains, currentScope, containsPath,
fetchDoc, NULL, NULL, docCache, schemaCache);
constraint.setSubschema(subschema);
} else if (contains.maybeObject()) {
// If a loosely-typed Adapter type is being used, then we'll
// assume that an empty schema has been provided.
constraint.setSubschema(rootSchema.emptySubschema());
} else {
// All other formats will result in an exception being thrown.
throw std::runtime_error(
"Expected valid schema for 'contains' constraint.");
}
return constraint;
}
/**
* @brief Make a new DependenciesConstraint object
*

View File

@ -189,6 +189,46 @@ public:
}
}
/**
* @brief Validate current node using a 'contains' constraint
*
* A contains constraint is satisfied if the target is not an array, or if it is an array,
* only if it contains at least one value that matches the specified schema.
*
* @param constraint ContainsConstraint that the current node must validate against
*
* @return \c true if validation passes; \c false otherwise
*/
virtual bool visit(const ContainsConstraint &constraint)
{
if ((strictTypes && !target.isArray()) || !target.maybeArray()) {
return true;
}
const Subschema *subschema = constraint.getSubschema();
const typename AdapterType::Array arr = target.asArray();
bool validated = false;
for (auto itr = arr.begin(); itr != arr.end(); ++itr) {
ValidationVisitor containsValidator(*itr, context, strictTypes, nullptr);
if (containsValidator.validateSchema(*subschema)) {
validated = true;
break;
}
}
if (!validated) {
if (results) {
results->pushError(context,
"Failed to any values against subschema in 'contains' constraint.");
}
return false;
}
return validated;
}
/**
* @brief Validate current node against a 'dependencies' constraint
*

View File

@ -453,7 +453,12 @@ TEST_F(TestValidator, Draft7_BooleanSchema)
// TODO: untested const
// TODO: untested contains
TEST_F(TestValidator, Draft7_Contains)
{
// TODO: currently failing due to missing support for const
processDraft7TestFile(TEST_SUITE_DIR "draft7/contains.json");
}
// TOOD: untested default