| // 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 "pointer.h" |
| #include "stringbuffer.h" |
| #include <cmath> // abs, floor |
| |
| #if !defined(RAPIDJSON_SCHEMA_USE_INTERNALREGEX) |
| #define RAPIDJSON_SCHEMA_USE_INTERNALREGEX 1 |
| #else |
| #define RAPIDJSON_SCHEMA_USE_INTERNALREGEX 0 |
| #endif |
| |
| #if !RAPIDJSON_SCHEMA_USE_INTERNALREGEX && defined(RAPIDJSON_SCHEMA_USE_STDREGEX) && (__cplusplus >=201103L || (defined(_MSC_VER) && _MSC_VER >= 1800)) |
| #define RAPIDJSON_SCHEMA_USE_STDREGEX 1 |
| #else |
| #define RAPIDJSON_SCHEMA_USE_STDREGEX 0 |
| #endif |
| |
| #if RAPIDJSON_SCHEMA_USE_INTERNALREGEX |
| #include "internal/regex.h" |
| #elif RAPIDJSON_SCHEMA_USE_STDREGEX |
| #include <regex> |
| #endif |
| |
| #if RAPIDJSON_SCHEMA_USE_INTERNALREGEX || RAPIDJSON_SCHEMA_USE_STDREGEX |
| #define RAPIDJSON_SCHEMA_HAS_REGEX 1 |
| #else |
| #define RAPIDJSON_SCHEMA_HAS_REGEX 0 |
| #endif |
| |
| #ifndef RAPIDJSON_SCHEMA_VERBOSE |
| #define RAPIDJSON_SCHEMA_VERBOSE 0 |
| #endif |
| |
| #if RAPIDJSON_SCHEMA_VERBOSE |
| #include "stringbuffer.h" |
| #endif |
| |
| RAPIDJSON_DIAG_PUSH |
| |
| #if defined(__GNUC__) |
| RAPIDJSON_DIAG_OFF(effc++) |
| #endif |
| |
| #ifdef __clang__ |
| RAPIDJSON_DIAG_OFF(weak-vtables) |
| RAPIDJSON_DIAG_OFF(exit-time-destructors) |
| RAPIDJSON_DIAG_OFF(c++98-compat-pedantic) |
| RAPIDJSON_DIAG_OFF(variadic-macros) |
| #elif defined(_MSC_VER) |
| RAPIDJSON_DIAG_OFF(4512) // assignment operator could not be generated |
| #endif |
| |
| RAPIDJSON_NAMESPACE_BEGIN |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // Verbose Utilities |
| |
| #if RAPIDJSON_SCHEMA_VERBOSE |
| |
| namespace internal { |
| |
| inline void PrintInvalidKeyword(const char* keyword) { |
| printf("Fail keyword: %s\n", keyword); |
| } |
| |
| inline void PrintInvalidKeyword(const wchar_t* keyword) { |
| wprintf(L"Fail keyword: %ls\n", keyword); |
| } |
| |
| inline void PrintInvalidDocument(const char* document) { |
| printf("Fail document: %s\n\n", document); |
| } |
| |
| inline void PrintInvalidDocument(const wchar_t* document) { |
| wprintf(L"Fail document: %ls\n\n", document); |
| } |
| |
| inline void PrintValidatorPointers(unsigned depth, const char* s, const char* d) { |
| printf("S: %*s%s\nD: %*s%s\n\n", depth * 4, " ", s, depth * 4, " ", d); |
| } |
| |
| inline void PrintValidatorPointers(unsigned depth, const wchar_t* s, const wchar_t* d) { |
| wprintf(L"S: %*ls%ls\nD: %*ls%ls\n\n", depth * 4, L" ", s, depth * 4, L" ", d); |
| } |
| |
| } // namespace internal |
| |
| #endif // RAPIDJSON_SCHEMA_VERBOSE |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // RAPIDJSON_INVALID_KEYWORD_RETURN |
| |
| #if RAPIDJSON_SCHEMA_VERBOSE |
| #define RAPIDJSON_INVALID_KEYWORD_VERBOSE(keyword) internal::PrintInvalidKeyword(keyword) |
| #else |
| #define RAPIDJSON_INVALID_KEYWORD_VERBOSE(keyword) |
| #endif |
| |
| #define RAPIDJSON_INVALID_KEYWORD_RETURN(keyword)\ |
| RAPIDJSON_MULTILINEMACRO_BEGIN\ |
| context.invalidKeyword = keyword.GetString();\ |
| RAPIDJSON_INVALID_KEYWORD_VERBOSE(keyword.GetString());\ |
| return false;\ |
| RAPIDJSON_MULTILINEMACRO_END |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // Forward declarations |
| |
| template <typename ValueType, typename Allocator> |
| class GenericSchemaDocument; |
| |
| namespace internal { |
| |
| template <typename SchemaDocumentType> |
| class Schema; |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // ISchemaValidator |
| |
| class ISchemaValidator { |
| public: |
| virtual ~ISchemaValidator() {} |
| virtual bool IsValid() const = 0; |
| }; |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // ISchemaStateFactory |
| |
| template <typename SchemaType> |
| class ISchemaStateFactory { |
| public: |
| virtual ~ISchemaStateFactory() {} |
| virtual ISchemaValidator* CreateSchemaValidator(const SchemaType&) = 0; |
| virtual void DestroySchemaValidator(ISchemaValidator* validator) = 0; |
| virtual void* CreateHasher() = 0; |
| virtual uint64_t GetHashCode(void* hasher) = 0; |
| virtual void DestroryHasher(void* hasher) = 0; |
| virtual void* MallocState(size_t size) = 0; |
| virtual void FreeState(void* p) = 0; |
| }; |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // IValidationErrorHandler |
| |
| template <typename SchemaType> |
| class IValidationErrorHandler { |
| public: |
| typedef typename SchemaType::Ch Ch; |
| typedef typename SchemaType::SValue SValue; |
| |
| virtual ~IValidationErrorHandler() {} |
| |
| virtual void NotMultipleOf(int64_t actual, const SValue& expected) = 0; |
| virtual void NotMultipleOf(uint64_t actual, const SValue& expected) = 0; |
| virtual void NotMultipleOf(double actual, const SValue& expected) = 0; |
| virtual void AboveMaximum(int64_t actual, const SValue& expected, bool exclusive) = 0; |
| virtual void AboveMaximum(uint64_t actual, const SValue& expected, bool exclusive) = 0; |
| virtual void AboveMaximum(double actual, const SValue& expected, bool exclusive) = 0; |
| virtual void BelowMinimum(int64_t actual, const SValue& expected, bool exclusive) = 0; |
| virtual void BelowMinimum(uint64_t actual, const SValue& expected, bool exclusive) = 0; |
| virtual void BelowMinimum(double actual, const SValue& expected, bool exclusive) = 0; |
| |
| virtual void TooLong(const Ch* str, SizeType length, SizeType expected) = 0; |
| virtual void TooShort(const Ch* str, SizeType length, SizeType expected) = 0; |
| virtual void DoesNotMatch(const Ch* str, SizeType length) = 0; |
| |
| virtual void DisallowedItem(SizeType index) = 0; |
| virtual void TooFewItems(SizeType actualCount, SizeType expectedCount) = 0; |
| virtual void TooManyItems(SizeType actualCount, SizeType expectedCount) = 0; |
| virtual void DuplicateItems(SizeType index1, SizeType index2) = 0; |
| |
| virtual void TooManyProperties(SizeType actualCount, SizeType expectedCount) = 0; |
| virtual void TooFewProperties(SizeType actualCount, SizeType expectedCount) = 0; |
| virtual void StartMissingProperties() = 0; |
| virtual void AddMissingProperty(const SValue& name) = 0; |
| virtual bool EndMissingProperties() = 0; |
| virtual void PropertyViolations(ISchemaValidator** subvalidators, SizeType count) = 0; |
| virtual void DisallowedProperty(const Ch* name, SizeType length) = 0; |
| |
| virtual void StartDependencyErrors() = 0; |
| virtual void StartMissingDependentProperties() = 0; |
| virtual void AddMissingDependentProperty(const SValue& targetName) = 0; |
| virtual void EndMissingDependentProperties(const SValue& sourceName) = 0; |
| virtual void AddDependencySchemaError(const SValue& souceName, ISchemaValidator* subvalidator) = 0; |
| virtual bool EndDependencyErrors() = 0; |
| |
| virtual void DisallowedValue() = 0; |
| virtual void StartDisallowedType() = 0; |
| virtual void AddExpectedType(const typename SchemaType::ValueType& expectedType) = 0; |
| virtual void EndDisallowedType(const typename SchemaType::ValueType& actualType) = 0; |
| virtual void NotAllOf(ISchemaValidator** subvalidators, SizeType count) = 0; |
| virtual void NoneOf(ISchemaValidator** subvalidators, SizeType count) = 0; |
| virtual void NotOneOf(ISchemaValidator** subvalidators, SizeType count) = 0; |
| virtual void Disallowed() = 0; |
| }; |
| |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // Hasher |
| |
| // For comparison of compound value |
| template<typename Encoding, typename Allocator> |
| class Hasher { |
| public: |
| typedef typename Encoding::Ch Ch; |
| |
| Hasher(Allocator* allocator = 0, size_t stackCapacity = kDefaultSize) : stack_(allocator, stackCapacity) {} |
| |
| bool Null() { return WriteType(kNullType); } |
| bool Bool(bool b) { return WriteType(b ? kTrueType : kFalseType); } |
| bool Int(int i) { Number n; n.u.i = i; n.d = static_cast<double>(i); return WriteNumber(n); } |
| bool Uint(unsigned u) { Number n; n.u.u = u; n.d = static_cast<double>(u); return WriteNumber(n); } |
| bool Int64(int64_t i) { Number n; n.u.i = i; n.d = static_cast<double>(i); return WriteNumber(n); } |
| bool Uint64(uint64_t u) { Number n; n.u.u = u; n.d = static_cast<double>(u); return WriteNumber(n); } |
| bool Double(double d) { |
| Number n; |
| if (d < 0) n.u.i = static_cast<int64_t>(d); |
| else n.u.u = static_cast<uint64_t>(d); |
| n.d = d; |
| return WriteNumber(n); |
| } |
| |
| bool RawNumber(const Ch* str, SizeType len, bool) { |
| WriteBuffer(kNumberType, str, len * sizeof(Ch)); |
| return true; |
| } |
| |
| bool String(const Ch* str, SizeType len, bool) { |
| WriteBuffer(kStringType, str, len * sizeof(Ch)); |
| return true; |
| } |
| |
| bool StartObject() { return true; } |
| bool Key(const Ch* str, SizeType len, bool copy) { return String(str, len, copy); } |
| bool EndObject(SizeType memberCount) { |
| uint64_t h = Hash(0, kObjectType); |
| uint64_t* kv = stack_.template Pop<uint64_t>(memberCount * 2); |
| for (SizeType i = 0; i < memberCount; i++) |
| h ^= Hash(kv[i * 2], kv[i * 2 + 1]); // Use xor to achieve member order insensitive |
| *stack_.template Push<uint64_t>() = h; |
| return true; |
| } |
| |
| bool StartArray() { return true; } |
| bool EndArray(SizeType elementCount) { |
| uint64_t h = Hash(0, kArrayType); |
| uint64_t* e = stack_.template Pop<uint64_t>(elementCount); |
| for (SizeType i = 0; i < elementCount; i++) |
| h = Hash(h, e[i]); // Use hash to achieve element order sensitive |
| *stack_.template Push<uint64_t>() = h; |
| return true; |
| } |
| |
| bool IsValid() const { return stack_.GetSize() == sizeof(uint64_t); } |
| |
| uint64_t GetHashCode() const { |
| RAPIDJSON_ASSERT(IsValid()); |
| return *stack_.template Top<uint64_t>(); |
| } |
| |
| private: |
| static const size_t kDefaultSize = 256; |
| struct Number { |
| union U { |
| uint64_t u; |
| int64_t i; |
| }u; |
| double d; |
| }; |
| |
| bool WriteType(Type type) { return WriteBuffer(type, 0, 0); } |
| |
| bool WriteNumber(const Number& n) { return WriteBuffer(kNumberType, &n, sizeof(n)); } |
| |
| bool WriteBuffer(Type type, const void* data, size_t len) { |
| // FNV-1a from http://isthe.com/chongo/tech/comp/fnv/ |
| uint64_t h = Hash(RAPIDJSON_UINT64_C2(0x84222325, 0xcbf29ce4), type); |
| const unsigned char* d = static_cast<const unsigned char*>(data); |
| for (size_t i = 0; i < len; i++) |
| h = Hash(h, d[i]); |
| *stack_.template Push<uint64_t>() = h; |
| return true; |
| } |
| |
| static uint64_t Hash(uint64_t h, uint64_t d) { |
| static const uint64_t kPrime = RAPIDJSON_UINT64_C2(0x00000100, 0x000001b3); |
| h ^= d; |
| h *= kPrime; |
| return h; |
| } |
| |
| Stack<Allocator> stack_; |
| }; |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // SchemaValidationContext |
| |
| template <typename SchemaDocumentType> |
| struct SchemaValidationContext { |
| typedef Schema<SchemaDocumentType> SchemaType; |
| typedef ISchemaStateFactory<SchemaType> SchemaValidatorFactoryType; |
| typedef IValidationErrorHandler<SchemaType> ErrorHandlerType; |
| typedef typename SchemaType::ValueType ValueType; |
| typedef typename ValueType::Ch Ch; |
| |
| enum PatternValidatorType { |
| kPatternValidatorOnly, |
| kPatternValidatorWithProperty, |
| kPatternValidatorWithAdditionalProperty |
| }; |
| |
| SchemaValidationContext(SchemaValidatorFactoryType& f, ErrorHandlerType& eh, const SchemaType* s) : |
| factory(f), |
| error_handler(eh), |
| schema(s), |
| valueSchema(), |
| invalidKeyword(), |
| hasher(), |
| arrayElementHashCodes(), |
| validators(), |
| validatorCount(), |
| patternPropertiesValidators(), |
| patternPropertiesValidatorCount(), |
| patternPropertiesSchemas(), |
| patternPropertiesSchemaCount(), |
| valuePatternValidatorType(kPatternValidatorOnly), |
| propertyExist(), |
| inArray(false), |
| valueUniqueness(false), |
| arrayUniqueness(false) |
| { |
| } |
| |
| ~SchemaValidationContext() { |
| if (hasher) |
| factory.DestroryHasher(hasher); |
| if (validators) { |
| for (SizeType i = 0; i < validatorCount; i++) |
| factory.DestroySchemaValidator(validators[i]); |
| factory.FreeState(validators); |
| } |
| if (patternPropertiesValidators) { |
| for (SizeType i = 0; i < patternPropertiesValidatorCount; i++) |
| factory.DestroySchemaValidator(patternPropertiesValidators[i]); |
| factory.FreeState(patternPropertiesValidators); |
| } |
| if (patternPropertiesSchemas) |
| factory.FreeState(patternPropertiesSchemas); |
| if (propertyExist) |
| factory.FreeState(propertyExist); |
| } |
| |
| SchemaValidatorFactoryType& factory; |
| ErrorHandlerType& error_handler; |
| const SchemaType* schema; |
| const SchemaType* valueSchema; |
| const Ch* invalidKeyword; |
| void* hasher; // Only validator access |
| void* arrayElementHashCodes; // Only validator access this |
| ISchemaValidator** validators; |
| SizeType validatorCount; |
| ISchemaValidator** patternPropertiesValidators; |
| SizeType patternPropertiesValidatorCount; |
| const SchemaType** patternPropertiesSchemas; |
| SizeType patternPropertiesSchemaCount; |
| PatternValidatorType valuePatternValidatorType; |
| PatternValidatorType objectPatternValidatorType; |
| SizeType arrayElementIndex; |
| bool* propertyExist; |
| bool inArray; |
| bool valueUniqueness; |
| bool arrayUniqueness; |
| }; |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // Schema |
| |
| template <typename SchemaDocumentType> |
| class Schema { |
| public: |
| typedef typename SchemaDocumentType::ValueType ValueType; |
| typedef typename SchemaDocumentType::AllocatorType AllocatorType; |
| typedef typename SchemaDocumentType::PointerType PointerType; |
| typedef typename ValueType::EncodingType EncodingType; |
| typedef typename EncodingType::Ch Ch; |
| typedef SchemaValidationContext<SchemaDocumentType> Context; |
| typedef Schema<SchemaDocumentType> SchemaType; |
| typedef GenericValue<EncodingType, AllocatorType> SValue; |
| typedef IValidationErrorHandler<Schema> ErrorHandler; |
| friend class GenericSchemaDocument<ValueType, AllocatorType>; |
| |
| Schema(SchemaDocumentType* schemaDocument, const PointerType& p, const ValueType& value, const ValueType& document, AllocatorType* allocator) : |
| allocator_(allocator), |
| uri_(schemaDocument->GetURI(), *allocator), |
| pointer_(p, allocator), |
| typeless_(schemaDocument->GetTypeless()), |
| enum_(), |
| enumCount_(), |
| not_(), |
| type_((1 << kTotalSchemaType) - 1), // typeless |
| validatorCount_(), |
| notValidatorIndex_(), |
| properties_(), |
| additionalPropertiesSchema_(), |
| patternProperties_(), |
| patternPropertyCount_(), |
| propertyCount_(), |
| minProperties_(), |
| maxProperties_(SizeType(~0)), |
| additionalProperties_(true), |
| hasDependencies_(), |
| hasRequired_(), |
| hasSchemaDependencies_(), |
| additionalItemsSchema_(), |
| itemsList_(), |
| itemsTuple_(), |
| itemsTupleCount_(), |
| minItems_(), |
| maxItems_(SizeType(~0)), |
| additionalItems_(true), |
| uniqueItems_(false), |
| pattern_(), |
| minLength_(0), |
| maxLength_(~SizeType(0)), |
| exclusiveMinimum_(false), |
| exclusiveMaximum_(false), |
| defaultValueLength_(0) |
| { |
| typedef typename SchemaDocumentType::ValueType ValueType; |
| typedef typename ValueType::ConstValueIterator ConstValueIterator; |
| typedef typename ValueType::ConstMemberIterator ConstMemberIterator; |
| |
| if (!value.IsObject()) |
| return; |
| |
| if (const ValueType* v = GetMember(value, GetTypeString())) { |
| type_ = 0; |
| if (v->IsString()) |
| AddType(*v); |
| else if (v->IsArray()) |
| for (ConstValueIterator itr = v->Begin(); itr != v->End(); ++itr) |
| AddType(*itr); |
| } |
| |
| if (const ValueType* v = GetMember(value, GetEnumString())) |
| if (v->IsArray() && v->Size() > 0) { |
| enum_ = static_cast<uint64_t*>(allocator_->Malloc(sizeof(uint64_t) * v->Size())); |
| for (ConstValueIterator itr = v->Begin(); itr != v->End(); ++itr) { |
| typedef Hasher<EncodingType, MemoryPoolAllocator<> > EnumHasherType; |
| char buffer[256u + 24]; |
| MemoryPoolAllocator<> hasherAllocator(buffer, sizeof(buffer)); |
| EnumHasherType h(&hasherAllocator, 256); |
| itr->Accept(h); |
| enum_[enumCount_++] = h.GetHashCode(); |
| } |
| } |
| |
| if (schemaDocument) { |
| AssignIfExist(allOf_, *schemaDocument, p, value, GetAllOfString(), document); |
| AssignIfExist(anyOf_, *schemaDocument, p, value, GetAnyOfString(), document); |
| AssignIfExist(oneOf_, *schemaDocument, p, value, GetOneOfString(), document); |
| } |
| |
| if (const ValueType* v = GetMember(value, GetNotString())) { |
| schemaDocument->CreateSchema(¬_, p.Append(GetNotString(), allocator_), *v, document); |
| notValidatorIndex_ = validatorCount_; |
| validatorCount_++; |
| } |
| |
| // Object |
| |
| const ValueType* properties = GetMember(value, GetPropertiesString()); |
| const ValueType* required = GetMember(value, GetRequiredString()); |
| const ValueType* dependencies = GetMember(value, GetDependenciesString()); |
| { |
| // Gather properties from properties/required/dependencies |
| SValue allProperties(kArrayType); |
| |
| if (properties && properties->IsObject()) |
| for (ConstMemberIterator itr = properties->MemberBegin(); itr != properties->MemberEnd(); ++itr) |
| AddUniqueElement(allProperties, itr->name); |
| |
| if (required && required->IsArray()) |
| for (ConstValueIterator itr = required->Begin(); itr != required->End(); ++itr) |
| if (itr->IsString()) |
| AddUniqueElement(allProperties, *itr); |
| |
| if (dependencies && dependencies->IsObject()) |
| for (ConstMemberIterator itr = dependencies->MemberBegin(); itr != dependencies->MemberEnd(); ++itr) { |
| AddUniqueElement(allProperties, itr->name); |
| if (itr->value.IsArray()) |
| for (ConstValueIterator i = itr->value.Begin(); i != itr->value.End(); ++i) |
| if (i->IsString()) |
| AddUniqueElement(allProperties, *i); |
| } |
| |
| if (allProperties.Size() > 0) { |
| propertyCount_ = allProperties.Size(); |
| properties_ = static_cast<Property*>(allocator_->Malloc(sizeof(Property) * propertyCount_)); |
| for (SizeType i = 0; i < propertyCount_; i++) { |
| new (&properties_[i]) Property(); |
| properties_[i].name = allProperties[i]; |
| properties_[i].schema = typeless_; |
| } |
| } |
| } |
| |
| if (properties && properties->IsObject()) { |
| PointerType q = p.Append(GetPropertiesString(), allocator_); |
| for (ConstMemberIterator itr = properties->MemberBegin(); itr != properties->MemberEnd(); ++itr) { |
| SizeType index; |
| if (FindPropertyIndex(itr->name, &index)) |
| schemaDocument->CreateSchema(&properties_[index].schema, q.Append(itr->name, allocator_), itr->value, document); |
| } |
| } |
| |
| if (const ValueType* v = GetMember(value, GetPatternPropertiesString())) { |
| PointerType q = p.Append(GetPatternPropertiesString(), allocator_); |
| patternProperties_ = static_cast<PatternProperty*>(allocator_->Malloc(sizeof(PatternProperty) * v->MemberCount())); |
| patternPropertyCount_ = 0; |
| |
| for (ConstMemberIterator itr = v->MemberBegin(); itr != v->MemberEnd(); ++itr) { |
| new (&patternProperties_[patternPropertyCount_]) PatternProperty(); |
| patternProperties_[patternPropertyCount_].pattern = CreatePattern(itr->name); |
| schemaDocument->CreateSchema(&patternProperties_[patternPropertyCount_].schema, q.Append(itr->name, allocator_), itr->value, document); |
| patternPropertyCount_++; |
| } |
| } |
| |
| if (required && required->IsArray()) |
| for (ConstValueIterator itr = required->Begin(); itr != required->End(); ++itr) |
| if (itr->IsString()) { |
| SizeType index; |
| if (FindPropertyIndex(*itr, &index)) { |
| properties_[index].required = true; |
| hasRequired_ = true; |
| } |
| } |
| |
| if (dependencies && dependencies->IsObject()) { |
| PointerType q = p.Append(GetDependenciesString(), allocator_); |
| hasDependencies_ = true; |
| for (ConstMemberIterator itr = dependencies->MemberBegin(); itr != dependencies->MemberEnd(); ++itr) { |
| SizeType sourceIndex; |
| if (FindPropertyIndex(itr->name, &sourceIndex)) { |
| if (itr->value.IsArray()) { |
| properties_[sourceIndex].dependencies = static_cast<bool*>(allocator_->Malloc(sizeof(bool) * propertyCount_)); |
| std::memset(properties_[sourceIndex].dependencies, 0, sizeof(bool)* propertyCount_); |
| for (ConstValueIterator targetItr = itr->value.Begin(); targetItr != itr->value.End(); ++targetItr) { |
| SizeType targetIndex; |
| if (FindPropertyIndex(*targetItr, &targetIndex)) |
| properties_[sourceIndex].dependencies[targetIndex] = true; |
| } |
| } |
| else if (itr->value.IsObject()) { |
| hasSchemaDependencies_ = true; |
| schemaDocument->CreateSchema(&properties_[sourceIndex].dependenciesSchema, q.Append(itr->name, allocator_), itr->value, document); |
| properties_[sourceIndex].dependenciesValidatorIndex = validatorCount_; |
| validatorCount_++; |
| } |
| } |
| } |
| } |
| |
| if (const ValueType* v = GetMember(value, GetAdditionalPropertiesString())) { |
| if (v->IsBool()) |
| additionalProperties_ = v->GetBool(); |
| else if (v->IsObject()) |
| schemaDocument->CreateSchema(&additionalPropertiesSchema_, p.Append(GetAdditionalPropertiesString(), allocator_), *v, document); |
| } |
| |
| AssignIfExist(minProperties_, value, GetMinPropertiesString()); |
| AssignIfExist(maxProperties_, value, GetMaxPropertiesString()); |
| |
| // Array |
| if (const ValueType* v = GetMember(value, GetItemsString())) { |
| PointerType q = p.Append(GetItemsString(), allocator_); |
| if (v->IsObject()) // List validation |
| schemaDocument->CreateSchema(&itemsList_, q, *v, document); |
| else if (v->IsArray()) { // Tuple validation |
| itemsTuple_ = static_cast<const Schema**>(allocator_->Malloc(sizeof(const Schema*) * v->Size())); |
| SizeType index = 0; |
| for (ConstValueIterator itr = v->Begin(); itr != v->End(); ++itr, index++) |
| schemaDocument->CreateSchema(&itemsTuple_[itemsTupleCount_++], q.Append(index, allocator_), *itr, document); |
| } |
| } |
| |
| AssignIfExist(minItems_, value, GetMinItemsString()); |
| AssignIfExist(maxItems_, value, GetMaxItemsString()); |
| |
| if (const ValueType* v = GetMember(value, GetAdditionalItemsString())) { |
| if (v->IsBool()) |
| additionalItems_ = v->GetBool(); |
| else if (v->IsObject()) |
| schemaDocument->CreateSchema(&additionalItemsSchema_, p.Append(GetAdditionalItemsString(), allocator_), *v, document); |
| } |
| |
| AssignIfExist(uniqueItems_, value, GetUniqueItemsString()); |
| |
| // String |
| AssignIfExist(minLength_, value, GetMinLengthString()); |
| AssignIfExist(maxLength_, value, GetMaxLengthString()); |
| |
| if (const ValueType* v = GetMember(value, GetPatternString())) |
| pattern_ = CreatePattern(*v); |
| |
| // Number |
| if (const ValueType* v = GetMember(value, GetMinimumString())) |
| if (v->IsNumber()) |
| minimum_.CopyFrom(*v, *allocator_); |
| |
| if (const ValueType* v = GetMember(value, GetMaximumString())) |
| if (v->IsNumber()) |
| maximum_.CopyFrom(*v, *allocator_); |
| |
| AssignIfExist(exclusiveMinimum_, value, GetExclusiveMinimumString()); |
| AssignIfExist(exclusiveMaximum_, value, GetExclusiveMaximumString()); |
| |
| if (const ValueType* v = GetMember(value, GetMultipleOfString())) |
| if (v->IsNumber() && v->GetDouble() > 0.0) |
| multipleOf_.CopyFrom(*v, *allocator_); |
| |
| // Default |
| if (const ValueType* v = GetMember(value, GetDefaultValueString())) |
| if (v->IsString()) |
| defaultValueLength_ = v->GetStringLength(); |
| |
| } |
| |
| ~Schema() { |
| AllocatorType::Free(enum_); |
| if (properties_) { |
| for (SizeType i = 0; i < propertyCount_; i++) |
| properties_[i].~Property(); |
| AllocatorType::Free(properties_); |
| } |
| if (patternProperties_) { |
| for (SizeType i = 0; i < patternPropertyCount_; i++) |
| patternProperties_[i].~PatternProperty(); |
| AllocatorType::Free(patternProperties_); |
| } |
| AllocatorType::Free(itemsTuple_); |
| #if RAPIDJSON_SCHEMA_HAS_REGEX |
| if (pattern_) { |
| pattern_->~RegexType(); |
| AllocatorType::Free(pattern_); |
| } |
| #endif |
| } |
| |
| const SValue& GetURI() const { |
| return uri_; |
| } |
| |
| const PointerType& GetPointer() const { |
| return pointer_; |
| } |
| |
| bool BeginValue(Context& context) const { |
| if (context.inArray) { |
| if (uniqueItems_) |
| context.valueUniqueness = true; |
| |
| if (itemsList_) |
| context.valueSchema = itemsList_; |
| else if (itemsTuple_) { |
| if (context.arrayElementIndex < itemsTupleCount_) |
| context.valueSchema = itemsTuple_[context.arrayElementIndex]; |
| else if (additionalItemsSchema_) |
| context.valueSchema = additionalItemsSchema_; |
| else if (additionalItems_) |
| context.valueSchema = typeless_; |
| else { |
| context.error_handler.DisallowedItem(context.arrayElementIndex); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(GetItemsString()); |
| } |
| } |
| else |
| context.valueSchema = typeless_; |
| |
| context.arrayElementIndex++; |
| } |
| return true; |
| } |
| |
| RAPIDJSON_FORCEINLINE bool EndValue(Context& context) const { |
| if (context.patternPropertiesValidatorCount > 0) { |
| bool otherValid = false; |
| SizeType count = context.patternPropertiesValidatorCount; |
| if (context.objectPatternValidatorType != Context::kPatternValidatorOnly) |
| otherValid = context.patternPropertiesValidators[--count]->IsValid(); |
| |
| bool patternValid = true; |
| for (SizeType i = 0; i < count; i++) |
| if (!context.patternPropertiesValidators[i]->IsValid()) { |
| patternValid = false; |
| break; |
| } |
| |
| if (context.objectPatternValidatorType == Context::kPatternValidatorOnly) { |
| if (!patternValid) { |
| context.error_handler.PropertyViolations(context.patternPropertiesValidators, count); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(GetPatternPropertiesString()); |
| } |
| } |
| else if (context.objectPatternValidatorType == Context::kPatternValidatorWithProperty) { |
| if (!patternValid || !otherValid) { |
| context.error_handler.PropertyViolations(context.patternPropertiesValidators, count + 1); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(GetPatternPropertiesString()); |
| } |
| } |
| else if (!patternValid && !otherValid) { // kPatternValidatorWithAdditionalProperty) |
| context.error_handler.PropertyViolations(context.patternPropertiesValidators, count + 1); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(GetPatternPropertiesString()); |
| } |
| } |
| |
| if (enum_) { |
| const uint64_t h = context.factory.GetHashCode(context.hasher); |
| for (SizeType i = 0; i < enumCount_; i++) |
| if (enum_[i] == h) |
| goto foundEnum; |
| context.error_handler.DisallowedValue(); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(GetEnumString()); |
| foundEnum:; |
| } |
| |
| if (allOf_.schemas) |
| for (SizeType i = allOf_.begin; i < allOf_.begin + allOf_.count; i++) |
| if (!context.validators[i]->IsValid()) { |
| context.error_handler.NotAllOf(&context.validators[allOf_.begin], allOf_.count); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(GetAllOfString()); |
| } |
| |
| if (anyOf_.schemas) { |
| for (SizeType i = anyOf_.begin; i < anyOf_.begin + anyOf_.count; i++) |
| if (context.validators[i]->IsValid()) |
| goto foundAny; |
| context.error_handler.NoneOf(&context.validators[anyOf_.begin], anyOf_.count); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(GetAnyOfString()); |
| foundAny:; |
| } |
| |
| if (oneOf_.schemas) { |
| bool oneValid = false; |
| for (SizeType i = oneOf_.begin; i < oneOf_.begin + oneOf_.count; i++) |
| if (context.validators[i]->IsValid()) { |
| if (oneValid) { |
| context.error_handler.NotOneOf(&context.validators[oneOf_.begin], oneOf_.count); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(GetOneOfString()); |
| } else |
| oneValid = true; |
| } |
| if (!oneValid) { |
| context.error_handler.NotOneOf(&context.validators[oneOf_.begin], oneOf_.count); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(GetOneOfString()); |
| } |
| } |
| |
| if (not_ && context.validators[notValidatorIndex_]->IsValid()) { |
| context.error_handler.Disallowed(); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(GetNotString()); |
| } |
| |
| return true; |
| } |
| |
| bool Null(Context& context) const { |
| if (!(type_ & (1 << kNullSchemaType))) { |
| DisallowedType(context, GetNullString()); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); |
| } |
| return CreateParallelValidator(context); |
| } |
| |
| bool Bool(Context& context, bool) const { |
| if (!(type_ & (1 << kBooleanSchemaType))) { |
| DisallowedType(context, GetBooleanString()); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); |
| } |
| return CreateParallelValidator(context); |
| } |
| |
| bool Int(Context& context, int i) const { |
| if (!CheckInt(context, i)) |
| return false; |
| return CreateParallelValidator(context); |
| } |
| |
| bool Uint(Context& context, unsigned u) const { |
| if (!CheckUint(context, u)) |
| return false; |
| return CreateParallelValidator(context); |
| } |
| |
| bool Int64(Context& context, int64_t i) const { |
| if (!CheckInt(context, i)) |
| return false; |
| return CreateParallelValidator(context); |
| } |
| |
| bool Uint64(Context& context, uint64_t u) const { |
| if (!CheckUint(context, u)) |
| return false; |
| return CreateParallelValidator(context); |
| } |
| |
| bool Double(Context& context, double d) const { |
| if (!(type_ & (1 << kNumberSchemaType))) { |
| DisallowedType(context, GetNumberString()); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); |
| } |
| |
| if (!minimum_.IsNull() && !CheckDoubleMinimum(context, d)) |
| return false; |
| |
| if (!maximum_.IsNull() && !CheckDoubleMaximum(context, d)) |
| return false; |
| |
| if (!multipleOf_.IsNull() && !CheckDoubleMultipleOf(context, d)) |
| return false; |
| |
| return CreateParallelValidator(context); |
| } |
| |
| bool String(Context& context, const Ch* str, SizeType length, bool) const { |
| if (!(type_ & (1 << kStringSchemaType))) { |
| DisallowedType(context, GetStringString()); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); |
| } |
| |
| if (minLength_ != 0 || maxLength_ != SizeType(~0)) { |
| SizeType count; |
| if (internal::CountStringCodePoint<EncodingType>(str, length, &count)) { |
| if (count < minLength_) { |
| context.error_handler.TooShort(str, length, minLength_); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinLengthString()); |
| } |
| if (count > maxLength_) { |
| context.error_handler.TooLong(str, length, maxLength_); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaxLengthString()); |
| } |
| } |
| } |
| |
| if (pattern_ && !IsPatternMatch(pattern_, str, length)) { |
| context.error_handler.DoesNotMatch(str, length); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(GetPatternString()); |
| } |
| |
| return CreateParallelValidator(context); |
| } |
| |
| bool StartObject(Context& context) const { |
| if (!(type_ & (1 << kObjectSchemaType))) { |
| DisallowedType(context, GetObjectString()); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); |
| } |
| |
| if (hasDependencies_ || hasRequired_) { |
| context.propertyExist = static_cast<bool*>(context.factory.MallocState(sizeof(bool) * propertyCount_)); |
| std::memset(context.propertyExist, 0, sizeof(bool) * propertyCount_); |
| } |
| |
| if (patternProperties_) { // pre-allocate schema array |
| SizeType count = patternPropertyCount_ + 1; // extra for valuePatternValidatorType |
| context.patternPropertiesSchemas = static_cast<const SchemaType**>(context.factory.MallocState(sizeof(const SchemaType*) * count)); |
| context.patternPropertiesSchemaCount = 0; |
| std::memset(context.patternPropertiesSchemas, 0, sizeof(SchemaType*) * count); |
| } |
| |
| return CreateParallelValidator(context); |
| } |
| |
| bool Key(Context& context, const Ch* str, SizeType len, bool) const { |
| if (patternProperties_) { |
| context.patternPropertiesSchemaCount = 0; |
| for (SizeType i = 0; i < patternPropertyCount_; i++) |
| if (patternProperties_[i].pattern && IsPatternMatch(patternProperties_[i].pattern, str, len)) { |
| context.patternPropertiesSchemas[context.patternPropertiesSchemaCount++] = patternProperties_[i].schema; |
| context.valueSchema = typeless_; |
| } |
| } |
| |
| SizeType index; |
| if (FindPropertyIndex(ValueType(str, len).Move(), &index)) { |
| if (context.patternPropertiesSchemaCount > 0) { |
| context.patternPropertiesSchemas[context.patternPropertiesSchemaCount++] = properties_[index].schema; |
| context.valueSchema = typeless_; |
| context.valuePatternValidatorType = Context::kPatternValidatorWithProperty; |
| } |
| else |
| context.valueSchema = properties_[index].schema; |
| |
| if (context.propertyExist) |
| context.propertyExist[index] = true; |
| |
| return true; |
| } |
| |
| if (additionalPropertiesSchema_) { |
| if (additionalPropertiesSchema_ && context.patternPropertiesSchemaCount > 0) { |
| context.patternPropertiesSchemas[context.patternPropertiesSchemaCount++] = additionalPropertiesSchema_; |
| context.valueSchema = typeless_; |
| context.valuePatternValidatorType = Context::kPatternValidatorWithAdditionalProperty; |
| } |
| else |
| context.valueSchema = additionalPropertiesSchema_; |
| return true; |
| } |
| else if (additionalProperties_) { |
| context.valueSchema = typeless_; |
| return true; |
| } |
| |
| if (context.patternPropertiesSchemaCount == 0) { // patternProperties are not additional properties |
| context.error_handler.DisallowedProperty(str, len); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(GetAdditionalPropertiesString()); |
| } |
| |
| return true; |
| } |
| |
| bool EndObject(Context& context, SizeType memberCount) const { |
| if (hasRequired_) { |
| context.error_handler.StartMissingProperties(); |
| for (SizeType index = 0; index < propertyCount_; index++) |
| if (properties_[index].required && !context.propertyExist[index]) |
| if (properties_[index].schema->defaultValueLength_ == 0 ) |
| context.error_handler.AddMissingProperty(properties_[index].name); |
| if (context.error_handler.EndMissingProperties()) |
| RAPIDJSON_INVALID_KEYWORD_RETURN(GetRequiredString()); |
| } |
| |
| if (memberCount < minProperties_) { |
| context.error_handler.TooFewProperties(memberCount, minProperties_); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinPropertiesString()); |
| } |
| |
| if (memberCount > maxProperties_) { |
| context.error_handler.TooManyProperties(memberCount, maxProperties_); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaxPropertiesString()); |
| } |
| |
| if (hasDependencies_) { |
| context.error_handler.StartDependencyErrors(); |
| for (SizeType sourceIndex = 0; sourceIndex < propertyCount_; sourceIndex++) { |
| const Property& source = properties_[sourceIndex]; |
| if (context.propertyExist[sourceIndex]) { |
| if (source.dependencies) { |
| context.error_handler.StartMissingDependentProperties(); |
| for (SizeType targetIndex = 0; targetIndex < propertyCount_; targetIndex++) |
| if (source.dependencies[targetIndex] && !context.propertyExist[targetIndex]) |
| context.error_handler.AddMissingDependentProperty(properties_[targetIndex].name); |
| context.error_handler.EndMissingDependentProperties(source.name); |
| } |
| else if (source.dependenciesSchema) { |
| ISchemaValidator* dependenciesValidator = context.validators[source.dependenciesValidatorIndex]; |
| if (!dependenciesValidator->IsValid()) |
| context.error_handler.AddDependencySchemaError(source.name, dependenciesValidator); |
| } |
| } |
| } |
| if (context.error_handler.EndDependencyErrors()) |
| RAPIDJSON_INVALID_KEYWORD_RETURN(GetDependenciesString()); |
| } |
| |
| return true; |
| } |
| |
| bool StartArray(Context& context) const { |
| if (!(type_ & (1 << kArraySchemaType))) { |
| DisallowedType(context, GetArrayString()); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); |
| } |
| |
| context.arrayElementIndex = 0; |
| context.inArray = true; |
| |
| return CreateParallelValidator(context); |
| } |
| |
| bool EndArray(Context& context, SizeType elementCount) const { |
| context.inArray = false; |
| |
| if (elementCount < minItems_) { |
| context.error_handler.TooFewItems(elementCount, minItems_); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinItemsString()); |
| } |
| |
| if (elementCount > maxItems_) { |
| context.error_handler.TooManyItems(elementCount, maxItems_); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaxItemsString()); |
| } |
| |
| return true; |
| } |
| |
| // Generate functions for string literal according to Ch |
| #define RAPIDJSON_STRING_(name, ...) \ |
| static const ValueType& Get##name##String() {\ |
| static const Ch s[] = { __VA_ARGS__, '\0' };\ |
| static const ValueType v(s, static_cast<SizeType>(sizeof(s) / sizeof(Ch) - 1));\ |
| return v;\ |
| } |
| |
| RAPIDJSON_STRING_(Null, 'n', 'u', 'l', 'l') |
| RAPIDJSON_STRING_(Boolean, 'b', 'o', 'o', 'l', 'e', 'a', 'n') |
| RAPIDJSON_STRING_(Object, 'o', 'b', 'j', 'e', 'c', 't') |
| RAPIDJSON_STRING_(Array, 'a', 'r', 'r', 'a', 'y') |
| RAPIDJSON_STRING_(String, 's', 't', 'r', 'i', 'n', 'g') |
| RAPIDJSON_STRING_(Number, 'n', 'u', 'm', 'b', 'e', 'r') |
| RAPIDJSON_STRING_(Integer, 'i', 'n', 't', 'e', 'g', 'e', 'r') |
| RAPIDJSON_STRING_(Type, 't', 'y', 'p', 'e') |
| RAPIDJSON_STRING_(Enum, 'e', 'n', 'u', 'm') |
| RAPIDJSON_STRING_(AllOf, 'a', 'l', 'l', 'O', 'f') |
| RAPIDJSON_STRING_(AnyOf, 'a', 'n', 'y', 'O', 'f') |
| RAPIDJSON_STRING_(OneOf, 'o', 'n', 'e', 'O', 'f') |
| RAPIDJSON_STRING_(Not, 'n', 'o', 't') |
| RAPIDJSON_STRING_(Properties, 'p', 'r', 'o', 'p', 'e', 'r', 't', 'i', 'e', 's') |
| RAPIDJSON_STRING_(Required, 'r', 'e', 'q', 'u', 'i', 'r', 'e', 'd') |
| RAPIDJSON_STRING_(Dependencies, 'd', 'e', 'p', 'e', 'n', 'd', 'e', 'n', 'c', 'i', 'e', 's') |
| RAPIDJSON_STRING_(PatternProperties, 'p', 'a', 't', 't', 'e', 'r', 'n', 'P', 'r', 'o', 'p', 'e', 'r', 't', 'i', 'e', 's') |
| RAPIDJSON_STRING_(AdditionalProperties, 'a', 'd', 'd', 'i', 't', 'i', 'o', 'n', 'a', 'l', 'P', 'r', 'o', 'p', 'e', 'r', 't', 'i', 'e', 's') |
| RAPIDJSON_STRING_(MinProperties, 'm', 'i', 'n', 'P', 'r', 'o', 'p', 'e', 'r', 't', 'i', 'e', 's') |
| RAPIDJSON_STRING_(MaxProperties, 'm', 'a', 'x', 'P', 'r', 'o', 'p', 'e', 'r', 't', 'i', 'e', 's') |
| RAPIDJSON_STRING_(Items, 'i', 't', 'e', 'm', 's') |
| RAPIDJSON_STRING_(MinItems, 'm', 'i', 'n', 'I', 't', 'e', 'm', 's') |
| RAPIDJSON_STRING_(MaxItems, 'm', 'a', 'x', 'I', 't', 'e', 'm', 's') |
| RAPIDJSON_STRING_(AdditionalItems, 'a', 'd', 'd', 'i', 't', 'i', 'o', 'n', 'a', 'l', 'I', 't', 'e', 'm', 's') |
| RAPIDJSON_STRING_(UniqueItems, 'u', 'n', 'i', 'q', 'u', 'e', 'I', 't', 'e', 'm', 's') |
| RAPIDJSON_STRING_(MinLength, 'm', 'i', 'n', 'L', 'e', 'n', 'g', 't', 'h') |
| RAPIDJSON_STRING_(MaxLength, 'm', 'a', 'x', 'L', 'e', 'n', 'g', 't', 'h') |
| RAPIDJSON_STRING_(Pattern, 'p', 'a', 't', 't', 'e', 'r', 'n') |
| RAPIDJSON_STRING_(Minimum, 'm', 'i', 'n', 'i', 'm', 'u', 'm') |
| RAPIDJSON_STRING_(Maximum, 'm', 'a', 'x', 'i', 'm', 'u', 'm') |
| RAPIDJSON_STRING_(ExclusiveMinimum, 'e', 'x', 'c', 'l', 'u', 's', 'i', 'v', 'e', 'M', 'i', 'n', 'i', 'm', 'u', 'm') |
| RAPIDJSON_STRING_(ExclusiveMaximum, 'e', 'x', 'c', 'l', 'u', 's', 'i', 'v', 'e', 'M', 'a', 'x', 'i', 'm', 'u', 'm') |
| RAPIDJSON_STRING_(MultipleOf, 'm', 'u', 'l', 't', 'i', 'p', 'l', 'e', 'O', 'f') |
| RAPIDJSON_STRING_(DefaultValue, 'd', 'e', 'f', 'a', 'u', 'l', 't') |
| |
| #undef RAPIDJSON_STRING_ |
| |
| private: |
| enum SchemaValueType { |
| kNullSchemaType, |
| kBooleanSchemaType, |
| kObjectSchemaType, |
| kArraySchemaType, |
| kStringSchemaType, |
| kNumberSchemaType, |
| kIntegerSchemaType, |
| kTotalSchemaType |
| }; |
| |
| #if RAPIDJSON_SCHEMA_USE_INTERNALREGEX |
| typedef internal::GenericRegex<EncodingType, AllocatorType> RegexType; |
| #elif RAPIDJSON_SCHEMA_USE_STDREGEX |
| typedef std::basic_regex<Ch> RegexType; |
| #else |
| typedef char RegexType; |
| #endif |
| |
| struct SchemaArray { |
| SchemaArray() : schemas(), count() {} |
| ~SchemaArray() { AllocatorType::Free(schemas); } |
| const SchemaType** schemas; |
| SizeType begin; // begin index of context.validators |
| SizeType count; |
| }; |
| |
| template <typename V1, typename V2> |
| void AddUniqueElement(V1& a, const V2& v) { |
| for (typename V1::ConstValueIterator itr = a.Begin(); itr != a.End(); ++itr) |
| if (*itr == v) |
| return; |
| V1 c(v, *allocator_); |
| a.PushBack(c, *allocator_); |
| } |
| |
| static const ValueType* GetMember(const ValueType& value, const ValueType& name) { |
| typename ValueType::ConstMemberIterator itr = value.FindMember(name); |
| return itr != value.MemberEnd() ? &(itr->value) : 0; |
| } |
| |
| static void AssignIfExist(bool& out, const ValueType& value, const ValueType& name) { |
| if (const ValueType* v = GetMember(value, name)) |
| if (v->IsBool()) |
| out = v->GetBool(); |
| } |
| |
| static void AssignIfExist(SizeType& out, const ValueType& value, const ValueType& name) { |
| if (const ValueType* v = GetMember(value, name)) |
| if (v->IsUint64() && v->GetUint64() <= SizeType(~0)) |
| out = static_cast<SizeType>(v->GetUint64()); |
| } |
| |
| void AssignIfExist(SchemaArray& out, SchemaDocumentType& schemaDocument, const PointerType& p, const ValueType& value, const ValueType& name, const ValueType& document) { |
| if (const ValueType* v = GetMember(value, name)) { |
| if (v->IsArray() && v->Size() > 0) { |
| PointerType q = p.Append(name, allocator_); |
| out.count = v->Size(); |
| out.schemas = static_cast<const Schema**>(allocator_->Malloc(out.count * sizeof(const Schema*))); |
| memset(out.schemas, 0, sizeof(Schema*)* out.count); |
| for (SizeType i = 0; i < out.count; i++) |
| schemaDocument.CreateSchema(&out.schemas[i], q.Append(i, allocator_), (*v)[i], document); |
| out.begin = validatorCount_; |
| validatorCount_ += out.count; |
| } |
| } |
| } |
| |
| #if RAPIDJSON_SCHEMA_USE_INTERNALREGEX |
| template <typename ValueType> |
| RegexType* CreatePattern(const ValueType& value) { |
| if (value.IsString()) { |
| RegexType* r = new (allocator_->Malloc(sizeof(RegexType))) RegexType(value.GetString(), allocator_); |
| if (!r->IsValid()) { |
| r->~RegexType(); |
| AllocatorType::Free(r); |
| r = 0; |
| } |
| return r; |
| } |
| return 0; |
| } |
| |
| static bool IsPatternMatch(const RegexType* pattern, const Ch *str, SizeType) { |
| GenericRegexSearch<RegexType> rs(*pattern); |
| return rs.Search(str); |
| } |
| #elif RAPIDJSON_SCHEMA_USE_STDREGEX |
| template <typename ValueType> |
| RegexType* CreatePattern(const ValueType& value) { |
| if (value.IsString()) |
| RegexType *r = static_cast<RegexType*>(allocator_->Malloc(sizeof(RegexType))); |
| try { |
| return new (r) RegexType(value.GetString(), std::size_t(value.GetStringLength()), std::regex_constants::ECMAScript); |
| } |
| catch (const std::regex_error&) { |
| AllocatorType::Free(r); |
| } |
| return 0; |
| } |
| |
| static bool IsPatternMatch(const RegexType* pattern, const Ch *str, SizeType length) { |
| std::match_results<const Ch*> r; |
| return std::regex_search(str, str + length, r, *pattern); |
| } |
| #else |
| template <typename ValueType> |
| RegexType* CreatePattern(const ValueType&) { return 0; } |
| |
| static bool IsPatternMatch(const RegexType*, const Ch *, SizeType) { return true; } |
| #endif // RAPIDJSON_SCHEMA_USE_STDREGEX |
| |
| void AddType(const ValueType& type) { |
| if (type == GetNullString() ) type_ |= 1 << kNullSchemaType; |
| else if (type == GetBooleanString()) type_ |= 1 << kBooleanSchemaType; |
| else if (type == GetObjectString() ) type_ |= 1 << kObjectSchemaType; |
| else if (type == GetArrayString() ) type_ |= 1 << kArraySchemaType; |
| else if (type == GetStringString() ) type_ |= 1 << kStringSchemaType; |
| else if (type == GetIntegerString()) type_ |= 1 << kIntegerSchemaType; |
| else if (type == GetNumberString() ) type_ |= (1 << kNumberSchemaType) | (1 << kIntegerSchemaType); |
| } |
| |
| bool CreateParallelValidator(Context& context) const { |
| if (enum_ || context.arrayUniqueness) |
| context.hasher = context.factory.CreateHasher(); |
| |
| if (validatorCount_) { |
| RAPIDJSON_ASSERT(context.validators == 0); |
| context.validators = static_cast<ISchemaValidator**>(context.factory.MallocState(sizeof(ISchemaValidator*) * validatorCount_)); |
| context.validatorCount = validatorCount_; |
| |
| if (allOf_.schemas) |
| CreateSchemaValidators(context, allOf_); |
| |
| if (anyOf_.schemas) |
| CreateSchemaValidators(context, anyOf_); |
| |
| if (oneOf_.schemas) |
| CreateSchemaValidators(context, oneOf_); |
| |
| if (not_) |
| context.validators[notValidatorIndex_] = context.factory.CreateSchemaValidator(*not_); |
| |
| if (hasSchemaDependencies_) { |
| for (SizeType i = 0; i < propertyCount_; i++) |
| if (properties_[i].dependenciesSchema) |
| context.validators[properties_[i].dependenciesValidatorIndex] = context.factory.CreateSchemaValidator(*properties_[i].dependenciesSchema); |
| } |
| } |
| |
| return true; |
| } |
| |
| void CreateSchemaValidators(Context& context, const SchemaArray& schemas) const { |
| for (SizeType i = 0; i < schemas.count; i++) |
| context.validators[schemas.begin + i] = context.factory.CreateSchemaValidator(*schemas.schemas[i]); |
| } |
| |
| // O(n) |
| bool FindPropertyIndex(const ValueType& name, SizeType* outIndex) const { |
| SizeType len = name.GetStringLength(); |
| const Ch* str = name.GetString(); |
| for (SizeType index = 0; index < propertyCount_; index++) |
| if (properties_[index].name.GetStringLength() == len && |
| (std::memcmp(properties_[index].name.GetString(), str, sizeof(Ch) * len) == 0)) |
| { |
| *outIndex = index; |
| return true; |
| } |
| return false; |
| } |
| |
| bool CheckInt(Context& context, int64_t i) const { |
| if (!(type_ & ((1 << kIntegerSchemaType) | (1 << kNumberSchemaType)))) { |
| DisallowedType(context, GetIntegerString()); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); |
| } |
| |
| if (!minimum_.IsNull()) { |
| if (minimum_.IsInt64()) { |
| if (exclusiveMinimum_ ? i <= minimum_.GetInt64() : i < minimum_.GetInt64()) { |
| context.error_handler.BelowMinimum(i, minimum_, exclusiveMinimum_); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinimumString()); |
| } |
| } |
| else if (minimum_.IsUint64()) { |
| context.error_handler.BelowMinimum(i, minimum_, exclusiveMinimum_); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinimumString()); // i <= max(int64_t) < minimum.GetUint64() |
| } |
| else if (!CheckDoubleMinimum(context, static_cast<double>(i))) |
| return false; |
| } |
| |
| if (!maximum_.IsNull()) { |
| if (maximum_.IsInt64()) { |
| if (exclusiveMaximum_ ? i >= maximum_.GetInt64() : i > maximum_.GetInt64()) { |
| context.error_handler.AboveMaximum(i, maximum_, exclusiveMaximum_); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaximumString()); |
| } |
| } |
| else if (maximum_.IsUint64()) { } |
| /* do nothing */ // i <= max(int64_t) < maximum_.GetUint64() |
| else if (!CheckDoubleMaximum(context, static_cast<double>(i))) |
| return false; |
| } |
| |
| if (!multipleOf_.IsNull()) { |
| if (multipleOf_.IsUint64()) { |
| if (static_cast<uint64_t>(i >= 0 ? i : -i) % multipleOf_.GetUint64() != 0) { |
| context.error_handler.NotMultipleOf(i, multipleOf_); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(GetMultipleOfString()); |
| } |
| } |
| else if (!CheckDoubleMultipleOf(context, static_cast<double>(i))) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool CheckUint(Context& context, uint64_t i) const { |
| if (!(type_ & ((1 << kIntegerSchemaType) | (1 << kNumberSchemaType)))) { |
| DisallowedType(context, GetIntegerString()); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); |
| } |
| |
| if (!minimum_.IsNull()) { |
| if (minimum_.IsUint64()) { |
| if (exclusiveMinimum_ ? i <= minimum_.GetUint64() : i < minimum_.GetUint64()) { |
| context.error_handler.BelowMinimum(i, minimum_, exclusiveMinimum_); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinimumString()); |
| } |
| } |
| else if (minimum_.IsInt64()) |
| /* do nothing */; // i >= 0 > minimum.Getint64() |
| else if (!CheckDoubleMinimum(context, static_cast<double>(i))) |
| return false; |
| } |
| |
| if (!maximum_.IsNull()) { |
| if (maximum_.IsUint64()) { |
| if (exclusiveMaximum_ ? i >= maximum_.GetUint64() : i > maximum_.GetUint64()) { |
| context.error_handler.AboveMaximum(i, maximum_, exclusiveMaximum_); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaximumString()); |
| } |
| } |
| else if (maximum_.IsInt64()) { |
| context.error_handler.AboveMaximum(i, maximum_, exclusiveMaximum_); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaximumString()); // i >= 0 > maximum_ |
| } |
| else if (!CheckDoubleMaximum(context, static_cast<double>(i))) |
| return false; |
| } |
| |
| if (!multipleOf_.IsNull()) { |
| if (multipleOf_.IsUint64()) { |
| if (i % multipleOf_.GetUint64() != 0) { |
| context.error_handler.NotMultipleOf(i, multipleOf_); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(GetMultipleOfString()); |
| } |
| } |
| else if (!CheckDoubleMultipleOf(context, static_cast<double>(i))) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool CheckDoubleMinimum(Context& context, double d) const { |
| if (exclusiveMinimum_ ? d <= minimum_.GetDouble() : d < minimum_.GetDouble()) { |
| context.error_handler.BelowMinimum(d, minimum_, exclusiveMinimum_); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinimumString()); |
| } |
| return true; |
| } |
| |
| bool CheckDoubleMaximum(Context& context, double d) const { |
| if (exclusiveMaximum_ ? d >= maximum_.GetDouble() : d > maximum_.GetDouble()) { |
| context.error_handler.AboveMaximum(d, maximum_, exclusiveMaximum_); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaximumString()); |
| } |
| return true; |
| } |
| |
| bool CheckDoubleMultipleOf(Context& context, double d) const { |
| double a = std::abs(d), b = std::abs(multipleOf_.GetDouble()); |
| double q = std::floor(a / b); |
| double r = a - q * b; |
| if (r > 0.0) { |
| context.error_handler.NotMultipleOf(d, multipleOf_); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(GetMultipleOfString()); |
| } |
| return true; |
| } |
| |
| void DisallowedType(Context& context, const ValueType& actualType) const { |
| ErrorHandler& eh = context.error_handler; |
| eh.StartDisallowedType(); |
| |
| if (type_ & (1 << kNullSchemaType)) eh.AddExpectedType(GetNullString()); |
| if (type_ & (1 << kBooleanSchemaType)) eh.AddExpectedType(GetBooleanString()); |
| if (type_ & (1 << kObjectSchemaType)) eh.AddExpectedType(GetObjectString()); |
| if (type_ & (1 << kArraySchemaType)) eh.AddExpectedType(GetArrayString()); |
| if (type_ & (1 << kStringSchemaType)) eh.AddExpectedType(GetStringString()); |
| |
| if (type_ & (1 << kNumberSchemaType)) eh.AddExpectedType(GetNumberString()); |
| else if (type_ & (1 << kIntegerSchemaType)) eh.AddExpectedType(GetIntegerString()); |
| |
| eh.EndDisallowedType(actualType); |
| } |
| |
| struct Property { |
| Property() : schema(), dependenciesSchema(), dependenciesValidatorIndex(), dependencies(), required(false) {} |
| ~Property() { AllocatorType::Free(dependencies); } |
| SValue name; |
| const SchemaType* schema; |
| const SchemaType* dependenciesSchema; |
| SizeType dependenciesValidatorIndex; |
| bool* dependencies; |
| bool required; |
| }; |
| |
| struct PatternProperty { |
| PatternProperty() : schema(), pattern() {} |
| ~PatternProperty() { |
| if (pattern) { |
| pattern->~RegexType(); |
| AllocatorType::Free(pattern); |
| } |
| } |
| const SchemaType* schema; |
| RegexType* pattern; |
| }; |
| |
| AllocatorType* allocator_; |
| SValue uri_; |
| PointerType pointer_; |
| const SchemaType* typeless_; |
| uint64_t* enum_; |
| SizeType enumCount_; |
| SchemaArray allOf_; |
| SchemaArray anyOf_; |
| SchemaArray oneOf_; |
| const SchemaType* not_; |
| unsigned type_; // bitmask of kSchemaType |
| SizeType validatorCount_; |
| SizeType notValidatorIndex_; |
| |
| Property* properties_; |
| const SchemaType* additionalPropertiesSchema_; |
| PatternProperty* patternProperties_; |
| SizeType patternPropertyCount_; |
| SizeType propertyCount_; |
| SizeType minProperties_; |
| SizeType maxProperties_; |
| bool additionalProperties_; |
| bool hasDependencies_; |
| bool hasRequired_; |
| bool hasSchemaDependencies_; |
| |
| const SchemaType* additionalItemsSchema_; |
| const SchemaType* itemsList_; |
| const SchemaType** itemsTuple_; |
| SizeType itemsTupleCount_; |
| SizeType minItems_; |
| SizeType maxItems_; |
| bool additionalItems_; |
| bool uniqueItems_; |
| |
| RegexType* pattern_; |
| SizeType minLength_; |
| SizeType maxLength_; |
| |
| SValue minimum_; |
| SValue maximum_; |
| SValue multipleOf_; |
| bool exclusiveMinimum_; |
| bool exclusiveMaximum_; |
| |
| SizeType defaultValueLength_; |
| }; |
| |
| template<typename Stack, typename Ch> |
| struct TokenHelper { |
| RAPIDJSON_FORCEINLINE static void AppendIndexToken(Stack& documentStack, SizeType index) { |
| *documentStack.template Push<Ch>() = '/'; |
| char buffer[21]; |
| size_t length = static_cast<size_t>((sizeof(SizeType) == 4 ? u32toa(index, buffer) : u64toa(index, buffer)) - buffer); |
| for (size_t i = 0; i < length; i++) |
| *documentStack.template Push<Ch>() = static_cast<Ch>(buffer[i]); |
| } |
| }; |
| |
| // Partial specialized version for char to prevent buffer copying. |
| template <typename Stack> |
| struct TokenHelper<Stack, char> { |
| RAPIDJSON_FORCEINLINE static void AppendIndexToken(Stack& documentStack, SizeType index) { |
| if (sizeof(SizeType) == 4) { |
| char *buffer = documentStack.template Push<char>(1 + 10); // '/' + uint |
| *buffer++ = '/'; |
| const char* end = internal::u32toa(index, buffer); |
| documentStack.template Pop<char>(static_cast<size_t>(10 - (end - buffer))); |
| } |
| else { |
| char *buffer = documentStack.template Push<char>(1 + 20); // '/' + uint64 |
| *buffer++ = '/'; |
| const char* end = internal::u64toa(index, buffer); |
| documentStack.template Pop<char>(static_cast<size_t>(20 - (end - buffer))); |
| } |
| } |
| }; |
| |
| } // namespace internal |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // IGenericRemoteSchemaDocumentProvider |
| |
| template <typename SchemaDocumentType> |
| class IGenericRemoteSchemaDocumentProvider { |
| public: |
| typedef typename SchemaDocumentType::Ch Ch; |
| |
| virtual ~IGenericRemoteSchemaDocumentProvider() {} |
| virtual const SchemaDocumentType* GetRemoteDocument(const Ch* uri, SizeType length) = 0; |
| }; |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // GenericSchemaDocument |
| |
| //! JSON schema document. |
| /*! |
| A JSON schema document is a compiled version of a JSON schema. |
| It is basically a tree of internal::Schema. |
| |
| \note This is an immutable class (i.e. its instance cannot be modified after construction). |
| \tparam ValueT Type of JSON value (e.g. \c Value ), which also determine the encoding. |
| \tparam Allocator Allocator type for allocating memory of this document. |
| */ |
| template <typename ValueT, typename Allocator = CrtAllocator> |
| class GenericSchemaDocument { |
| public: |
| typedef ValueT ValueType; |
| typedef IGenericRemoteSchemaDocumentProvider<GenericSchemaDocument> IRemoteSchemaDocumentProviderType; |
| typedef Allocator AllocatorType; |
| typedef typename ValueType::EncodingType EncodingType; |
| typedef typename EncodingType::Ch Ch; |
| typedef internal::Schema<GenericSchemaDocument> SchemaType; |
| typedef GenericPointer<ValueType, Allocator> PointerType; |
| typedef GenericValue<EncodingType, Allocator> URIType; |
| friend class internal::Schema<GenericSchemaDocument>; |
| template <typename, typename, typename> |
| friend class GenericSchemaValidator; |
| |
| //! Constructor. |
| /*! |
| Compile a JSON document into schema document. |
| |
| \param document A JSON document as source. |
| \param uri The base URI of this schema document for purposes of violation reporting. |
| \param uriLength Length of \c name, in code points. |
| \param remoteProvider An optional remote schema document provider for resolving remote reference. Can be null. |
| \param allocator An optional allocator instance for allocating memory. Can be null. |
| */ |
| explicit GenericSchemaDocument(const ValueType& document, const Ch* uri = 0, SizeType uriLength = 0, |
| IRemoteSchemaDocumentProviderType* remoteProvider = 0, Allocator* allocator = 0) : |
| remoteProvider_(remoteProvider), |
| allocator_(allocator), |
| ownAllocator_(), |
| root_(), |
| typeless_(), |
| schemaMap_(allocator, kInitialSchemaMapSize), |
| schemaRef_(allocator, kInitialSchemaRefSize) |
| { |
| if (!allocator_) |
| ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator)(); |
| |
| Ch noUri[1] = {0}; |
| uri_.SetString(uri ? uri : noUri, uriLength, *allocator_); |
| |
| typeless_ = static_cast<SchemaType*>(allocator_->Malloc(sizeof(SchemaType))); |
| new (typeless_) SchemaType(this, PointerType(), ValueType(kObjectType).Move(), ValueType(kObjectType).Move(), allocator_); |
| |
| // Generate root schema, it will call CreateSchema() to create sub-schemas, |
| // And call AddRefSchema() if there are $ref. |
| CreateSchemaRecursive(&root_, PointerType(), document, document); |
| |
| // Resolve $ref |
| while (!schemaRef_.Empty()) { |
| SchemaRefEntry* refEntry = schemaRef_.template Pop<SchemaRefEntry>(1); |
| if (const SchemaType* s = GetSchema(refEntry->target)) { |
| if (refEntry->schema) |
| *refEntry->schema = s; |
| |
| // Create entry in map if not exist |
| if (!GetSchema(refEntry->source)) { |
| new (schemaMap_.template Push<SchemaEntry>()) SchemaEntry(refEntry->source, const_cast<SchemaType*>(s), false, allocator_); |
| } |
| } |
| else if (refEntry->schema) |
| *refEntry->schema = typeless_; |
| |
| refEntry->~SchemaRefEntry(); |
| } |
| |
| RAPIDJSON_ASSERT(root_ != 0); |
| |
| schemaRef_.ShrinkToFit(); // Deallocate all memory for ref |
| } |
| |
| #if RAPIDJSON_HAS_CXX11_RVALUE_REFS |
| //! Move constructor in C++11 |
| GenericSchemaDocument(GenericSchemaDocument&& rhs) RAPIDJSON_NOEXCEPT : |
| remoteProvider_(rhs.remoteProvider_), |
| allocator_(rhs.allocator_), |
| ownAllocator_(rhs.ownAllocator_), |
| root_(rhs.root_), |
| typeless_(rhs.typeless_), |
| schemaMap_(std::move(rhs.schemaMap_)), |
| schemaRef_(std::move(rhs.schemaRef_)), |
| uri_(std::move(rhs.uri_)) |
| { |
| rhs.remoteProvider_ = 0; |
| rhs.allocator_ = 0; |
| rhs.ownAllocator_ = 0; |
| rhs.typeless_ = 0; |
| } |
| #endif |
| |
| //! Destructor |
| ~GenericSchemaDocument() { |
| while (!schemaMap_.Empty()) |
| schemaMap_.template Pop<SchemaEntry>(1)->~SchemaEntry(); |
| |
| if (typeless_) { |
| typeless_->~SchemaType(); |
| Allocator::Free(typeless_); |
| } |
| |
| RAPIDJSON_DELETE(ownAllocator_); |
| } |
| |
| const URIType& GetURI() const { return uri_; } |
| |
| //! Get the root schema. |
| const SchemaType& GetRoot() const { return *root_; } |
| |
| private: |
| //! Prohibit copying |
| GenericSchemaDocument(const GenericSchemaDocument&); |
| //! Prohibit assignment |
| GenericSchemaDocument& operator=(const GenericSchemaDocument&); |
| |
| struct SchemaRefEntry { |
| SchemaRefEntry(const PointerType& s, const PointerType& t, const SchemaType** outSchema, Allocator *allocator) : source(s, allocator), target(t, allocator), schema(outSchema) {} |
| PointerType source; |
| PointerType target; |
| const SchemaType** schema; |
| }; |
| |
| struct SchemaEntry { |
| SchemaEntry(const PointerType& p, SchemaType* s, bool o, Allocator* allocator) : pointer(p, allocator), schema(s), owned(o) {} |
| ~SchemaEntry() { |
| if (owned) { |
| schema->~SchemaType(); |
| Allocator::Free(schema); |
| } |
| } |
| PointerType pointer; |
| SchemaType* schema; |
| bool owned; |
| }; |
| |
| void CreateSchemaRecursive(const SchemaType** schema, const PointerType& pointer, const ValueType& v, const ValueType& document) { |
| if (schema) |
| *schema = typeless_; |
| |
| if (v.GetType() == kObjectType) { |
| const SchemaType* s = GetSchema(pointer); |
| if (!s) |
| CreateSchema(schema, pointer, v, document); |
| |
| for (typename ValueType::ConstMemberIterator itr = v.MemberBegin(); itr != v.MemberEnd(); ++itr) |
| CreateSchemaRecursive(0, pointer.Append(itr->name, allocator_), itr->value, document); |
| } |
| else if (v.GetType() == kArrayType) |
| for (SizeType i = 0; i < v.Size(); i++) |
| CreateSchemaRecursive(0, pointer.Append(i, allocator_), v[i], document); |
| } |
| |
| void CreateSchema(const SchemaType** schema, const PointerType& pointer, const ValueType& v, const ValueType& document) { |
| RAPIDJSON_ASSERT(pointer.IsValid()); |
| if (v.IsObject()) { |
| if (!HandleRefSchema(pointer, schema, v, document)) { |
| SchemaType* s = new (allocator_->Malloc(sizeof(SchemaType))) SchemaType(this, pointer, v, document, allocator_); |
| new (schemaMap_.template Push<SchemaEntry>()) SchemaEntry(pointer, s, true, allocator_); |
| if (schema) |
| *schema = s; |
| } |
| } |
| } |
| |
| bool HandleRefSchema(const PointerType& source, const SchemaType** schema, const ValueType& v, const ValueType& document) { |
| static const Ch kRefString[] = { '$', 'r', 'e', 'f', '\0' }; |
| static const ValueType kRefValue(kRefString, 4); |
| |
| typename ValueType::ConstMemberIterator itr = v.FindMember(kRefValue); |
| if (itr == v.MemberEnd()) |
| return false; |
| |
| if (itr->value.IsString()) { |
| SizeType len = itr->value.GetStringLength(); |
| if (len > 0) { |
| const Ch* s = itr->value.GetString(); |
| SizeType i = 0; |
| while (i < len && s[i] != '#') // Find the first # |
| i++; |
| |
| if (i > 0) { // Remote reference, resolve immediately |
| if (remoteProvider_) { |
| if (const GenericSchemaDocument* remoteDocument = remoteProvider_->GetRemoteDocument(s, i)) { |
| PointerType pointer(&s[i], len - i, allocator_); |
| if (pointer.IsValid()) { |
| if (const SchemaType* sc = remoteDocument->GetSchema(pointer)) { |
| if (schema) |
| *schema = sc; |
| new (schemaMap_.template Push<SchemaEntry>()) SchemaEntry(source, const_cast<SchemaType*>(sc), false, allocator_); |
| return true; |
| } |
| } |
| } |
| } |
| } |
| else if (s[i] == '#') { // Local reference, defer resolution |
| PointerType pointer(&s[i], len - i, allocator_); |
| if (pointer.IsValid()) { |
| if (const ValueType* nv = pointer.Get(document)) |
| if (HandleRefSchema(source, schema, *nv, document)) |
| return true; |
| |
| new (schemaRef_.template Push<SchemaRefEntry>()) SchemaRefEntry(source, pointer, schema, allocator_); |
| return true; |
| } |
| } |
| } |
| } |
| return false; |
| } |
| |
| const SchemaType* GetSchema(const PointerType& pointer) const { |
| for (const SchemaEntry* target = schemaMap_.template Bottom<SchemaEntry>(); target != schemaMap_.template End<SchemaEntry>(); ++target) |
| if (pointer == target->pointer) |
| return target->schema; |
| return 0; |
| } |
| |
| PointerType GetPointer(const SchemaType* schema) const { |
| for (const SchemaEntry* target = schemaMap_.template Bottom<SchemaEntry>(); target != schemaMap_.template End<SchemaEntry>(); ++target) |
| if (schema == target->schema) |
| return target->pointer; |
| return PointerType(); |
| } |
| |
| const SchemaType* GetTypeless() const { return typeless_; } |
| |
| static const size_t kInitialSchemaMapSize = 64; |
| static const size_t kInitialSchemaRefSize = 64; |
| |
| IRemoteSchemaDocumentProviderType* remoteProvider_; |
| Allocator *allocator_; |
| Allocator *ownAllocator_; |
| const SchemaType* root_; //!< Root schema. |
| SchemaType* typeless_; |
| internal::Stack<Allocator> schemaMap_; // Stores created Pointer -> Schemas |
| internal::Stack<Allocator> schemaRef_; // Stores Pointer from $ref and schema which holds the $ref |
| URIType uri_; |
| }; |
| |
| //! GenericSchemaDocument using Value type. |
| typedef GenericSchemaDocument<Value> SchemaDocument; |
| //! IGenericRemoteSchemaDocumentProvider using SchemaDocument. |
| typedef IGenericRemoteSchemaDocumentProvider<SchemaDocument> IRemoteSchemaDocumentProvider; |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // GenericSchemaValidator |
| |
| //! JSON Schema Validator. |
| /*! |
| A SAX style JSON schema validator. |
| It uses a \c GenericSchemaDocument to validate SAX events. |
| It delegates the incoming SAX events to an output handler. |
| The default output handler does nothing. |
| It can be reused multiple times by calling \c Reset(). |
| |
| \tparam SchemaDocumentType Type of schema document. |
| \tparam OutputHandler Type of output handler. Default handler does nothing. |
| \tparam StateAllocator Allocator for storing the internal validation states. |
| */ |
| template < |
| typename SchemaDocumentType, |
| typename OutputHandler = BaseReaderHandler<typename SchemaDocumentType::SchemaType::EncodingType>, |
| typename StateAllocator = CrtAllocator> |
| class GenericSchemaValidator : |
| public internal::ISchemaStateFactory<typename SchemaDocumentType::SchemaType>, |
| public internal::ISchemaValidator, |
| public internal::IValidationErrorHandler<typename SchemaDocumentType::SchemaType> |
| { |
| public: |
| typedef typename SchemaDocumentType::SchemaType SchemaType; |
| typedef typename SchemaDocumentType::PointerType PointerType; |
| typedef typename SchemaType::EncodingType EncodingType; |
| typedef typename SchemaType::SValue SValue; |
| typedef typename EncodingType::Ch Ch; |
| typedef GenericStringRef<Ch> StringRefType; |
| typedef GenericValue<EncodingType, StateAllocator> ValueType; |
| |
| //! Constructor without output handler. |
| /*! |
| \param schemaDocument The schema document to conform to. |
| \param allocator Optional allocator for storing internal validation states. |
| \param schemaStackCapacity Optional initial capacity of schema path stack. |
| \param documentStackCapacity Optional initial capacity of document path stack. |
| */ |
| GenericSchemaValidator( |
| const SchemaDocumentType& schemaDocument, |
| StateAllocator* allocator = 0, |
| size_t schemaStackCapacity = kDefaultSchemaStackCapacity, |
| size_t documentStackCapacity = kDefaultDocumentStackCapacity) |
| : |
| schemaDocument_(&schemaDocument), |
| root_(schemaDocument.GetRoot()), |
| stateAllocator_(allocator), |
| ownStateAllocator_(0), |
| schemaStack_(allocator, schemaStackCapacity), |
| documentStack_(allocator, documentStackCapacity), |
| outputHandler_(0), |
| error_(kObjectType), |
| currentError_(), |
| missingDependents_(), |
| valid_(true) |
| #if RAPIDJSON_SCHEMA_VERBOSE |
| , depth_(0) |
| #endif |
| { |
| } |
| |
| //! Constructor with output handler. |
| /*! |
| \param schemaDocument The schema document to conform to. |
| \param allocator Optional allocator for storing internal validation states. |
| \param schemaStackCapacity Optional initial capacity of schema path stack. |
| \param documentStackCapacity Optional initial capacity of document path stack. |
| */ |
| GenericSchemaValidator( |
| const SchemaDocumentType& schemaDocument, |
| OutputHandler& outputHandler, |
| StateAllocator* allocator = 0, |
| size_t schemaStackCapacity = kDefaultSchemaStackCapacity, |
| size_t documentStackCapacity = kDefaultDocumentStackCapacity) |
| : |
| schemaDocument_(&schemaDocument), |
| root_(schemaDocument.GetRoot()), |
| stateAllocator_(allocator), |
| ownStateAllocator_(0), |
| schemaStack_(allocator, schemaStackCapacity), |
| documentStack_(allocator, documentStackCapacity), |
| outputHandler_(&outputHandler), |
| error_(kObjectType), |
| currentError_(), |
| missingDependents_(), |
| valid_(true) |
| #if RAPIDJSON_SCHEMA_VERBOSE |
| , depth_(0) |
| #endif |
| { |
| } |
| |
| //! Destructor. |
| ~GenericSchemaValidator() { |
| Reset(); |
| RAPIDJSON_DELETE(ownStateAllocator_); |
| } |
| |
| //! Reset the internal states. |
| void Reset() { |
| while (!schemaStack_.Empty()) |
| PopSchema(); |
| documentStack_.Clear(); |
| error_.SetObject(); |
| currentError_.SetNull(); |
| missingDependents_.SetNull(); |
| valid_ = true; |
| } |
| |
| //! Checks whether the current state is valid. |
| // Implementation of ISchemaValidator |
| virtual bool IsValid() const { return valid_; } |
| |
| //! Gets the error object. |
| ValueType& GetError() { return error_; } |
| const ValueType& GetError() const { return error_; } |
| |
| //! Gets the JSON pointer pointed to the invalid schema. |
| PointerType GetInvalidSchemaPointer() const { |
| return schemaStack_.Empty() ? PointerType() : CurrentSchema().GetPointer(); |
| } |
| |
| //! Gets the keyword of invalid schema. |
| const Ch* GetInvalidSchemaKeyword() const { |
| return schemaStack_.Empty() ? 0 : CurrentContext().invalidKeyword; |
| } |
| |
| //! Gets the JSON pointer pointed to the invalid value. |
| PointerType GetInvalidDocumentPointer() const { |
| if (documentStack_.Empty()) { |
| return PointerType(); |
| } |
| else { |
| return PointerType(documentStack_.template Bottom<Ch>(), documentStack_.GetSize() / sizeof(Ch)); |
| } |
| } |
| |
| void NotMultipleOf(int64_t actual, const SValue& expected) { |
| AddNumberError(SchemaType::GetMultipleOfString(), ValueType(actual).Move(), expected); |
| } |
| void NotMultipleOf(uint64_t actual, const SValue& expected) { |
| AddNumberError(SchemaType::GetMultipleOfString(), ValueType(actual).Move(), expected); |
| } |
| void NotMultipleOf(double actual, const SValue& expected) { |
| AddNumberError(SchemaType::GetMultipleOfString(), ValueType(actual).Move(), expected); |
| } |
| void AboveMaximum(int64_t actual, const SValue& expected, bool exclusive) { |
| AddNumberError(SchemaType::GetMaximumString(), ValueType(actual).Move(), expected, |
| exclusive ? &SchemaType::GetExclusiveMaximumString : 0); |
| } |
| void AboveMaximum(uint64_t actual, const SValue& expected, bool exclusive) { |
| AddNumberError(SchemaType::GetMaximumString(), ValueType(actual).Move(), expected, |
| exclusive ? &SchemaType::GetExclusiveMaximumString : 0); |
| } |
| void AboveMaximum(double actual, const SValue& expected, bool exclusive) { |
| AddNumberError(SchemaType::GetMaximumString(), ValueType(actual).Move(), expected, |
| exclusive ? &SchemaType::GetExclusiveMaximumString : 0); |
| } |
| void BelowMinimum(int64_t actual, const SValue& expected, bool exclusive) { |
| AddNumberError(SchemaType::GetMinimumString(), ValueType(actual).Move(), expected, |
| exclusive ? &SchemaType::GetExclusiveMinimumString : 0); |
| } |
| void BelowMinimum(uint64_t actual, const SValue& expected, bool exclusive) { |
| AddNumberError(SchemaType::GetMinimumString(), ValueType(actual).Move(), expected, |
| exclusive ? &SchemaType::GetExclusiveMinimumString : 0); |
| } |
| void BelowMinimum(double actual, const SValue& expected, bool exclusive) { |
| AddNumberError(SchemaType::GetMinimumString(), ValueType(actual).Move(), expected, |
| exclusive ? &SchemaType::GetExclusiveMinimumString : 0); |
| } |
| |
| void TooLong(const Ch* str, SizeType length, SizeType expected) { |
| AddNumberError(SchemaType::GetMaxLengthString(), |
| ValueType(str, length, GetStateAllocator()).Move(), SValue(expected).Move()); |
| } |
| void TooShort(const Ch* str, SizeType length, SizeType expected) { |
| AddNumberError(SchemaType::GetMinLengthString(), |
| ValueType(str, length, GetStateAllocator()).Move(), SValue(expected).Move()); |
| } |
| void DoesNotMatch(const Ch* str, SizeType length) { |
| currentError_.SetObject(); |
| currentError_.AddMember(GetActualString(), ValueType(str, length, GetStateAllocator()).Move(), GetStateAllocator()); |
| AddCurrentError(SchemaType::GetPatternString()); |
| } |
| |
| void DisallowedItem(SizeType index) { |
| currentError_.SetObject(); |
| currentError_.AddMember(GetDisallowedString(), ValueType(index).Move(), GetStateAllocator()); |
| AddCurrentError(SchemaType::GetAdditionalItemsString(), true); |
| } |
| void TooFewItems(SizeType actualCount, SizeType expectedCount) { |
| AddNumberError(SchemaType::GetMinItemsString(), |
| ValueType(actualCount).Move(), SValue(expectedCount).Move()); |
| } |
| void TooManyItems(SizeType actualCount, SizeType expectedCount) { |
| AddNumberError(SchemaType::GetMaxItemsString(), |
| ValueType(actualCount).Move(), SValue(expectedCount).Move()); |
| } |
| void DuplicateItems(SizeType index1, SizeType index2) { |
| ValueType duplicates(kArrayType); |
| duplicates.PushBack(index1, GetStateAllocator()); |
| duplicates.PushBack(index2, GetStateAllocator()); |
| currentError_.SetObject(); |
| currentError_.AddMember(GetDuplicatesString(), duplicates, GetStateAllocator()); |
| AddCurrentError(SchemaType::GetUniqueItemsString(), true); |
| } |
| |
| void TooManyProperties(SizeType actualCount, SizeType expectedCount) { |
| AddNumberError(SchemaType::GetMaxPropertiesString(), |
| ValueType(actualCount).Move(), SValue(expectedCount).Move()); |
| } |
| void TooFewProperties(SizeType actualCount, SizeType expectedCount) { |
| AddNumberError(SchemaType::GetMinPropertiesString(), |
| ValueType(actualCount).Move(), SValue(expectedCount).Move()); |
| } |
| void StartMissingProperties() { |
| currentError_.SetArray(); |
| } |
| void AddMissingProperty(const SValue& name) { |
| currentError_.PushBack(ValueType(name, GetStateAllocator()).Move(), GetStateAllocator()); |
| } |
| bool EndMissingProperties() { |
| if (currentError_.Empty()) |
| return false; |
| ValueType error(kObjectType); |
| error.AddMember(GetMissingString(), currentError_, GetStateAllocator()); |
| currentError_ = error; |
| AddCurrentError(SchemaType::GetRequiredString()); |
| return true; |
| } |
| void PropertyViolations(ISchemaValidator** subvalidators, SizeType count) { |
| for (SizeType i = 0; i < count; ++i) |
| MergeError(static_cast<GenericSchemaValidator*>(subvalidators[i])->GetError()); |
| } |
| void DisallowedProperty(const Ch* name, SizeType length) { |
| currentError_.SetObject(); |
| currentError_.AddMember(GetDisallowedString(), ValueType(name, length, GetStateAllocator()).Move(), GetStateAllocator()); |
| AddCurrentError(SchemaType::GetAdditionalPropertiesString(), true); |
| } |
| |
| void StartDependencyErrors() { |
| currentError_.SetObject(); |
| } |
| void StartMissingDependentProperties() { |
| missingDependents_.SetArray(); |
| } |
| void AddMissingDependentProperty(const SValue& targetName) { |
| missingDependents_.PushBack(ValueType(targetName, GetStateAllocator()).Move(), GetStateAllocator()); |
| } |
| void EndMissingDependentProperties(const SValue& sourceName) { |
| if (!missingDependents_.Empty()) |
| currentError_.AddMember(ValueType(sourceName, GetStateAllocator()).Move(), |
| missingDependents_, GetStateAllocator()); |
| } |
| void AddDependencySchemaError(const SValue& sourceName, ISchemaValidator* subvalidator) { |
| currentError_.AddMember(ValueType(sourceName, GetStateAllocator()).Move(), |
| static_cast<GenericSchemaValidator*>(subvalidator)->GetError(), GetStateAllocator()); |
| } |
| bool EndDependencyErrors() { |
| if (currentError_.ObjectEmpty()) |
| return false; |
| ValueType error(kObjectType); |
| error.AddMember(GetErrorsString(), currentError_, GetStateAllocator()); |
| currentError_ = error; |
| AddCurrentError(SchemaType::GetDependenciesString()); |
| return true; |
| } |
| |
| void DisallowedValue() { |
| currentError_.SetObject(); |
| AddCurrentError(SchemaType::GetEnumString()); |
| } |
| void StartDisallowedType() { |
| currentError_.SetArray(); |
| } |
| void AddExpectedType(const typename SchemaType::ValueType& expectedType) { |
| currentError_.PushBack(ValueType(expectedType, GetStateAllocator()).Move(), GetStateAllocator()); |
| } |
| void EndDisallowedType(const typename SchemaType::ValueType& actualType) { |
| ValueType error(kObjectType); |
| error.AddMember(GetExpectedString(), currentError_, GetStateAllocator()); |
| error.AddMember(GetActualString(), ValueType(actualType, GetStateAllocator()).Move(), GetStateAllocator()); |
| currentError_ = error; |
| AddCurrentError(SchemaType::GetTypeString()); |
| } |
| void NotAllOf(ISchemaValidator** subvalidators, SizeType count) { |
| for (SizeType i = 0; i < count; ++i) { |
| MergeError(static_cast<GenericSchemaValidator*>(subvalidators[i])->GetError()); |
| } |
| } |
| void NoneOf(ISchemaValidator** subvalidators, SizeType count) { |
| AddErrorArray(SchemaType::GetAnyOfString(), subvalidators, count); |
| } |
| void NotOneOf(ISchemaValidator** subvalidators, SizeType count) { |
| AddErrorArray(SchemaType::GetOneOfString(), subvalidators, count); |
| } |
| void Disallowed() { |
| currentError_.SetObject(); |
| AddCurrentError(SchemaType::GetNotString()); |
| } |
| |
| #define RAPIDJSON_STRING_(name, ...) \ |
| static const StringRefType& Get##name##String() {\ |
| static const Ch s[] = { __VA_ARGS__, '\0' };\ |
| static const StringRefType v(s, static_cast<SizeType>(sizeof(s) / sizeof(Ch) - 1)); \ |
| return v;\ |
| } |
| |
| RAPIDJSON_STRING_(InstanceRef, 'i', 'n', 's', 't', 'a', 'n', 'c', 'e', 'R', 'e', 'f') |
| RAPIDJSON_STRING_(SchemaRef, 's', 'c', 'h', 'e', 'm', 'a', 'R', 'e', 'f') |
| RAPIDJSON_STRING_(Expected, 'e', 'x', 'p', 'e', 'c', 't', 'e', 'd') |
| RAPIDJSON_STRING_(Actual, 'a', 'c', 't', 'u', 'a', 'l') |
| RAPIDJSON_STRING_(Disallowed, 'd', 'i', 's', 'a', 'l', 'l', 'o', 'w', 'e', 'd') |
| RAPIDJSON_STRING_(Missing, 'm', 'i', 's', 's', 'i', 'n', 'g') |
| RAPIDJSON_STRING_(Errors, 'e', 'r', 'r', 'o', 'r', 's') |
| RAPIDJSON_STRING_(Duplicates, 'd', 'u', 'p', 'l', 'i', 'c', 'a', 't', 'e', 's') |
| |
| #undef RAPIDJSON_STRING_ |
| |
| #if RAPIDJSON_SCHEMA_VERBOSE |
| #define RAPIDJSON_SCHEMA_HANDLE_BEGIN_VERBOSE_() \ |
| RAPIDJSON_MULTILINEMACRO_BEGIN\ |
| *documentStack_.template Push<Ch>() = '\0';\ |
| documentStack_.template Pop<Ch>(1);\ |
| internal::PrintInvalidDocument(documentStack_.template Bottom<Ch>());\ |
| RAPIDJSON_MULTILINEMACRO_END |
| #else |
| #define RAPIDJSON_SCHEMA_HANDLE_BEGIN_VERBOSE_() |
| #endif |
| |
| #define RAPIDJSON_SCHEMA_HANDLE_BEGIN_(method, arg1)\ |
| if (!valid_) return false; \ |
| if (!BeginValue() || !CurrentSchema().method arg1) {\ |
| RAPIDJSON_SCHEMA_HANDLE_BEGIN_VERBOSE_();\ |
| return valid_ = false;\ |
| } |
| |
| #define RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(method, arg2)\ |
| for (Context* context = schemaStack_.template Bottom<Context>(); context != schemaStack_.template End<Context>(); context++) {\ |
| if (context->hasher)\ |
| static_cast<HasherType*>(context->hasher)->method arg2;\ |
| if (context->validators)\ |
| for (SizeType i_ = 0; i_ < context->validatorCount; i_++)\ |
| static_cast<GenericSchemaValidator*>(context->validators[i_])->method arg2;\ |
| if (context->patternPropertiesValidators)\ |
| for (SizeType i_ = 0; i_ < context->patternPropertiesValidatorCount; i_++)\ |
| static_cast<GenericSchemaValidator*>(context->patternPropertiesValidators[i_])->method arg2;\ |
| } |
| |
| #define RAPIDJSON_SCHEMA_HANDLE_END_(method, arg2)\ |
| return valid_ = EndValue() && (!outputHandler_ || outputHandler_->method arg2) |
| |
| #define RAPIDJSON_SCHEMA_HANDLE_VALUE_(method, arg1, arg2) \ |
| RAPIDJSON_SCHEMA_HANDLE_BEGIN_ (method, arg1);\ |
| RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(method, arg2);\ |
| RAPIDJSON_SCHEMA_HANDLE_END_ (method, arg2) |
| |
| bool Null() { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Null, (CurrentContext()), ( )); } |
| bool Bool(bool b) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Bool, (CurrentContext(), b), (b)); } |
| bool Int(int i) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Int, (CurrentContext(), i), (i)); } |
| bool Uint(unsigned u) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Uint, (CurrentContext(), u), (u)); } |
| bool Int64(int64_t i) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Int64, (CurrentContext(), i), (i)); } |
| bool Uint64(uint64_t u) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Uint64, (CurrentContext(), u), (u)); } |
| bool Double(double d) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Double, (CurrentContext(), d), (d)); } |
| bool RawNumber(const Ch* str, SizeType length, bool copy) |
| { RAPIDJSON_SCHEMA_HANDLE_VALUE_(String, (CurrentContext(), str, length, copy), (str, length, copy)); } |
| bool String(const Ch* str, SizeType length, bool copy) |
| { RAPIDJSON_SCHEMA_HANDLE_VALUE_(String, (CurrentContext(), str, length, copy), (str, length, copy)); } |
| |
| bool StartObject() { |
| RAPIDJSON_SCHEMA_HANDLE_BEGIN_(StartObject, (CurrentContext())); |
| RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(StartObject, ()); |
| return valid_ = !outputHandler_ || outputHandler_->StartObject(); |
| } |
| |
| bool Key(const Ch* str, SizeType len, bool copy) { |
| if (!valid_) return false; |
| AppendToken(str, len); |
| if (!CurrentSchema().Key(CurrentContext(), str, len, copy)) return valid_ = false; |
| RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(Key, (str, len, copy)); |
| return valid_ = !outputHandler_ || outputHandler_->Key(str, len, copy); |
| } |
| |
| bool EndObject(SizeType memberCount) { |
| if (!valid_) return false; |
| RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(EndObject, (memberCount)); |
| if (!CurrentSchema().EndObject(CurrentContext(), memberCount)) return valid_ = false; |
| RAPIDJSON_SCHEMA_HANDLE_END_(EndObject, (memberCount)); |
| } |
| |
| bool StartArray() { |
| RAPIDJSON_SCHEMA_HANDLE_BEGIN_(StartArray, (CurrentContext())); |
| RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(StartArray, ()); |
| return valid_ = !outputHandler_ || outputHandler_->StartArray(); |
| } |
| |
| bool EndArray(SizeType elementCount) { |
| if (!valid_) return false; |
| RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(EndArray, (elementCount)); |
| if (!CurrentSchema().EndArray(CurrentContext(), elementCount)) return valid_ = false; |
| RAPIDJSON_SCHEMA_HANDLE_END_(EndArray, (elementCount)); |
| } |
| |
| #undef RAPIDJSON_SCHEMA_HANDLE_BEGIN_VERBOSE_ |
| #undef RAPIDJSON_SCHEMA_HANDLE_BEGIN_ |
| #undef RAPIDJSON_SCHEMA_HANDLE_PARALLEL_ |
| #undef RAPIDJSON_SCHEMA_HANDLE_VALUE_ |
| |
| // Implementation of ISchemaStateFactory<SchemaType> |
| virtual ISchemaValidator* CreateSchemaValidator(const SchemaType& root) { |
| return new (GetStateAllocator().Malloc(sizeof(GenericSchemaValidator))) GenericSchemaValidator(*schemaDocument_, root, documentStack_.template Bottom<char>(), documentStack_.GetSize(), |
| #if RAPIDJSON_SCHEMA_VERBOSE |
| depth_ + 1, |
| #endif |
| &GetStateAllocator()); |
| } |
| |
| virtual void DestroySchemaValidator(ISchemaValidator* validator) { |
| GenericSchemaValidator* v = static_cast<GenericSchemaValidator*>(validator); |
| v->~GenericSchemaValidator(); |
| StateAllocator::Free(v); |
| } |
| |
| virtual void* CreateHasher() { |
| return new (GetStateAllocator().Malloc(sizeof(HasherType))) HasherType(&GetStateAllocator()); |
| } |
| |
| virtual uint64_t GetHashCode(void* hasher) { |
| return static_cast<HasherType*>(hasher)->GetHashCode(); |
| } |
| |
| virtual void DestroryHasher(void* hasher) { |
| HasherType* h = static_cast<HasherType*>(hasher); |
| h->~HasherType(); |
| StateAllocator::Free(h); |
| } |
| |
| virtual void* MallocState(size_t size) { |
| return GetStateAllocator().Malloc(size); |
| } |
| |
| virtual void FreeState(void* p) { |
| StateAllocator::Free(p); |
| } |
| |
| private: |
| typedef typename SchemaType::Context Context; |
| typedef GenericValue<UTF8<>, StateAllocator> HashCodeArray; |
| typedef internal::Hasher<EncodingType, StateAllocator> HasherType; |
| |
| GenericSchemaValidator( |
| const SchemaDocumentType& schemaDocument, |
| const SchemaType& root, |
| const char* basePath, size_t basePathSize, |
| #if RAPIDJSON_SCHEMA_VERBOSE |
| unsigned depth, |
| #endif |
| StateAllocator* allocator = 0, |
| size_t schemaStackCapacity = kDefaultSchemaStackCapacity, |
| size_t documentStackCapacity = kDefaultDocumentStackCapacity) |
| : |
| schemaDocument_(&schemaDocument), |
| root_(root), |
| stateAllocator_(allocator), |
| ownStateAllocator_(0), |
| schemaStack_(allocator, schemaStackCapacity), |
| documentStack_(allocator, documentStackCapacity), |
| outputHandler_(0), |
| error_(kObjectType), |
| currentError_(), |
| missingDependents_(), |
| valid_(true) |
| #if RAPIDJSON_SCHEMA_VERBOSE |
| , depth_(depth) |
| #endif |
| { |
| if (basePath && basePathSize) |
| memcpy(documentStack_.template Push<char>(basePathSize), basePath, basePathSize); |
| } |
| |
| StateAllocator& GetStateAllocator() { |
| if (!stateAllocator_) |
| stateAllocator_ = ownStateAllocator_ = RAPIDJSON_NEW(StateAllocator)(); |
| return *stateAllocator_; |
| } |
| |
| bool BeginValue() { |
| if (schemaStack_.Empty()) |
| PushSchema(root_); |
| else { |
| if (CurrentContext().inArray) |
| internal::TokenHelper<internal::Stack<StateAllocator>, Ch>::AppendIndexToken(documentStack_, CurrentContext().arrayElementIndex); |
| |
| if (!CurrentSchema().BeginValue(CurrentContext())) |
| return false; |
| |
| SizeType count = CurrentContext().patternPropertiesSchemaCount; |
| const SchemaType** sa = CurrentContext().patternPropertiesSchemas; |
| typename Context::PatternValidatorType patternValidatorType = CurrentContext().valuePatternValidatorType; |
| bool valueUniqueness = CurrentContext().valueUniqueness; |
| RAPIDJSON_ASSERT(CurrentContext().valueSchema); |
| PushSchema(*CurrentContext().valueSchema); |
| |
| if (count > 0) { |
| CurrentContext().objectPatternValidatorType = patternValidatorType; |
| ISchemaValidator**& va = CurrentContext().patternPropertiesValidators; |
| SizeType& validatorCount = CurrentContext().patternPropertiesValidatorCount; |
| va = static_cast<ISchemaValidator**>(MallocState(sizeof(ISchemaValidator*) * count)); |
| for (SizeType i = 0; i < count; i++) |
| va[validatorCount++] = CreateSchemaValidator(*sa[i]); |
| } |
| |
| CurrentContext().arrayUniqueness = valueUniqueness; |
| } |
| return true; |
| } |
| |
| bool EndValue() { |
| if (!CurrentSchema().EndValue(CurrentContext())) |
| return false; |
| |
| #if RAPIDJSON_SCHEMA_VERBOSE |
| GenericStringBuffer<EncodingType> sb; |
| schemaDocument_->GetPointer(&CurrentSchema()).Stringify(sb); |
| |
| *documentStack_.template Push<Ch>() = '\0'; |
| documentStack_.template Pop<Ch>(1); |
| internal::PrintValidatorPointers(depth_, sb.GetString(), documentStack_.template Bottom<Ch>()); |
| #endif |
| |
| uint64_t h = CurrentContext().arrayUniqueness ? static_cast<HasherType*>(CurrentContext().hasher)->GetHashCode() : 0; |
| |
| PopSchema(); |
| |
| if (!schemaStack_.Empty()) { |
| Context& context = CurrentContext(); |
| if (context.valueUniqueness) { |
| HashCodeArray* a = static_cast<HashCodeArray*>(context.arrayElementHashCodes); |
| if (!a) |
| CurrentContext().arrayElementHashCodes = a = new (GetStateAllocator().Malloc(sizeof(HashCodeArray))) HashCodeArray(kArrayType); |
| for (typename HashCodeArray::ConstValueIterator itr = a->Begin(); itr != a->End(); ++itr) |
| if (itr->GetUint64() == h) { |
| DuplicateItems(static_cast<SizeType>(itr - a->Begin()), a->Size()); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(SchemaType::GetUniqueItemsString()); |
| } |
| a->PushBack(h, GetStateAllocator()); |
| } |
| } |
| |
| // Remove the last token of document pointer |
| while (!documentStack_.Empty() && *documentStack_.template Pop<Ch>(1) != '/') |
| ; |
| |
| return true; |
| } |
| |
| void AppendToken(const Ch* str, SizeType len) { |
| documentStack_.template Reserve<Ch>(1 + len * 2); // worst case all characters are escaped as two characters |
| *documentStack_.template PushUnsafe<Ch>() = '/'; |
| for (SizeType i = 0; i < len; i++) { |
| if (str[i] == '~') { |
| *documentStack_.template PushUnsafe<Ch>() = '~'; |
| *documentStack_.template PushUnsafe<Ch>() = '0'; |
| } |
| else if (str[i] == '/') { |
| *documentStack_.template PushUnsafe<Ch>() = '~'; |
| *documentStack_.template PushUnsafe<Ch>() = '1'; |
| } |
| else |
| *documentStack_.template PushUnsafe<Ch>() = str[i]; |
| } |
| } |
| |
| RAPIDJSON_FORCEINLINE void PushSchema(const SchemaType& schema) { new (schemaStack_.template Push<Context>()) Context(*this, *this, &schema); } |
| |
| RAPIDJSON_FORCEINLINE void PopSchema() { |
| Context* c = schemaStack_.template Pop<Context>(1); |
| if (HashCodeArray* a = static_cast<HashCodeArray*>(c->arrayElementHashCodes)) { |
| a->~HashCodeArray(); |
| StateAllocator::Free(a); |
| } |
| c->~Context(); |
| } |
| |
| void AddErrorLocation(ValueType& result, bool parent) { |
| GenericStringBuffer<EncodingType> sb; |
| PointerType instancePointer = GetInvalidDocumentPointer(); |
| ((parent && instancePointer.GetTokenCount() > 0) |
| ? PointerType(instancePointer.GetTokens(), instancePointer.GetTokenCount() - 1) |
| : instancePointer).StringifyUriFragment(sb); |
| ValueType instanceRef(sb.GetString(), static_cast<SizeType>(sb.GetSize() / sizeof(Ch)), |
| GetStateAllocator()); |
| result.AddMember(GetInstanceRefString(), instanceRef, GetStateAllocator()); |
| sb.Clear(); |
| memcpy(sb.Push(CurrentSchema().GetURI().GetStringLength()), |
| CurrentSchema().GetURI().GetString(), |
| CurrentSchema().GetURI().GetStringLength() * sizeof(Ch)); |
| GetInvalidSchemaPointer().StringifyUriFragment(sb); |
| ValueType schemaRef(sb.GetString(), static_cast<SizeType>(sb.GetSize() / sizeof(Ch)), |
| GetStateAllocator()); |
| result.AddMember(GetSchemaRefString(), schemaRef, GetStateAllocator()); |
| } |
| |
| void AddError(ValueType& keyword, ValueType& error) { |
| typename ValueType::MemberIterator member = error_.FindMember(keyword); |
| if (member == error_.MemberEnd()) |
| error_.AddMember(keyword, error, GetStateAllocator()); |
| else { |
| if (member->value.IsObject()) { |
| ValueType errors(kArrayType); |
| errors.PushBack(member->value, GetStateAllocator()); |
| member->value = errors; |
| } |
| member->value.PushBack(error, GetStateAllocator()); |
| } |
| } |
| |
| void AddCurrentError(const typename SchemaType::ValueType& keyword, bool parent = false) { |
| AddErrorLocation(currentError_, parent); |
| AddError(ValueType(keyword, GetStateAllocator(), false).Move(), currentError_); |
| } |
| |
| void MergeError(ValueType& other) { |
| for (typename ValueType::MemberIterator it = other.MemberBegin(), end = other.MemberEnd(); it != end; ++it) { |
| AddError(it->name, it->value); |
| } |
| } |
| |
| void AddNumberError(const typename SchemaType::ValueType& keyword, ValueType& actual, const SValue& expected, |
| const typename SchemaType::ValueType& (*exclusive)() = 0) { |
| currentError_.SetObject(); |
| currentError_.AddMember(GetActualString(), actual, GetStateAllocator()); |
| currentError_.AddMember(GetExpectedString(), ValueType(expected, GetStateAllocator()).Move(), GetStateAllocator()); |
| if (exclusive) |
| currentError_.AddMember(ValueType(exclusive(), GetStateAllocator()).Move(), true, GetStateAllocator()); |
| AddCurrentError(keyword); |
| } |
| |
| void AddErrorArray(const typename SchemaType::ValueType& keyword, |
| ISchemaValidator** subvalidators, SizeType count) { |
| ValueType errors(kArrayType); |
| for (SizeType i = 0; i < count; ++i) |
| errors.PushBack(static_cast<GenericSchemaValidator*>(subvalidators[i])->GetError(), GetStateAllocator()); |
| currentError_.SetObject(); |
| currentError_.AddMember(GetErrorsString(), errors, GetStateAllocator()); |
| AddCurrentError(keyword); |
| } |
| |
| const SchemaType& CurrentSchema() const { return *schemaStack_.template Top<Context>()->schema; } |
| Context& CurrentContext() { return *schemaStack_.template Top<Context>(); } |
| const Context& CurrentContext() const { return *schemaStack_.template Top<Context>(); } |
| |
| static const size_t kDefaultSchemaStackCapacity = 1024; |
| static const size_t kDefaultDocumentStackCapacity = 256; |
| const SchemaDocumentType* schemaDocument_; |
| const SchemaType& root_; |
| StateAllocator* stateAllocator_; |
| StateAllocator* ownStateAllocator_; |
| internal::Stack<StateAllocator> schemaStack_; //!< stack to store the current path of schema (BaseSchemaType *) |
| internal::Stack<StateAllocator> documentStack_; //!< stack to store the current path of validating document (Ch) |
| OutputHandler* outputHandler_; |
| ValueType error_; |
| ValueType currentError_; |
| ValueType missingDependents_; |
| bool valid_; |
| #if RAPIDJSON_SCHEMA_VERBOSE |
| unsigned depth_; |
| #endif |
| }; |
| |
| typedef GenericSchemaValidator<SchemaDocument> SchemaValidator; |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // SchemaValidatingReader |
| |
| //! A helper class for parsing with validation. |
| /*! |
| This helper class is a functor, designed as a parameter of \ref GenericDocument::Populate(). |
| |
| \tparam parseFlags Combination of \ref ParseFlag. |
| \tparam InputStream Type of input stream, implementing Stream concept. |
| \tparam SourceEncoding Encoding of the input stream. |
| \tparam SchemaDocumentType Type of schema document. |
| \tparam StackAllocator Allocator type for stack. |
| */ |
| template < |
| unsigned parseFlags, |
| typename InputStream, |
| typename SourceEncoding, |
| typename SchemaDocumentType = SchemaDocument, |
| typename StackAllocator = CrtAllocator> |
| class SchemaValidatingReader { |
| public: |
| typedef typename SchemaDocumentType::PointerType PointerType; |
| typedef typename InputStream::Ch Ch; |
| typedef GenericValue<SourceEncoding, StackAllocator> ValueType; |
| |
| //! Constructor |
| /*! |
| \param is Input stream. |
| \param sd Schema document. |
| */ |
| SchemaValidatingReader(InputStream& is, const SchemaDocumentType& sd) : is_(is), sd_(sd), invalidSchemaKeyword_(), error_(kObjectType), isValid_(true) {} |
| |
| template <typename Handler> |
| bool operator()(Handler& handler) { |
| GenericReader<SourceEncoding, typename SchemaDocumentType::EncodingType, StackAllocator> reader; |
| GenericSchemaValidator<SchemaDocumentType, Handler> validator(sd_, handler); |
| parseResult_ = reader.template Parse<parseFlags>(is_, validator); |
| |
| isValid_ = validator.IsValid(); |
| if (isValid_) { |
| invalidSchemaPointer_ = PointerType(); |
| invalidSchemaKeyword_ = 0; |
| invalidDocumentPointer_ = PointerType(); |
| error_.SetObject(); |
| } |
| else { |
| invalidSchemaPointer_ = validator.GetInvalidSchemaPointer(); |
| invalidSchemaKeyword_ = validator.GetInvalidSchemaKeyword(); |
| invalidDocumentPointer_ = validator.GetInvalidDocumentPointer(); |
| error_.CopyFrom(validator.GetError(), allocator_); |
| } |
| |
| return parseResult_; |
| } |
| |
| const ParseResult& GetParseResult() const { return parseResult_; } |
| bool IsValid() const { return isValid_; } |
| const PointerType& GetInvalidSchemaPointer() const { return invalidSchemaPointer_; } |
| const Ch* GetInvalidSchemaKeyword() const { return invalidSchemaKeyword_; } |
| const PointerType& GetInvalidDocumentPointer() const { return invalidDocumentPointer_; } |
| const ValueType& GetError() const { return error_; } |
| |
| private: |
| InputStream& is_; |
| const SchemaDocumentType& sd_; |
| |
| ParseResult parseResult_; |
| PointerType invalidSchemaPointer_; |
| const Ch* invalidSchemaKeyword_; |
| PointerType invalidDocumentPointer_; |
| StackAllocator allocator_; |
| ValueType error_; |
| bool isValid_; |
| }; |
| |
| RAPIDJSON_NAMESPACE_END |
| RAPIDJSON_DIAG_POP |
| |
| #endif // RAPIDJSON_SCHEMA_H_ |