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 path
include_directories( include_directories(
include include
)
include_directories(SYSTEM
${Boost_INCLUDE_DIRS}
thirdparty/gtest-1.7.0/include thirdparty/gtest-1.7.0/include
thirdparty/jsoncpp-0.9.4/include thirdparty/jsoncpp-0.9.4/include
thirdparty/rapidjson-1.0.2/include thirdparty/rapidjson-1.0.2/include
thirdparty/picojson-1.3.0 thirdparty/picojson-1.3.0
thirdparty/nlohmann-json-1.1.0 thirdparty/nlohmann-json-1.1.0
${Boost_INCLUDE_DIRS}
) )
if(VALIJSON_CXX11_ADAPTERS_ARE_ENABLED) if(VALIJSON_CXX11_ADAPTERS_ARE_ENABLED)
include_directories( include_directories(SYSTEM
thirdparty/json11-2016-01-26 thirdparty/json11-2016-01-26
) )
endif() endif()
@ -78,6 +80,7 @@ set(TEST_SOURCES
tests/test_picojson_adapter.cpp tests/test_picojson_adapter.cpp
tests/test_validation_errors.cpp tests/test_validation_errors.cpp
tests/test_validator.cpp tests/test_validator.cpp
tests/test_poly_constraint.cpp
) )
if(VALIJSON_CXX11_ADAPTERS_ARE_ENABLED) if(VALIJSON_CXX11_ADAPTERS_ARE_ENABLED)

View File

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

View File

@ -31,6 +31,7 @@
#include <valijson/schema.hpp> #include <valijson/schema.hpp>
namespace valijson { namespace valijson {
class ValidationResults;
namespace constraints { namespace constraints {
/** /**
@ -1060,6 +1061,12 @@ public:
: BasicConstraint(allocFn, freeFn) { } : 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 constraints
} // namespace valijson } // namespace valijson

View File

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

View File

@ -81,6 +81,47 @@ public:
mutableSubschema(subschema)->addConstraint(constraint); 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 * @brief Create a new Subschema instance that is owned by this Schema
* *

View File

@ -7,6 +7,7 @@
#include <boost/foreach.hpp> #include <boost/foreach.hpp>
#include <boost/function.hpp> #include <boost/function.hpp>
#include <boost/optional.hpp> #include <boost/optional.hpp>
#include <memory>
#include <valijson/constraints/constraint.hpp> #include <valijson/constraints/constraint.hpp>
@ -93,6 +94,39 @@ public:
constraints.push_back(constraint.clone(allocFn, freeFn)); 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 * @brief Invoke a function on each child Constraint
* *

View File

@ -794,6 +794,18 @@ public:
return true; 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 * @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));
}