diff --git a/include/rapidjson/schema.h b/include/rapidjson/schema.h index 8fc3fcd..ec1f83f 100644 --- a/include/rapidjson/schema.h +++ b/include/rapidjson/schema.h @@ -259,6 +259,10 @@ public: BaseSchema(value), properties_(), additionalPropertySchema_(), +#if RAPIDJSON_SCHEMA_HAS_REGEX + patternProperties_(), + patternPropertyCount_(), +#endif propertyCount_(), requiredCount_(), minProperties_(), @@ -279,6 +283,31 @@ public: } } +#if RAPIDJSON_SCHEMA_HAS_REGEX + typename ValueType::ConstMemberIterator patternPropretiesItr = value.FindMember("patternProperties"); + if (patternPropretiesItr != value.MemberEnd()) { + const ValueType& patternProperties = patternPropretiesItr->value; + patternProperties_ = new PatternProperty[patternProperties.MemberCount()]; + patternPropertyCount_ = 0; + + for (typename ValueType::ConstMemberIterator propertyItr = patternProperties.MemberBegin(); propertyItr != patternProperties.MemberEnd(); ++propertyItr) { +#if RAPIDJSON_SCHEMA_USE_STDREGEX + try { + patternProperties_[patternPropertyCount_].pattern = new std::basic_regex( + propertyItr->name.GetString(), + std::size_t(propertyItr->name.GetStringLength()), + std::regex_constants::ECMAScript); + } + catch (const std::regex_error&) { + // Error + } +#endif + patternProperties_[patternPropertyCount_].schema = CreateSchema(propertyItr->value); // TODO: Check error + patternPropertyCount_++; + } + } +#endif + // Establish required after properties typename ValueType::ConstMemberIterator requiredItr = value.FindMember("required"); if (requiredItr != value.MemberEnd()) { @@ -372,6 +401,9 @@ public: ~ObjectSchema() { delete [] properties_; delete additionalPropertySchema_; +#if RAPIDJSON_SCHEMA_HAS_REGEX + delete [] patternProperties_; +#endif } virtual SchemaType GetSchemaType() const { return kObjectSchemaType; } @@ -408,6 +440,22 @@ public: return true; } +#if RAPIDJSON_SCHEMA_HAS_REGEX + if (patternProperties_) { + for (SizeType i = 0; i < patternPropertyCount_; i++) { +#if RAPIDJSON_SCHEMA_USE_STDREGEX + if (patternProperties_[i].pattern) { + std::match_results r; + if (std::regex_search(str, str + len, r, *patternProperties_[i].pattern)) { + context.valueSchema = patternProperties_[i].schema; + return true; + } + } +#endif // RAPIDJSON_SCHEMA_USE_STDREGEX + } + } +#endif + if (additionalPropertySchema_) { context.valueSchema = additionalPropertySchema_; return true; @@ -477,9 +525,28 @@ private: bool required; }; +#if RAPIDJSON_SCHEMA_HAS_REGEX + struct PatternProperty { + PatternProperty() : schema(), pattern() {} + ~PatternProperty() { + delete schema; + delete pattern; + } + + BaseSchema* schema; +#if RAPIDJSON_SCHEMA_USE_STDREGEX + std::basic_regex* pattern; +#endif + }; +#endif + TypelessSchema typeless_; Property* properties_; BaseSchema* additionalPropertySchema_; +#if RAPIDJSON_SCHEMA_HAS_REGEX + PatternProperty* patternProperties_; + SizeType patternPropertyCount_; +#endif SizeType propertyCount_; SizeType requiredCount_; SizeType minProperties_; @@ -689,7 +756,7 @@ public: if (pattern_) { #if RAPIDJSON_SCHEMA_USE_STDREGEX std::match_results r; - if (!std::regex_match(str, str + length, r, *pattern_)) + if (!std::regex_search(str, str + length, r, *pattern_)) return false; #endif // RAPIDJSON_SCHEMA_USE_STDREGEX } diff --git a/test/unittest/schematest.cpp b/test/unittest/schematest.cpp index 293ea9e..51167df 100644 --- a/test/unittest/schematest.cpp +++ b/test/unittest/schematest.cpp @@ -314,6 +314,46 @@ TEST(SchemaValidator, Object_PropertyDependencies) { VALIDATE(s, "{ \"name\": \"John Doe\", \"billing_address\": \"555 Debtor's Lane\" }", true); } +TEST(SchemaValidator, Object_PatternProperties) { + Document sd; + sd.Parse( + "{" + " \"type\": \"object\"," + " \"patternProperties\": {" + " \"^S_\": { \"type\": \"string\" }," + " \"^I_\": { \"type\": \"integer\" }" + " }" + "}"); + Schema s(sd); + + VALIDATE(s, "{ \"S_25\": \"This is a string\" }", true); + VALIDATE(s, "{ \"I_0\": 42 }", true); + VALIDATE(s, "{ \"S_0\": 42 }", false); + VALIDATE(s, "{ \"I_42\": \"This is a string\" }", false); + VALIDATE(s, "{ \"keyword\": \"value\" }", true); +} + +TEST(SchemaValidator, Object_PatternProperties_AdditionalProperties) { + Document sd; + sd.Parse( + "{" + " \"type\": \"object\"," + " \"properties\": {" + " \"builtin\": { \"type\": \"number\" }" + " }," + " \"patternProperties\": {" + " \"^S_\": { \"type\": \"string\" }," + " \"^I_\": { \"type\": \"integer\" }" + " }," + " \"additionalProperties\": { \"type\": \"string\" }" + "}"); + Schema s(sd); + + VALIDATE(s, "{ \"builtin\": 42 }", true); + VALIDATE(s, "{ \"keyword\": \"value\" }", true); + VALIDATE(s, "{ \"keyword\": 42 }", false); +} + TEST(SchemaValidator, Array) { Document sd; sd.Parse("{\"type\":\"array\"}");