From c1bcccb16a5d9f92659dcbb0af0a574de49d0e14 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Fri, 1 May 2015 17:59:31 +0800 Subject: [PATCH] Very basic JSON schema implementation --- include/rapidjson/schema.h | 834 +++++++++++++++++++++++++++++++++++ test/unittest/CMakeLists.txt | 1 + test/unittest/schematest.cpp | 394 +++++++++++++++++ 3 files changed, 1229 insertions(+) create mode 100644 include/rapidjson/schema.h create mode 100644 test/unittest/schematest.cpp diff --git a/include/rapidjson/schema.h b/include/rapidjson/schema.h new file mode 100644 index 0000000..cd24989 --- /dev/null +++ b/include/rapidjson/schema.h @@ -0,0 +1,834 @@ +// Tencent is pleased to support the open source community by making RapidJSON available-> +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip-> All rights reserved-> +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License-> You may obtain a copy of the License at +// +// http://opensource->org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied-> See the License for the +// specific language governing permissions and limitations under the License-> + +#ifndef RAPIDJSON_SCHEMA_H_ +#define RAPIDJSON_SCHEMA_H_ + +#include "document.h" +#include // HUGE_VAL, fmod + +RAPIDJSON_NAMESPACE_BEGIN + +template +class BaseSchema; + +template +struct SchemaValidationContext { + SchemaValidationContext(const BaseSchema* s) : schema(s), valueSchema() {} + + ~SchemaValidationContext() {} + + const BaseSchema* schema; + const BaseSchema* valueSchema; + SizeType objectRequiredCount; + SizeType arrayElementIndex; +}; + +template +class BaseSchema { +public: + typedef typename Encoding::Ch Ch; //!< Character type derived from Encoding. + typedef SchemaValidationContext Context; + + BaseSchema() {} + + template + BaseSchema(const ValueType& value) + { + ValueType::ConstMemberIterator enumItr = value.FindMember("enum"); + if (enumItr != value.MemberEnd()) { + if (enumItr->value.IsArray() && enumItr->value.Size() > 0) + enum_.CopyFrom(enumItr->value, allocator_); + else { + // Error + } + } + } + + virtual ~BaseSchema() {} + + virtual void BeginValue(Context& context) const {} + + virtual bool Null() const { return enum_.IsArray() ? CheckEnum(GenericValue()) : true; } + virtual bool Bool(bool b) const { return enum_.IsArray() ? CheckEnum(GenericValue(b)) : true; } + virtual bool Int(int i) const { return enum_.IsArray() ? CheckEnum(GenericValue(i)) : true; } + virtual bool Uint(unsigned u) const { return enum_.IsArray() ? CheckEnum(GenericValue(u)) : true; } + virtual bool Int64(int64_t i) const { return enum_.IsArray() ? CheckEnum(GenericValue(i)) : true; } + virtual bool Uint64(uint64_t u) const { return enum_.IsArray() ? CheckEnum(GenericValue(u)) : true; } + virtual bool Double(double d) const { return enum_.IsArray() ? CheckEnum(GenericValue(d)) : true; } + virtual bool String(const Ch* s, SizeType length, bool) const { return enum_.IsArray() ? CheckEnum(GenericValue(s, length)) : true; } + virtual bool StartObject(Context&) const { return true; } + virtual bool Key(Context&, const Ch*, SizeType, bool) const { return true; } + virtual bool EndObject(Context&, SizeType) const { return true; } + virtual bool StartArray(Context&) const { return true; } + virtual bool EndArray(Context&, SizeType) const { return true; } + +protected: + bool CheckEnum(const GenericValue& v) const { + for (GenericValue::ConstValueIterator itr = enum_.Begin(); itr != enum_.End(); ++itr) + if (v == *itr) + return true; + return false; + } + + MemoryPoolAllocator<> allocator_; + GenericValue enum_; +}; + +template +inline BaseSchema* CreateSchema(const ValueType& value) { + if (!value.IsObject()) + return 0; + + ValueType::ConstMemberIterator typeItr = value.FindMember("type"); + + if (typeItr == value.MemberEnd()) return new TypelessSchema(value); + else if (typeItr->value == Value("null")) return new NullSchema(value); + else if (typeItr->value == Value("boolean")) return new BooleanSchema(value); + else if (typeItr->value == Value("object")) return new ObjectSchema(value); + else if (typeItr->value == Value("array")) return new ArraySchema(value); + else if (typeItr->value == Value("string")) return new StringSchema(value); + else if (typeItr->value == Value("integer")) return new IntegerSchema(value); + else if (typeItr->value == Value("number")) return new NumberSchema(value); + else return 0; +} + +template +class TypelessSchema : public BaseSchema { +public: + TypelessSchema() {} + + template + TypelessSchema(const ValueType& value) : BaseSchema(value) {} + + virtual void BeginValue(Context& context) const { context.valueSchema = this; } +}; + +template +class NullSchema : public BaseSchema { +public: + template + NullSchema(const ValueType& value) : BaseSchema(value) {} + + virtual bool Null() const { return BaseSchema::Null(); } + virtual bool Bool(bool) const { return false; } + virtual bool Int(int) const { return false; } + virtual bool Uint(unsigned) const { return false; } + virtual bool Int64(int64_t) const { return false; } + virtual bool Uint64(uint64_t) const { return false; } + virtual bool Double(double) const { return false; } + virtual bool String(const Ch*, SizeType, bool) const { return false; } + virtual bool StartObject(Context&) const { return false; } + virtual bool Key(Context&, const Ch*, SizeType, bool) const { return false; } + virtual bool EndObject(Context&, SizeType) const { return false; } + virtual bool StartArray(Context&) const { return false; } + virtual bool EndArray(Context&, SizeType) const { return false; } +}; + +template +class BooleanSchema : public BaseSchema { +public: + template + BooleanSchema(const ValueType& value) : BaseSchema(value) {} + + virtual bool Null() const { return false; } + virtual bool Bool(bool b) const { return BaseSchema::Bool(b); } + virtual bool Int(int) const { return false; } + virtual bool Uint(unsigned) const { return false; } + virtual bool Int64(int64_t) const { return false; } + virtual bool Uint64(uint64_t) const { return false; } + virtual bool Double(double) const { return false; } + virtual bool String(const Ch*, SizeType, bool) const { return false; } +}; + +template +class ObjectSchema : public BaseSchema { +public: + template + ObjectSchema(const ValueType& value) : + BaseSchema(value), + properties_(), + additionalPropertySchema_(), + propertyCount_(), + requiredCount_(), + minProperties_(), + maxProperties_(SizeType(~0)), + additionalProperty_(true) + { + ValueType::ConstMemberIterator propretiesItr = value.FindMember(Value("properties")); + if (propretiesItr != value.MemberEnd()) { + const ValueType& properties = propretiesItr->value; + properties_ = new Property[properties.MemberCount()]; + propertyCount_ = 0; + + for (ValueType::ConstMemberIterator propertyItr = properties.MemberBegin(); propertyItr != properties.MemberEnd(); ++propertyItr) { + properties_[propertyCount_].name.SetString(propertyItr->name.GetString(), propertyItr->name.GetStringLength(), allocator_); + properties_[propertyCount_].schema = CreateSchema(propertyItr->value); // TODO: Check error + propertyCount_++; + } + } + + // Establish required after properties + ValueType::ConstMemberIterator requiredItr = value.FindMember(Value("required")); + if (requiredItr != value.MemberEnd()) { + if (requiredItr->value.IsArray()) { + for (ValueType::ConstValueIterator itr = requiredItr->value.Begin(); itr != requiredItr->value.End(); ++itr) { + if (itr->IsString()) { + SizeType index; + if (FindPropertyIndex(*itr, &index)) { + properties_[index].required = true; + requiredCount_++; + } + } + } + + if (requiredCount_ != requiredItr->value.Size()) { + // Error + } + } + } + + ValueType::ConstMemberIterator additionalPropretiesItr = value.FindMember(Value("additionalProperties")); + if (additionalPropretiesItr != value.MemberEnd()) { + if (additionalPropretiesItr->value.IsBool()) + additionalProperty_ = additionalPropretiesItr->value.GetBool(); + else if (additionalPropretiesItr->value.IsObject()) + additionalPropertySchema_ = CreateSchema(additionalPropretiesItr->value); + else { + // Error + } + } + + ValueType::ConstMemberIterator minPropertiesItr = value.FindMember(Value("minProperties")); + if (minPropertiesItr != value.MemberEnd()) { + if (minPropertiesItr->value.IsUint64() && minPropertiesItr->value.GetUint64() <= SizeType(~0)) + minProperties_ = static_cast(minPropertiesItr->value.GetUint64()); + else { + // Error + } + } + + ValueType::ConstMemberIterator maxPropertiesItr = value.FindMember(Value("maxProperties")); + if (maxPropertiesItr != value.MemberEnd()) { + if (maxPropertiesItr->value.IsUint64() && maxPropertiesItr->value.GetUint64() <= SizeType(~0)) + maxProperties_ = static_cast(maxPropertiesItr->value.GetUint64()); + else { + // Error + } + } + } + + ~ObjectSchema() { + delete [] properties_; + delete additionalPropertySchema_; + } + + virtual bool Null() const { return false; } + virtual bool Bool(bool) const { return false; } + virtual bool Int(int) const { return false; } + virtual bool Uint(unsigned) const { return false; } + virtual bool Int64(int64_t) const { return false; } + virtual bool Uint64(uint64_t) const { return false; } + virtual bool Double(double) const { return false; } + virtual bool String(const Ch*, SizeType, bool) const { return false; } + + virtual bool StartObject(Context& context) const { + context.objectRequiredCount = 0; + return true; + } + + virtual bool Key(Context& context, const Ch* str, SizeType len, bool copy) const { + SizeType index; + if (FindPropertyIndex(str, len, &index)) { + context.valueSchema = properties_[index].schema; + + if (properties_[index].required) + context.objectRequiredCount++; + + return true; + } + + if (additionalPropertySchema_) { + context.valueSchema = additionalPropertySchema_; + return true; + } + else if (additionalProperty_) { + context.valueSchema = &typeless_; + return true; + } + else + return false; + } + + virtual bool EndObject(Context& context, SizeType memberCount) const { + return context.objectRequiredCount == requiredCount_ && + memberCount >= minProperties_ && + memberCount <= maxProperties_; + } + + virtual bool StartArray(Context&) const { return false; } + virtual bool EndArray(Context&, SizeType) const { return false; } + +private: + // O(n) + template + bool FindPropertyIndex(const ValueType& name, SizeType* outIndex) const { + for (SizeType index = 0; index < propertyCount_; index++) { + if (properties_[index].name == name) { + *outIndex = index; + return true; + } + } + return false; + } + + // O(n) + bool FindPropertyIndex(const Ch* str, SizeType length, SizeType* outIndex) const { + for (SizeType index = 0; index < propertyCount_; index++) { + if (properties_[index].name.GetStringLength() == length && + std::memcmp(properties_[index].name.GetString(), str, length) == 0) + { + *outIndex = index; + return true; + } + } + return false; + } + + struct Property { + Property() : schema(), required(false) {} + ~Property() { + delete schema; + } + + GenericValue name; + BaseSchema* schema; + bool required; + }; + + TypelessSchema typeless_; + Property* properties_; + BaseSchema* additionalPropertySchema_; + SizeType propertyCount_; + SizeType requiredCount_; + SizeType minProperties_; + SizeType maxProperties_; + bool additionalProperty_; +}; + +template +class ArraySchema : public BaseSchema { +public: + template + ArraySchema(const ValueType& value) : + BaseSchema(value), + itemsList_(), + itemsTuple_(), + itemsTupleCount_(), + minItems_(), + maxItems_(SizeType(~0)) + { + ValueType::ConstMemberIterator itemsItr = value.FindMember(Value("items")); + if (itemsItr != value.MemberEnd()) { + if (itemsItr->value.IsObject()) + itemsList_ = CreateSchema(itemsItr->value); // List validation + else if (itemsItr->value.IsArray()) { + // Tuple validation + itemsTuple_ = new BaseSchema*[itemsItr->value.Size()]; + for (ValueType::ConstValueIterator itr = itemsItr->value.Begin(); itr != itemsItr->value.End(); ++itr) { + itemsTuple_[itemsTupleCount_] = CreateSchema(*itr); + itemsTupleCount_++; + } + } + else { + // Error + } + } + + ValueType::ConstMemberIterator minItemsItr = value.FindMember(Value("minItems")); + if (minItemsItr != value.MemberEnd()) { + if (minItemsItr->value.IsUint64() && minItemsItr->value.GetUint64() <= SizeType(~0)) + minItems_ = static_cast(minItemsItr->value.GetUint64()); + else { + // Error + } + } + + ValueType::ConstMemberIterator maxItemsItr = value.FindMember(Value("maxItems")); + if (maxItemsItr != value.MemberEnd()) { + if (maxItemsItr->value.IsUint64() && maxItemsItr->value.GetUint64() <= SizeType(~0)) + maxItems_ = static_cast(maxItemsItr->value.GetUint64()); + else { + // Error + } + } + } + + ~ArraySchema() { + delete itemsList_; + for (SizeType i = 0; i < itemsTupleCount_; i++) + delete itemsTuple_[i]; + delete itemsTuple_; + } + + virtual void BeginValue(Context& context) const { + if (itemsList_) + context.valueSchema = itemsList_; + else if (itemsTuple_ && context.arrayElementIndex < itemsTupleCount_) + context.valueSchema = itemsTuple_[context.arrayElementIndex]; + else + context.valueSchema = &typeless_; + + context.arrayElementIndex++; + } + + virtual bool Null() const { return false; } + virtual bool Bool(bool) const { return false; } + virtual bool Int(int) const { return false; } + virtual bool Uint(unsigned) const { return false; } + virtual bool Int64(int64_t) const { return false; } + virtual bool Uint64(uint64_t) const { return false; } + virtual bool Double(double) const { return false; } + virtual bool String(const Ch*, SizeType, bool) const { return false; } + virtual bool StartObject(Context&) const { return false; } + virtual bool Key(Context&, const Ch*, SizeType, bool) const { return false; } + virtual bool EndObject(Context&, SizeType) const { return false; } + + virtual bool StartArray(Context& context) const { + context.arrayElementIndex = 0; + return true; + } + + virtual bool EndArray(Context&, SizeType elementCount) const { + return elementCount >= minItems_ && elementCount <= maxItems_; + } + +private: + TypelessSchema typeless_; + BaseSchema* itemsList_; + BaseSchema** itemsTuple_; + SizeType itemsTupleCount_; + SizeType minItems_; + SizeType maxItems_; +}; + +template +class StringSchema : public BaseSchema { +public: + template + StringSchema(const ValueType& value) : + BaseSchema(value), + minLength_(0), + maxLength_(~SizeType(0)) + { + ValueType::ConstMemberIterator minLengthItr = value.FindMember(Value("minLength")); + if (minLengthItr != value.MemberEnd()) { + if (minLengthItr->value.IsUint64() && minLengthItr->value.GetUint64() <= ~SizeType(0)) + minLength_ = static_cast(minLengthItr->value.GetUint64()); + else { + // Error + } + } + + ValueType::ConstMemberIterator maxLengthItr = value.FindMember(Value("maxLength")); + if (maxLengthItr != value.MemberEnd()) { + if (maxLengthItr->value.IsUint64() && maxLengthItr->value.GetUint64() <= ~SizeType(0)) + maxLength_ = static_cast(maxLengthItr->value.GetUint64()); + else { + // Error + } + } + } + + virtual bool Null() const { return false; } + virtual bool Bool(bool) const { return false; } + virtual bool Int(int) const { return false; } + virtual bool Uint(unsigned) const { return false; } + virtual bool Int64(int64_t) const { return false; } + virtual bool Uint64(uint64_t) const { return false; } + virtual bool Double(double) const { return false; } + + virtual bool String(const Ch* str, SizeType length, bool copy) const { + return BaseSchema::String(str, length, copy) && length >= minLength_ && length <= maxLength_; + } + + virtual bool StartArray(Context&) const { return true; } + virtual bool EndArray(Context&, SizeType) const { return true; } + +private: + SizeType minLength_; + SizeType maxLength_; +}; + +template +class IntegerSchema : public BaseSchema { +public: + template + IntegerSchema(const ValueType& value) : + BaseSchema(value), + multipleOf_(0), + exclusiveMinimum_(false), + exclusiveMaximum_(false) + { + ValueType::ConstMemberIterator minimumItr = value.FindMember(Value("minimum")); + if (minimumItr != value.MemberEnd()) { + if (minimumItr->value.IsInt64()) + minimum_.SetInt64(minimumItr->value.GetInt64()); + else if (minimumItr->value.IsUint64()) + minimum_.SetUint64(minimumItr->value.GetUint64()); + else { + // Error + } + } + + ValueType::ConstMemberIterator maximumItr = value.FindMember(Value("maximum")); + if (maximumItr != value.MemberEnd()) { + if (maximumItr->value.IsInt64()) + maximum_.SetInt64(maximumItr->value.GetInt64()); + else if (maximumItr->value.IsUint64()) + maximum_.SetUint64(maximumItr->value.GetUint64()); + else { + // Error + } + } + + ValueType::ConstMemberIterator exclusiveMinimumItr = value.FindMember(Value("exclusiveMinimum")); + if (exclusiveMinimumItr != value.MemberEnd()) { + if (exclusiveMinimumItr->value.IsBool()) + exclusiveMinimum_ = exclusiveMinimumItr->value.GetBool(); + else { + // Error + } + } + + ValueType::ConstMemberIterator exclusiveMaximumItr = value.FindMember(Value("exclusiveMaximum")); + if (exclusiveMaximumItr != value.MemberEnd()) { + if (exclusiveMaximumItr->value.IsBool()) + exclusiveMaximum_ = exclusiveMaximumItr->value.GetBool(); + else { + // Error + } + } + + ValueType::ConstMemberIterator multipleOfItr = value.FindMember(Value("multipleOf")); + if (multipleOfItr != value.MemberEnd()) { + if (multipleOfItr->value.IsUint64()) + multipleOf_ = multipleOfItr->value.GetUint64(); + else { + // Error + } + } + } + + virtual bool Null() const { return false; } + virtual bool Bool(bool) const { return false; } + + virtual bool Int(int i) const { return BaseSchema::Int64(i) && Int64(i); } + virtual bool Uint(unsigned u) const { return BaseSchema::Uint64(u) && Uint64(u); } + virtual bool Int64(int64_t i) const { return BaseSchema::Int64(i) && CheckInt64(i); } + virtual bool Uint64(uint64_t u) const { return BaseSchema::Uint64(u) && CheckUint64(u); } + + virtual bool Double(double) const { return false; } + virtual bool String(const Ch*, SizeType, bool) const { return false; } + virtual bool StartObject(Context&) const { return false; } + virtual bool Key(Context&, const Ch*, SizeType, bool) const { return false; } + virtual bool EndObject(Context&, SizeType) const { return false; } + virtual bool StartArray(Context&) const { return false; } + virtual bool EndArray(Context&, SizeType) const { return false; } + +private: + bool CheckInt64(int64_t i) const { + if (!minimum_.IsNull()) { + if (minimum_.IsInt64()) { + if (exclusiveMinimum_ ? i <= minimum_.GetInt64() : i < minimum_.GetInt64()) + return false; + } + else { + RAPIDJSON_ASSERT(minimum_.IsUint64()); + if (i < 0 || (exclusiveMinimum_ ? static_cast(i) <= minimum_.GetUint64() : static_cast(i) < minimum_.GetUint64())) + return false; + } + } + + if (!maximum_.IsNull()) { + if (maximum_.IsInt64()) { + if (exclusiveMaximum_ ? i >= maximum_.GetInt64() : i > maximum_.GetInt64()) + return false; + } + else { + RAPIDJSON_ASSERT(maximum_.IsUint64()); + if (i >= 0 && (exclusiveMaximum_ ? static_cast(i) >= maximum_.GetUint64() : static_cast(i) < maximum_.GetUint64())) + return false; + } + } + + if (multipleOf_ != 0 && i % multipleOf_ != 0) + return false; + + return true; + } + + bool CheckUint64(uint64_t u) const { + if (!minimum_.IsNull()) { + if (minimum_.IsUint64()) { + if (exclusiveMinimum_ ? u <= minimum_.GetUint64() : u < minimum_.GetUint64()) + return false; + } + RAPIDJSON_ASSERT(minimum_.IsInt64() && minimum_.GetInt64() < 0); // In this case always valid + } + + if (!maximum_.IsNull()) { + if (maximum_.IsUint64()) { + if (exclusiveMaximum_ ? u >= maximum_.GetUint64() : u > maximum_.GetUint64()) + return false; + } + else { + RAPIDJSON_ASSERT(maximum_.IsInt64() && minimum_.GetInt64() < 0); // In this case always invalid + return false; + } + } + + if (multipleOf_ != 0 && u % multipleOf_ != 0) + return false; + + return true; + } + + GenericValue minimum_; // Null means not specified + GenericValue maximum_; // Null means not specified + uint64_t multipleOf_; // 0 means not specified + bool exclusiveMinimum_; + bool exclusiveMaximum_; +}; + +template +class NumberSchema : public BaseSchema { +public: + template + NumberSchema(const ValueType& value) : + BaseSchema(value), + minimum_(-HUGE_VAL), + maximum_(HUGE_VAL), + multipleOf_(0), + hasMultipleOf_(false), + exclusiveMinimum_(false), + exclusiveMaximum_(false) + { + ValueType::ConstMemberIterator minimumItr = value.FindMember(Value("minimum")); + if (minimumItr != value.MemberEnd()) { + if (minimumItr->value.IsNumber()) + minimum_ = minimumItr->value.GetDouble(); + else { + // Error + } + } + + ValueType::ConstMemberIterator maximumItr = value.FindMember(Value("maximum")); + if (maximumItr != value.MemberEnd()) { + if (maximumItr->value.IsNumber()) + maximum_ = maximumItr->value.GetDouble(); + else { + // Error + } + } + + ValueType::ConstMemberIterator exclusiveMinimumItr = value.FindMember(Value("exclusiveMinimum")); + if (exclusiveMinimumItr != value.MemberEnd()) { + if (exclusiveMinimumItr->value.IsBool()) + exclusiveMinimum_ = exclusiveMinimumItr->value.GetBool(); + else { + // Error + } + } + + ValueType::ConstMemberIterator exclusiveMaximumItr = value.FindMember(Value("exclusiveMaximum")); + if (exclusiveMaximumItr != value.MemberEnd()) { + if (exclusiveMaximumItr->value.IsBool()) + exclusiveMaximum_ = exclusiveMaximumItr->value.GetBool(); + else { + // Error + } + } + + ValueType::ConstMemberIterator multipleOfItr = value.FindMember(Value("multipleOf")); + if (multipleOfItr != value.MemberEnd()) { + if (multipleOfItr->value.IsNumber()) { + multipleOf_ = multipleOfItr->value.GetDouble(); + hasMultipleOf_ = true; + } + else { + // Error + } + } + } + + virtual bool Null() const { return false; } + virtual bool Bool(bool) const { return false; } + + virtual bool Int(int i) const { return BaseSchema::Int(i) && CheckDouble(i); } + virtual bool Uint(unsigned u) const { return BaseSchema::Uint(u) && CheckDouble(u); } + virtual bool Int64(int64_t i) const { return BaseSchema::Int64(i) && CheckDouble(i); } + virtual bool Uint64(uint64_t u) const { return BaseSchema::Uint64(u) && CheckDouble(u); } + virtual bool Double(double d) const { return BaseSchema::Double(d) && CheckDouble(d); } + + virtual bool String(const Ch*, SizeType, bool) const { return false; } + virtual bool StartObject(Context&) const { return false; } + virtual bool Key(Context&, const Ch*, SizeType, bool) const { return false; } + virtual bool EndObject(Context&, SizeType) const { return false; } + virtual bool StartArray(Context&) const { return false; } + virtual bool EndArray(Context&, SizeType) const { return false; } + +private: + bool CheckDouble(double d) const { + if (exclusiveMinimum_ ? d <= minimum_ : d < minimum_) return false; + if (exclusiveMaximum_ ? d >= maximum_ : d > maximum_) return false; + if (hasMultipleOf_ && std::fmod(d, multipleOf_) != 0.0) return false; + return true; + } + + double minimum_; + double maximum_; + double multipleOf_; + bool hasMultipleOf_; + bool exclusiveMinimum_; + bool exclusiveMaximum_; +}; + +template > +class GenericSchema { +public: + template + friend class GenericSchemaValidator; + + template + GenericSchema(const DocumentType& document) : root_() { + root_ = CreateSchema(document); + } + + ~GenericSchema() { + delete root_; + } + + bool IsValid() const { return root_ != 0; } + +private: + BaseSchema* root_; +}; + +typedef GenericSchema > Schema; + +template , typename Allocator = CrtAllocator > +class GenericSchemaValidator { +public: + typedef typename Encoding::Ch Ch; //!< Character type derived from Encoding. + + GenericSchemaValidator( + const Schema& schema, + Allocator* allocator = 0, + size_t schemaStackCapacity = kDefaultSchemaStackCapacity, + size_t documentStackCapacity = kDefaultDocumentStackCapacity) + : + schema_(schema), + outputHandler_(nullOutputHandler_), + schemaStack_(allocator, schemaStackCapacity), + documentStack_(allocator, documentStackCapacity) + { + Reset(); + } + + GenericSchemaValidator( + const Schema& schema, + OutputHandler& outputHandler, + Allocator* allocator = 0, + size_t schemaStackCapacity = kDefaultSchemaStackCapacity, + size_t documentStackCapacity = kDefaultDocumentStackCapacity) + : + schema_(schema), + outputHandler_(outputHandler), + schemaStack_(allocator, schemaStackCapacity), + documentStack_(allocator, documentStackCapacity) + { + Reset(); + } + + void Reset() { + schemaStack_.Clear(); + documentStack_.Clear(); + }; + + bool Null() { BeginValue(); return PopSchema().Null() ? outputHandler_.Null() : false; } + bool Bool(bool b) { BeginValue(); return PopSchema().Bool(b) ? outputHandler_.Bool(b) : false; } + bool Int(int i) { BeginValue(); return PopSchema().Int(i) ? outputHandler_.Int(i) : false; } + bool Uint(unsigned u) { BeginValue(); return PopSchema().Uint(u) ? outputHandler_.Uint(u) : false; } + bool Int64(int64_t i64) { BeginValue(); return PopSchema().Int64(i64) ? outputHandler_.Int64(i64) : false; } + bool Uint64(uint64_t u64) { BeginValue(); return PopSchema().Uint64(u64) ? outputHandler_.Uint64(u64) : false; } + bool Double(double d) { BeginValue(); return PopSchema().Double(d) ? outputHandler_.Double(d) : false; } + bool String(const Ch* str, SizeType length, bool copy) { BeginValue(); return PopSchema().String(str, length, copy) ? outputHandler_.String(str, length, copy) : false; } + bool StartObject() { BeginValue(); return CurrentSchema().StartObject(CurrentContext()) ? outputHandler_.StartObject() : false; } + bool Key(const Ch* str, SizeType len, bool copy) { return CurrentSchema().Key(CurrentContext(), str, len, copy) ? outputHandler_.Key(str, len, copy) : false; } + + bool EndObject(SizeType memberCount) { + if (CurrentSchema().EndObject(CurrentContext(), memberCount)) { + PopSchema(); + return outputHandler_.EndObject(memberCount); + } + else + return false; + } + + bool StartArray() { BeginValue(); return CurrentSchema().StartArray(CurrentContext()) ? outputHandler_.StartArray(): false; } + + bool EndArray(SizeType elementCount) { + if (CurrentSchema().EndArray(CurrentContext(), elementCount)) { + PopSchema(); + return outputHandler_.EndArray(elementCount); + } + else + return false; + } + +private: + typedef BaseSchema BaseSchemaType; + typedef typename BaseSchemaType::Context Context; + + void BeginValue() { + if (schemaStack_.Empty()) { + PushSchema(*schema_.root_); + } + else { + CurrentSchema().BeginValue(CurrentContext()); + if (CurrentContext().valueSchema) + PushSchema(*CurrentContext().valueSchema); + } + } + + void PushSchema(const BaseSchemaType& schema) { *schemaStack_.template Push() = Context(&schema); } + const BaseSchemaType& PopSchema() { return *schemaStack_.template Pop(1)->schema; } + const BaseSchemaType& CurrentSchema() { return *schemaStack_.Top()->schema; } + Context& CurrentContext() { return *schemaStack_.Top(); } + + static const size_t kDefaultSchemaStackCapacity = 256; + static const size_t kDefaultDocumentStackCapacity = 256; + const Schema& schema_; + BaseReaderHandler nullOutputHandler_; + OutputHandler& outputHandler_; + internal::Stack schemaStack_; //!< stack to store the current path of schema (BaseSchemaType *) + internal::Stack documentStack_; //!< stack to store the current path of validating document (Value *) +}; + +typedef GenericSchemaValidator > SchemaValidator; + +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_SCHEMA_H_ diff --git a/test/unittest/CMakeLists.txt b/test/unittest/CMakeLists.txt index bcc16d9..6c151c3 100644 --- a/test/unittest/CMakeLists.txt +++ b/test/unittest/CMakeLists.txt @@ -10,6 +10,7 @@ set(UNITTEST_SOURCES namespacetest.cpp prettywritertest.cpp readertest.cpp + schematest.cpp simdtest.cpp stringbuffertest.cpp strtodtest.cpp diff --git a/test/unittest/schematest.cpp b/test/unittest/schematest.cpp new file mode 100644 index 0000000..1709bf9 --- /dev/null +++ b/test/unittest/schematest.cpp @@ -0,0 +1,394 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#include "unittest.h" +#include "rapidjson/schema.h" + +using namespace rapidjson; + +// Test cases following http://spacetelescope.github.io/understanding-json-schema + +#define VALIDATE(schema, json, expected) \ +{\ + EXPECT_TRUE(schema.IsValid());\ + SchemaValidator validator(schema);\ + Document d;\ + d.Parse(json);\ + EXPECT_FALSE(d.HasParseError());\ + EXPECT_EQ(expected, d.Accept(validator));\ +} + +TEST(SchemaValidator, Typeless) { + Document sd; + sd.Parse("{}"); + Schema s(sd); + + VALIDATE(s, "42", true); + VALIDATE(s, "\"I'm a string\"", true); + VALIDATE(s, "{ \"an\": [ \"arbitrarily\", \"nested\" ], \"data\": \"structure\" }", true); +} + +TEST(SchemaValidator, Enum_Typed) { + Document sd; + sd.Parse("{ \"type\": \"string\", \"enum\" : [\"red\", \"amber\", \"green\"] }"); + Schema s(sd); + + VALIDATE(s, "\"red\"", true); + VALIDATE(s, "\"blue\"", false); +} + +TEST(SchemaValidator, Enum_Typless) { + Document sd; + sd.Parse("{ \"enum\": [\"red\", \"amber\", \"green\", null, 42] }"); + Schema s(sd); + + VALIDATE(s, "\"red\"", true); + VALIDATE(s, "null", true); + VALIDATE(s, "42", true); + VALIDATE(s, "0", false); +} + +TEST(SchemaValidator, Enum_InvalidType) { + Document sd; + sd.Parse("{ \"type\": \"string\", \"enum\": [\"red\", \"amber\", \"green\", null] }"); + Schema s(sd); + + VALIDATE(s, "\"red\"", true); + VALIDATE(s, "null", false); +} + +TEST(SchemaValidator, String) { + Document sd; + sd.Parse("{\"type\":\"string\"}"); + Schema s(sd); + + VALIDATE(s, "\"I'm a string\"", true); + VALIDATE(s, "42", false); +} + +TEST(SchemaValidator, String_LengthRange) { + Document sd; + sd.Parse("{\"type\":\"string\",\"minLength\":2,\"maxLength\":3}"); + Schema s(sd); + + VALIDATE(s, "\"A\"", false); + VALIDATE(s, "\"AB\"", true); + VALIDATE(s, "\"ABC\"", true); + VALIDATE(s, "\"ABCD\"", false); +} + +TEST(SchemaValidator, Integer) { + Document sd; + sd.Parse("{\"type\":\"integer\"}"); + Schema s(sd); + + VALIDATE(s, "42", true); + VALIDATE(s, "-1", true); + VALIDATE(s, "3.1415926", false); + VALIDATE(s, "\"42\"", false); +} + +TEST(SchemaValidator, Integer_Range) { + Document sd; + sd.Parse("{\"type\":\"integer\",\"minimum\":0,\"maximum\":100,\"exclusiveMaximum\":true}"); + Schema s(sd); + + VALIDATE(s, "-1", false); + VALIDATE(s, "0", true); + VALIDATE(s, "10", true); + VALIDATE(s, "99", true); + VALIDATE(s, "100", false); + VALIDATE(s, "101", false); +} + +TEST(SchemaValidator, Integer_MultipleOf) { + Document sd; + sd.Parse("{\"type\":\"integer\",\"multipleOf\":10}"); + Schema s(sd); + + VALIDATE(s, "0", true); + VALIDATE(s, "10", true); + VALIDATE(s, "20", true); + VALIDATE(s, "23", false); +} + +TEST(SchemaValidator, Number_Range) { + Document sd; + sd.Parse("{\"type\":\"number\",\"minimum\":0,\"maximum\":100,\"exclusiveMaximum\":true}"); + Schema s(sd); + + VALIDATE(s, "-1", false); + VALIDATE(s, "0", true); + VALIDATE(s, "10", true); + VALIDATE(s, "99", true); + VALIDATE(s, "100", false); + VALIDATE(s, "101", false); +} + +TEST(SchemaValidator, Number_MultipleOf) { + Document sd; + sd.Parse("{\"type\":\"number\",\"multipleOf\":10}"); + Schema s(sd); + + VALIDATE(s, "0", true); + VALIDATE(s, "10", true); + VALIDATE(s, "20", true); + VALIDATE(s, "23", false); +} + +TEST(SchemaValidator, Number_MultipleOfOne) { + Document sd; + sd.Parse("{\"type\":\"number\",\"multipleOf\":1}"); + Schema s(sd); + + VALIDATE(s, "42", true); + VALIDATE(s, "42.0", true); + VALIDATE(s, "3.1415926", false); +} + +TEST(SchemaValidator, Object) { + Document sd; + sd.Parse("{\"type\":\"object\"}"); + Schema s(sd); + + VALIDATE(s, "{\"key\":\"value\",\"another_key\":\"another_value\"}", true); + VALIDATE(s, "{\"Sun\":1.9891e30,\"Jupiter\":1.8986e27,\"Saturn\":5.6846e26,\"Neptune\":10.243e25,\"Uranus\":8.6810e25,\"Earth\":5.9736e24,\"Venus\":4.8685e24,\"Mars\":6.4185e23,\"Mercury\":3.3022e23,\"Moon\":7.349e22,\"Pluto\":1.25e22}", true); + VALIDATE(s, "[\"An\", \"array\", \"not\", \"an\", \"object\"]", false); + VALIDATE(s, "\"Not an object\"", false); +} + +TEST(SchemaValidator, Object_Properties) { + Document sd; + sd.Parse( + "{" + " \"type\": \"object\"," + " \"properties\" : {" + " \"number\": { \"type\": \"number\" }," + " \"street_name\" : { \"type\": \"string\" }," + " \"street_type\" : { \"type\": \"string\", \"enum\" : [\"Street\", \"Avenue\", \"Boulevard\"] }" + " }" + "}"); + + Schema s(sd); + + VALIDATE(s, "{ \"number\": 1600, \"street_name\": \"Pennsylvania\", \"street_type\": \"Avenue\" }", true); + VALIDATE(s, "{ \"number\": \"1600\", \"street_name\": \"Pennsylvania\", \"street_type\": \"Avenue\" }", false); + VALIDATE(s, "{ \"number\": 1600, \"street_name\": \"Pennsylvania\" }", true); + VALIDATE(s, "{}", true); + VALIDATE(s, "{ \"number\": 1600, \"street_name\": \"Pennsylvania\", \"street_type\": \"Avenue\", \"direction\": \"NW\" }", true); +} + +TEST(SchemaValidator, Object_AdditionalPropertiesBoolean) { + Document sd; + sd.Parse( + "{" + " \"type\": \"object\"," + " \"properties\" : {" + " \"number\": { \"type\": \"number\" }," + " \"street_name\" : { \"type\": \"string\" }," + " \"street_type\" : { \"type\": \"string\"," + " \"enum\" : [\"Street\", \"Avenue\", \"Boulevard\"]" + " }" + " }," + " \"additionalProperties\": false" + "}"); + + Schema s(sd); + + VALIDATE(s, "{ \"number\": 1600, \"street_name\": \"Pennsylvania\", \"street_type\": \"Avenue\" }", true); + VALIDATE(s, "{ \"number\": 1600, \"street_name\": \"Pennsylvania\", \"street_type\": \"Avenue\", \"direction\": \"NW\" }", false); +} + +TEST(SchemaValidator, Object_AdditionalPropertiesObject) { + Document sd; + sd.Parse( + "{" + " \"type\": \"object\"," + " \"properties\" : {" + " \"number\": { \"type\": \"number\" }," + " \"street_name\" : { \"type\": \"string\" }," + " \"street_type\" : { \"type\": \"string\"," + " \"enum\" : [\"Street\", \"Avenue\", \"Boulevard\"]" + " }" + " }," + " \"additionalProperties\": { \"type\": \"string\" }" + "}"); + Schema s(sd); + + VALIDATE(s, "{ \"number\": 1600, \"street_name\": \"Pennsylvania\", \"street_type\": \"Avenue\" }", true); + VALIDATE(s, "{ \"number\": 1600, \"street_name\": \"Pennsylvania\", \"street_type\": \"Avenue\", \"direction\": \"NW\" }", true); + VALIDATE(s, "{ \"number\": 1600, \"street_name\": \"Pennsylvania\", \"street_type\": \"Avenue\", \"office_number\": 201 }", false); +} + +TEST(SchemaValidator, Object_Required) { + Document sd; + sd.Parse( + "{" + " \"type\": \"object\"," + " \"properties\" : {" + " \"name\": { \"type\": \"string\" }," + " \"email\" : { \"type\": \"string\" }," + " \"address\" : { \"type\": \"string\" }," + " \"telephone\" : { \"type\": \"string\" }" + " }," + " \"required\":[\"name\", \"email\"]" + "}"); + Schema s(sd); + + VALIDATE(s, "{ \"name\": \"William Shakespeare\", \"email\" : \"bill@stratford-upon-avon.co.uk\" }", true); + VALIDATE(s, "{ \"name\": \"William Shakespeare\", \"email\" : \"bill@stratford-upon-avon.co.uk\", \"address\" : \"Henley Street, Stratford-upon-Avon, Warwickshire, England\", \"authorship\" : \"in question\"}", true); + VALIDATE(s, "{ \"name\": \"William Shakespeare\", \"address\" : \"Henley Street, Stratford-upon-Avon, Warwickshire, England\" }", false); +} + + +TEST(SchemaValidator, Object_PropertiesRange) { + Document sd; + sd.Parse("{\"type\":\"object\", \"minProperties\":2, \"maxProperties\":3}"); + Schema s(sd); + + VALIDATE(s, "{}", false); + VALIDATE(s, "{\"a\":0}", false); + VALIDATE(s, "{\"a\":0,\"b\":1}", true); + VALIDATE(s, "{\"a\":0,\"b\":1,\"c\":2}", true); + VALIDATE(s, "{\"a\":0,\"b\":1,\"c\":2,\"d\":3}", false); +} + +#if 0 +// TODO +TEST(SchemaValidator, Object_PropertyDependencies) { + Document sd; + sd.Parse( + "{" + " \"type\": \"object\"," + " \"properties\": {" + " \"name\": { \"type\": \"string\" }," + " \"credit_card\": { \"type\": \"number\" }," + " \"billing_address\": { \"type\": \"string\" }" + " }," + " \"required\": [\"name\"]," + " \"dependencies\": {" + " \"credit_card\": [\"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\"}", true); + VALIDATE(s, "{ \"name\": \"John Doe\", \"billing_address\": \"555 Debtor's Lane\" }", true); +} +#endif + +TEST(SchemaValidator, Array) { + Document sd; + sd.Parse("{\"type\":\"array\"}"); + Schema s(sd); + + VALIDATE(s, "[1, 2, 3, 4, 5]", true); + VALIDATE(s, "[3, \"different\", { \"types\" : \"of values\" }]", true); + VALIDATE(s, "{\"Not\": \"an array\"}", false); +} + +TEST(SchemaValidator, Array_ItemsList) { + Document sd; + sd.Parse( + "{" + " \"type\": \"array\"," + " \"items\" : {" + " \"type\": \"number\"" + " }" + "}"); + Schema s(sd); + + VALIDATE(s, "[1, 2, 3, 4, 5]", true); + VALIDATE(s, "[1, 2, \"3\", 4, 5]", false); + VALIDATE(s, "[]", true); +} + +TEST(SchemaValidator, Array_ItemsTuple) { + Document sd; + sd.Parse( + "{" + " \"type\": \"array\"," + " \"items\": [" + " {" + " \"type\": \"number\"" + " }," + " {" + " \"type\": \"string\"" + " }," + " {" + " \"type\": \"string\"," + " \"enum\": [\"Street\", \"Avenue\", \"Boulevard\"]" + " }," + " {" + " \"type\": \"string\"," + " \"enum\": [\"NW\", \"NE\", \"SW\", \"SE\"]" + " }" + " ]" + "}"); + Schema s(sd); + + VALIDATE(s, "[1600, \"Pennsylvania\", \"Avenue\", \"NW\"]", true); + VALIDATE(s, "[24, \"Sussex\", \"Drive\"]", false); + VALIDATE(s, "[\"Palais de l'Elysee\"]", false); + VALIDATE(s, "[10, \"Downing\", \"Street\"]", true); + VALIDATE(s, "[1600, \"Pennsylvania\", \"Avenue\", \"NW\", \"Washington\"]", true); +} + +TEST(SchemaValidator, Array_ItemsRange) { + Document sd; + sd.Parse("{\"type\": \"array\",\"minItems\": 2,\"maxItems\" : 3}"); + Schema s(sd); + + VALIDATE(s, "[]", false); + VALIDATE(s, "[1]", false); + VALIDATE(s, "[1, 2]", true); + VALIDATE(s, "[1, 2, 3]", true); + VALIDATE(s, "[1, 2, 3, 4]", false); +} + +#if 0 +// TODO +TEST(SchemaValidator, Array_Uniqueness) { + Document sd; + sd.Parse("{\"type\": \"array\", \"uniqueItems\": true}"); + Schema s(sd); + + VALIDATE(s, "[1, 2, 3, 4, 5]", true); + VALIDATE(s, "[1, 2, 3, 4, 5]", false); +} +#endif + +TEST(SchemaValidator, Boolean) { + Document sd; + sd.Parse("{\"type\":\"boolean\"}"); + Schema s(sd); + + VALIDATE(s, "true", true); + VALIDATE(s, "false", true); + VALIDATE(s, "\"true\"", false); + VALIDATE(s, "0", false); +} + +TEST(SchemaValidator, Null) { + Document sd; + sd.Parse("{\"type\":\"null\"}"); + Schema s(sd); + + VALIDATE(s, "null", true); + VALIDATE(s, "false", false); + VALIDATE(s, "0", false); + VALIDATE(s, "\"\"", false); +}