From 925ff8ff6818aa6f0a6c9e07ebdc3a6ffb0a4647 Mon Sep 17 00:00:00 2001 From: Tristan Penman Date: Thu, 29 Aug 2019 10:31:37 +1000 Subject: [PATCH] Initial support for 'if', 'then' and 'else' subschemas --- .../constraints/concrete_constraints.hpp | 46 ++++++++++++++++++- include/valijson/schema_parser.hpp | 40 +++++++++++++++- include/valijson/validation_visitor.hpp | 23 +++++++++- tests/test_validator.cpp | 10 ++++ 4 files changed, 115 insertions(+), 4 deletions(-) diff --git a/include/valijson/constraints/concrete_constraints.hpp b/include/valijson/constraints/concrete_constraints.hpp index 8c3d36a..b2bcdc3 100644 --- a/include/valijson/constraints/concrete_constraints.hpp +++ b/include/valijson/constraints/concrete_constraints.hpp @@ -118,7 +118,51 @@ private: class ConditionalConstraint: public BasicConstraint { public: - ConditionalConstraint() { } + ConditionalConstraint() + : ifSubschema(NULL), + thenSubschema(NULL), + elseSubschema(NULL) { } + + ConditionalConstraint(CustomAlloc allocFn, CustomFree freeFn) + : BasicConstraint(allocFn, freeFn), + ifSubschema(NULL), + thenSubschema(NULL), + elseSubschema(NULL) { } + + const Subschema * getIfSubschema() const + { + return ifSubschema; + } + + const Subschema * getThenSubschema() const + { + return thenSubschema; + } + + const Subschema * getElseSubschema() const + { + return elseSubschema; + } + + void setIfSubschema(const Subschema *subschema) + { + ifSubschema = subschema; + } + + void setThenSubschema(const Subschema *subschema) + { + thenSubschema = subschema; + } + + void setElseSubschema(const Subschema *subschema) + { + elseSubschema = subschema; + } + +private: + const Subschema *ifSubschema; + const Subschema *thenSubschema; + const Subschema *elseSubschema; }; /** diff --git a/include/valijson/schema_parser.hpp b/include/valijson/schema_parser.hpp index f47ed30..9d649b2 100644 --- a/include/valijson/schema_parser.hpp +++ b/include/valijson/schema_parser.hpp @@ -37,7 +37,8 @@ public: /// Supported versions of JSON Schema enum Version { kDraft3, ///< @deprecated JSON Schema v3 has been superseded by v4 - kDraft4 + kDraft4, + kDraft7 }; /// Version of JSON Schema that should be expected when parsing @@ -710,6 +711,25 @@ private: } } + { + const typename AdapterType::Object::const_iterator ifItr = object.find("if"); + const typename AdapterType::Object::const_iterator thenItr = object.find("then"); + const typename AdapterType::Object::const_iterator elseItr = object.find("end"); + + if (object.end() != ifItr && object.end() != thenItr) { + if (version == kDraft7) { + rootSchema.addConstraintToSubschema( + makeConditionalConstraint(rootSchema, rootNode, + ifItr->second, thenItr->second, + elseItr == object.end() ? NULL : &elseItr->second, + updatedScope, nodePath, fetchDoc, docCache, schemaCache), + &subschema); + } else { + throw std::runtime_error("Not supported"); + } + } + } + if ((itr = object.find("maximum")) != object.end()) { typename AdapterType::Object::const_iterator exclusiveMaximumItr = object.find("exclusiveMaximum"); @@ -1138,6 +1158,24 @@ private: { constraints::ConditionalConstraint constraint; + const Subschema *ifSubschema = makeOrReuseSchema( + rootSchema, rootNode, ifNode, currentScope, + nodePath, fetchDoc, NULL, NULL, docCache, + schemaCache); + constraint.setIfSubschema(ifSubschema); + + const Subschema *thenSubschema = makeOrReuseSchema( + rootSchema, rootNode, thenNode, currentScope, nodePath, fetchDoc, NULL, NULL, + docCache, schemaCache); + constraint.setThenSubschema(thenSubschema); + + if (elseNode) { + const Subschema *elseSubschema = makeOrReuseSchema( + rootSchema, rootNode, *elseNode, currentScope, nodePath, fetchDoc, NULL, NULL, + docCache, schemaCache); + constraint.setElseSubschema(elseSubschema); + } + return constraint; } diff --git a/include/valijson/validation_visitor.hpp b/include/valijson/validation_visitor.hpp index d8d07c8..1d20a99 100644 --- a/include/valijson/validation_visitor.hpp +++ b/include/valijson/validation_visitor.hpp @@ -158,11 +158,30 @@ public: return numValidated > 0; } + /** + * @brief Validate current node using a set of 'if', 'then' and 'else' subschemas + * + * A conditional constraint allows a document to validated against one of two additional + * subschemas (specified via 'then' or 'else' properties) depending on whether the document + * satifies an optional subschema (specified via the 'if' property). + * + * @param constraint ConditionalConstraint that the current node must validate against + * + * @return \c true if validation passes; \c false otherwise + */ virtual bool visit(const ConditionalConstraint &constraint) { - return false; + // Create a validator to evaluate the conditional + ValidationVisitor ifValidator(target, context, strictTypes, NULL); + ValidationVisitor thenElseValidator(target, context, strictTypes, NULL); + + if (ifValidator.validateSchema(*constraint.getIfSubschema())) { + return thenElseValidator.validateSchema(*constraint.getThenSubschema()); + } else { + return thenElseValidator.validateSchema(*constraint.getElseSubschema()); + } } - + /** * @brief Validate current node against a 'dependencies' constraint * diff --git a/tests/test_validator.cpp b/tests/test_validator.cpp index 2a7a529..f71fec7 100644 --- a/tests/test_validator.cpp +++ b/tests/test_validator.cpp @@ -183,6 +183,11 @@ protected: { return processTestFile(testFile, SchemaParser::kDraft4); } + + void processDraft7TestFile(const std::string &testFile) + { + return processTestFile(testFile, SchemaParser::kDraft7); + } }; TEST_F(TestValidator, Draft3_AdditionalItems) @@ -414,3 +419,8 @@ TEST_F(TestValidator, Draft4_UniqueItems) { processDraft4TestFile(TEST_SUITE_DIR "draft4/uniqueItems.json"); } + +TEST_F(TestValidator, Draft7_IfThenElse) +{ + processDraft7TestFile(TEST_SUITE_DIR "draft7/if-then-else.json"); +}