From 0713b8931d83b4978d3c4af97ff92958000c5be6 Mon Sep 17 00:00:00 2001 From: miloyip Date: Sat, 2 May 2015 17:46:55 +0800 Subject: [PATCH] Implement Multi-type --- include/rapidjson/schema.h | 160 ++++++++++++++++++++++++++++------- test/unittest/schematest.cpp | 54 +++++++++++- 2 files changed, 181 insertions(+), 33 deletions(-) diff --git a/include/rapidjson/schema.h b/include/rapidjson/schema.h index d2bb2f1..2fb58b0 100644 --- a/include/rapidjson/schema.h +++ b/include/rapidjson/schema.h @@ -26,17 +26,31 @@ RAPIDJSON_DIAG_OFF(float-equal) RAPIDJSON_NAMESPACE_BEGIN +enum SchemaType { + kNullSchemaType, + kBooleanSchemaType, + kObjectSchemaType, + kArraySchemaType, + kStringSchemaType, + kNumberSchemaType, + kIntegerSchemaType, + kTotalBasicSchemaType, + kTypelessSchemaType = kTotalBasicSchemaType, + kMultiTypeSchemaType, +}; + template class BaseSchema; template struct SchemaValidationContext { - SchemaValidationContext(const BaseSchema* s) : schema(s), valueSchema() {} + SchemaValidationContext(const BaseSchema* s) : schema(s), valueSchema(), multiTypeSchema() {} ~SchemaValidationContext() {} const BaseSchema* schema; const BaseSchema* valueSchema; + const BaseSchema* multiTypeSchema; SizeType objectRequiredCount; SizeType arrayElementIndex; }; @@ -50,8 +64,7 @@ public: BaseSchema() {} template - BaseSchema(const ValueType& value) - { + BaseSchema(const ValueType& value) { typename ValueType::ConstMemberIterator enumItr = value.FindMember("enum"); if (enumItr != value.MemberEnd()) { if (enumItr->value.IsArray() && enumItr->value.Size() > 0) @@ -64,6 +77,9 @@ public: virtual ~BaseSchema() {} + virtual SchemaType GetSchemaType() const = 0; + + virtual bool HandleMultiType(Context&, SchemaType) const { return true; } virtual bool BeginValue(Context&) const { return true; } virtual bool Null() const { return !enum_.IsArray() || CheckEnum(GenericValue().Move()); } @@ -92,6 +108,9 @@ protected: GenericValue enum_; }; +template +inline BaseSchema* CreateSchema(const ValueType& value, const ValueType& type); + template inline BaseSchema* CreateSchema(const ValueType& value); @@ -105,9 +124,61 @@ public: template TypelessSchema(const ValueType& value) : BaseSchema(value) {} + virtual SchemaType GetSchemaType() const { return kTypelessSchemaType; } + virtual bool BeginValue(Context& context) const { context.valueSchema = this; return true; } }; +template +class MultiTypeSchema : public BaseSchema { +public: + typedef SchemaValidationContext Context; + + template + MultiTypeSchema(const ValueType& value, const ValueType& type) : BaseSchema(), typedSchemas_() { + RAPIDJSON_ASSERT(type.IsArray()); + for (typename ValueType::ConstValueIterator itr = type.Begin(); itr != type.End(); ++itr) { + if (itr->IsString()) { + BaseSchema* schema = CreateSchema(value, *itr); + SchemaType schemaType = schema->GetSchemaType(); + RAPIDJSON_ASSERT(schemaType < kTotalBasicSchemaType); + if (typedSchemas_[schemaType] == 0) + typedSchemas_[schemaType] = schema; + else { + // Erorr: not unique type + } + } + else { + // Error + } + } + } + + ~MultiTypeSchema() { + for (size_t i = 0; i < kTotalBasicSchemaType; i++) + delete typedSchemas_[i]; + } + + virtual SchemaType GetSchemaType() const { return kMultiTypeSchemaType; }; + + virtual bool HandleMultiType(Context& context, SchemaType schemaType) const { + RAPIDJSON_ASSERT(schemaType < kTotalBasicSchemaType); + if (typedSchemas_[schemaType]) { + context.multiTypeSchema = typedSchemas_[schemaType]; + return true; + } + else if (schemaType == kIntegerSchemaType && typedSchemas_[kNumberSchemaType]) { + context.multiTypeSchema = typedSchemas_[kNumberSchemaType]; + return true; + } + else + return false; + } + +private: + BaseSchema* typedSchemas_[kTotalBasicSchemaType]; +}; + template class NullSchema : public BaseSchema { public: @@ -117,6 +188,8 @@ public: template NullSchema(const ValueType& value) : BaseSchema(value) {} + virtual SchemaType GetSchemaType() const { return kNullSchemaType; } + virtual bool Null() const { return BaseSchema::Null(); } virtual bool Bool(bool) const { return false; } virtual bool Int(int) const { return false; } @@ -141,6 +214,8 @@ public: template BooleanSchema(const ValueType& value) : BaseSchema(value) {} + virtual SchemaType GetSchemaType() const { return kBooleanSchemaType; } + virtual bool Null() const { return false; } virtual bool Bool(bool b) const { return BaseSchema::Bool(b); } virtual bool Int(int) const { return false; } @@ -241,6 +316,8 @@ public: delete additionalPropertySchema_; } + virtual SchemaType GetSchemaType() const { return kObjectSchemaType; } + virtual bool Null() const { return false; } virtual bool Bool(bool) const { return false; } virtual bool Int(int) const { return false; } @@ -402,6 +479,8 @@ public: delete [] itemsTuple_; } + virtual SchemaType GetSchemaType() const { return kArraySchemaType; } + virtual bool BeginValue(Context& context) const { if (itemsList_) context.valueSchema = itemsList_; @@ -482,6 +561,8 @@ public: } } + virtual SchemaType GetSchemaType() const { return kStringSchemaType; } + virtual bool Null() const { return false; } virtual bool Bool(bool) const { return false; } virtual bool Int(int) const { return false; } @@ -568,6 +649,8 @@ public: } } + virtual SchemaType GetSchemaType() const { return kIntegerSchemaType; } + virtual bool Null() const { return false; } virtual bool Bool(bool) const { return false; } @@ -713,6 +796,8 @@ public: } } + virtual SchemaType GetSchemaType() const { return kNumberSchemaType; } + virtual bool Null() const { return false; } virtual bool Bool(bool) const { return false; } @@ -745,6 +830,18 @@ private: bool exclusiveMaximum_; }; +template +inline BaseSchema* CreateSchema(const ValueType& value, const ValueType& type) { + if (type == Value("null" ).Move()) return new NullSchema(value); + else if (type == Value("boolean").Move()) return new BooleanSchema(value); + else if (type == Value("object" ).Move()) return new ObjectSchema(value); + else if (type == Value("array" ).Move()) return new ArraySchema(value); + else if (type == Value("string" ).Move()) return new StringSchema(value); + else if (type == Value("integer").Move()) return new IntegerSchema(value); + else if (type == Value("number" ).Move()) return new NumberSchema(value); + else return 0; +} + template inline BaseSchema* CreateSchema(const ValueType& value) { if (!value.IsObject()) @@ -752,15 +849,9 @@ inline BaseSchema* CreateSchema(const ValueType& value) { typename ValueType::ConstMemberIterator typeItr = value.FindMember("type"); - if (typeItr == value.MemberEnd()) return new TypelessSchema(value); - else if (typeItr->value == Value("null" ).Move()) return new NullSchema(value); - else if (typeItr->value == Value("boolean").Move()) return new BooleanSchema(value); - else if (typeItr->value == Value("object" ).Move()) return new ObjectSchema(value); - else if (typeItr->value == Value("array" ).Move()) return new ArraySchema(value); - else if (typeItr->value == Value("string" ).Move()) return new StringSchema(value); - else if (typeItr->value == Value("integer").Move()) return new IntegerSchema(value); - else if (typeItr->value == Value("number" ).Move()) return new NumberSchema(value); - else return 0; + if (typeItr == value.MemberEnd()) return new TypelessSchema(value); + else if (typeItr->value.IsArray()) return new MultiTypeSchema(value, typeItr->value); + else return CreateSchema(value, typeItr->value); } template > @@ -790,10 +881,10 @@ template SchemaType; + typedef GenericSchema SchemaT; GenericSchemaValidator( - const Schema& schema, + const SchemaT& schema, Allocator* allocator = 0, size_t schemaStackCapacity = kDefaultSchemaStackCapacity, size_t documentStackCapacity = kDefaultDocumentStackCapacity) @@ -807,7 +898,7 @@ public: } GenericSchemaValidator( - const Schema& schema, + const SchemaT& schema, OutputHandler& outputHandler, Allocator* allocator = 0, size_t schemaStackCapacity = kDefaultSchemaStackCapacity, @@ -826,43 +917,50 @@ public: documentStack_.Clear(); }; - bool Null() { return BeginValue() && CurrentSchema().Null() && EndValue() && outputHandler_.Null(); } - bool Bool(bool b) { return BeginValue() && CurrentSchema().Bool(b) && EndValue() && outputHandler_.Bool(b); } - bool Int(int i) { return BeginValue() && CurrentSchema().Int(i) && EndValue() && outputHandler_.Int(i); } - bool Uint(unsigned u) { return BeginValue() && CurrentSchema().Uint(u) && EndValue() && outputHandler_.Uint(u); } - bool Int64(int64_t i64) { return BeginValue() && CurrentSchema().Int64(i64) && EndValue() && outputHandler_.Int64(i64); } - bool Uint64(uint64_t u64) { return BeginValue() && CurrentSchema().Uint64(u64) && EndValue() && outputHandler_.Uint64(u64); } - bool Double(double d) { return BeginValue() && CurrentSchema().Double(d) && EndValue() && outputHandler_.Double(d); } - bool String(const Ch* str, SizeType length, bool copy) { return BeginValue() && CurrentSchema().String(str, length, copy) && EndValue() && outputHandler_.String(str, length, copy); } + bool Null() { return BeginValue(kNullSchemaType) && CurrentSchema().Null() && EndValue() && outputHandler_.Null(); } + bool Bool(bool b) { return BeginValue(kBooleanSchemaType) && CurrentSchema().Bool(b) && EndValue() && outputHandler_.Bool(b); } + bool Int(int i) { return BeginValue(kIntegerSchemaType) && CurrentSchema().Int(i) && EndValue() && outputHandler_.Int(i); } + bool Uint(unsigned u) { return BeginValue(kIntegerSchemaType) && CurrentSchema().Uint(u) && EndValue() && outputHandler_.Uint(u); } + bool Int64(int64_t i64) { return BeginValue(kIntegerSchemaType) && CurrentSchema().Int64(i64) && EndValue() && outputHandler_.Int64(i64); } + bool Uint64(uint64_t u64) { return BeginValue(kIntegerSchemaType) && CurrentSchema().Uint64(u64) && EndValue() && outputHandler_.Uint64(u64); } + bool Double(double d) { return BeginValue(kNumberSchemaType) && CurrentSchema().Double(d) && EndValue() && outputHandler_.Double(d); } + bool String(const Ch* str, SizeType length, bool copy) { return BeginValue(kStringSchemaType) && CurrentSchema().String(str, length, copy) && EndValue() && outputHandler_.String(str, length, copy); } - bool StartObject() { return BeginValue() && CurrentSchema().StartObject(CurrentContext()) && outputHandler_.StartObject(); } + bool StartObject() { return BeginValue(kObjectSchemaType) && CurrentSchema().StartObject(CurrentContext()) && outputHandler_.StartObject(); } bool Key(const Ch* str, SizeType len, bool copy) { return CurrentSchema().Key(CurrentContext(), str, len, copy) && outputHandler_.Key(str, len, copy); } bool EndObject(SizeType memberCount) { return CurrentSchema().EndObject(CurrentContext(), memberCount) && EndValue() && outputHandler_.EndObject(memberCount); } - bool StartArray() { return BeginValue() && CurrentSchema().StartArray(CurrentContext()) ? outputHandler_.StartArray() : false; } + bool StartArray() { return BeginValue(kArraySchemaType) && CurrentSchema().StartArray(CurrentContext()) ? outputHandler_.StartArray() : false; } bool EndArray(SizeType elementCount) { return CurrentSchema().EndArray(CurrentContext(), elementCount) && EndValue() && outputHandler_.EndArray(elementCount); } private: typedef BaseSchema BaseSchemaType; typedef typename BaseSchemaType::Context Context; - bool BeginValue() { - if (schemaStack_.Empty()) { + bool BeginValue(SchemaType schemaType) { + if (schemaStack_.Empty()) PushSchema(*schema_.root_); - return true; - } else { if (!CurrentSchema().BeginValue(CurrentContext())) return false; if (CurrentContext().valueSchema) PushSchema(*CurrentContext().valueSchema); - return true; } + + if (!CurrentSchema().HandleMultiType(CurrentContext(), schemaType)) + return false; + + if (CurrentContext().multiTypeSchema) + PushSchema(*CurrentContext().multiTypeSchema); + + return true; } bool EndValue() { PopSchema(); + if (!schemaStack_.Empty() && CurrentContext().multiTypeSchema) + PopSchema(); return true; } @@ -873,7 +971,7 @@ private: static const size_t kDefaultSchemaStackCapacity = 256; static const size_t kDefaultDocumentStackCapacity = 256; - const SchemaType& schema_; + const SchemaT& schema_; BaseReaderHandler nullOutputHandler_; OutputHandler& outputHandler_; internal::Stack schemaStack_; //!< stack to store the current path of schema (BaseSchemaType *) diff --git a/test/unittest/schematest.cpp b/test/unittest/schematest.cpp index 5bad2c3..7c8f019 100644 --- a/test/unittest/schematest.cpp +++ b/test/unittest/schematest.cpp @@ -21,9 +21,10 @@ using namespace rapidjson; #define VALIDATE(schema, json, expected) \ {\ - EXPECT_TRUE(schema.IsValid());\ + ASSERT_TRUE(schema.IsValid());\ SchemaValidator validator(schema);\ Document d;\ + /*printf("\n%s\n", json);*/\ d.Parse(json);\ EXPECT_FALSE(d.HasParseError());\ if (expected)\ @@ -42,6 +43,16 @@ TEST(SchemaValidator, Typeless) { VALIDATE(s, "{ \"an\": [ \"arbitrarily\", \"nested\" ], \"data\": \"structure\" }", true); } +TEST(SchemaValidator, MultiType) { + Document sd; + sd.Parse("{ \"type\": [\"number\", \"string\"] }"); + Schema s(sd); + + VALIDATE(s, "42", true); + VALIDATE(s, "\"Life, the universe, and everything\"", true); + VALIDATE(s, "[\"Life\", \"the universe\", \"and everything\"]", false); +} + TEST(SchemaValidator, Enum_Typed) { Document sd; sd.Parse("{ \"type\": \"string\", \"enum\" : [\"red\", \"amber\", \"green\"] }"); @@ -426,6 +437,8 @@ TEST(SchemaValidator, Null) { VALIDATE(s, "\"\"", false); } +// Additional tests + TEST(SchemaValidator, ObjectInArray) { Document sd; sd.Parse("{\"type\":\"array\", \"items\": { \"type\":\"string\" }}"); @@ -434,4 +447,41 @@ TEST(SchemaValidator, ObjectInArray) { VALIDATE(s, "[\"a\"]", true); VALIDATE(s, "[1]", false); VALIDATE(s, "[{}]", false); -} \ No newline at end of file +} + +TEST(SchemaValidator, MultiTypeInObject) { + Document sd; + sd.Parse( + "{" + " \"type\":\"object\"," + " \"properties\": {" + " \"tel\" : {" + " \"type\":[\"integer\", \"string\"]" + " }" + " }" + "}"); + Schema s(sd); + + VALIDATE(s, "{ \"tel\": 999 }", true); + VALIDATE(s, "{ \"tel\": \"123-456\" }", true); + VALIDATE(s, "{ \"tel\": true }", false); +} + +TEST(SchemaValidator, MultiTypeWithObject) { + Document sd; + sd.Parse( + "{" + " \"type\": [\"object\",\"string\"]," + " \"properties\": {" + " \"tel\" : {" + " \"type\": \"integer\"" + " }" + " }" + "}"); + Schema s(sd); + + VALIDATE(s, "\"Hello\"", true); + VALIDATE(s, "{ \"tel\": 999 }", true); + VALIDATE(s, "{ \"tel\": \"fail\" }", false); +} +