| // 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 "error/en.h" |
| #include "uri.h" |
| #include <cmath> // abs, floor |
| |
| #if !defined(RAPIDJSON_SCHEMA_USE_INTERNALREGEX) |
| #define RAPIDJSON_SCHEMA_USE_INTERNALREGEX 1 |
| #endif |
| |
| #if !defined(RAPIDJSON_SCHEMA_USE_STDREGEX) || !(__cplusplus >=201103L || (defined(_MSC_VER) && _MSC_VER >= 1800)) |
| #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 |
| |
| 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 PrintInvalidKeywordData(const char* keyword) { |
| printf(" Fail keyword: '%s'\n", keyword); |
| } |
| |
| inline void PrintInvalidKeywordData(const wchar_t* keyword) { |
| wprintf(L" Fail keyword: '%ls'\n", keyword); |
| } |
| |
| inline void PrintInvalidDocumentData(const char* document) { |
| printf(" Fail document: '%s'\n", document); |
| } |
| |
| inline void PrintInvalidDocumentData(const wchar_t* document) { |
| wprintf(L" Fail document: '%ls'\n", document); |
| } |
| |
| inline void PrintValidatorPointersData(const char* s, const char* d, unsigned depth) { |
| printf(" Sch: %*s'%s'\n Doc: %*s'%s'\n", depth * 4, " ", s, depth * 4, " ", d); |
| } |
| |
| inline void PrintValidatorPointersData(const wchar_t* s, const wchar_t* d, unsigned depth) { |
| wprintf(L" Sch: %*ls'%ls'\n Doc: %*ls'%ls'\n", depth * 4, L" ", s, depth * 4, L" ", d); |
| } |
| |
| inline void PrintSchemaIdsData(const char* base, const char* local, const char* resolved) { |
| printf(" Resolving id: Base: '%s', Local: '%s', Resolved: '%s'\n", base, local, resolved); |
| } |
| |
| inline void PrintSchemaIdsData(const wchar_t* base, const wchar_t* local, const wchar_t* resolved) { |
| wprintf(L" Resolving id: Base: '%ls', Local: '%ls', Resolved: '%ls'\n", base, local, resolved); |
| } |
| |
| inline void PrintMethodData(const char* method) { |
| printf("%s\n", method); |
| } |
| |
| inline void PrintMethodData(const char* method, bool b) { |
| printf("%s, Data: '%s'\n", method, b ? "true" : "false"); |
| } |
| |
| inline void PrintMethodData(const char* method, int64_t i) { |
| printf("%s, Data: '%" PRId64 "'\n", method, i); |
| } |
| |
| inline void PrintMethodData(const char* method, uint64_t u) { |
| printf("%s, Data: '%" PRIu64 "'\n", method, u); |
| } |
| |
| inline void PrintMethodData(const char* method, double d) { |
| printf("%s, Data: '%lf'\n", method, d); |
| } |
| |
| inline void PrintMethodData(const char* method, const char* s) { |
| printf("%s, Data: '%s'\n", method, s); |
| } |
| |
| inline void PrintMethodData(const char* method, const wchar_t* s) { |
| wprintf(L"%hs, Data: '%ls'\n", method, s); |
| } |
| |
| inline void PrintMethodData(const char* method, const char* s1, const char* s2) { |
| printf("%s, Data: '%s', '%s'\n", method, s1, s2); |
| } |
| |
| inline void PrintMethodData(const char* method, const wchar_t* s1, const wchar_t* s2) { |
| wprintf(L"%hs, Data: '%ls', '%ls'\n", method, s1, s2); |
| } |
| |
| } // namespace internal |
| |
| #endif // RAPIDJSON_SCHEMA_VERBOSE |
| |
| #ifndef RAPIDJSON_SCHEMA_PRINT |
| #if RAPIDJSON_SCHEMA_VERBOSE |
| #define RAPIDJSON_SCHEMA_PRINT(name, ...) internal::Print##name##Data(__VA_ARGS__) |
| #else |
| #define RAPIDJSON_SCHEMA_PRINT(name, ...) |
| #endif |
| #endif |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // RAPIDJSON_INVALID_KEYWORD_RETURN |
| |
| #define RAPIDJSON_INVALID_KEYWORD_RETURN(code)\ |
| RAPIDJSON_MULTILINEMACRO_BEGIN\ |
| context.invalidCode = code;\ |
| context.invalidKeyword = SchemaType::GetValidateErrorKeyword(code).GetString();\ |
| RAPIDJSON_SCHEMA_PRINT(InvalidKeyword, context.invalidKeyword);\ |
| return false;\ |
| RAPIDJSON_MULTILINEMACRO_END |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // ValidateFlag |
| |
| /*! \def RAPIDJSON_VALIDATE_DEFAULT_FLAGS |
| \ingroup RAPIDJSON_CONFIG |
| \brief User-defined kValidateDefaultFlags definition. |
| |
| User can define this as any \c ValidateFlag combinations. |
| */ |
| #ifndef RAPIDJSON_VALIDATE_DEFAULT_FLAGS |
| #define RAPIDJSON_VALIDATE_DEFAULT_FLAGS kValidateNoFlags |
| #endif |
| |
| //! Combination of validate flags |
| enum ValidateFlag { |
| kValidateNoFlags = 0, //!< No flags are set. |
| kValidateContinueOnErrorFlag = 1, //!< Don't stop after first validation error. |
| kValidateReadFlag = 2, //!< Validation is for a read semantic. |
| kValidateWriteFlag = 4, //!< Validation is for a write semantic. |
| kValidateDefaultFlags = RAPIDJSON_VALIDATE_DEFAULT_FLAGS //!< Default validate flags. Can be customized by defining RAPIDJSON_VALIDATE_DEFAULT_FLAGS |
| }; |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // Specification |
| enum SchemaDraft { |
| kDraftUnknown = -1, |
| kDraftNone = 0, |
| kDraft03 = 3, |
| kDraftMin = 4, //!< Current minimum supported draft |
| kDraft04 = 4, |
| kDraft05 = 5, |
| kDraftMax = 5, //!< Current maximum supported draft |
| kDraft06 = 6, |
| kDraft07 = 7, |
| kDraft2019_09 = 8, |
| kDraft2020_12 = 9 |
| }; |
| |
| enum OpenApiVersion { |
| kVersionUnknown = -1, |
| kVersionNone = 0, |
| kVersionMin = 2, //!< Current minimum supported version |
| kVersion20 = 2, |
| kVersion30 = 3, |
| kVersionMax = 3, //!< Current maximum supported version |
| kVersion31 = 4, |
| }; |
| |
| struct Specification { |
| Specification(SchemaDraft d) : draft(d), oapi(kVersionNone) {} |
| Specification(OpenApiVersion o) : oapi(o) { |
| if (oapi == kVersion20) draft = kDraft04; |
| else if (oapi == kVersion30) draft = kDraft05; |
| else if (oapi == kVersion31) draft = kDraft2020_12; |
| else draft = kDraft04; |
| } |
| ~Specification() {} |
| bool IsSupported() const { |
| return ((draft >= kDraftMin && draft <= kDraftMax) && ((oapi == kVersionNone) || (oapi >= kVersionMin && oapi <= kVersionMax))); |
| } |
| SchemaDraft draft; |
| OpenApiVersion oapi; |
| }; |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // 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; |
| virtual void SetValidateFlags(unsigned flags) = 0; |
| virtual unsigned GetValidateFlags() const = 0; |
| }; |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // ISchemaStateFactory |
| |
| template <typename SchemaType> |
| class ISchemaStateFactory { |
| public: |
| virtual ~ISchemaStateFactory() {} |
| virtual ISchemaValidator* CreateSchemaValidator(const SchemaType&, const bool inheritContinueOnErrors) = 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(const ValidateErrorCode code) = 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 MultipleOneOf(SizeType index1, SizeType index2) = 0; |
| virtual void Disallowed() = 0; |
| virtual void DisallowedWhenWriting() = 0; |
| virtual void DisallowedWhenReading() = 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++) |
| // Issue #2205 |
| // Hasing the key to avoid key=value cases with bug-prone zero-value hash |
| h ^= Hash(Hash(0, 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(0xcbf29ce4, 0x84222325), 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, unsigned fl = 0) : |
| factory(f), |
| error_handler(eh), |
| schema(s), |
| flags(fl), |
| valueSchema(), |
| invalidKeyword(), |
| invalidCode(), |
| 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++) { |
| if (validators[i]) { |
| factory.DestroySchemaValidator(validators[i]); |
| } |
| } |
| factory.FreeState(validators); |
| } |
| if (patternPropertiesValidators) { |
| for (SizeType i = 0; i < patternPropertiesValidatorCount; i++) { |
| if (patternPropertiesValidators[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; |
| unsigned flags; |
| const SchemaType* valueSchema; |
| const Ch* invalidKeyword; |
| ValidateErrorCode invalidCode; |
| 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; |
| typedef GenericUri<ValueType, AllocatorType> UriType; |
| friend class GenericSchemaDocument<ValueType, AllocatorType>; |
| |
| Schema(SchemaDocumentType* schemaDocument, const PointerType& p, const ValueType& value, const ValueType& document, AllocatorType* allocator, const UriType& id = UriType()) : |
| allocator_(allocator), |
| uri_(schemaDocument->GetURI(), *allocator), |
| id_(id, allocator), |
| spec_(schemaDocument->GetSpecification()), |
| 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), |
| readOnly_(false), |
| writeOnly_(false), |
| nullable_(false) |
| { |
| GenericStringBuffer<EncodingType> sb; |
| p.StringifyUriFragment(sb); |
| RAPIDJSON_SCHEMA_PRINT(Method, "Schema::Schema", sb.GetString(), id.GetString()); |
| |
| typedef typename ValueType::ConstValueIterator ConstValueIterator; |
| typedef typename ValueType::ConstMemberIterator ConstMemberIterator; |
| |
| // PR #1393 |
| // Early add this Schema and its $ref(s) in schemaDocument's map to avoid infinite |
| // recursion (with recursive schemas), since schemaDocument->getSchema() is always |
| // checked before creating a new one. Don't cache typeless_, though. |
| if (this != typeless_) { |
| typedef typename SchemaDocumentType::SchemaEntry SchemaEntry; |
| SchemaEntry *entry = schemaDocument->schemaMap_.template Push<SchemaEntry>(); |
| new (entry) SchemaEntry(pointer_, this, true, allocator_); |
| schemaDocument->AddSchemaRefs(this); |
| } |
| |
| if (!value.IsObject()) |
| return; |
| |
| // If we have an id property, resolve it with the in-scope id |
| // Not supported for open api 2.0 or 3.0 |
| if (spec_.oapi != kVersion20 && spec_.oapi != kVersion30) |
| if (const ValueType* v = GetMember(value, GetIdString())) { |
| if (v->IsString()) { |
| UriType local(*v, allocator); |
| id_ = local.Resolve(id_, allocator); |
| RAPIDJSON_SCHEMA_PRINT(SchemaIds, id.GetString(), v->GetString(), id_.GetString()); |
| } |
| } |
| |
| 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<AllocatorType> > EnumHasherType; |
| char buffer[256u + 24]; |
| MemoryPoolAllocator<AllocatorType> hasherAllocator(buffer, sizeof(buffer)); |
| EnumHasherType h(&hasherAllocator, 256); |
| itr->Accept(h); |
| enum_[enumCount_++] = h.GetHashCode(); |
| } |
| } |
| } |
| |
| if (schemaDocument) |
| AssignIfExist(allOf_, *schemaDocument, p, value, GetAllOfString(), document); |
| |
| // AnyOf, OneOf, Not not supported for open api 2.0 |
| if (schemaDocument && spec_.oapi != kVersion20) { |
| 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, id_); |
| 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); |
| |
| // Dependencies not supported for open api 2.0 and 3.0 |
| if (spec_.oapi != kVersion20 && spec_.oapi != kVersion30) |
| 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, id_); |
| } |
| } |
| |
| // PatternProperties not supported for open api 2.0 and 3.0 |
| if (spec_.oapi != kVersion20 && spec_.oapi != kVersion30) |
| 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(); |
| PointerType r = q.Append(itr->name, allocator_); |
| patternProperties_[patternPropertyCount_].pattern = CreatePattern(itr->name, schemaDocument, r); |
| schemaDocument->CreateSchema(&patternProperties_[patternPropertyCount_].schema, r, itr->value, document, id_); |
| 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; |
| } |
| } |
| |
| // Dependencies not supported for open api 2.0 and 3.0 |
| if (spec_.oapi != kVersion20 && spec_.oapi != kVersion30) |
| 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, id_); |
| 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, id_); |
| } |
| |
| 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, id_); |
| 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, id_); |
| } |
| } |
| |
| AssignIfExist(minItems_, value, GetMinItemsString()); |
| AssignIfExist(maxItems_, value, GetMaxItemsString()); |
| |
| // AdditionalItems not supported for openapi 2.0 and 3.0 |
| if (spec_.oapi != kVersion20 && spec_.oapi != kVersion30) |
| 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, id_); |
| } |
| |
| AssignIfExist(uniqueItems_, value, GetUniqueItemsString()); |
| |
| // String |
| AssignIfExist(minLength_, value, GetMinLengthString()); |
| AssignIfExist(maxLength_, value, GetMaxLengthString()); |
| |
| if (const ValueType* v = GetMember(value, GetPatternString())) |
| pattern_ = CreatePattern(*v, schemaDocument, p.Append(GetPatternString(), allocator_)); |
| |
| // 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(); |
| |
| // ReadOnly - open api only (until draft 7 supported) |
| // WriteOnly - open api 3 only (until draft 7 supported) |
| // Both can't be true |
| if (spec_.oapi != kVersionNone) |
| AssignIfExist(readOnly_, value, GetReadOnlyString()); |
| if (spec_.oapi >= kVersion30) |
| AssignIfExist(writeOnly_, value, GetWriteOnlyString()); |
| if (readOnly_ && writeOnly_) |
| schemaDocument->SchemaError(kSchemaErrorReadOnlyAndWriteOnly, p); |
| |
| // Nullable - open api 3 only |
| // If true add 'null' as allowable type |
| if (spec_.oapi >= kVersion30) { |
| AssignIfExist(nullable_, value, GetNullableString()); |
| if (nullable_) |
| AddType(GetNullString()); |
| } |
| } |
| |
| ~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 UriType& GetId() const { |
| return id_; |
| } |
| |
| const Specification& GetSpecification() const { |
| return spec_; |
| } |
| |
| const PointerType& GetPointer() const { |
| return pointer_; |
| } |
| |
| bool BeginValue(Context& context) const { |
| RAPIDJSON_SCHEMA_PRINT(Method, "Schema::BeginValue"); |
| 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); |
| // Must set valueSchema for when kValidateContinueOnErrorFlag is set, else reports spurious type error |
| context.valueSchema = typeless_; |
| // Must bump arrayElementIndex for when kValidateContinueOnErrorFlag is set |
| context.arrayElementIndex++; |
| RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorAdditionalItems); |
| } |
| } |
| else |
| context.valueSchema = typeless_; |
| |
| context.arrayElementIndex++; |
| } |
| return true; |
| } |
| |
| RAPIDJSON_FORCEINLINE bool EndValue(Context& context) const { |
| RAPIDJSON_SCHEMA_PRINT(Method, "Schema::EndValue"); |
| // Only check pattern properties if we have validators |
| 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(kValidateErrorPatternProperties); |
| } |
| } |
| else if (context.objectPatternValidatorType == Context::kPatternValidatorWithProperty) { |
| if (!patternValid || !otherValid) { |
| context.error_handler.PropertyViolations(context.patternPropertiesValidators, count + 1); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorPatternProperties); |
| } |
| } |
| else if (!patternValid && !otherValid) { // kPatternValidatorWithAdditionalProperty) |
| context.error_handler.PropertyViolations(context.patternPropertiesValidators, count + 1); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorPatternProperties); |
| } |
| } |
| |
| // For enums only check if we have a hasher |
| if (enum_ && context.hasher) { |
| 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(kValidateErrorEnum); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorEnum); |
| foundEnum:; |
| } |
| |
| // Only check allOf etc if we have validators |
| if (context.validatorCount > 0) { |
| 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(kValidateErrorAllOf); |
| } |
| |
| 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(kValidateErrorAnyOf); |
| foundAny:; |
| } |
| |
| if (oneOf_.schemas) { |
| bool oneValid = false; |
| SizeType firstMatch = 0; |
| for (SizeType i = oneOf_.begin; i < oneOf_.begin + oneOf_.count; i++) |
| if (context.validators[i]->IsValid()) { |
| if (oneValid) { |
| context.error_handler.MultipleOneOf(firstMatch, i - oneOf_.begin); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorOneOfMatch); |
| } else { |
| oneValid = true; |
| firstMatch = i - oneOf_.begin; |
| } |
| } |
| if (!oneValid) { |
| context.error_handler.NotOneOf(&context.validators[oneOf_.begin], oneOf_.count); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorOneOf); |
| } |
| } |
| |
| if (not_ && context.validators[notValidatorIndex_]->IsValid()) { |
| context.error_handler.Disallowed(); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorNot); |
| } |
| } |
| |
| return true; |
| } |
| |
| bool Null(Context& context) const { |
| RAPIDJSON_SCHEMA_PRINT(Method, "Schema::Null"); |
| if (!(type_ & (1 << kNullSchemaType))) { |
| DisallowedType(context, GetNullString()); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorType); |
| } |
| return CreateParallelValidator(context); |
| } |
| |
| bool Bool(Context& context, bool b) const { |
| RAPIDJSON_SCHEMA_PRINT(Method, "Schema::Bool", b); |
| if (!CheckBool(context, b)) |
| return false; |
| return CreateParallelValidator(context); |
| } |
| |
| bool Int(Context& context, int i) const { |
| RAPIDJSON_SCHEMA_PRINT(Method, "Schema::Int", (int64_t)i); |
| if (!CheckInt(context, i)) |
| return false; |
| return CreateParallelValidator(context); |
| } |
| |
| bool Uint(Context& context, unsigned u) const { |
| RAPIDJSON_SCHEMA_PRINT(Method, "Schema::Uint", (uint64_t)u); |
| if (!CheckUint(context, u)) |
| return false; |
| return CreateParallelValidator(context); |
| } |
| |
| bool Int64(Context& context, int64_t i) const { |
| RAPIDJSON_SCHEMA_PRINT(Method, "Schema::Int64", i); |
| if (!CheckInt(context, i)) |
| return false; |
| return CreateParallelValidator(context); |
| } |
| |
| bool Uint64(Context& context, uint64_t u) const { |
| RAPIDJSON_SCHEMA_PRINT(Method, "Schema::Uint64", u); |
| if (!CheckUint(context, u)) |
| return false; |
| return CreateParallelValidator(context); |
| } |
| |
| bool Double(Context& context, double d) const { |
| RAPIDJSON_SCHEMA_PRINT(Method, "Schema::Double", d); |
| if (!(type_ & (1 << kNumberSchemaType))) { |
| DisallowedType(context, GetNumberString()); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorType); |
| } |
| |
| 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 { |
| RAPIDJSON_SCHEMA_PRINT(Method, "Schema::String", str); |
| if (!(type_ & (1 << kStringSchemaType))) { |
| DisallowedType(context, GetStringString()); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorType); |
| } |
| |
| 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(kValidateErrorMinLength); |
| } |
| if (count > maxLength_) { |
| context.error_handler.TooLong(str, length, maxLength_); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorMaxLength); |
| } |
| } |
| } |
| |
| if (pattern_ && !IsPatternMatch(pattern_, str, length)) { |
| context.error_handler.DoesNotMatch(str, length); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorPattern); |
| } |
| |
| return CreateParallelValidator(context); |
| } |
| |
| bool StartObject(Context& context) const { |
| RAPIDJSON_SCHEMA_PRINT(Method, "Schema::StartObject"); |
| if (!(type_ & (1 << kObjectSchemaType))) { |
| DisallowedType(context, GetObjectString()); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorType); |
| } |
| |
| 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 { |
| RAPIDJSON_SCHEMA_PRINT(Method, "Schema::Key", str); |
| |
| 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 = 0; |
| 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 (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 |
| // Must set valueSchema for when kValidateContinueOnErrorFlag is set, else reports spurious type error |
| context.valueSchema = typeless_; |
| context.error_handler.DisallowedProperty(str, len); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorAdditionalProperties); |
| } |
| |
| return true; |
| } |
| |
| bool EndObject(Context& context, SizeType memberCount) const { |
| RAPIDJSON_SCHEMA_PRINT(Method, "Schema::EndObject"); |
| 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(kValidateErrorRequired); |
| } |
| |
| if (memberCount < minProperties_) { |
| context.error_handler.TooFewProperties(memberCount, minProperties_); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorMinProperties); |
| } |
| |
| if (memberCount > maxProperties_) { |
| context.error_handler.TooManyProperties(memberCount, maxProperties_); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorMaxProperties); |
| } |
| |
| 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(kValidateErrorDependencies); |
| } |
| |
| return true; |
| } |
| |
| bool StartArray(Context& context) const { |
| RAPIDJSON_SCHEMA_PRINT(Method, "Schema::StartArray"); |
| context.arrayElementIndex = 0; |
| context.inArray = true; // Ensure we note that we are in an array |
| |
| if (!(type_ & (1 << kArraySchemaType))) { |
| DisallowedType(context, GetArrayString()); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorType); |
| } |
| |
| return CreateParallelValidator(context); |
| } |
| |
| bool EndArray(Context& context, SizeType elementCount) const { |
| RAPIDJSON_SCHEMA_PRINT(Method, "Schema::EndArray"); |
| context.inArray = false; |
| |
| if (elementCount < minItems_) { |
| context.error_handler.TooFewItems(elementCount, minItems_); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorMinItems); |
| } |
| |
| if (elementCount > maxItems_) { |
| context.error_handler.TooManyItems(elementCount, maxItems_); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorMaxItems); |
| } |
| |
| return true; |
| } |
| |
| static const ValueType& GetValidateErrorKeyword(ValidateErrorCode validateErrorCode) { |
| switch (validateErrorCode) { |
| case kValidateErrorMultipleOf: return GetMultipleOfString(); |
| case kValidateErrorMaximum: return GetMaximumString(); |
| case kValidateErrorExclusiveMaximum: return GetMaximumString(); // Same |
| case kValidateErrorMinimum: return GetMinimumString(); |
| case kValidateErrorExclusiveMinimum: return GetMinimumString(); // Same |
| |
| case kValidateErrorMaxLength: return GetMaxLengthString(); |
| case kValidateErrorMinLength: return GetMinLengthString(); |
| case kValidateErrorPattern: return GetPatternString(); |
| |
| case kValidateErrorMaxItems: return GetMaxItemsString(); |
| case kValidateErrorMinItems: return GetMinItemsString(); |
| case kValidateErrorUniqueItems: return GetUniqueItemsString(); |
| case kValidateErrorAdditionalItems: return GetAdditionalItemsString(); |
| |
| case kValidateErrorMaxProperties: return GetMaxPropertiesString(); |
| case kValidateErrorMinProperties: return GetMinPropertiesString(); |
| case kValidateErrorRequired: return GetRequiredString(); |
| case kValidateErrorAdditionalProperties: return GetAdditionalPropertiesString(); |
| case kValidateErrorPatternProperties: return GetPatternPropertiesString(); |
| case kValidateErrorDependencies: return GetDependenciesString(); |
| |
| case kValidateErrorEnum: return GetEnumString(); |
| case kValidateErrorType: return GetTypeString(); |
| |
| case kValidateErrorOneOf: return GetOneOfString(); |
| case kValidateErrorOneOfMatch: return GetOneOfString(); // Same |
| case kValidateErrorAllOf: return GetAllOfString(); |
| case kValidateErrorAnyOf: return GetAnyOfString(); |
| case kValidateErrorNot: return GetNotString(); |
| |
| case kValidateErrorReadOnly: return GetReadOnlyString(); |
| case kValidateErrorWriteOnly: return GetWriteOnlyString(); |
| |
| default: return GetNullString(); |
| } |
| } |
| |
| |
| // 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') |
| RAPIDJSON_STRING_(Schema, '$', 's', 'c', 'h', 'e', 'm', 'a') |
| RAPIDJSON_STRING_(Ref, '$', 'r', 'e', 'f') |
| RAPIDJSON_STRING_(Id, 'i', 'd') |
| RAPIDJSON_STRING_(Swagger, 's', 'w', 'a', 'g', 'g', 'e', 'r') |
| RAPIDJSON_STRING_(OpenApi, 'o', 'p', 'e', 'n', 'a', 'p', 'i') |
| RAPIDJSON_STRING_(ReadOnly, 'r', 'e', 'a', 'd', 'O', 'n', 'l', 'y') |
| RAPIDJSON_STRING_(WriteOnly, 'w', 'r', 'i', 't', 'e', 'O', 'n', 'l', 'y') |
| RAPIDJSON_STRING_(Nullable, 'n', 'u', 'l', 'l', 'a', 'b', 'l', 'e') |
| |
| #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, id_); |
| out.begin = validatorCount_; |
| validatorCount_ += out.count; |
| } |
| } |
| } |
| |
| #if RAPIDJSON_SCHEMA_USE_INTERNALREGEX |
| template <typename ValueType> |
| RegexType* CreatePattern(const ValueType& value, SchemaDocumentType* sd, const PointerType& p) { |
| if (value.IsString()) { |
| RegexType* r = new (allocator_->Malloc(sizeof(RegexType))) RegexType(value.GetString(), allocator_); |
| if (!r->IsValid()) { |
| sd->SchemaErrorValue(kSchemaErrorRegexInvalid, p, value.GetString(), value.GetStringLength()); |
| 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, SchemaDocumentType* sd, const PointerType& p) { |
| 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& e) { |
| sd->SchemaErrorValue(kSchemaErrorRegexInvalid, p, value.GetString(), value.GetStringLength()); |
| 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); |
| } |
| |
| // Creates parallel validators for allOf, anyOf, oneOf, not and schema dependencies, if required. |
| // Also creates a hasher for enums and array uniqueness, if required. |
| // Also a useful place to add type-independent error checks. |
| 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_)); |
| std::memset(context.validators, 0, sizeof(ISchemaValidator*) * validatorCount_); |
| context.validatorCount = validatorCount_; |
| |
| // Always return after first failure for these sub-validators |
| if (allOf_.schemas) |
| CreateSchemaValidators(context, allOf_, false); |
| |
| if (anyOf_.schemas) |
| CreateSchemaValidators(context, anyOf_, false); |
| |
| if (oneOf_.schemas) |
| CreateSchemaValidators(context, oneOf_, false); |
| |
| if (not_) |
| context.validators[notValidatorIndex_] = context.factory.CreateSchemaValidator(*not_, false); |
| |
| if (hasSchemaDependencies_) { |
| for (SizeType i = 0; i < propertyCount_; i++) |
| if (properties_[i].dependenciesSchema) |
| context.validators[properties_[i].dependenciesValidatorIndex] = context.factory.CreateSchemaValidator(*properties_[i].dependenciesSchema, false); |
| } |
| } |
| |
| // Add any other type-independent checks here |
| if (readOnly_ && (context.flags & kValidateWriteFlag)) { |
| context.error_handler.DisallowedWhenWriting(); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorReadOnly); |
| } |
| if (writeOnly_ && (context.flags & kValidateReadFlag)) { |
| context.error_handler.DisallowedWhenReading(); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorWriteOnly); |
| } |
| |
| return true; |
| } |
| |
| void CreateSchemaValidators(Context& context, const SchemaArray& schemas, const bool inheritContinueOnErrors) const { |
| for (SizeType i = 0; i < schemas.count; i++) |
| context.validators[schemas.begin + i] = context.factory.CreateSchemaValidator(*schemas.schemas[i], inheritContinueOnErrors); |
| } |
| |
| // 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 CheckBool(Context& context, bool) const { |
| if (!(type_ & (1 << kBooleanSchemaType))) { |
| DisallowedType(context, GetBooleanString()); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorType); |
| } |
| return true; |
| } |
| |
| bool CheckInt(Context& context, int64_t i) const { |
| if (!(type_ & ((1 << kIntegerSchemaType) | (1 << kNumberSchemaType)))) { |
| DisallowedType(context, GetIntegerString()); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorType); |
| } |
| |
| 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(exclusiveMinimum_ ? kValidateErrorExclusiveMinimum : kValidateErrorMinimum); |
| } |
| } |
| else if (minimum_.IsUint64()) { |
| context.error_handler.BelowMinimum(i, minimum_, exclusiveMinimum_); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(exclusiveMinimum_ ? kValidateErrorExclusiveMinimum : kValidateErrorMinimum); // 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(exclusiveMaximum_ ? kValidateErrorExclusiveMaximum : kValidateErrorMaximum); |
| } |
| } |
| 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(kValidateErrorMultipleOf); |
| } |
| } |
| 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(kValidateErrorType); |
| } |
| |
| 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(exclusiveMinimum_ ? kValidateErrorExclusiveMinimum : kValidateErrorMinimum); |
| } |
| } |
| 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(exclusiveMaximum_ ? kValidateErrorExclusiveMaximum : kValidateErrorMaximum); |
| } |
| } |
| else if (maximum_.IsInt64()) { |
| context.error_handler.AboveMaximum(i, maximum_, exclusiveMaximum_); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(exclusiveMaximum_ ? kValidateErrorExclusiveMaximum : kValidateErrorMaximum); // 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(kValidateErrorMultipleOf); |
| } |
| } |
| 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(exclusiveMinimum_ ? kValidateErrorExclusiveMinimum : kValidateErrorMinimum); |
| } |
| 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(exclusiveMaximum_ ? kValidateErrorExclusiveMaximum : kValidateErrorMaximum); |
| } |
| return true; |
| } |
| |
| bool CheckDoubleMultipleOf(Context& context, double d) const { |
| double a = std::abs(d), b = std::abs(multipleOf_.GetDouble()); |
| double q = a / b; |
| double qRounded = std::floor(q + 0.5); |
| double scaledEpsilon = (q + qRounded) * std::numeric_limits<double>::epsilon(); |
| double difference = std::abs(qRounded - q); |
| bool isMultiple = (difference <= scaledEpsilon) |
| || (difference < std::numeric_limits<double>::min()); |
| if (!isMultiple) { |
| context.error_handler.NotMultipleOf(d, multipleOf_); |
| RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorMultipleOf); |
| } |
| 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_; |
| UriType id_; |
| Specification spec_; |
| 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_; |
| |
| bool readOnly_; |
| bool writeOnly_; |
| bool nullable_; |
| }; |
| |
| 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; |
| typedef typename SchemaDocumentType::ValueType ValueType; |
| typedef typename SchemaDocumentType::AllocatorType AllocatorType; |
| |
| virtual ~IGenericRemoteSchemaDocumentProvider() {} |
| virtual const SchemaDocumentType* GetRemoteDocument(const Ch* uri, SizeType length) = 0; |
| virtual const SchemaDocumentType* GetRemoteDocument(const GenericUri<ValueType, AllocatorType> uri, Specification& spec) { |
| // Default implementation just calls through for compatibility |
| // Following line suppresses unused parameter warning |
| (void)spec; |
| // printf("GetRemoteDocument: %d %d\n", spec.draft, spec.oapi); |
| return GetRemoteDocument(uri.GetBaseString(), uri.GetBaseStringLength()); |
| } |
| }; |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // 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, AllocatorType> GValue; |
| typedef GenericUri<ValueType, Allocator> UriType; |
| typedef GenericStringRef<Ch> StringRefType; |
| 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. |
| \param pointer An optional JSON pointer to the start of the schema document |
| \param spec Optional schema draft or OpenAPI version. Used if no specification in document. Defaults to draft-04. |
| */ |
| explicit GenericSchemaDocument(const ValueType& document, const Ch* uri = 0, SizeType uriLength = 0, |
| IRemoteSchemaDocumentProviderType* remoteProvider = 0, Allocator* allocator = 0, |
| const PointerType& pointer = PointerType(), // PR #1393 |
| const Specification& spec = Specification(kDraft04)) : |
| remoteProvider_(remoteProvider), |
| allocator_(allocator), |
| ownAllocator_(), |
| root_(), |
| typeless_(), |
| schemaMap_(allocator, kInitialSchemaMapSize), |
| schemaRef_(allocator, kInitialSchemaRefSize), |
| spec_(spec), |
| error_(kObjectType), |
| currentError_() |
| { |
| RAPIDJSON_SCHEMA_PRINT(Method, "GenericSchemaDocument::GenericSchemaDocument"); |
| if (!allocator_) |
| ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator)(); |
| |
| Ch noUri[1] = {0}; |
| uri_.SetString(uri ? uri : noUri, uriLength, *allocator_); |
| docId_ = UriType(uri_, allocator_); |
| |
| typeless_ = static_cast<SchemaType*>(allocator_->Malloc(sizeof(SchemaType))); |
| new (typeless_) SchemaType(this, PointerType(), ValueType(kObjectType).Move(), ValueType(kObjectType).Move(), allocator_, docId_); |
| |
| // Establish the schema draft or open api version. |
| // We only ever look for '$schema' or 'swagger' or 'openapi' at the root of the document. |
| SetSchemaSpecification(document); |
| |
| // Generate root schema, it will call CreateSchema() to create sub-schemas, |
| // And call HandleRefSchema() if there are $ref. |
| // PR #1393 use input pointer if supplied |
| root_ = typeless_; |
| if (pointer.GetTokenCount() == 0) { |
| CreateSchemaRecursive(&root_, pointer, document, document, docId_); |
| } |
| else if (const ValueType* v = pointer.Get(document)) { |
| CreateSchema(&root_, pointer, *v, document, docId_); |
| } |
| else { |
| GenericStringBuffer<EncodingType> sb; |
| pointer.StringifyUriFragment(sb); |
| SchemaErrorValue(kSchemaErrorStartUnknown, PointerType(), sb.GetString(), static_cast<SizeType>(sb.GetSize() / sizeof(Ch))); |
| } |
| |
| 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_)), |
| docId_(std::move(rhs.docId_)), |
| spec_(rhs.spec_), |
| error_(std::move(rhs.error_)), |
| currentError_(std::move(rhs.currentError_)) |
| { |
| 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_); |
| } |
| |
| // these may contain some allocator data so clear before deleting ownAllocator_ |
| uri_.SetNull(); |
| error_.SetNull(); |
| currentError_.SetNull(); |
| |
| RAPIDJSON_DELETE(ownAllocator_); |
| } |
| |
| const GValue& GetURI() const { return uri_; } |
| |
| const Specification& GetSpecification() const { return spec_; } |
| bool IsSupportedSpecification() const { return spec_.IsSupported(); } |
| |
| //! Static method to get the specification of any schema document |
| // Returns kDraftNone if document is silent |
| static const Specification GetSpecification(const ValueType& document) { |
| SchemaDraft draft = GetSchemaDraft(document); |
| if (draft != kDraftNone) |
| return Specification(draft); |
| else { |
| OpenApiVersion oapi = GetOpenApiVersion(document); |
| if (oapi != kVersionNone) |
| return Specification(oapi); |
| } |
| return Specification(kDraftNone); |
| } |
| |
| //! Get the root schema. |
| const SchemaType& GetRoot() const { return *root_; } |
| |
| //! Gets the error object. |
| GValue& GetError() { return error_; } |
| const GValue& GetError() const { return error_; } |
| |
| static const StringRefType& GetSchemaErrorKeyword(SchemaErrorCode schemaErrorCode) { |
| switch (schemaErrorCode) { |
| case kSchemaErrorStartUnknown: return GetStartUnknownString(); |
| case kSchemaErrorRefPlainName: return GetRefPlainNameString(); |
| case kSchemaErrorRefInvalid: return GetRefInvalidString(); |
| case kSchemaErrorRefPointerInvalid: return GetRefPointerInvalidString(); |
| case kSchemaErrorRefUnknown: return GetRefUnknownString(); |
| case kSchemaErrorRefCyclical: return GetRefCyclicalString(); |
| case kSchemaErrorRefNoRemoteProvider: return GetRefNoRemoteProviderString(); |
| case kSchemaErrorRefNoRemoteSchema: return GetRefNoRemoteSchemaString(); |
| case kSchemaErrorRegexInvalid: return GetRegexInvalidString(); |
| case kSchemaErrorSpecUnknown: return GetSpecUnknownString(); |
| case kSchemaErrorSpecUnsupported: return GetSpecUnsupportedString(); |
| case kSchemaErrorSpecIllegal: return GetSpecIllegalString(); |
| case kSchemaErrorReadOnlyAndWriteOnly: return GetReadOnlyAndWriteOnlyString(); |
| default: return GetNullString(); |
| } |
| } |
| |
| //! Default error method |
| void SchemaError(const SchemaErrorCode code, const PointerType& location) { |
| currentError_ = GValue(kObjectType); |
| AddCurrentError(code, location); |
| } |
| |
| //! Method for error with single string value insert |
| void SchemaErrorValue(const SchemaErrorCode code, const PointerType& location, const Ch* value, SizeType length) { |
| currentError_ = GValue(kObjectType); |
| currentError_.AddMember(GetValueString(), GValue(value, length, *allocator_).Move(), *allocator_); |
| AddCurrentError(code, location); |
| } |
| |
| //! Method for error with invalid pointer |
| void SchemaErrorPointer(const SchemaErrorCode code, const PointerType& location, const Ch* value, SizeType length, const PointerType& pointer) { |
| currentError_ = GValue(kObjectType); |
| currentError_.AddMember(GetValueString(), GValue(value, length, *allocator_).Move(), *allocator_); |
| currentError_.AddMember(GetOffsetString(), static_cast<SizeType>(pointer.GetParseErrorOffset() / sizeof(Ch)), *allocator_); |
| AddCurrentError(code, location); |
| } |
| |
| private: |
| //! Prohibit copying |
| GenericSchemaDocument(const GenericSchemaDocument&); |
| //! Prohibit assignment |
| GenericSchemaDocument& operator=(const GenericSchemaDocument&); |
| |
| typedef const PointerType* SchemaRefPtr; // PR #1393 |
| |
| 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 AddErrorInstanceLocation(GValue& result, const PointerType& location) { |
| GenericStringBuffer<EncodingType> sb; |
| location.StringifyUriFragment(sb); |
| GValue instanceRef(sb.GetString(), static_cast<SizeType>(sb.GetSize() / sizeof(Ch)), *allocator_); |
| result.AddMember(GetInstanceRefString(), instanceRef, *allocator_); |
| } |
| |
| void AddError(GValue& keyword, GValue& error) { |
| typename GValue::MemberIterator member = error_.FindMember(keyword); |
| if (member == error_.MemberEnd()) |
| error_.AddMember(keyword, error, *allocator_); |
| else { |
| if (member->value.IsObject()) { |
| GValue errors(kArrayType); |
| errors.PushBack(member->value, *allocator_); |
| member->value = errors; |
| } |
| member->value.PushBack(error, *allocator_); |
| } |
| } |
| |
| void AddCurrentError(const SchemaErrorCode code, const PointerType& location) { |
| RAPIDJSON_SCHEMA_PRINT(InvalidKeyword, GetSchemaErrorKeyword(code)); |
| currentError_.AddMember(GetErrorCodeString(), code, *allocator_); |
| AddErrorInstanceLocation(currentError_, location); |
| AddError(GValue(GetSchemaErrorKeyword(code)).Move(), currentError_); |
| } |
| |
| #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_(ErrorCode, 'e', 'r', 'r', 'o', 'r', 'C', 'o', 'd', 'e') |
| RAPIDJSON_STRING_(Value, 'v', 'a', 'l', 'u', 'e') |
| RAPIDJSON_STRING_(Offset, 'o', 'f', 'f', 's', 'e', 't') |
| |
| RAPIDJSON_STRING_(Null, 'n', 'u', 'l', 'l') |
| RAPIDJSON_STRING_(SpecUnknown, 'S', 'p', 'e', 'c', 'U', 'n', 'k', 'n', 'o', 'w', 'n') |
| RAPIDJSON_STRING_(SpecUnsupported, 'S', 'p', 'e', 'c', 'U', 'n', 's', 'u', 'p', 'p', 'o', 'r', 't', 'e', 'd') |
| RAPIDJSON_STRING_(SpecIllegal, 'S', 'p', 'e', 'c', 'I', 'l', 'l', 'e', 'g', 'a', 'l') |
| RAPIDJSON_STRING_(StartUnknown, 'S', 't', 'a', 'r', 't', 'U', 'n', 'k', 'n', 'o', 'w', 'n') |
| RAPIDJSON_STRING_(RefPlainName, 'R', 'e', 'f', 'P', 'l', 'a', 'i', 'n', 'N', 'a', 'm', 'e') |
| RAPIDJSON_STRING_(RefInvalid, 'R', 'e', 'f', 'I', 'n', 'v', 'a', 'l', 'i', 'd') |
| RAPIDJSON_STRING_(RefPointerInvalid, 'R', 'e', 'f', 'P', 'o', 'i', 'n', 't', 'e', 'r', 'I', 'n', 'v', 'a', 'l', 'i', 'd') |
| RAPIDJSON_STRING_(RefUnknown, 'R', 'e', 'f', 'U', 'n', 'k', 'n', 'o', 'w', 'n') |
| RAPIDJSON_STRING_(RefCyclical, 'R', 'e', 'f', 'C', 'y', 'c', 'l', 'i', 'c', 'a', 'l') |
| RAPIDJSON_STRING_(RefNoRemoteProvider, 'R', 'e', 'f', 'N', 'o', 'R', 'e', 'm', 'o', 't', 'e', 'P', 'r', 'o', 'v', 'i', 'd', 'e', 'r') |
| RAPIDJSON_STRING_(RefNoRemoteSchema, 'R', 'e', 'f', 'N', 'o', 'R', 'e', 'm', 'o', 't', 'e', 'S', 'c', 'h', 'e', 'm', 'a') |
| RAPIDJSON_STRING_(ReadOnlyAndWriteOnly, 'R', 'e', 'a', 'd', 'O', 'n', 'l', 'y', 'A', 'n', 'd', 'W', 'r', 'i', 't', 'e', 'O', 'n', 'l', 'y') |
| RAPIDJSON_STRING_(RegexInvalid, 'R', 'e', 'g', 'e', 'x', 'I', 'n', 'v', 'a', 'l', 'i', 'd') |
| |
| #undef RAPIDJSON_STRING_ |
| |
| // Static method to get schema draft of any schema document |
| static SchemaDraft GetSchemaDraft(const ValueType& document) { |
| static const Ch kDraft03String[] = { 'h', 't', 't', 'p', ':', '/', '/', 'j', 's', 'o', 'n', '-', 's', 'c', 'h', 'e', 'm', 'a', '.', 'o', 'r', 'g', '/', 'd', 'r', 'a', 'f', 't', '-', '0', '3', '/', 's', 'c', 'h', 'e', 'm', 'a', '#', '\0' }; |
| static const Ch kDraft04String[] = { 'h', 't', 't', 'p', ':', '/', '/', 'j', 's', 'o', 'n', '-', 's', 'c', 'h', 'e', 'm', 'a', '.', 'o', 'r', 'g', '/', 'd', 'r', 'a', 'f', 't', '-', '0', '4', '/', 's', 'c', 'h', 'e', 'm', 'a', '#', '\0' }; |
| static const Ch kDraft05String[] = { 'h', 't', 't', 'p', ':', '/', '/', 'j', 's', 'o', 'n', '-', 's', 'c', 'h', 'e', 'm', 'a', '.', 'o', 'r', 'g', '/', 'd', 'r', 'a', 'f', 't', '-', '0', '5', '/', 's', 'c', 'h', 'e', 'm', 'a', '#', '\0' }; |
| static const Ch kDraft06String[] = { 'h', 't', 't', 'p', ':', '/', '/', 'j', 's', 'o', 'n', '-', 's', 'c', 'h', 'e', 'm', 'a', '.', 'o', 'r', 'g', '/', 'd', 'r', 'a', 'f', 't', '-', '0', '6', '/', 's', 'c', 'h', 'e', 'm', 'a', '#', '\0' }; |
| static const Ch kDraft07String[] = { 'h', 't', 't', 'p', ':', '/', '/', 'j', 's', 'o', 'n', '-', 's', 'c', 'h', 'e', 'm', 'a', '.', 'o', 'r', 'g', '/', 'd', 'r', 'a', 'f', 't', '-', '0', '7', '/', 's', 'c', 'h', 'e', 'm', 'a', '#', '\0' }; |
| static const Ch kDraft2019_09String[] = { 'h', 't', 't', 'p', 's', ':', '/', '/', 'j', 's', 'o', 'n', '-', 's', 'c', 'h', 'e', 'm', 'a', '.', 'o', 'r', 'g', '/', 'd', 'r', 'a', 'f', 't', '/', '2', '0', '1', '9', '-', '0', '9', '/', 's', 'c', 'h', 'e', 'm', 'a', '\0' }; |
| static const Ch kDraft2020_12String[] = { 'h', 't', 't', 'p', 's', ':', '/', '/', 'j', 's', 'o', 'n', '-', 's', 'c', 'h', 'e', 'm', 'a', '.', 'o', 'r', 'g', '/', 'd', 'r', 'a', 'f', 't', '/', '2', '0', '2', '0', '-', '1', '2', '/', 's', 'c', 'h', 'e', 'm', 'a', '\0' }; |
| |
| if (!document.IsObject()) { |
| return kDraftNone; |
| } |
| |
| // Get the schema draft from the $schema keyword at the supplied location |
| typename ValueType::ConstMemberIterator itr = document.FindMember(SchemaType::GetSchemaString()); |
| if (itr != document.MemberEnd()) { |
| if (!itr->value.IsString()) return kDraftUnknown; |
| const UriType draftUri(itr->value); |
| // Check base uri for match |
| if (draftUri.Match(UriType(kDraft04String), false)) return kDraft04; |
| if (draftUri.Match(UriType(kDraft05String), false)) return kDraft05; |
| if (draftUri.Match(UriType(kDraft06String), false)) return kDraft06; |
| if (draftUri.Match(UriType(kDraft07String), false)) return kDraft07; |
| if (draftUri.Match(UriType(kDraft03String), false)) return kDraft03; |
| if (draftUri.Match(UriType(kDraft2019_09String), false)) return kDraft2019_09; |
| if (draftUri.Match(UriType(kDraft2020_12String), false)) return kDraft2020_12; |
| return kDraftUnknown; |
| } |
| // $schema not found |
| return kDraftNone; |
| } |
| |
| |
| // Get open api version of any schema document |
| static OpenApiVersion GetOpenApiVersion(const ValueType& document) { |
| static const Ch kVersion20String[] = { '2', '.', '0', '\0' }; |
| static const Ch kVersion30String[] = { '3', '.', '0', '.', '\0' }; // ignore patch level |
| static const Ch kVersion31String[] = { '3', '.', '1', '.', '\0' }; // ignore patch level |
| static SizeType len = internal::StrLen<Ch>(kVersion30String); |
| |
| if (!document.IsObject()) { |
| return kVersionNone; |
| } |
| |
| // Get the open api version from the swagger / openapi keyword at the supplied location |
| typename ValueType::ConstMemberIterator itr = document.FindMember(SchemaType::GetSwaggerString()); |
| if (itr == document.MemberEnd()) itr = document.FindMember(SchemaType::GetOpenApiString()); |
| if (itr != document.MemberEnd()) { |
| if (!itr->value.IsString()) return kVersionUnknown; |
| const ValueType kVersion20Value(kVersion20String); |
| if (kVersion20Value == itr->value) return kVersion20; // must match 2.0 exactly |
| const ValueType kVersion30Value(kVersion30String); |
| if (itr->value.GetStringLength() > len && kVersion30Value == ValueType(itr->value.GetString(), len)) return kVersion30; // must match 3.0.x |
| const ValueType kVersion31Value(kVersion31String); |
| if (itr->value.GetStringLength() > len && kVersion31Value == ValueType(itr->value.GetString(), len)) return kVersion31; // must match 3.1.x |
| return kVersionUnknown; |
| } |
| // swagger or openapi not found |
| return kVersionNone; |
| } |
| |
| // Get the draft of the schema or the open api version (which implies the draft). |
| // Report an error if schema draft or open api version not supported or not recognized, or both in document, and carry on. |
| void SetSchemaSpecification(const ValueType& document) { |
| // Look for '$schema', 'swagger' or 'openapi' keyword at document root |
| SchemaDraft docDraft = GetSchemaDraft(document); |
| OpenApiVersion docOapi = GetOpenApiVersion(document); |
| // Error if both in document |
| if (docDraft != kDraftNone && docOapi != kVersionNone) |
| SchemaError(kSchemaErrorSpecIllegal, PointerType()); |
| // Use document draft or open api version if present or use spec from constructor |
| if (docDraft != kDraftNone) |
| spec_ = Specification(docDraft); |
| else if (docOapi != kVersionNone) |
| spec_ = Specification(docOapi); |
| // Error if draft or version unknown |
| if (spec_.draft == kDraftUnknown || spec_.oapi == kVersionUnknown) |
| SchemaError(kSchemaErrorSpecUnknown, PointerType()); |
| else if (!spec_.IsSupported()) |
| SchemaError(kSchemaErrorSpecUnsupported, PointerType()); |
| } |
| |
| // Changed by PR #1393 |
| void CreateSchemaRecursive(const SchemaType** schema, const PointerType& pointer, const ValueType& v, const ValueType& document, const UriType& id) { |
| if (v.GetType() == kObjectType) { |
| UriType newid = UriType(CreateSchema(schema, pointer, v, document, id), allocator_); |
| |
| for (typename ValueType::ConstMemberIterator itr = v.MemberBegin(); itr != v.MemberEnd(); ++itr) |
| CreateSchemaRecursive(0, pointer.Append(itr->name, allocator_), itr->value, document, newid); |
| } |
| else if (v.GetType() == kArrayType) |
| for (SizeType i = 0; i < v.Size(); i++) |
| CreateSchemaRecursive(0, pointer.Append(i, allocator_), v[i], document, id); |
| } |
| |
| // Changed by PR #1393 |
| const UriType& CreateSchema(const SchemaType** schema, const PointerType& pointer, const ValueType& v, const ValueType& document, const UriType& id) { |
| RAPIDJSON_ASSERT(pointer.IsValid()); |
| GenericStringBuffer<EncodingType> sb; |
| pointer.StringifyUriFragment(sb); |
| RAPIDJSON_SCHEMA_PRINT(Method, "GenericSchemaDocument::CreateSchema", sb.GetString(), id.GetString()); |
| if (v.IsObject()) { |
| if (const SchemaType* sc = GetSchema(pointer)) { |
| if (schema) |
| *schema = sc; |
| AddSchemaRefs(const_cast<SchemaType*>(sc)); |
| } |
| else if (!HandleRefSchema(pointer, schema, v, document, id)) { |
| // The new schema constructor adds itself and its $ref(s) to schemaMap_ |
| SchemaType* s = new (allocator_->Malloc(sizeof(SchemaType))) SchemaType(this, pointer, v, document, allocator_, id); |
| if (schema) |
| *schema = s; |
| return s->GetId(); |
| } |
| } |
| else { |
| if (schema) |
| *schema = typeless_; |
| AddSchemaRefs(typeless_); |
| } |
| return id; |
| } |
| |
| // Changed by PR #1393 |
| // TODO should this return a UriType& ? |
| bool HandleRefSchema(const PointerType& source, const SchemaType** schema, const ValueType& v, const ValueType& document, const UriType& id) { |
| typename ValueType::ConstMemberIterator itr = v.FindMember(SchemaType::GetRefString()); |
| if (itr == v.MemberEnd()) |
| return false; |
| |
| GenericStringBuffer<EncodingType> sb; |
| source.StringifyUriFragment(sb); |
| RAPIDJSON_SCHEMA_PRINT(Method, "GenericSchemaDocument::HandleRefSchema", sb.GetString(), id.GetString()); |
| // Resolve the source pointer to the $ref'ed schema (finally) |
| new (schemaRef_.template Push<SchemaRefPtr>()) SchemaRefPtr(&source); |
| |
| if (itr->value.IsString()) { |
| SizeType len = itr->value.GetStringLength(); |
| if (len == 0) |
| SchemaError(kSchemaErrorRefInvalid, source); |
| else { |
| // First resolve $ref against the in-scope id |
| UriType scopeId = UriType(id, allocator_); |
| UriType ref = UriType(itr->value, allocator_).Resolve(scopeId, allocator_); |
| RAPIDJSON_SCHEMA_PRINT(SchemaIds, id.GetString(), itr->value.GetString(), ref.GetString()); |
| // See if the resolved $ref minus the fragment matches a resolved id in this document |
| // Search from the root. Returns the subschema in the document and its absolute JSON pointer. |
| PointerType basePointer = PointerType(); |
| const ValueType *base = FindId(document, ref, basePointer, docId_, false); |
| if (!base) { |
| // Remote reference - call the remote document provider |
| if (!remoteProvider_) |
| SchemaError(kSchemaErrorRefNoRemoteProvider, source); |
| else { |
| if (const GenericSchemaDocument* remoteDocument = remoteProvider_->GetRemoteDocument(ref, spec_)) { |
| const Ch* s = ref.GetFragString(); |
| len = ref.GetFragStringLength(); |
| if (len <= 1 || s[1] == '/') { |
| // JSON pointer fragment, absolute in the remote schema |
| const PointerType pointer(s, len, allocator_); |
| if (!pointer.IsValid()) |
| SchemaErrorPointer(kSchemaErrorRefPointerInvalid, source, s, len, pointer); |
| else { |
| // Get the subschema |
| if (const SchemaType *sc = remoteDocument->GetSchema(pointer)) { |
| if (schema) |
| *schema = sc; |
| AddSchemaRefs(const_cast<SchemaType *>(sc)); |
| return true; |
| } else |
| SchemaErrorValue(kSchemaErrorRefUnknown, source, ref.GetString(), ref.GetStringLength()); |
| } |
| } else |
| // Plain name fragment, not allowed in remote schema |
| SchemaErrorValue(kSchemaErrorRefPlainName, source, s, len); |
| } else |
| SchemaErrorValue(kSchemaErrorRefNoRemoteSchema, source, ref.GetString(), ref.GetStringLength()); |
| } |
| } |
| else { // Local reference |
| const Ch* s = ref.GetFragString(); |
| len = ref.GetFragStringLength(); |
| if (len <= 1 || s[1] == '/') { |
| // JSON pointer fragment, relative to the resolved URI |
| const PointerType relPointer(s, len, allocator_); |
| if (!relPointer.IsValid()) |
| SchemaErrorPointer(kSchemaErrorRefPointerInvalid, source, s, len, relPointer); |
| else { |
| // Get the subschema |
| if (const ValueType *pv = relPointer.Get(*base)) { |
| // Now get the absolute JSON pointer by adding relative to base |
| PointerType pointer(basePointer, allocator_); |
| for (SizeType i = 0; i < relPointer.GetTokenCount(); i++) |
| pointer = pointer.Append(relPointer.GetTokens()[i], allocator_); |
| if (IsCyclicRef(pointer)) |
| SchemaErrorValue(kSchemaErrorRefCyclical, source, ref.GetString(), ref.GetStringLength()); |
| else { |
| // Call CreateSchema recursively, but first compute the in-scope id for the $ref target as we have jumped there |
| // TODO: cache pointer <-> id mapping |
| size_t unresolvedTokenIndex; |
| scopeId = pointer.GetUri(document, docId_, &unresolvedTokenIndex, allocator_); |
| CreateSchema(schema, pointer, *pv, document, scopeId); |
| return true; |
| } |
| } else |
| SchemaErrorValue(kSchemaErrorRefUnknown, source, ref.GetString(), ref.GetStringLength()); |
| } |
| } else { |
| // Plain name fragment, relative to the resolved URI |
| // Not supported in open api 2.0 and 3.0 |
| PointerType pointer(allocator_); |
| if (spec_.oapi == kVersion20 || spec_.oapi == kVersion30) |
| SchemaErrorValue(kSchemaErrorRefPlainName, source, s, len); |
| // See if the fragment matches an id in this document. |
| // Search from the base we just established. Returns the subschema in the document and its absolute JSON pointer. |
| else if (const ValueType *pv = FindId(*base, ref, pointer, UriType(ref.GetBaseString(), ref.GetBaseStringLength(), allocator_), true, basePointer)) { |
| if (IsCyclicRef(pointer)) |
| SchemaErrorValue(kSchemaErrorRefCyclical, source, ref.GetString(), ref.GetStringLength()); |
| else { |
| // Call CreateSchema recursively, but first compute the in-scope id for the $ref target as we have jumped there |
| // TODO: cache pointer <-> id mapping |
| size_t unresolvedTokenIndex; |
| scopeId = pointer.GetUri(document, docId_, &unresolvedTokenIndex, allocator_); |
| CreateSchema(schema, pointer, *pv, document, scopeId); |
| return true; |
| } |
| } else |
| SchemaErrorValue(kSchemaErrorRefUnknown, source, ref.GetString(), ref.GetStringLength()); |
| } |
| } |
| } |
| } |
| |
| // Invalid/Unknown $ref |
| if (schema) |
| *schema = typeless_; |
| AddSchemaRefs(typeless_); |
| return true; |
| } |
| |
| //! Find the first subschema with a resolved 'id' that matches the specified URI. |
| // If full specified use all URI else ignore fragment. |
| // If found, return a pointer to the subschema and its JSON pointer. |
| // TODO cache pointer <-> id mapping |
| ValueType* FindId(const ValueType& doc, const UriType& finduri, PointerType& resptr, const UriType& baseuri, bool full, const PointerType& here = PointerType()) const { |
| SizeType i = 0; |
| ValueType* resval = 0; |
| UriType tempuri = UriType(finduri, allocator_); |
| UriType localuri = UriType(baseuri, allocator_); |
| if (doc.GetType() == kObjectType) { |
| // Establish the base URI of this object |
| typename ValueType::ConstMemberIterator m = doc.FindMember(SchemaType::GetIdString()); |
| if (m != doc.MemberEnd() && m->value.GetType() == kStringType) { |
| localuri = UriType(m->value, allocator_).Resolve(baseuri, allocator_); |
| } |
| // See if it matches |
| if (localuri.Match(finduri, full)) { |
| RAPIDJSON_SCHEMA_PRINT(Method, "GenericSchemaDocument::FindId (match)", full ? localuri.GetString() : localuri.GetBaseString()); |
| resval = const_cast<ValueType *>(&doc); |
| resptr = here; |
| return resval; |
| } |
| // No match, continue looking |
| for (m = doc.MemberBegin(); m != doc.MemberEnd(); ++m) { |
| if (m->value.GetType() == kObjectType || m->value.GetType() == kArrayType) { |
| resval = FindId(m->value, finduri, resptr, localuri, full, here.Append(m->name.GetString(), m->name.GetStringLength(), allocator_)); |
| } |
| if (resval) break; |
| } |
| } else if (doc.GetType() == kArrayType) { |
| // Continue looking |
| for (typename ValueType::ConstValueIterator v = doc.Begin(); v != doc.End(); ++v) { |
| if (v->GetType() == kObjectType || v->GetType() == kArrayType) { |
| resval = FindId(*v, finduri, resptr, localuri, full, here.Append(i, allocator_)); |
| } |
| if (resval) break; |
| i++; |
| } |
| } |
| return resval; |
| } |
| |
| // Added by PR #1393 |
| void AddSchemaRefs(SchemaType* schema) { |
| RAPIDJSON_SCHEMA_PRINT(Method, "GenericSchemaDocument::AddSchemaRefs"); |
| while (!schemaRef_.Empty()) { |
| SchemaRefPtr *ref = schemaRef_.template Pop<SchemaRefPtr>(1); |
| SchemaEntry *entry = schemaMap_.template Push<SchemaEntry>(); |
| new (entry) SchemaEntry(**ref, schema, false, allocator_); |
| } |
| } |
| |
| // Added by PR #1393 |
| bool IsCyclicRef(const PointerType& pointer) const { |
| for (const SchemaRefPtr* ref = schemaRef_.template Bottom<SchemaRefPtr>(); ref != schemaRef_.template End<SchemaRefPtr>(); ++ref) |
| if (pointer == **ref) |
| 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(s) from $ref(s) until resolved |
| GValue uri_; // Schema document URI |
| UriType docId_; |
| Specification spec_; |
| GValue error_; |
| GValue currentError_; |
| }; |
| |
| //! 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), |
| flags_(kValidateDefaultFlags), |
| depth_(0) |
| { |
| RAPIDJSON_SCHEMA_PRINT(Method, "GenericSchemaValidator::GenericSchemaValidator"); |
| } |
| |
| //! 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), |
| flags_(kValidateDefaultFlags), |
| depth_(0) |
| { |
| RAPIDJSON_SCHEMA_PRINT(Method, "GenericSchemaValidator::GenericSchemaValidator (output handler)"); |
| } |
| |
| //! Destructor. |
| ~GenericSchemaValidator() { |
| Reset(); |
| RAPIDJSON_DELETE(ownStateAllocator_); |
| } |
| |
| //! Reset the internal states. |
| void Reset() { |
| while (!schemaStack_.Empty()) |
| PopSchema(); |
| documentStack_.Clear(); |
| ResetError(); |
| } |
| |
| //! Reset the error state. |
| void ResetError() { |
| error_.SetObject(); |
| currentError_.SetNull(); |
| missingDependents_.SetNull(); |
| valid_ = true; |
| } |
| |
| //! Implementation of ISchemaValidator |
| void SetValidateFlags(unsigned flags) { |
| flags_ = flags; |
| } |
| virtual unsigned GetValidateFlags() const { |
| return flags_; |
| } |
| |
| virtual bool IsValid() const { |
| if (!valid_) return false; |
| if (GetContinueOnErrors() && !error_.ObjectEmpty()) return false; |
| return true; |
| } |
| //! End of Implementation of ISchemaValidator |
| |
| //! Gets the error object. |
| ValueType& GetError() { return error_; } |
| const ValueType& GetError() const { return error_; } |
| |
| //! Gets the JSON pointer pointed to the invalid schema. |
| // If reporting all errors, the stack will be empty. |
| PointerType GetInvalidSchemaPointer() const { |
| return schemaStack_.Empty() ? PointerType() : CurrentSchema().GetPointer(); |
| } |
| |
| //! Gets the keyword of invalid schema. |
| // If reporting all errors, the stack will be empty, so return "errors". |
| const Ch* GetInvalidSchemaKeyword() const { |
| if (!schemaStack_.Empty()) return CurrentContext().invalidKeyword; |
| if (GetContinueOnErrors() && !error_.ObjectEmpty()) return static_cast<const Ch*>(GetErrorsString()); |
| return 0; |
| } |
| |
| //! Gets the error code of invalid schema. |
| // If reporting all errors, the stack will be empty, so return kValidateErrors. |
| ValidateErrorCode GetInvalidSchemaCode() const { |
| if (!schemaStack_.Empty()) return CurrentContext().invalidCode; |
| if (GetContinueOnErrors() && !error_.ObjectEmpty()) return kValidateErrors; |
| return kValidateErrorNone; |
| } |
| |
| //! Gets the JSON pointer pointed to the invalid value. |
| // If reporting all errors, the stack will be empty. |
| 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(kValidateErrorMultipleOf, ValueType(actual).Move(), expected); |
| } |
| void NotMultipleOf(uint64_t actual, const SValue& expected) { |
| AddNumberError(kValidateErrorMultipleOf, ValueType(actual).Move(), expected); |
| } |
| void NotMultipleOf(double actual, const SValue& expected) { |
| AddNumberError(kValidateErrorMultipleOf, ValueType(actual).Move(), expected); |
| } |
| void AboveMaximum(int64_t actual, const SValue& expected, bool exclusive) { |
| AddNumberError(exclusive ? kValidateErrorExclusiveMaximum : kValidateErrorMaximum, ValueType(actual).Move(), expected, |
| exclusive ? &SchemaType::GetExclusiveMaximumString : 0); |
| } |
| void AboveMaximum(uint64_t actual, const SValue& expected, bool exclusive) { |
| AddNumberError(exclusive ? kValidateErrorExclusiveMaximum : kValidateErrorMaximum, ValueType(actual).Move(), expected, |
| exclusive ? &SchemaType::GetExclusiveMaximumString : 0); |
| } |
| void AboveMaximum(double actual, const SValue& expected, bool exclusive) { |
| AddNumberError(exclusive ? kValidateErrorExclusiveMaximum : kValidateErrorMaximum, ValueType(actual).Move(), expected, |
| exclusive ? &SchemaType::GetExclusiveMaximumString : 0); |
| } |
| void BelowMinimum(int64_t actual, const SValue& expected, bool exclusive) { |
| AddNumberError(exclusive ? kValidateErrorExclusiveMinimum : kValidateErrorMinimum, ValueType(actual).Move(), expected, |
| exclusive ? &SchemaType::GetExclusiveMinimumString : 0); |
| } |
| void BelowMinimum(uint64_t actual, const SValue& expected, bool exclusive) { |
| AddNumberError(exclusive ? kValidateErrorExclusiveMinimum : kValidateErrorMinimum, ValueType(actual).Move(), expected, |
| exclusive ? &SchemaType::GetExclusiveMinimumString : 0); |
| } |
| void BelowMinimum(double actual, const SValue& expected, bool exclusive) { |
| AddNumberError(exclusive ? kValidateErrorExclusiveMinimum : kValidateErrorMinimum, ValueType(actual).Move(), expected, |
| exclusive ? &SchemaType::GetExclusiveMinimumString : 0); |
| } |
| |
| void TooLong(const Ch* str, SizeType length, SizeType expected) { |
| AddNumberError(kValidateErrorMaxLength, |
| ValueType(str, length, GetStateAllocator()).Move(), SValue(expected).Move()); |
| } |
| void TooShort(const Ch* str, SizeType length, SizeType expected) { |
| AddNumberError(kValidateErrorMinLength, |
| 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(kValidateErrorPattern); |
| } |
| |
| void DisallowedItem(SizeType index) { |
| currentError_.SetObject(); |
| currentError_.AddMember(GetDisallowedString(), ValueType(index).Move(), GetStateAllocator()); |
| AddCurrentError(kValidateErrorAdditionalItems, true); |
| } |
| void TooFewItems(SizeType actualCount, SizeType expectedCount) { |
| AddNumberError(kValidateErrorMinItems, |
| ValueType(actualCount).Move(), SValue(expectedCount).Move()); |
| } |
| void TooManyItems(SizeType actualCount, SizeType expectedCount) { |
| AddNumberError(kValidateErrorMaxItems, |
| 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(kValidateErrorUniqueItems, true); |
| } |
| |
| void TooManyProperties(SizeType actualCount, SizeType expectedCount) { |
| AddNumberError(kValidateErrorMaxProperties, |
| ValueType(actualCount).Move(), SValue(expectedCount).Move()); |
| } |
| void TooFewProperties(SizeType actualCount, SizeType expectedCount) { |
| AddNumberError(kValidateErrorMinProperties, |
| 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(kValidateErrorRequired); |
| 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(kValidateErrorAdditionalProperties, 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()) { |
| // Create equivalent 'required' error |
| ValueType error(kObjectType); |
| ValidateErrorCode code = kValidateErrorRequired; |
| error.AddMember(GetMissingString(), missingDependents_.Move(), GetStateAllocator()); |
| AddErrorCode(error, code); |
| AddErrorInstanceLocation(error, false); |
| // When appending to a pointer ensure its allocator is used |
| PointerType schemaRef = GetInvalidSchemaPointer().Append(SchemaType::GetValidateErrorKeyword(kValidateErrorDependencies), &GetInvalidSchemaPointer().GetAllocator()); |
| AddErrorSchemaLocation(error, schemaRef.Append(sourceName.GetString(), sourceName.GetStringLength(), &GetInvalidSchemaPointer().GetAllocator())); |
| ValueType wrapper(kObjectType); |
| wrapper.AddMember(ValueType(SchemaType::GetValidateErrorKeyword(code), GetStateAllocator()).Move(), error, GetStateAllocator()); |
| currentError_.AddMember(ValueType(sourceName, GetStateAllocator()).Move(), wrapper, 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(kValidateErrorDependencies); |
| return true; |
| } |
| |
| void DisallowedValue(const ValidateErrorCode code = kValidateErrorEnum) { |
| currentError_.SetObject(); |
| AddCurrentError(code); |
| } |
| 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(kValidateErrorType); |
| } |
| void NotAllOf(ISchemaValidator** subvalidators, SizeType count) { |
| // Treat allOf like oneOf and anyOf to match https://rapidjson.org/md_doc_schema.html#allOf-anyOf-oneOf |
| AddErrorArray(kValidateErrorAllOf, subvalidators, count); |
| //for (SizeType i = 0; i < count; ++i) { |
| // MergeError(static_cast<GenericSchemaValidator*>(subvalidators[i])->GetError()); |
| //} |
| } |
| void NoneOf(ISchemaValidator** subvalidators, SizeType count) { |
| AddErrorArray(kValidateErrorAnyOf, subvalidators, count); |
| } |
| void NotOneOf(ISchemaValidator** subvalidators, SizeType count) { |
| AddErrorArray(kValidateErrorOneOf, subvalidators, count); |
| } |
| void MultipleOneOf(SizeType index1, SizeType index2) { |
| ValueType matches(kArrayType); |
| matches.PushBack(index1, GetStateAllocator()); |
| matches.PushBack(index2, GetStateAllocator()); |
| currentError_.SetObject(); |
| currentError_.AddMember(GetMatchesString(), matches, GetStateAllocator()); |
| AddCurrentError(kValidateErrorOneOfMatch); |
| } |
| void Disallowed() { |
| currentError_.SetObject(); |
| AddCurrentError(kValidateErrorNot); |
| } |
| void DisallowedWhenWriting() { |
| currentError_.SetObject(); |
| AddCurrentError(kValidateErrorReadOnly); |
| } |
| void DisallowedWhenReading() { |
| currentError_.SetObject(); |
| AddCurrentError(kValidateErrorWriteOnly); |
| } |
| |
| #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_(ErrorCode, 'e', 'r', 'r', 'o', 'r', 'C', 'o', 'd', 'e') |
| RAPIDJSON_STRING_(ErrorMessage, 'e', 'r', 'r', 'o', 'r', 'M', 'e', 's', 's', 'a', 'g', 'e') |
| RAPIDJSON_STRING_(Duplicates, 'd', 'u', 'p', 'l', 'i', 'c', 'a', 't', 'e', 's') |
| RAPIDJSON_STRING_(Matches, 'm', 'a', 't', 'c', 'h', 'e', 's') |
| |
| #undef RAPIDJSON_STRING_ |
| |
| #define RAPIDJSON_SCHEMA_HANDLE_BEGIN_(method, arg1)\ |
| if (!valid_) return false; \ |
| if ((!BeginValue() && !GetContinueOnErrors()) || (!CurrentSchema().method arg1 && !GetContinueOnErrors())) {\ |
| *documentStack_.template Push<Ch>() = '\0';\ |
| documentStack_.template Pop<Ch>(1);\ |
| RAPIDJSON_SCHEMA_PRINT(InvalidDocument, documentStack_.template Bottom<Ch>());\ |
| valid_ = false;\ |
| return valid_;\ |
| } |
| |
| #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)\ |
| valid_ = (EndValue() || GetContinueOnErrors()) && (!outputHandler_ || outputHandler_->method arg2);\ |
| return valid_; |
| |
| #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_PRINT(Method, "GenericSchemaValidator::StartObject"); |
| RAPIDJSON_SCHEMA_HANDLE_BEGIN_(StartObject, (CurrentContext())); |
| RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(StartObject, ()); |
| valid_ = !outputHandler_ || outputHandler_->StartObject(); |
| return valid_; |
| } |
| |
| bool Key(const Ch* str, SizeType len, bool copy) { |
| RAPIDJSON_SCHEMA_PRINT(Method, "GenericSchemaValidator::Key", str); |
| if (!valid_) return false; |
| AppendToken(str, len); |
| if (!CurrentSchema().Key(CurrentContext(), str, len, copy) && !GetContinueOnErrors()) { |
| valid_ = false; |
| return valid_; |
| } |
| RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(Key, (str, len, copy)); |
| valid_ = !outputHandler_ || outputHandler_->Key(str, len, copy); |
| return valid_; |
| } |
| |
| bool EndObject(SizeType memberCount) { |
| RAPIDJSON_SCHEMA_PRINT(Method, "GenericSchemaValidator::EndObject"); |
| if (!valid_) return false; |
| RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(EndObject, (memberCount)); |
| if (!CurrentSchema().EndObject(CurrentContext(), memberCount) && !GetContinueOnErrors()) { |
| valid_ = false; |
| return valid_; |
| } |
| RAPIDJSON_SCHEMA_HANDLE_END_(EndObject, (memberCount)); |
| } |
| |
| bool StartArray() { |
| RAPIDJSON_SCHEMA_PRINT(Method, "GenericSchemaValidator::StartArray"); |
| RAPIDJSON_SCHEMA_HANDLE_BEGIN_(StartArray, (CurrentContext())); |
| RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(StartArray, ()); |
| valid_ = !outputHandler_ || outputHandler_->StartArray(); |
| return valid_; |
| } |
| |
| bool EndArray(SizeType elementCount) { |
| RAPIDJSON_SCHEMA_PRINT(Method, "GenericSchemaValidator::EndArray"); |
| if (!valid_) return false; |
| RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(EndArray, (elementCount)); |
| if (!CurrentSchema().EndArray(CurrentContext(), elementCount) && !GetContinueOnErrors()) { |
| valid_ = false; |
| return valid_; |
| } |
| RAPIDJSON_SCHEMA_HANDLE_END_(EndArray, (elementCount)); |
| } |
| |
| #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, const bool inheritContinueOnErrors) { |
| *documentStack_.template Push<Ch>() = '\0'; |
| documentStack_.template Pop<Ch>(1); |
| ISchemaValidator* sv = new (GetStateAllocator().Malloc(sizeof(GenericSchemaValidator))) GenericSchemaValidator(*schemaDocument_, root, documentStack_.template Bottom<char>(), documentStack_.GetSize(), |
| depth_ + 1, |
| &GetStateAllocator()); |
| sv->SetValidateFlags(inheritContinueOnErrors ? GetValidateFlags() : GetValidateFlags() & ~static_cast<unsigned>(kValidateContinueOnErrorFlag)); |
| return sv; |
| } |
| |
| 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); |
| } |
| // End of implementation of ISchemaStateFactory<SchemaType> |
| |
| 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, |
| unsigned depth, |
| 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), |
| flags_(kValidateDefaultFlags), |
| depth_(depth) |
| { |
| RAPIDJSON_SCHEMA_PRINT(Method, "GenericSchemaValidator::GenericSchemaValidator (internal)", basePath && basePathSize ? basePath : ""); |
| if (basePath && basePathSize) |
| memcpy(documentStack_.template Push<char>(basePathSize), basePath, basePathSize); |
| } |
| |
| StateAllocator& GetStateAllocator() { |
| if (!stateAllocator_) |
| stateAllocator_ = ownStateAllocator_ = RAPIDJSON_NEW(StateAllocator)(); |
| return *stateAllocator_; |
| } |
| |
| bool GetContinueOnErrors() const { |
| return flags_ & kValidateContinueOnErrorFlag; |
| } |
| |
| bool BeginValue() { |
| RAPIDJSON_SCHEMA_PRINT(Method, "GenericSchemaValidator::BeginValue"); |
| if (schemaStack_.Empty()) |
| PushSchema(root_); |
| else { |
| if (CurrentContext().inArray) |
| internal::TokenHelper<internal::Stack<StateAllocator>, Ch>::AppendIndexToken(documentStack_, CurrentContext().arrayElementIndex); |
| |
| if (!CurrentSchema().BeginValue(CurrentContext()) && !GetContinueOnErrors()) |
| 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)); |
| std::memset(va, 0, sizeof(ISchemaValidator*) * count); |
| for (SizeType i = 0; i < count; i++) |
| va[validatorCount++] = CreateSchemaValidator(*sa[i], true); // Inherit continueOnError |
| } |
| |
| CurrentContext().arrayUniqueness = valueUniqueness; |
| } |
| return true; |
| } |
| |
| bool EndValue() { |
| RAPIDJSON_SCHEMA_PRINT(Method, "GenericSchemaValidator::EndValue"); |
| if (!CurrentSchema().EndValue(CurrentContext()) && !GetContinueOnErrors()) |
| return false; |
| |
| GenericStringBuffer<EncodingType> sb; |
| schemaDocument_->GetPointer(&CurrentSchema()).StringifyUriFragment(sb); |
| *documentStack_.template Push<Ch>() = '\0'; |
| documentStack_.template Pop<Ch>(1); |
| RAPIDJSON_SCHEMA_PRINT(ValidatorPointers, sb.GetString(), documentStack_.template Bottom<Ch>(), depth_); |
| void* hasher = CurrentContext().hasher; |
| uint64_t h = hasher && CurrentContext().arrayUniqueness ? static_cast<HasherType*>(hasher)->GetHashCode() : 0; |
| |
| PopSchema(); |
| |
| if (!schemaStack_.Empty()) { |
| Context& context = CurrentContext(); |
| // Only check uniqueness if there is a hasher |
| if (hasher && 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()); |
| // Cleanup before returning if continuing |
| if (GetContinueOnErrors()) { |
| a->PushBack(h, GetStateAllocator()); |
| while (!documentStack_.Empty() && *documentStack_.template Pop<Ch>(1) != '/'); |
| } |
| RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorUniqueItems); |
| } |
| 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, flags_); } |
| |
| 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 AddErrorInstanceLocation(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()); |
| } |
| |
| void AddErrorSchemaLocation(ValueType& result, PointerType schema = PointerType()) { |
| GenericStringBuffer<EncodingType> sb; |
| SizeType len = CurrentSchema().GetURI().GetStringLength(); |
| if (len) memcpy(sb.Push(len), CurrentSchema().GetURI().GetString(), len * sizeof(Ch)); |
| if (schema.GetTokenCount()) schema.StringifyUriFragment(sb); |
| else GetInvalidSchemaPointer().StringifyUriFragment(sb); |
| ValueType schemaRef(sb.GetString(), static_cast<SizeType>(sb.GetSize() / sizeof(Ch)), |
| GetStateAllocator()); |
| result.AddMember(GetSchemaRefString(), schemaRef, GetStateAllocator()); |
| } |
| |
| void AddErrorCode(ValueType& result, const ValidateErrorCode code) { |
| result.AddMember(GetErrorCodeString(), code, 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 ValidateErrorCode code, bool parent = false) { |
| AddErrorCode(currentError_, code); |
| AddErrorInstanceLocation(currentError_, parent); |
| AddErrorSchemaLocation(currentError_); |
| AddError(ValueType(SchemaType::GetValidateErrorKeyword(code), 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 ValidateErrorCode code, 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(code); |
| } |
| |
| void AddErrorArray(const ValidateErrorCode code, |
| 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(code); |
| } |
| |
| 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_; |
| unsigned flags_; |
| unsigned depth_; |
| }; |
| |
| 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_(), invalidSchemaCode_(kValidateErrorNone), 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(); |
| invalidSchemaCode_ = validator.GetInvalidSchemaCode(); |
| 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_; } |
| ValidateErrorCode GetInvalidSchemaCode() const { return invalidSchemaCode_; } |
| |
| private: |
| InputStream& is_; |
| const SchemaDocumentType& sd_; |
| |
| ParseResult parseResult_; |
| PointerType invalidSchemaPointer_; |
| const Ch* invalidSchemaKeyword_; |
| PointerType invalidDocumentPointer_; |
| ValidateErrorCode invalidSchemaCode_; |
| StackAllocator allocator_; |
| ValueType error_; |
| bool isValid_; |
| }; |
| |
| RAPIDJSON_NAMESPACE_END |
| RAPIDJSON_DIAG_POP |
| |
| #endif // RAPIDJSON_SCHEMA_H_ |