Add polymorphic constraint, and modifications neccessary for using them

Modify CMakeLists.txt to make third party include files be included by -isystem.  This reduces external warnings/errors.

Add a unit test for the PolyConstraints.  The test doesn't pass, because the right method for inserting a constraint isn't clear to me.

Add Authors file for tracking modifications

Add Akamai to the License file.
This commit is contained in:
James Jensen 2016-04-06 09:31:06 -04:00
parent 00d0fa8d43
commit 8f446704f7
9 changed files with 277 additions and 3 deletions

2
Authors Normal file
View File

@ -0,0 +1,2 @@
James Jensen, jjensen@akamai.com, changes Copyright (c) 2016 Akamai Technologies
Polymorphic constraint support

View File

@ -44,16 +44,18 @@ add_subdirectory(thirdparty/gtest-1.7.0)
# Include path
include_directories(
include
)
include_directories(SYSTEM
${Boost_INCLUDE_DIRS}
thirdparty/gtest-1.7.0/include
thirdparty/jsoncpp-0.9.4/include
thirdparty/rapidjson-1.0.2/include
thirdparty/picojson-1.3.0
thirdparty/nlohmann-json-1.1.0
${Boost_INCLUDE_DIRS}
)
if(VALIJSON_CXX11_ADAPTERS_ARE_ENABLED)
include_directories(
include_directories(SYSTEM
thirdparty/json11-2016-01-26
)
endif()
@ -78,6 +80,7 @@ set(TEST_SOURCES
tests/test_picojson_adapter.cpp
tests/test_validation_errors.cpp
tests/test_validator.cpp
tests/test_poly_constraint.cpp
)
if(VALIJSON_CXX11_ADAPTERS_ARE_ENABLED)

View File

@ -1,4 +1,5 @@
Copyright (c) 2016, Tristan Penman
Copyright (c) 2016, Akamai Technolgies, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without

View File

@ -31,6 +31,7 @@
#include <valijson/schema.hpp>
namespace valijson {
class ValidationResults;
namespace constraints {
/**
@ -1060,6 +1061,12 @@ public:
: BasicConstraint(allocFn, freeFn) { }
};
class PolyConstraint : public BasicConstraint<PolyConstraint> {
public:
virtual bool validate(const adapters::Adapter &target, const std::vector<std::string>& context, valijson::ValidationResults *results) const {throw std::runtime_error("attempt to validate incomplete"); }
virtual PolyConstraint * clone() const {throw std::runtime_error("attempt to validate incomplete"); }
};
} // namespace constraints
} // namespace valijson

View File

@ -28,6 +28,7 @@ class RequiredConstraint;
class SingularItemsConstraint;
class TypeConstraint;
class UniqueItemsConstraint;
class PolyConstraint;
/// Interface to allow usage of the visitor pattern with Constraints
class ConstraintVisitor
@ -58,6 +59,7 @@ protected:
typedef constraints::SingularItemsConstraint SingularItemsConstraint;
typedef constraints::TypeConstraint TypeConstraint;
typedef constraints::UniqueItemsConstraint UniqueItemsConstraint;
typedef constraints::PolyConstraint PolyConstraint;
public:
@ -84,6 +86,7 @@ public:
virtual bool visit(const SingularItemsConstraint &) = 0;
virtual bool visit(const TypeConstraint &) = 0;
virtual bool visit(const UniqueItemsConstraint &) = 0;
virtual bool visit(const PolyConstraint &) = 0;
};

View File

@ -81,6 +81,47 @@ public:
mutableSubschema(subschema)->addConstraint(constraint);
}
/**
* @brief move a constraint to a specific sub-schema
*
* @param constraint reference to a constraint that will be copied into
* the sub-schema
* @param subschema pointer to the sub-schema that will own the copied
* constraint
*
* @throws std::runtime_error if the sub-schema is not owned by this Schema
* instance
*/
void addConstraintToSubschema(Constraint* constraint,
const Subschema *subschema)
{
// TODO: Check heirarchy for subschemas that do not belong...
mutableSubschema(subschema)->addConstraint(constraint);
}
//#if _cplusplus >=201103L
#if 0
/**
* @brief Copy a constraint to a specific sub-schema
* exception safe.
*
* @param constraint reference to a constraint that will be copied into
* the sub-schema
* @param subschema pointer to the sub-schema that will own the copied
* constraint
*
* @throws std::runtime_error if the sub-schema is not owned by this Schema
* instance
*/
void addConstraintToSubschema(std::unique_ptr<Constraint> constraint,
const Subschema *subschema)
{
// TODO: Check heirarchy for subschemas that do not belong...
mutableSubschema(subschema)->addConstraint(std::move(constraint));
}
#endif
/**
* @brief Create a new Subschema instance that is owned by this Schema
*

View File

@ -7,6 +7,7 @@
#include <boost/foreach.hpp>
#include <boost/function.hpp>
#include <boost/optional.hpp>
#include <memory>
#include <valijson/constraints/constraint.hpp>
@ -93,6 +94,39 @@ public:
constraints.push_back(constraint.clone(allocFn, freeFn));
}
/**
* @brief move a constraint to this sub-schema
*
* The constraint will be added to the list of constraints for this
* Subschema.
*
* @param constraint pointer to the constraint to be added. Ownership
* assumed.
*/
void addConstraint(const Constraint *constraint)
{
constraints.push_back(constraint);
}
//#if _cplusplus >=201103L
#if 0
/**
* @brief Add a constraint to this sub-schema
*
* The constraint will be copied before being added to the list of
* constraints for this Subschema. Note that constraints will be copied
* only as deep as references to other Subschemas - e.g. copies of
* constraints that refer to sub-schemas, will continue to refer to the
* same Subschema instances.
*
* @param constraint Reference to the constraint to copy
*/
void addConstraint(std::unique_ptr<Constraint>constraint)
{
constraints.push_back(constraint.release());
}
#endif
/**
* @brief Invoke a function on each child Constraint
*

View File

@ -794,6 +794,18 @@ public:
return true;
}
/**
* @brief Validate a value against a PatternConstraint
*
* @param constraint Constraint that the target must validate against
*
* @return \c true if the constraint is satisfied; \c false otherwise
*/
virtual bool visit(const constraints::PolyConstraint &constraint)
{
return constraint.validate(target, context, results);
}
/**
* @brief Validate a value against a PropertiesConstraint
*

View File

@ -0,0 +1,171 @@
#include <gtest/gtest.h>
#include "json/json.h" // jsoncpp rolled up
#define STRINGY1(arg) #arg
#define STRINGY2(arg) STRINGY1(arg)
#define deblog(args) { \
std::cerr << __FILE__ ":" STRINGY2(__LINE__ ) ": " ; \
std::cerr << args << " "<< __PRETTY_FUNCTION__ << '\n'; \
}
#include <iostream>
#include <regex>
#include <boost/regex.hpp>
#include <valijson/adapters/jsoncpp_adapter.hpp>
#include <valijson/schema.hpp>
#include <valijson/schema_parser.hpp>
#include <valijson/validation_results.hpp>
#include <valijson/validator.hpp>
namespace {
using namespace valijson;
using valijson::Schema;
using valijson::SchemaParser;
using valijson::Validator;
using valijson::ValidationResults;
using valijson::adapters::JsonCppAdapter;
using namespace std;
Json::Value jsonParse(const string &jsonStr) {
Json::Value retJson;
Json::Reader json_reader;
if (jsonStr.length() != 0) {
if (!json_reader.parse(jsonStr, retJson)) {
throw runtime_error("json parse failure\n"
+ json_reader.getFormattedErrorMessages());
}
}
return retJson;
}
const static string emplrec (R"(
{
"employee" : {
"John" : {
"fullname": "John Doe"
}
},
"elist" : [
{"rec" : "John"},
{"rec" : "Jane"}
]
}
)");
const static string tschema(R"(
{
"type" : "object",
"id" : "eroot",
"properties" : {
"employee": {
"type":"object"
},
"elist" : {
"type" : "array",
"items": {
"type" : "object",
"additionalProperties": false,
"required": ["rec"],
"properties" : {
"rec" : {
"type" : "string"
}
}
}
}
},
"additionalProperties": false
}
)");
std::string err2String(ValidationResults& results) {
ValidationResults::Error error;
std::stringstream strstr;
while (results.popError(error)) {
strstr << error.description;
for (std::string& str : error.context)
strstr << str;
strstr << "\n";
}
return strstr.str();
}
using namespace constraints;
Json::Value *root; // needs to be a better way to do this.
class PathConstraint : public valijson::constraints::PolyConstraint {
const std::string path;
public:
PathConstraint(const std::string &path) : path(path){ }
virtual valijson::constraints::PolyConstraint * clone() const {
deblog("clone !");
return new PathConstraint(path);
};
virtual bool validate(const adapters::Adapter &target, const std::vector<std::string> &context, ValidationResults *results) const {
std::string spath(path + "." + target.asString());
const Json::Path jpath(spath);
const Json::Value& find(jpath.resolve(*root));
if (!find) {
std::string estring("Failed to find " + spath + " in input");
if (results) {
results->pushError(context, estring);
}
return false;
} else {
return true;
}
}
virtual Constraint * clone(CustomAlloc allocFn, CustomFree freeFn) const
{
void *ptr = allocFn(sizeof(PathConstraint));
if (!ptr) {
throw std::runtime_error(
"Failed to allocate memory for cloned constraint");
}
try {
return new (ptr) PathConstraint(
*static_cast<const PathConstraint*>(this));
} catch (...) {
freeFn(ptr);
throw;
}
}
};
}
TEST(PolyConstraint, Insert) {
Json::Value schemaJson(jsonParse(tschema));
JsonCppAdapter schemaDocumentAdapter(schemaJson);
SchemaParser parser;
Schema schema;
parser.populateSchema(schemaDocumentAdapter, schema);
std::string empl(".employee");
PathConstraint empConstraint(empl);
// how to add this to schema?
Json::Value doc = jsonParse(emplrec);
root = &doc;
JsonCppAdapter targetDocumentAdapter(doc);
deblog("doc " << doc);
deblog("schema" << schemaJson);
ValidationResults results;
Validator validator;
std::regex John("Failed.*John");
std::regex Jane("Failed.*Jane");
EXPECT_FALSE(validator.validate(schema,targetDocumentAdapter, &results));
std::string err(err2String(results));
deblog("err " << err);
EXPECT_FALSE(regex_search(err, John));
EXPECT_TRUE(regex_search(err, Jane));
}