mirror of
https://github.com/tristanpenman/valijson.git
synced 2025-01-19 00:46:03 +01:00
Refactor DependenciesConstraint to use custom allocator, hidden behind a more robust encapsulation strategy
This commit is contained in:
parent
76f8655d21
commit
c7f2f9b802
@ -5,6 +5,8 @@
|
||||
#include <valijson/constraints/constraint.hpp>
|
||||
#include <valijson/constraints/constraint_visitor.hpp>
|
||||
|
||||
#include <valijson/internal/custom_allocator.hpp>
|
||||
|
||||
namespace valijson {
|
||||
namespace constraints {
|
||||
|
||||
@ -18,6 +20,17 @@ namespace constraints {
|
||||
template<typename ConstraintType>
|
||||
struct BasicConstraint: Constraint
|
||||
{
|
||||
typedef internal::CustomAllocator<void *> Allocator;
|
||||
|
||||
typedef std::basic_string<char, std::char_traits<char>,
|
||||
internal::CustomAllocator<char> > String;
|
||||
|
||||
BasicConstraint()
|
||||
: allocator() { }
|
||||
|
||||
BasicConstraint(Allocator::CustomAlloc allocFn, Allocator::CustomFree freeFn)
|
||||
: allocator(allocFn, freeFn) { }
|
||||
|
||||
virtual ~BasicConstraint<ConstraintType>() { }
|
||||
|
||||
virtual bool accept(ConstraintVisitor &visitor) const
|
||||
@ -41,6 +54,10 @@ struct BasicConstraint: Constraint
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
Allocator allocator;
|
||||
};
|
||||
|
||||
} // namespace constraints
|
||||
|
@ -27,6 +27,7 @@
|
||||
|
||||
#include <valijson/adapters/frozen_value.hpp>
|
||||
#include <valijson/constraints/basic_constraint.hpp>
|
||||
#include <valijson/internal/custom_allocator.hpp>
|
||||
#include <valijson/schema.hpp>
|
||||
|
||||
namespace valijson {
|
||||
@ -77,23 +78,113 @@ struct AnyOfConstraint: BasicConstraint<AnyOfConstraint>
|
||||
* A dependency constraint ensures that a given property is valid only if the
|
||||
* properties that it depends on are present.
|
||||
*/
|
||||
struct DependenciesConstraint: BasicConstraint<DependenciesConstraint>
|
||||
class DependenciesConstraint: public BasicConstraint<DependenciesConstraint>
|
||||
{
|
||||
// A mapping from property names to the set of names of their dependencies
|
||||
typedef std::set<std::string> Dependencies;
|
||||
typedef std::map<std::string, Dependencies> PropertyDependenciesMap;
|
||||
public:
|
||||
DependenciesConstraint()
|
||||
: propertyDependencies(allocator),
|
||||
schemaDependencies(allocator)
|
||||
{ }
|
||||
|
||||
// A mapping from property names to dependent schemas
|
||||
typedef std::map<std::string, const Subschema *>
|
||||
PropertyDependentSchemasMap;
|
||||
DependenciesConstraint(CustomAlloc allocFn, CustomFree freeFn)
|
||||
: BasicConstraint(allocFn, freeFn),
|
||||
propertyDependencies(allocator),
|
||||
schemaDependencies(allocator)
|
||||
{ }
|
||||
|
||||
DependenciesConstraint(const PropertyDependenciesMap &dependencies,
|
||||
const PropertyDependentSchemasMap &dependentSchemas)
|
||||
: dependencies(dependencies),
|
||||
dependentSchemas(dependentSchemas) { }
|
||||
template<typename StringType>
|
||||
DependenciesConstraint & addPropertyDependency(
|
||||
const StringType &propertyName,
|
||||
const StringType &dependencyName)
|
||||
{
|
||||
const String key(propertyName.c_str(), allocator);
|
||||
PropertyDependencies::iterator itr = propertyDependencies.find(key);
|
||||
if (itr == propertyDependencies.end()) {
|
||||
itr = propertyDependencies.insert(PropertyDependencies::value_type(
|
||||
key, PropertySet(allocator))).first;
|
||||
}
|
||||
|
||||
const PropertyDependenciesMap dependencies;
|
||||
const PropertyDependentSchemasMap dependentSchemas;
|
||||
itr->second.insert(String(dependencyName.c_str(), allocator));
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename StringType, typename ContainerType>
|
||||
DependenciesConstraint & addPropertyDependencies(
|
||||
const StringType &propertyName,
|
||||
const ContainerType &dependencyNames)
|
||||
{
|
||||
const String key(propertyName.c_str(), allocator);
|
||||
PropertyDependencies::iterator itr = propertyDependencies.find(key);
|
||||
if (itr == propertyDependencies.end()) {
|
||||
itr = propertyDependencies.insert(PropertyDependencies::value_type(
|
||||
key, PropertySet(allocator))).first;
|
||||
}
|
||||
|
||||
typedef typename ContainerType::value_type ValueType;
|
||||
BOOST_FOREACH( const ValueType &dependencyName, dependencyNames ) {
|
||||
itr->second.insert(String(dependencyName.c_str(), allocator));
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename StringType>
|
||||
DependenciesConstraint & addSchemaDependency(
|
||||
const StringType &propertyName,
|
||||
const Subschema *schemaDependency)
|
||||
{
|
||||
if (schemaDependencies.insert(SchemaDependencies::value_type(
|
||||
String(propertyName.c_str(), allocator),
|
||||
schemaDependency)).second) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
throw std::runtime_error(
|
||||
"Dependencies constraint already contains a dependent "
|
||||
"schema for the property '" + propertyName + "'");
|
||||
}
|
||||
|
||||
template<typename FunctorType>
|
||||
bool applyToPropertyDependencies(const FunctorType &fn) const
|
||||
{
|
||||
BOOST_FOREACH( const PropertyDependencies::value_type &v,
|
||||
propertyDependencies ) {
|
||||
if (!fn(v.first, v.second)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename FunctorType>
|
||||
bool applyToSchemaDependencies(const FunctorType &fn) const
|
||||
{
|
||||
BOOST_FOREACH( const SchemaDependencies::value_type &v,
|
||||
schemaDependencies ) {
|
||||
if (!fn(v.first, v.second)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
typedef std::set<String, std::less<String>, Allocator> PropertySet;
|
||||
|
||||
typedef std::map<String, PropertySet, std::less<String>, Allocator>
|
||||
PropertyDependencies;
|
||||
|
||||
typedef std::map<String, const Subschema *, std::less<String>, Allocator>
|
||||
SchemaDependencies;
|
||||
|
||||
/// Mapping from property names to their property-based dependencies
|
||||
PropertyDependencies propertyDependencies;
|
||||
|
||||
/// Mapping from property names to their schema-based dependencies
|
||||
SchemaDependencies schemaDependencies;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -7,7 +7,6 @@ namespace constraints {
|
||||
|
||||
struct AllOfConstraint;
|
||||
struct AnyOfConstraint;
|
||||
struct DependenciesConstraint;
|
||||
struct EnumConstraint;
|
||||
struct ItemsConstraint;
|
||||
struct FormatConstraint;
|
||||
@ -28,6 +27,8 @@ struct RequiredConstraint;
|
||||
struct TypeConstraint;
|
||||
struct UniqueItemsConstraint;
|
||||
|
||||
class DependenciesConstraint;
|
||||
|
||||
/// Interface to allow usage of the visitor pattern with Constraints
|
||||
class ConstraintVisitor
|
||||
{
|
||||
|
111
include/valijson/internal/custom_allocator.hpp
Normal file
111
include/valijson/internal/custom_allocator.hpp
Normal file
@ -0,0 +1,111 @@
|
||||
#ifndef __VALIJSON_CUSTOM_ALLOCATOR_HPP
|
||||
#define __VALIJSON_CUSTOM_ALLOCATOR_HPP
|
||||
|
||||
namespace valijson {
|
||||
namespace internal {
|
||||
|
||||
template<class T>
|
||||
class CustomAllocator
|
||||
{
|
||||
public:
|
||||
/// Typedef for custom new-/malloc-like function
|
||||
typedef void * (*CustomAlloc)(size_t size);
|
||||
|
||||
/// Typedef for custom free-like function
|
||||
typedef void (*CustomFree)(void *);
|
||||
|
||||
// Standard allocator typedefs
|
||||
typedef T value_type;
|
||||
typedef T* pointer;
|
||||
typedef const T* const_pointer;
|
||||
typedef T& reference;
|
||||
typedef const T& const_reference;
|
||||
typedef std::size_t size_type;
|
||||
typedef std::ptrdiff_t difference_type;
|
||||
|
||||
typedef std::true_type propagate_on_container_move_assignment;
|
||||
|
||||
template<typename U>
|
||||
struct rebind
|
||||
{
|
||||
typedef CustomAllocator<U> other;
|
||||
};
|
||||
|
||||
CustomAllocator()
|
||||
: allocFn(::operator new),
|
||||
freeFn(::operator delete) { }
|
||||
|
||||
CustomAllocator(CustomAlloc allocFn, CustomFree freeFn)
|
||||
: allocFn(allocFn),
|
||||
freeFn(freeFn) { }
|
||||
|
||||
CustomAllocator(const CustomAllocator &other)
|
||||
: allocFn(other.allocFn),
|
||||
freeFn(other.freeFn) { }
|
||||
|
||||
template<typename U>
|
||||
CustomAllocator(CustomAllocator<U> const &other)
|
||||
: allocFn(other.allocFn),
|
||||
freeFn(other.freeFn) { }
|
||||
|
||||
CustomAllocator & operator=(const CustomAllocator &other)
|
||||
{
|
||||
allocFn = other.allocFn;
|
||||
freeFn = other.freeFn;
|
||||
}
|
||||
|
||||
pointer address(reference r)
|
||||
{
|
||||
return &r;
|
||||
}
|
||||
|
||||
const_pointer address(const_reference r)
|
||||
{
|
||||
return &r;
|
||||
}
|
||||
|
||||
pointer allocate(size_type cnt,
|
||||
typename std::allocator<void>::const_pointer = 0)
|
||||
{
|
||||
return reinterpret_cast<pointer>(allocFn(cnt * sizeof(T)));
|
||||
}
|
||||
|
||||
void deallocate(pointer p, size_type)
|
||||
{
|
||||
freeFn(p);
|
||||
}
|
||||
|
||||
size_type max_size() const
|
||||
{
|
||||
return std::numeric_limits<size_type>::max() / sizeof(T);
|
||||
}
|
||||
|
||||
void construct(pointer p, const T& t)
|
||||
{
|
||||
new(p) T(t);
|
||||
}
|
||||
|
||||
void destroy(pointer p)
|
||||
{
|
||||
p->~T();
|
||||
}
|
||||
|
||||
bool operator==(CustomAllocator const &other)
|
||||
{
|
||||
return other.allocFn == allocFn && other.freeFn == freeFn;
|
||||
}
|
||||
|
||||
bool operator!=(CustomAllocator const &other)
|
||||
{
|
||||
return !operator==(other);
|
||||
}
|
||||
|
||||
CustomAlloc allocFn;
|
||||
|
||||
CustomFree freeFn;
|
||||
};
|
||||
|
||||
} // end namespace internal
|
||||
} // end namespace valijson
|
||||
|
||||
#endif
|
@ -617,8 +617,7 @@ private:
|
||||
throw std::runtime_error("Expected object value for 'dependencies' constraint.");
|
||||
}
|
||||
|
||||
constraints::DependenciesConstraint::PropertyDependenciesMap pdm;
|
||||
constraints::DependenciesConstraint::PropertyDependentSchemasMap pdsm;
|
||||
constraints::DependenciesConstraint dependenciesConstraint;
|
||||
|
||||
// Process each of the dependency mappings defined by the object
|
||||
BOOST_FOREACH ( const typename AdapterType::ObjectMember member, node.asObject() ) {
|
||||
@ -632,16 +631,19 @@ private:
|
||||
// be detected.
|
||||
if (member.second.maybeArray()) {
|
||||
// Parse an array of dependency names
|
||||
constraints::DependenciesConstraint::Dependencies &dependencies = pdm[member.first];
|
||||
std::vector<std::string> dependentPropertyNames;
|
||||
BOOST_FOREACH( const AdapterType dependencyName, member.second.asArray() ) {
|
||||
if (dependencyName.maybeString()) {
|
||||
dependencies.insert(dependencyName.getString());
|
||||
dependentPropertyNames.push_back(dependencyName.getString());
|
||||
} else {
|
||||
throw std::runtime_error("Expected string value in dependency list of property '" +
|
||||
member.first + "' in 'dependencies' constraint.");
|
||||
}
|
||||
}
|
||||
|
||||
dependenciesConstraint.addPropertyDependencies(member.first,
|
||||
dependentPropertyNames);
|
||||
|
||||
// If the value of dependency mapping could not be processed as an
|
||||
// array, we'll try to process it as an object instead. Note that
|
||||
// strict type comparison is used here, since we've already
|
||||
@ -651,15 +653,17 @@ private:
|
||||
} else if (member.second.isObject()) {
|
||||
// Parse dependent subschema
|
||||
const Subschema *childSubschema = rootSchema.createSubschema();
|
||||
pdsm[member.first] = childSubschema;
|
||||
populateSchema<AdapterType>(rootSchema, rootNode, member.second,
|
||||
*childSubschema, currentScope, nodePath, fetchDoc);
|
||||
dependenciesConstraint.addSchemaDependency(member.first,
|
||||
childSubschema);
|
||||
|
||||
// If we're supposed to be parsing a Draft3 schema, then the value
|
||||
// of the dependency mapping can also be a string containing the
|
||||
// name of a single dependency.
|
||||
} else if (version == kDraft3 && member.second.isString()) {
|
||||
pdm[member.first].insert(member.second.getString());
|
||||
dependenciesConstraint.addPropertyDependency(member.first,
|
||||
member.second.getString());
|
||||
|
||||
// All other types result in an exception being thrown.
|
||||
} else {
|
||||
@ -667,7 +671,7 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
return constraints::DependenciesConstraint(pdm, pdsm);
|
||||
return dependenciesConstraint;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -176,16 +176,21 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Validate against the dependencies constraint represented by a
|
||||
* DependenciesConstraint object.
|
||||
* @brief Validate current node against a 'dependencies' constraint
|
||||
*
|
||||
* A dependencies constraint can specify either a mapping of attribute names
|
||||
* to their dependencies, or a mapping of attribute names to child schemas
|
||||
* that must be satisfied if a given attribute is present.
|
||||
* A 'dependencies' constraint can be used to specify property-based or
|
||||
* schema-based dependencies that must be fulfilled when a particular
|
||||
* property is present in an object.
|
||||
*
|
||||
* @param constraint Constraint that the target must validate against.
|
||||
* Property-based dependencies define a set of properties that must be
|
||||
* present in addition to a particular property, whereas a schema-based
|
||||
* dependency defines an additional schema that the current document must
|
||||
* validate against.
|
||||
*
|
||||
* @return true if validation passes, false otherwise.
|
||||
* @param constraint DependenciesConstraint that the current node
|
||||
* must validate against
|
||||
*
|
||||
* @return \c true if validation passes; \c false otherwise
|
||||
*/
|
||||
virtual bool visit(const DependenciesConstraint &constraint)
|
||||
{
|
||||
@ -194,54 +199,28 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
// Typedef and reference for conciseness in nested loops
|
||||
typedef DependenciesConstraint::PropertyDependenciesMap PDM;
|
||||
const PDM &deps = constraint.dependencies;
|
||||
|
||||
typedef DependenciesConstraint::PropertyDependentSchemasMap PDSM;
|
||||
const PDSM &depSchemas = constraint.dependentSchemas;
|
||||
|
||||
// Get access to the target as an object
|
||||
// Object to be validated
|
||||
const typename AdapterType::Object object = target.asObject();
|
||||
|
||||
// Flag used to track validation status if errors are non-fatal
|
||||
// Cleared if validation fails
|
||||
bool validated = true;
|
||||
|
||||
// For each property in the object, check for a list of dependencies in
|
||||
// the constraint object and verify that the dependencies have been
|
||||
// satisfied.
|
||||
BOOST_FOREACH( const typename AdapterType::ObjectMember m, object ) {
|
||||
// Iterate over all dependent properties defined by this constraint,
|
||||
// invoking the DependentPropertyValidator functor once for each
|
||||
// set of dependent properties
|
||||
constraint.applyToPropertyDependencies(ValidatePropertyDependencies(
|
||||
object, context, results, &validated));
|
||||
if (!results && !validated) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for this property in the dependency map. If it is not
|
||||
// present, we can move on to the next one...
|
||||
PDM::const_iterator itr = deps.find(m.first);
|
||||
if (itr != deps.end()) {
|
||||
BOOST_FOREACH( const std::string &name, itr->second ) {
|
||||
if (object.find(name) == object.end()) {
|
||||
if (!results) {
|
||||
return false;
|
||||
}
|
||||
validated = false;
|
||||
results->pushError(context, "Missing dependency '" + name + "'.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for this property in the dependent schemas map. If it is
|
||||
// present then we need to validate the current target against the
|
||||
// dependent schema.
|
||||
PDSM::const_iterator depSchemasItr = depSchemas.find(m.first);
|
||||
if (depSchemasItr != depSchemas.end()) {
|
||||
const Subschema *subschema = depSchemasItr->second;
|
||||
if (!validateSchema(*subschema)) {
|
||||
if (results) {
|
||||
results->pushError(context, "Failed to validate against dependent schema.");
|
||||
validated = false;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Iterate over all dependent schemas defined by this constraint,
|
||||
// invoking the DependentSchemaValidator function once for each schema
|
||||
// that must be validated if a given property is present
|
||||
constraint.applyToSchemaDependencies(ValidateSchemaDependencies(
|
||||
object, context, *this, results, &validated));
|
||||
if (!results && !validated) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return validated;
|
||||
@ -1087,6 +1066,101 @@ public:
|
||||
|
||||
private:
|
||||
|
||||
struct ValidatePropertyDependencies
|
||||
{
|
||||
ValidatePropertyDependencies(
|
||||
const typename AdapterType::Object &object,
|
||||
const std::vector<std::string> &context,
|
||||
ValidationResults *results,
|
||||
bool *validated)
|
||||
: object(object),
|
||||
context(context),
|
||||
results(results),
|
||||
validated(validated) { }
|
||||
|
||||
template<typename StringType, typename ContainerType>
|
||||
bool operator()(
|
||||
const StringType &propertyName,
|
||||
const ContainerType &dependencyNames) const
|
||||
{
|
||||
const std::string propertyNameKey(propertyName.c_str());
|
||||
if (object.find(propertyNameKey) == object.end()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
typedef typename ContainerType::value_type ValueType;
|
||||
BOOST_FOREACH( const ValueType &dependencyName, dependencyNames ) {
|
||||
const std::string dependencyNameKey(dependencyName.c_str());
|
||||
if (object.find(dependencyNameKey) == object.end()) {
|
||||
if (validated) {
|
||||
*validated = false;
|
||||
}
|
||||
if (results) {
|
||||
results->pushError(context, "Missing dependency '" +
|
||||
dependencyNameKey + "'.");
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
const typename AdapterType::Object &object;
|
||||
const std::vector<std::string> &context;
|
||||
ValidationResults * const results;
|
||||
bool * const validated;
|
||||
};
|
||||
|
||||
struct ValidateSchemaDependencies
|
||||
{
|
||||
ValidateSchemaDependencies(
|
||||
const typename AdapterType::Object &object,
|
||||
const std::vector<std::string> &context,
|
||||
ValidationVisitor &validationVisitor,
|
||||
ValidationResults *results,
|
||||
bool *validated)
|
||||
: object(object),
|
||||
context(context),
|
||||
validationVisitor(validationVisitor),
|
||||
results(results),
|
||||
validated(validated) { }
|
||||
|
||||
template<typename StringType>
|
||||
bool operator()(
|
||||
const StringType &propertyName,
|
||||
const Subschema *schemaDependency) const
|
||||
{
|
||||
const std::string propertyNameKey(propertyName.c_str());
|
||||
if (object.find(propertyNameKey) == object.end()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!validationVisitor.validateSchema(*schemaDependency)) {
|
||||
if (validated) {
|
||||
*validated = false;
|
||||
}
|
||||
if (results) {
|
||||
results->pushError(context,
|
||||
"Failed to validate against dependent schema.");
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
const typename AdapterType::Object &object;
|
||||
const std::vector<std::string> &context;
|
||||
ValidationVisitor &validationVisitor;
|
||||
ValidationResults * const results;
|
||||
bool * const validated;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Callback function that passes a visitor to a constraint.
|
||||
*
|
||||
|
@ -236,6 +236,7 @@
|
||||
6AD030F51B03FD9900B957E5 /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = "<group>"; };
|
||||
6AE1D76617E3F993008CF38C /* custom_schema.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = custom_schema.cpp; sourceTree = "<group>"; };
|
||||
6AEFECC81AB53F1000275614 /* libjsoncpp.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libjsoncpp.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
6AF3F5071C2D874F001B3445 /* custom_allocator.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = custom_allocator.hpp; path = internal/custom_allocator.hpp; sourceTree = "<group>"; };
|
||||
6AF70AE118FE71F600342325 /* gtest-death-test.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "gtest-death-test.h"; sourceTree = "<group>"; };
|
||||
6AF70AE218FE71F600342325 /* gtest-message.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "gtest-message.h"; sourceTree = "<group>"; };
|
||||
6AF70AE318FE71F600342325 /* gtest-param-test.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "gtest-param-test.h"; sourceTree = "<group>"; };
|
||||
@ -362,6 +363,7 @@
|
||||
6A506D1D1AF88D5E00C2C818 /* internal */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
6AF3F5071C2D874F001B3445 /* custom_allocator.hpp */,
|
||||
6A506D1E1AF88D8700C2C818 /* json_pointer.hpp */,
|
||||
6A34698C1BD109A900C97DA2 /* json_reference.hpp */,
|
||||
6A356B0D1C1CFD020007EB8B /* uri.hpp */,
|
||||
|
Loading…
x
Reference in New Issue
Block a user