Implement Multi-type
This commit is contained in:
parent
e0c26e44c0
commit
0713b8931d
@ -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 <typename Encoding>
|
||||
class BaseSchema;
|
||||
|
||||
template <typename Encoding>
|
||||
struct SchemaValidationContext {
|
||||
SchemaValidationContext(const BaseSchema<Encoding>* s) : schema(s), valueSchema() {}
|
||||
SchemaValidationContext(const BaseSchema<Encoding>* s) : schema(s), valueSchema(), multiTypeSchema() {}
|
||||
|
||||
~SchemaValidationContext() {}
|
||||
|
||||
const BaseSchema<Encoding>* schema;
|
||||
const BaseSchema<Encoding>* valueSchema;
|
||||
const BaseSchema<Encoding>* multiTypeSchema;
|
||||
SizeType objectRequiredCount;
|
||||
SizeType arrayElementIndex;
|
||||
};
|
||||
@ -50,8 +64,7 @@ public:
|
||||
BaseSchema() {}
|
||||
|
||||
template <typename ValueType>
|
||||
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<Encoding>().Move()); }
|
||||
@ -92,6 +108,9 @@ protected:
|
||||
GenericValue<Encoding> enum_;
|
||||
};
|
||||
|
||||
template <typename Encoding, typename ValueType>
|
||||
inline BaseSchema<Encoding>* CreateSchema(const ValueType& value, const ValueType& type);
|
||||
|
||||
template <typename Encoding, typename ValueType>
|
||||
inline BaseSchema<Encoding>* CreateSchema(const ValueType& value);
|
||||
|
||||
@ -105,9 +124,61 @@ public:
|
||||
template <typename ValueType>
|
||||
TypelessSchema(const ValueType& value) : BaseSchema<Encoding>(value) {}
|
||||
|
||||
virtual SchemaType GetSchemaType() const { return kTypelessSchemaType; }
|
||||
|
||||
virtual bool BeginValue(Context& context) const { context.valueSchema = this; return true; }
|
||||
};
|
||||
|
||||
template <typename Encoding>
|
||||
class MultiTypeSchema : public BaseSchema<Encoding> {
|
||||
public:
|
||||
typedef SchemaValidationContext<Encoding> Context;
|
||||
|
||||
template <typename ValueType>
|
||||
MultiTypeSchema(const ValueType& value, const ValueType& type) : BaseSchema<Encoding>(), typedSchemas_() {
|
||||
RAPIDJSON_ASSERT(type.IsArray());
|
||||
for (typename ValueType::ConstValueIterator itr = type.Begin(); itr != type.End(); ++itr) {
|
||||
if (itr->IsString()) {
|
||||
BaseSchema<Encoding>* schema = CreateSchema<Encoding, ValueType>(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<Encoding>* typedSchemas_[kTotalBasicSchemaType];
|
||||
};
|
||||
|
||||
template <typename Encoding>
|
||||
class NullSchema : public BaseSchema<Encoding> {
|
||||
public:
|
||||
@ -117,6 +188,8 @@ public:
|
||||
template <typename ValueType>
|
||||
NullSchema(const ValueType& value) : BaseSchema<Encoding>(value) {}
|
||||
|
||||
virtual SchemaType GetSchemaType() const { return kNullSchemaType; }
|
||||
|
||||
virtual bool Null() const { return BaseSchema<Encoding>::Null(); }
|
||||
virtual bool Bool(bool) const { return false; }
|
||||
virtual bool Int(int) const { return false; }
|
||||
@ -141,6 +214,8 @@ public:
|
||||
template <typename ValueType>
|
||||
BooleanSchema(const ValueType& value) : BaseSchema<Encoding>(value) {}
|
||||
|
||||
virtual SchemaType GetSchemaType() const { return kBooleanSchemaType; }
|
||||
|
||||
virtual bool Null() const { return false; }
|
||||
virtual bool Bool(bool b) const { return BaseSchema<Encoding>::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 <typename Encoding, typename ValueType>
|
||||
inline BaseSchema<Encoding>* CreateSchema(const ValueType& value, const ValueType& type) {
|
||||
if (type == Value("null" ).Move()) return new NullSchema<Encoding>(value);
|
||||
else if (type == Value("boolean").Move()) return new BooleanSchema<Encoding>(value);
|
||||
else if (type == Value("object" ).Move()) return new ObjectSchema<Encoding>(value);
|
||||
else if (type == Value("array" ).Move()) return new ArraySchema<Encoding>(value);
|
||||
else if (type == Value("string" ).Move()) return new StringSchema<Encoding>(value);
|
||||
else if (type == Value("integer").Move()) return new IntegerSchema<Encoding>(value);
|
||||
else if (type == Value("number" ).Move()) return new NumberSchema<Encoding>(value);
|
||||
else return 0;
|
||||
}
|
||||
|
||||
template <typename Encoding, typename ValueType>
|
||||
inline BaseSchema<Encoding>* CreateSchema(const ValueType& value) {
|
||||
if (!value.IsObject())
|
||||
@ -752,15 +849,9 @@ inline BaseSchema<Encoding>* CreateSchema(const ValueType& value) {
|
||||
|
||||
typename ValueType::ConstMemberIterator typeItr = value.FindMember("type");
|
||||
|
||||
if (typeItr == value.MemberEnd()) return new TypelessSchema<Encoding>(value);
|
||||
else if (typeItr->value == Value("null" ).Move()) return new NullSchema<Encoding>(value);
|
||||
else if (typeItr->value == Value("boolean").Move()) return new BooleanSchema<Encoding>(value);
|
||||
else if (typeItr->value == Value("object" ).Move()) return new ObjectSchema<Encoding>(value);
|
||||
else if (typeItr->value == Value("array" ).Move()) return new ArraySchema<Encoding>(value);
|
||||
else if (typeItr->value == Value("string" ).Move()) return new StringSchema<Encoding>(value);
|
||||
else if (typeItr->value == Value("integer").Move()) return new IntegerSchema<Encoding>(value);
|
||||
else if (typeItr->value == Value("number" ).Move()) return new NumberSchema<Encoding>(value);
|
||||
else return 0;
|
||||
if (typeItr == value.MemberEnd()) return new TypelessSchema<Encoding>(value);
|
||||
else if (typeItr->value.IsArray()) return new MultiTypeSchema<Encoding>(value, typeItr->value);
|
||||
else return CreateSchema<Encoding, ValueType>(value, typeItr->value);
|
||||
}
|
||||
|
||||
template <typename Encoding, typename Allocator = MemoryPoolAllocator<> >
|
||||
@ -790,10 +881,10 @@ template <typename Encoding, typename OutputHandler = BaseReaderHandler<Encoding
|
||||
class GenericSchemaValidator {
|
||||
public:
|
||||
typedef typename Encoding::Ch Ch; //!< Character type derived from Encoding.
|
||||
typedef GenericSchema<Encoding> SchemaType;
|
||||
typedef GenericSchema<Encoding> 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<Encoding> 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<Encoding> nullOutputHandler_;
|
||||
OutputHandler& outputHandler_;
|
||||
internal::Stack<Allocator> schemaStack_; //!< stack to store the current path of schema (BaseSchemaType *)
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user