diff --git a/include/rapidjson/schema.h b/include/rapidjson/schema.h index 98186b0..6eaa4e3 100644 --- a/include/rapidjson/schema.h +++ b/include/rapidjson/schema.h @@ -60,11 +60,9 @@ template struct SchemaValidatorArray { SchemaValidatorArray() : validators(), count() {} ~SchemaValidatorArray() { - if (validators) { - for (SizeType i = 0; i < count; i++) - delete validators[i]; - delete[] validators; - } + for (SizeType i = 0; i < count; i++) + delete validators[i]; + delete[] validators; } GenericSchemaValidator, CrtAllocator>** validators; @@ -75,11 +73,9 @@ template struct BaseSchemaArray { BaseSchemaArray() : schemas(), count() {} ~BaseSchemaArray() { - if (schemas) { - for (SizeType i = 0; i < count; i++) - delete schemas[i]; - delete[] schemas; - } + for (SizeType i = 0; i < count; i++) + delete schemas[i]; + delete[] schemas; } BaseSchema** schemas; @@ -103,6 +99,7 @@ struct SchemaValidationContext { SchemaValidatorArray allOfValidators; SchemaValidatorArray anyOfValidators; SchemaValidatorArray oneOfValidators; + SchemaValidatorArray dependencyValidators; GenericSchemaValidator, CrtAllocator>* notValidator; SizeType objectRequiredCount; SizeType arrayElementIndex; @@ -132,6 +129,7 @@ public: maxProperties_(SizeType(~0)), additionalProperties_(true), hasDependencies_(), + hasSchemaDependencies_(), additionalItemsSchema_(), itemsList_(), itemsTuple_(), @@ -196,7 +194,7 @@ public: for (ConstMemberIterator itr = v->MemberBegin(); itr != v->MemberEnd(); ++itr) { patternProperties_[patternPropertyCount_].pattern = CreatePattern(itr->name); - patternProperties_[patternPropertyCount_].schema = new BaseSchema(itr->value); // TODO: Check error + patternProperties_[patternPropertyCount_].schema = new BaseSchema(itr->value); patternPropertyCount_++; } } @@ -229,7 +227,8 @@ public: } } else if (itr->value.IsObject()) { - // TODO + hasSchemaDependencies_ = true; + properties_[sourceIndex].dependenciesSchema = new BaseSchema(itr->value); } } } @@ -503,12 +502,19 @@ public: if (context.objectRequiredCount != requiredCount_ || memberCount < minProperties_ || memberCount > maxProperties_) return false; - if (hasDependencies_) + if (hasDependencies_) { for (SizeType sourceIndex = 0; sourceIndex < propertyCount_; sourceIndex++) - if (context.objectDependencies[sourceIndex] && properties_[sourceIndex].dependencies) - for (SizeType targetIndex = 0; targetIndex < propertyCount_; targetIndex++) - if (properties_[sourceIndex].dependencies[targetIndex] && !context.objectDependencies[targetIndex]) + if (context.objectDependencies[sourceIndex]) { + if (properties_[sourceIndex].dependencies) { + for (SizeType targetIndex = 0; targetIndex < propertyCount_; targetIndex++) + if (properties_[sourceIndex].dependencies[targetIndex] && !context.objectDependencies[targetIndex]) + return false; + } + else if (properties_[sourceIndex].dependenciesSchema) + if (!context.dependencyValidators.validators[sourceIndex]->IsValid()) return false; + } + } return true; } @@ -533,6 +539,7 @@ public: } private: + typedef GenericSchemaValidator, CrtAllocator> SchemaValidatorType; static const BaseSchema* GetTypeless() { static BaseSchema typeless(Value(kObjectType).Move()); return &typeless; @@ -610,15 +617,22 @@ private: if (anyOf_.schemas) CreateSchemaValidators(context.anyOfValidators, anyOf_); if (oneOf_.schemas) CreateSchemaValidators(context.oneOfValidators, oneOf_); if (not_ && !context.notValidator) - context.notValidator = new GenericSchemaValidator, CrtAllocator>(*not_); + context.notValidator = new SchemaValidatorType(*not_); + + if (hasSchemaDependencies_ && !context.dependencyValidators.validators) { + context.dependencyValidators.validators = new SchemaValidatorType*[propertyCount_]; + context.dependencyValidators.count = propertyCount_; + for (SizeType i = 0; i < propertyCount_; i++) + context.dependencyValidators.validators[i] = properties_[i].dependenciesSchema ? new SchemaValidatorType(*properties_[i].dependenciesSchema) : 0; + } } void CreateSchemaValidators(SchemaValidatorArray& validators, const BaseSchemaArray& schemas) const { if (!validators.validators) { - validators.validators = new GenericSchemaValidator, CrtAllocator>*[schemas.count]; + validators.validators = new SchemaValidatorType*[schemas.count]; validators.count = schemas.count; for (SizeType i = 0; i < schemas.count; i++) - validators.validators[i] = new GenericSchemaValidator, CrtAllocator>(*schemas.schemas[i]); + validators.validators[i] = new SchemaValidatorType(*schemas.schemas[i]); } } @@ -657,14 +671,16 @@ private: } struct Property { - Property() : schema(), dependencies(), required(false) {} + Property() : schema(), dependenciesSchema(), dependencies(), required(false) {} ~Property() { delete schema; + delete dependenciesSchema; delete[] dependencies; } GenericValue name; BaseSchema* schema; + BaseSchema* dependenciesSchema; bool* dependencies; bool required; }; @@ -704,6 +720,7 @@ private: SizeType maxProperties_; bool additionalProperties_; bool hasDependencies_; + bool hasSchemaDependencies_; BaseSchema* additionalItemsSchema_; BaseSchema* itemsList_; @@ -814,6 +831,10 @@ public: context->oneOfValidators.validators[i_]->method arg2;\ if (context->notValidator)\ context->notValidator->method arg2;\ + if (context->dependencyValidators.validators)\ + for (SizeType i_ = 0; i_ < context->dependencyValidators.count; i_++)\ + if (context->dependencyValidators.validators[i_])\ + context->dependencyValidators.validators[i_]->method arg2;\ } #define RAPIDJSON_SCHEMA_HANDLE_END_(method, arg2)\ @@ -849,8 +870,8 @@ public: bool EndObject(SizeType memberCount) { if (!valid_) return false; - if (!CurrentSchema().EndObject(CurrentContext(), memberCount)) return valid_ = false; RAPIDJSON_SCHEMA_HANDLE_LOGIC_(EndObject, (memberCount)); + if (!CurrentSchema().EndObject(CurrentContext(), memberCount)) return valid_ = false; RAPIDJSON_SCHEMA_HANDLE_END_ (EndObject, (memberCount)); } @@ -862,8 +883,8 @@ public: bool EndArray(SizeType elementCount) { if (!valid_) return false; - if (!CurrentSchema().EndArray(CurrentContext(), elementCount)) return valid_ = false; RAPIDJSON_SCHEMA_HANDLE_LOGIC_(EndArray, (elementCount)); + if (!CurrentSchema().EndArray(CurrentContext(), elementCount)) return valid_ = false; RAPIDJSON_SCHEMA_HANDLE_END_ (EndArray, (elementCount)); } diff --git a/test/unittest/schematest.cpp b/test/unittest/schematest.cpp index b96c38f..147200c 100644 --- a/test/unittest/schematest.cpp +++ b/test/unittest/schematest.cpp @@ -361,6 +361,32 @@ TEST(SchemaValidator, Object_PropertyDependencies) { VALIDATE(s, "{ \"name\": \"John Doe\", \"billing_address\": \"555 Debtor's Lane\" }", true); } +TEST(SchemaValidator, Object_SchemaDependencies) { + Document sd; + sd.Parse( + "{" + " \"type\": \"object\"," + " \"properties\" : {" + " \"name\": { \"type\": \"string\" }," + " \"credit_card\" : { \"type\": \"number\" }" + " }," + " \"required\" : [\"name\"]," + " \"dependencies\" : {" + " \"credit_card\": {" + " \"properties\": {" + " \"billing_address\": { \"type\": \"string\" }" + " }," + " \"required\" : [\"billing_address\"]" + " }" + " }" + "}"); + Schema s(sd); + + //VALIDATE(s, "{\"name\": \"John Doe\", \"credit_card\" : 5555555555555555,\"billing_address\" : \"555 Debtor's Lane\"}", true); + VALIDATE(s, "{\"name\": \"John Doe\", \"credit_card\" : 5555555555555555 }", false); + VALIDATE(s, "{\"name\": \"John Doe\", \"billing_address\" : \"555 Debtor's Lane\"}", true); +} + #if RAPIDJSON_SCHEMA_HAS_REGEX TEST(SchemaValidator, Object_PatternProperties) { @@ -645,7 +671,7 @@ TEST(SchemaValidator, TestSuite) { "allOf.json", "anyOf.json", //"definitions.json", - //"dependencies.json", + "dependencies.json", "enum.json", "items.json", "maximum.json", @@ -696,17 +722,18 @@ TEST(SchemaValidator, TestSuite) { for (Value::ConstValueIterator schemaItr = d.Begin(); schemaItr != d.End(); ++schemaItr) { Schema schema((*schemaItr)["schema"]); SchemaValidator validator(schema); + const char* description1 = (*schemaItr)["description"].GetString(); const Value& tests = (*schemaItr)["tests"]; for (Value::ConstValueIterator testItr = tests.Begin(); testItr != tests.End(); ++testItr) { - const char* description = (*testItr)["description"].GetString(); - if (!onlyRunDescription || strcmp(description, onlyRunDescription) == 0) { + const char* description2 = (*testItr)["description"].GetString(); + if (!onlyRunDescription || strcmp(description2, onlyRunDescription) == 0) { const Value& data = (*testItr)["data"]; bool expected = (*testItr)["valid"].GetBool(); testCount++; validator.Reset(); bool actual = data.Accept(validator); if (expected != actual) - printf("Fail: %30s \"%s\"\n", filename, description); + printf("Fail: %30s \"%s, %s\"\n", filename, description1, description2); else passCount++; }