diff --git a/Authors b/Authors new file mode 100644 index 0000000..3820c3c --- /dev/null +++ b/Authors @@ -0,0 +1,2 @@ +James Jensen, jjensen@akamai.com, changes Copyright (c) 2016 Akamai Technologies + Polymorphic constraint support diff --git a/CMakeLists.txt b/CMakeLists.txt index 9cff096..6d3f479 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/LICENSE b/LICENSE index 630b5c3..77d3816 100644 --- a/LICENSE +++ b/LICENSE @@ -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 diff --git a/include/valijson/constraints/concrete_constraints.hpp b/include/valijson/constraints/concrete_constraints.hpp index 6b3b7e0..bc0c542 100644 --- a/include/valijson/constraints/concrete_constraints.hpp +++ b/include/valijson/constraints/concrete_constraints.hpp @@ -31,6 +31,7 @@ #include namespace valijson { +class ValidationResults; namespace constraints { /** @@ -1060,6 +1061,12 @@ public: : BasicConstraint(allocFn, freeFn) { } }; +class PolyConstraint : public BasicConstraint { + public: + virtual bool validate(const adapters::Adapter &target, const std::vector& 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 diff --git a/include/valijson/constraints/constraint_visitor.hpp b/include/valijson/constraints/constraint_visitor.hpp index c2eda8f..49fdb83 100644 --- a/include/valijson/constraints/constraint_visitor.hpp +++ b/include/valijson/constraints/constraint_visitor.hpp @@ -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; }; diff --git a/include/valijson/schema.hpp b/include/valijson/schema.hpp index 2308e36..cbd52a9 100644 --- a/include/valijson/schema.hpp +++ b/include/valijson/schema.hpp @@ -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, + 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 * diff --git a/include/valijson/subschema.hpp b/include/valijson/subschema.hpp index a6510ab..8ac4a0c 100644 --- a/include/valijson/subschema.hpp +++ b/include/valijson/subschema.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include @@ -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_ptrconstraint) + { + constraints.push_back(constraint.release()); + } + #endif + /** * @brief Invoke a function on each child Constraint * diff --git a/include/valijson/validation_visitor.hpp b/include/valijson/validation_visitor.hpp index 9c1964c..085774f 100644 --- a/include/valijson/validation_visitor.hpp +++ b/include/valijson/validation_visitor.hpp @@ -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 * diff --git a/tests/test_poly_constraint.cpp b/tests/test_poly_constraint.cpp new file mode 100644 index 0000000..083b549 --- /dev/null +++ b/tests/test_poly_constraint.cpp @@ -0,0 +1,171 @@ +#include +#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 +#include +#include + +#include +#include +#include +#include +#include + +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 &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(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)); +}