From 1f59c69cd18cd508395fe0bb5c2f8ee909e3c48d Mon Sep 17 00:00:00 2001 From: Tom Briden Date: Sun, 15 May 2022 10:15:26 +0100 Subject: [PATCH 01/35] valuetest: fix potential write of terminating nul past the end of the destination Fixes 2 compile errors with gcc-12, eg: tesunittest/valuetest.cpp:1516:30: error: 'sprintf' may write a terminating nul past the end of the destination [-Werror=format-overflow=] test/unittest/valuetest.cpp:1516:20: note: 'sprintf' output between 2 and 11 bytes into a destination of size 10 --- test/unittest/valuetest.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/unittest/valuetest.cpp b/test/unittest/valuetest.cpp index 0a6b325..9211419 100644 --- a/test/unittest/valuetest.cpp +++ b/test/unittest/valuetest.cpp @@ -1581,7 +1581,7 @@ TEST(Value, ObjectHelperRangeFor) { { int i = 0; for (auto& m : x.GetObject()) { - char name[10]; + char name[11]; sprintf(name, "%d", i); EXPECT_STREQ(name, m.name.GetString()); EXPECT_EQ(i, m.value.GetInt()); @@ -1592,7 +1592,7 @@ TEST(Value, ObjectHelperRangeFor) { { int i = 0; for (const auto& m : const_cast(x).GetObject()) { - char name[10]; + char name[11]; sprintf(name, "%d", i); EXPECT_STREQ(name, m.name.GetString()); EXPECT_EQ(i, m.value.GetInt()); From 2b2c80450031028439ba2a17a09ef5aa10f2159b Mon Sep 17 00:00:00 2001 From: Tom Briden Date: Sun, 15 May 2022 10:20:21 +0100 Subject: [PATCH 02/35] encdedstreamtest: fix use-after-free compile error with gcc-12 --- test/unittest/encodedstreamtest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unittest/encodedstreamtest.cpp b/test/unittest/encodedstreamtest.cpp index d9b87e9..1f0f0e7 100644 --- a/test/unittest/encodedstreamtest.cpp +++ b/test/unittest/encodedstreamtest.cpp @@ -113,8 +113,8 @@ protected: EXPECT_EQ(expected, actual); } EXPECT_EQ('\0', s.Peek()); - free(data); EXPECT_EQ(size, eis.Tell()); + free(data); } } From 0390b1ad5753f94284bba8c7fa8acb97640b9212 Mon Sep 17 00:00:00 2001 From: Peter Kasting Date: Fri, 6 May 2022 16:03:36 -0700 Subject: [PATCH 03/35] Avoid exit-time destructors. operator[]() was recently changed to use the existing code in order to correctly align the returned pointer; however this broke -Wexit-time-destructors. Change to a method that is still correctly aligned but does not generate a destructor. --- include/rapidjson/document.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/rapidjson/document.h b/include/rapidjson/document.h index 74089cb..ea6d806 100644 --- a/include/rapidjson/document.h +++ b/include/rapidjson/document.h @@ -1235,8 +1235,8 @@ public: // return NullValue; // Use static buffer and placement-new to prevent destruction - static GenericValue buffer; - return *new (reinterpret_cast(&buffer)) GenericValue(); + alignas(GenericValue) static char buffer[sizeof(GenericValue)]; + return *new (buffer) GenericValue(); } } template From 46959535677c4c05cb0a0ed6087182662c1576fb Mon Sep 17 00:00:00 2001 From: Peter Kasting Date: Fri, 6 May 2022 16:03:36 -0700 Subject: [PATCH 04/35] Avoid exit-time destructors. operator[]() was recently changed to use the existing code in order to correctly align the returned pointer; however this broke -Wexit-time-destructors. Change to a method that is still correctly aligned but does not generate a destructor. --- include/rapidjson/document.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/include/rapidjson/document.h b/include/rapidjson/document.h index ea6d806..5136f98 100644 --- a/include/rapidjson/document.h +++ b/include/rapidjson/document.h @@ -1230,6 +1230,7 @@ public: else { RAPIDJSON_ASSERT(false); // see above note +#if defined(__cplusplus) && (__cplusplus >= 201103L) // This will generate -Wexit-time-destructors in clang // static GenericValue NullValue; // return NullValue; @@ -1237,6 +1238,10 @@ public: // Use static buffer and placement-new to prevent destruction alignas(GenericValue) static char buffer[sizeof(GenericValue)]; return *new (buffer) GenericValue(); +#else + static GenericValue buffer; + return *new (reinterpret_cast(&buffer)) GenericValue(); +#endif } } template From 88f8ddd70cebd8b0e7728467ac3958ad2a4c2cf1 Mon Sep 17 00:00:00 2001 From: Peter Kasting Date: Fri, 13 May 2022 09:09:59 -0700 Subject: [PATCH 05/35] Include conceptual change from PR 2001. --- include/rapidjson/document.h | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/include/rapidjson/document.h b/include/rapidjson/document.h index 5136f98..a72a04f 100644 --- a/include/rapidjson/document.h +++ b/include/rapidjson/document.h @@ -1230,19 +1230,29 @@ public: else { RAPIDJSON_ASSERT(false); // see above note -#if defined(__cplusplus) && (__cplusplus >= 201103L) - // This will generate -Wexit-time-destructors in clang - // static GenericValue NullValue; - // return NullValue; + // Use thread-local storage to prevent races between threads. +#if defined(_MSC_VER) && _MSC_VER < 1900 +// MSVC 2013 or earlier does not support `thread_local` attribute even in C++11 +// mode. +#define RAPIDJSON_THREAD_LOCAL __declspec(thread) +#elif RAPIDJSON_HAS_CXX11 +#define RAPIDJSON_THREAD_LOCAL thread_local +#elif defined(__GNUC__) || defined(__clang__) +#define RAPIDJSON_THREAD_LOCAL __thread +#else +#define RAPIDJSON_THREAD_LOCAL +#endif - // Use static buffer and placement-new to prevent destruction - alignas(GenericValue) static char buffer[sizeof(GenericValue)]; +#if RAPIDJSON_HAS_CXX11 + // Use static buffer and placement-new to prevent destruction. + alignas(GenericValue) RAPIDJSON_THREAD_LOCAL static char buffer[sizeof(GenericValue)]; return *new (buffer) GenericValue(); #else - static GenericValue buffer; - return *new (reinterpret_cast(&buffer)) GenericValue(); + // This will generate -Wexit-time-destructors in clang. + RAPIDJSON_THREAD_LOCAL static GenericValue buffer; + return buffer; #endif - } + } } template const GenericValue& operator[](const GenericValue& name) const { return const_cast(*this)[name]; } From 781a4e667d84aeedbeb8184b7b62425ea66ec59f Mon Sep 17 00:00:00 2001 From: Peter Kasting Date: Fri, 13 May 2022 10:07:56 -0700 Subject: [PATCH 06/35] Try to fix MSVC build. --- include/rapidjson/document.h | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/include/rapidjson/document.h b/include/rapidjson/document.h index a72a04f..0c4c229 100644 --- a/include/rapidjson/document.h +++ b/include/rapidjson/document.h @@ -1230,29 +1230,29 @@ public: else { RAPIDJSON_ASSERT(false); // see above note - // Use thread-local storage to prevent races between threads. -#if defined(_MSC_VER) && _MSC_VER < 1900 -// MSVC 2013 or earlier does not support `thread_local` attribute even in C++11 -// mode. -#define RAPIDJSON_THREAD_LOCAL __declspec(thread) -#elif RAPIDJSON_HAS_CXX11 -#define RAPIDJSON_THREAD_LOCAL thread_local -#elif defined(__GNUC__) || defined(__clang__) -#define RAPIDJSON_THREAD_LOCAL __thread -#else -#define RAPIDJSON_THREAD_LOCAL -#endif - #if RAPIDJSON_HAS_CXX11 - // Use static buffer and placement-new to prevent destruction. - alignas(GenericValue) RAPIDJSON_THREAD_LOCAL static char buffer[sizeof(GenericValue)]; + // Use thread-local storage to prevent races between threads. + // Use static buffer and placement-new to prevent destruction, with + // alignas() to ensure proper alignment. + alignas(GenericValue) thread_local static char buffer[sizeof(GenericValue)]; return *new (buffer) GenericValue(); +#elif defined(_MSC_VER) && _MSC_VER < 1900 + // There's no way to solve both thread locality and proper alignment + // simultaneously. + __declspec(thread) static char buffer[sizeof(GenericValue)]; + return *new (buffer) GenericValue(); +#elif defined(__GNUC__) || defined(__clang__) + // This will generate -Wexit-time-destructors in clang, but that's + // better than having under-alignment. + __thread static GenericValue buffer; + return buffer; #else - // This will generate -Wexit-time-destructors in clang. - RAPIDJSON_THREAD_LOCAL static GenericValue buffer; + // Don't know what compiler this is, so don't know how to ensure + // thread-locality. + static GenericValue buffer; return buffer; #endif - } + } } template const GenericValue& operator[](const GenericValue& name) const { return const_cast(*this)[name]; } From dd3f730d745257744cf110fd051fdcd27bf48009 Mon Sep 17 00:00:00 2001 From: Johnny Shaw Date: Fri, 20 May 2022 15:01:41 -0600 Subject: [PATCH 07/35] Make schema dtor robust against exceptions --- include/rapidjson/schema.h | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/include/rapidjson/schema.h b/include/rapidjson/schema.h index 248632b..188d659 100644 --- a/include/rapidjson/schema.h +++ b/include/rapidjson/schema.h @@ -380,13 +380,19 @@ struct SchemaValidationContext { if (hasher) factory.DestroryHasher(hasher); if (validators) { - for (SizeType i = 0; i < validatorCount; i++) - factory.DestroySchemaValidator(validators[i]); + 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++) - factory.DestroySchemaValidator(patternPropertiesValidators[i]); + for (SizeType i = 0; i < patternPropertiesValidatorCount; i++) { + if (patternPropertiesValidators[i]) { + factory.DestroySchemaValidator(patternPropertiesValidators[i]); + } + } factory.FreeState(patternPropertiesValidators); } if (patternPropertiesSchemas) @@ -1301,6 +1307,7 @@ private: if (validatorCount_) { RAPIDJSON_ASSERT(context.validators == 0); context.validators = static_cast(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 @@ -2544,6 +2551,7 @@ private: ISchemaValidator**& va = CurrentContext().patternPropertiesValidators; SizeType& validatorCount = CurrentContext().patternPropertiesValidatorCount; va = static_cast(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 } From 719304b113c39c6d60a14afe9a15ffac6e2bc0d3 Mon Sep 17 00:00:00 2001 From: Johnny Shaw Date: Sun, 17 Apr 2022 20:05:30 -0600 Subject: [PATCH 08/35] fixes for natvis --- contrib/natvis/rapidjson.natvis | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/contrib/natvis/rapidjson.natvis b/contrib/natvis/rapidjson.natvis index e7bd44b..2a4316e 100644 --- a/contrib/natvis/rapidjson.natvis +++ b/contrib/natvis/rapidjson.natvis @@ -2,30 +2,30 @@ - null + null true false {(const Ch*)data_.ss.str,na} - {(const Ch*)((size_t)data_.s.str & 0x0000FFFFFFFFFFFF),na} + {(const Ch*)((size_t)data_.s.str & 0x0000FFFFFFFFFFFF),[data_.s.length]na} {data_.n.i.i} {data_.n.u.u} {data_.n.i64} {data_.n.u64} {data_.n.d} - Object members={data_.o.size} - Array members={data_.a.size} + Object members={data_.o.size} + Array members={data_.a.size} - data_.o.size - data_.o.capacity - + data_.o.size + data_.o.capacity + data_.o.size (rapidjson::GenericMember<$T1,$T2>*)(((size_t)data_.o.members) & 0x0000FFFFFFFFFFFF) - data_.a.size - data_.a.capacity - + data_.a.size + data_.a.capacity + data_.a.size (rapidjson::GenericValue<$T1,$T2>*)(((size_t)data_.a.elements) & 0x0000FFFFFFFFFFFF) From 64faab2e9235ed51f37ef592010b35fc7f7324ec Mon Sep 17 00:00:00 2001 From: Kent Ross Date: Mon, 14 Mar 2022 12:25:26 -0700 Subject: [PATCH 09/35] gate definition of symmetric equality operators on impl, not lib These operators call themselves recursively if C++20 semantics are present in the compiler, regardless of standard library support for the operator; therefore the test should be on __cpp_impl_three_way_comparison, not __cpp_lib_[...]. This fixes the Value.EqualtoOperator test when the language standard is set to C++20 and the standard library does not yet define the library support macro. --- include/rapidjson/document.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/rapidjson/document.h b/include/rapidjson/document.h index 0c4c229..4f1e246 100644 --- a/include/rapidjson/document.h +++ b/include/rapidjson/document.h @@ -1092,7 +1092,7 @@ public: */ template RAPIDJSON_DISABLEIF_RETURN((internal::IsGenericValue), (bool)) operator!=(const T& rhs) const { return !(*this == rhs); } -#ifndef __cpp_lib_three_way_comparison +#ifndef __cpp_impl_three_way_comparison //! Equal-to operator with arbitrary types (symmetric version) /*! \return (rhs == lhs) */ From 232389d4f1012dddec4ef84861face2d2ba85709 Mon Sep 17 00:00:00 2001 From: Kent Ross Date: Mon, 14 Mar 2022 13:43:15 -0700 Subject: [PATCH 10/35] delete unused variable --- test/unittest/simdtest.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/unittest/simdtest.cpp b/test/unittest/simdtest.cpp index 649505f..6fa0d49 100644 --- a/test/unittest/simdtest.cpp +++ b/test/unittest/simdtest.cpp @@ -87,14 +87,12 @@ TEST(SIMD, SIMD_SUFFIX(SkipWhitespace_EncodedMemoryStream)) { MemoryStream ms(buffer, 1024); EncodedInputStream, MemoryStream> s(ms); - size_t i = 0; for (;;) { SkipWhitespace(s); if (s.Peek() == '\0') break; //EXPECT_EQ(i, s.Tell()); EXPECT_EQ('X', s.Take()); - i += step; } } } From 27c3a8dc0e2c9218fe94986d249a12b5ed838f1d Mon Sep 17 00:00:00 2001 From: Tim Gates Date: Wed, 20 Jul 2022 18:19:18 +1000 Subject: [PATCH 11/35] docs: fix simple typo, perecent -> percent There is a small typo in test/unittest/pointertest.cpp. Should read `percent` rather than `perecent`. Signed-off-by: Tim Gates --- test/unittest/pointertest.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/unittest/pointertest.cpp b/test/unittest/pointertest.cpp index 342086d..2c90cae 100644 --- a/test/unittest/pointertest.cpp +++ b/test/unittest/pointertest.cpp @@ -303,7 +303,7 @@ TEST(Pointer, Parse_URIFragment) { } { - // Decode UTF-8 perecent encoding to UTF-8 + // Decode UTF-8 percent encoding to UTF-8 Pointer p("#/%C2%A2"); EXPECT_TRUE(p.IsValid()); EXPECT_EQ(1u, p.GetTokenCount()); @@ -311,7 +311,7 @@ TEST(Pointer, Parse_URIFragment) { } { - // Decode UTF-8 perecent encoding to UTF-16 + // Decode UTF-8 percent encoding to UTF-16 GenericPointer > > p(L"#/%C2%A2"); EXPECT_TRUE(p.IsValid()); EXPECT_EQ(1u, p.GetTokenCount()); @@ -320,7 +320,7 @@ TEST(Pointer, Parse_URIFragment) { } { - // Decode UTF-8 perecent encoding to UTF-16 + // Decode UTF-8 percent encoding to UTF-16 GenericPointer > > p(L"#/%E2%82%AC"); EXPECT_TRUE(p.IsValid()); EXPECT_EQ(1u, p.GetTokenCount()); From 22a62fcc2c2fa2418f5d358cdf65c1df73b418ae Mon Sep 17 00:00:00 2001 From: jwillcox-telework <65623287+jwillcox-telework@users.noreply.github.com> Date: Mon, 22 Aug 2022 10:39:34 -0500 Subject: [PATCH 12/35] Update allocators.h Fixing compiler error on older compilers, such as SLED 11.0. cd rapidjson-master g++ -Wall -m32 -ggdb -Iinclude -O1 ./example/simpledom/simpledom.cpp -o simpledom 2>&1 | tee out.txt Changed SIZE_MAX to std::numeric_limits::max() in code to get rid of SIZE_MAX error. --- include/rapidjson/allocators.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/rapidjson/allocators.h b/include/rapidjson/allocators.h index 12bc5ba..ddcf478 100644 --- a/include/rapidjson/allocators.h +++ b/include/rapidjson/allocators.h @@ -19,6 +19,7 @@ #include "internal/meta.h" #include +#include #if RAPIDJSON_HAS_CXX11 #include @@ -433,7 +434,7 @@ namespace internal { template inline T* Realloc(A& a, T* old_p, size_t old_n, size_t new_n) { - RAPIDJSON_NOEXCEPT_ASSERT(old_n <= SIZE_MAX / sizeof(T) && new_n <= SIZE_MAX / sizeof(T)); + RAPIDJSON_NOEXCEPT_ASSERT(old_n <= std::numeric_limits::max() / sizeof(T) && new_n <= std::numeric_limits::max() / sizeof(T)); return static_cast(a.Realloc(old_p, old_n * sizeof(T), new_n * sizeof(T))); } From 06d58b9e848c650114556a23294d0b6440078c61 Mon Sep 17 00:00:00 2001 From: jwillcox-telework <65623287+jwillcox-telework@users.noreply.github.com> Date: Mon, 22 Aug 2022 10:46:45 -0500 Subject: [PATCH 13/35] Update dtoa.h Fixed DigitGen to use proper suffix for uint64_t numeric types. Change from U suffix to ULL suffix. On SLED 11.0 compiler, code would not compile. cd rapidjson-master g++ -Wall -m32 -ggdb -Iinclude -O1 ./example/simpledom/simpledom.cpp -o simpledom 2>&1 | tee out.txt --- include/rapidjson/internal/dtoa.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/include/rapidjson/internal/dtoa.h b/include/rapidjson/internal/dtoa.h index 9f6ae3b..cd45672 100644 --- a/include/rapidjson/internal/dtoa.h +++ b/include/rapidjson/internal/dtoa.h @@ -58,11 +58,11 @@ inline int CountDecimalDigit32(uint32_t n) { } inline void DigitGen(const DiyFp& W, const DiyFp& Mp, uint64_t delta, char* buffer, int* len, int* K) { - static const uint64_t kPow10[] = { 1U, 10U, 100U, 1000U, 10000U, 100000U, 1000000U, 10000000U, 100000000U, - 1000000000U, 10000000000U, 100000000000U, 1000000000000U, - 10000000000000U, 100000000000000U, 1000000000000000U, - 10000000000000000U, 100000000000000000U, 1000000000000000000U, - 10000000000000000000U }; + static const uint64_t kPow10[] = { 1ULL, 10ULL, 100ULL, 1000ULL, 10000ULL, 100000ULL, 1000000ULL, 10000000ULL, 100000000ULL, + 1000000000ULL, 10000000000ULL, 100000000000ULL, 1000000000000ULL, + 10000000000000ULL, 100000000000000ULL, 1000000000000000ULL, + 10000000000000000ULL, 100000000000000000ULL, 1000000000000000000ULL, + 10000000000000000000ULL }; const DiyFp one(uint64_t(1) << -Mp.e, Mp.e); const DiyFp wp_w = Mp - W; uint32_t p1 = static_cast(Mp.f >> -one.e); From 338d8defdb79eeff5f9801bd443ef9a4ae5bb8e7 Mon Sep 17 00:00:00 2001 From: Steve Hanson Date: Thu, 17 Jun 2021 08:58:37 +0100 Subject: [PATCH 14/35] initial --- include/rapidjson/error/en.h | 59 ++++++++- include/rapidjson/error/error.h | 70 ++++++++++- include/rapidjson/pointer.h | 14 +-- include/rapidjson/schema.h | 212 ++++++++++++++++++++++++++------ test/unittest/schematest.cpp | 193 ++++++++++++++++++++++++++--- 5 files changed, 473 insertions(+), 75 deletions(-) diff --git a/include/rapidjson/error/en.h b/include/rapidjson/error/en.h index 5d2e57b..cd4a99a 100644 --- a/include/rapidjson/error/en.h +++ b/include/rapidjson/error/en.h @@ -39,13 +39,13 @@ inline const RAPIDJSON_ERROR_CHARTYPE* GetParseError_En(ParseErrorCode parseErro case kParseErrorDocumentEmpty: return RAPIDJSON_ERROR_STRING("The document is empty."); case kParseErrorDocumentRootNotSingular: return RAPIDJSON_ERROR_STRING("The document root must not be followed by other values."); - + case kParseErrorValueInvalid: return RAPIDJSON_ERROR_STRING("Invalid value."); - + case kParseErrorObjectMissName: return RAPIDJSON_ERROR_STRING("Missing a name for object member."); case kParseErrorObjectMissColon: return RAPIDJSON_ERROR_STRING("Missing a colon after a name of object member."); case kParseErrorObjectMissCommaOrCurlyBracket: return RAPIDJSON_ERROR_STRING("Missing a comma or '}' after an object member."); - + case kParseErrorArrayMissCommaOrSquareBracket: return RAPIDJSON_ERROR_STRING("Missing a comma or ']' after an array element."); case kParseErrorStringUnicodeEscapeInvalidHex: return RAPIDJSON_ERROR_STRING("Incorrect hex digit after \\u escape in string."); @@ -104,7 +104,7 @@ inline const RAPIDJSON_ERROR_CHARTYPE* GetValidateError_En(ValidateErrorCode val case kValidateErrorType: return RAPIDJSON_ERROR_STRING("Property has a type '%actual' that is not in the following list: '%expected'."); case kValidateErrorOneOf: return RAPIDJSON_ERROR_STRING("Property did not match any of the sub-schemas specified by 'oneOf', refer to following errors."); - case kValidateErrorOneOfMatch: return RAPIDJSON_ERROR_STRING("Property matched more than one of the sub-schemas specified by 'oneOf'."); + case kValidateErrorOneOfMatch: return RAPIDJSON_ERROR_STRING("Property matched more than one of the sub-schemas specified by 'oneOf', indices '%matches'."); case kValidateErrorAllOf: return RAPIDJSON_ERROR_STRING("Property did not match all of the sub-schemas specified by 'allOf', refer to following errors."); case kValidateErrorAnyOf: return RAPIDJSON_ERROR_STRING("Property did not match any of the sub-schemas specified by 'anyOf', refer to following errors."); case kValidateErrorNot: return RAPIDJSON_ERROR_STRING("Property matched the sub-schema specified by 'not'."); @@ -113,6 +113,57 @@ inline const RAPIDJSON_ERROR_CHARTYPE* GetValidateError_En(ValidateErrorCode val } } +//! Maps error code of schema document compilation into error message. +/*! + \ingroup RAPIDJSON_ERRORS + \param schemaErrorCode Error code obtained from compiling the schema document. + \return the error message. + \note User can make a copy of this function for localization. + Using switch-case is safer for future modification of error codes. +*/ + inline const RAPIDJSON_ERROR_CHARTYPE* GetSchemaError_En(SchemaErrorCode schemaErrorCode) { + switch (schemaErrorCode) { + case kSchemaErrorNone: return RAPIDJSON_ERROR_STRING("No error."); + + case kSchemaErrorSpecUnknown: return RAPIDJSON_ERROR_STRING("JSON schema draft or OpenAPI version is not recognized."); + case kSchemaErrorSpecUnsupported: return RAPIDJSON_ERROR_STRING("JSON schema draft or OpenAPI version is not supported."); + case kSchemaErrorSpecIllegal: return RAPIDJSON_ERROR_STRING("Both JSON schema draft and OpenAPI version found in document."); + case kSchemaErrorStartUnknown: return RAPIDJSON_ERROR_STRING("Pointer '%value' to start of schema does not resolve to a location in the document."); + case kSchemaErrorRefPlainName: return RAPIDJSON_ERROR_STRING("$ref fragment '%value' must be a JSON pointer."); + case kSchemaErrorRefInvalid: return RAPIDJSON_ERROR_STRING("$ref must not be an empty string."); + case kSchemaErrorRefPointerInvalid: return RAPIDJSON_ERROR_STRING("$ref fragment '%value' is not a valid JSON pointer at offset '%offset'."); + case kSchemaErrorRefUnknown: return RAPIDJSON_ERROR_STRING("$ref '%value' does not resolve to a location in the target document."); + case kSchemaErrorRefCyclical: return RAPIDJSON_ERROR_STRING("$ref '%value' is cyclical."); + case kSchemaErrorRefNoRemoteProvider: return RAPIDJSON_ERROR_STRING("$ref is remote but there is no remote provider."); + case kSchemaErrorRefNoRemoteSchema: return RAPIDJSON_ERROR_STRING("$ref '%value' is remote but the remote provider did not return a schema."); + case kSchemaErrorReadOnlyAndWriteOnly: return RAPIDJSON_ERROR_STRING("Property must not be both 'readOnly' and 'writeOnly'."); + case kSchemaErrorRegexInvalid: return RAPIDJSON_ERROR_STRING("Invalid regular expression '%value' in 'pattern' or 'patternProperties'."); + + default: return RAPIDJSON_ERROR_STRING("Unknown error."); + } + } + +//! Maps error code of pointer parse into error message. +/*! + \ingroup RAPIDJSON_ERRORS + \param pointerParseErrorCode Error code obtained from pointer parse. + \return the error message. + \note User can make a copy of this function for localization. + Using switch-case is safer for future modification of error codes. +*/ +inline const RAPIDJSON_ERROR_CHARTYPE* GetPointerParseError_En(PointerParseErrorCode pointerParseErrorCode) { + switch (pointerParseErrorCode) { + case kPointerParseErrorNone: return RAPIDJSON_ERROR_STRING("No error."); + + case kPointerParseErrorTokenMustBeginWithSolidus: return RAPIDJSON_ERROR_STRING("A token must begin with a '/'."); + case kPointerParseErrorInvalidEscape: return RAPIDJSON_ERROR_STRING("Invalid escape."); + case kPointerParseErrorInvalidPercentEncoding: return RAPIDJSON_ERROR_STRING("Invalid percent encoding in URI fragment."); + case kPointerParseErrorCharacterMustPercentEncode: return RAPIDJSON_ERROR_STRING("A character must be percent encoded in a URI fragment."); + + default: return RAPIDJSON_ERROR_STRING("Unknown error."); + } +} + RAPIDJSON_NAMESPACE_END #ifdef __clang__ diff --git a/include/rapidjson/error/error.h b/include/rapidjson/error/error.h index 6270da1..64613be 100644 --- a/include/rapidjson/error/error.h +++ b/include/rapidjson/error/error.h @@ -185,8 +185,8 @@ enum ValidateErrorCode { kValidateErrorPatternProperties, //!< See other errors. kValidateErrorDependencies, //!< Object has missing property or schema dependencies. - kValidateErrorEnum, //!< Property has a value that is not one of its allowed enumerated values - kValidateErrorType, //!< Property has a type that is not allowed by the schema.. + kValidateErrorEnum, //!< Property has a value that is not one of its allowed enumerated values. + kValidateErrorType, //!< Property has a type that is not allowed by the schema. kValidateErrorOneOf, //!< Property did not match any of the sub-schemas specified by 'oneOf'. kValidateErrorOneOfMatch, //!< Property matched more than one of the sub-schemas specified by 'oneOf'. @@ -207,6 +207,72 @@ enum ValidateErrorCode { */ typedef const RAPIDJSON_ERROR_CHARTYPE* (*GetValidateErrorFunc)(ValidateErrorCode); +/////////////////////////////////////////////////////////////////////////////// +// SchemaErrorCode + +//! Error codes when validating. +/*! \ingroup RAPIDJSON_ERRORS + \see GenericSchemaValidator +*/ +enum SchemaErrorCode { + kSchemaErrorNone = 0, //!< No error. + + kSchemaErrorSpecUnknown, //!< JSON schema draft or OpenAPI version is not recognized + kSchemaErrorSpecUnsupported, //!< JSON schema draft or OpenAPI version is not supported + kSchemaErrorSpecIllegal, //!< Both JSON schema draft and OpenAPI version found in document + kSchemaErrorStartUnknown, //!< Pointer to start of schema does not resolve to a location in the document + kSchemaErrorRefPlainName, //!< $ref fragment must be a JSON pointer + kSchemaErrorRefInvalid, //!< $ref must not be an empty string + kSchemaErrorRefPointerInvalid, //!< $ref fragment is not a valid JSON pointer at offset + kSchemaErrorRefUnknown, //!< $ref does not resolve to a location in the target document + kSchemaErrorRefCyclical, //!< $ref is cyclical + kSchemaErrorRefNoRemoteProvider, //!< $ref is remote but there is no remote provider + kSchemaErrorRefNoRemoteSchema, //!< $ref is remote but the remote provider did not return a schema + kSchemaErrorReadOnlyAndWriteOnly, //!< Property must not be both 'readOnly' and 'writeOnly' + kSchemaErrorRegexInvalid //!< Invalid regular expression in 'pattern' or 'patternProperties' +}; + +//! Function pointer type of GetSchemaError(). +/*! \ingroup RAPIDJSON_ERRORS + + This is the prototype for \c GetSchemaError_X(), where \c X is a locale. + User can dynamically change locale in runtime, e.g.: +\code + GetSchemaErrorFunc GetSchemaError = GetSchemaError_En; // or whatever + const RAPIDJSON_ERROR_CHARTYPE* s = GetSchemaError(validator.GetInvalidSchemaCode()); +\endcode +*/ +typedef const RAPIDJSON_ERROR_CHARTYPE* (*GetSchemaErrorFunc)(SchemaErrorCode); + +/////////////////////////////////////////////////////////////////////////////// +// PointerParseErrorCode + +//! Error code of JSON pointer parsing. +/*! \ingroup RAPIDJSON_ERRORS + \see GenericPointer::GenericPointer, GenericPointer::GetParseErrorCode +*/ +enum PointerParseErrorCode { + kPointerParseErrorNone = 0, //!< The parse is successful + + kPointerParseErrorTokenMustBeginWithSolidus, //!< A token must begin with a '/' + kPointerParseErrorInvalidEscape, //!< Invalid escape + kPointerParseErrorInvalidPercentEncoding, //!< Invalid percent encoding in URI fragment + kPointerParseErrorCharacterMustPercentEncode //!< A character must percent encoded in URI fragment +}; + +//! Function pointer type of GetPointerParseError(). +/*! \ingroup RAPIDJSON_ERRORS + + This is the prototype for \c GetPointerParseError_X(), where \c X is a locale. + User can dynamically change locale in runtime, e.g.: +\code + GetPointerParseErrorFunc GetPointerParseError = GetPointerParseError_En; // or whatever + const RAPIDJSON_ERROR_CHARTYPE* s = GetPointerParseError(pointer.GetParseErrorCode()); +\endcode +*/ +typedef const RAPIDJSON_ERROR_CHARTYPE* (*GetPointerParseErrorFunc)(PointerParseErrorCode); + + RAPIDJSON_NAMESPACE_END #ifdef __clang__ diff --git a/include/rapidjson/pointer.h b/include/rapidjson/pointer.h index 67a9cb0..133df80 100644 --- a/include/rapidjson/pointer.h +++ b/include/rapidjson/pointer.h @@ -18,6 +18,7 @@ #include "document.h" #include "uri.h" #include "internal/itoa.h" +#include "error/error.h" // PointerParseErrorCode #ifdef __clang__ RAPIDJSON_DIAG_PUSH @@ -31,19 +32,6 @@ RAPIDJSON_NAMESPACE_BEGIN static const SizeType kPointerInvalidIndex = ~SizeType(0); //!< Represents an invalid index in GenericPointer::Token -//! Error code of parsing. -/*! \ingroup RAPIDJSON_ERRORS - \see GenericPointer::GenericPointer, GenericPointer::GetParseErrorCode -*/ -enum PointerParseErrorCode { - kPointerParseErrorNone = 0, //!< The parse is successful - - kPointerParseErrorTokenMustBeginWithSolidus, //!< A token must begin with a '/' - kPointerParseErrorInvalidEscape, //!< Invalid escape - kPointerParseErrorInvalidPercentEncoding, //!< Invalid percent encoding in URI fragment - kPointerParseErrorCharacterMustPercentEncode //!< A character must percent encoded in URI fragment -}; - /////////////////////////////////////////////////////////////////////////////// // GenericPointer diff --git a/include/rapidjson/schema.h b/include/rapidjson/schema.h index 188d659..c55e6a7 100644 --- a/include/rapidjson/schema.h +++ b/include/rapidjson/schema.h @@ -234,7 +234,8 @@ public: 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, bool matched) = 0; + virtual void NotOneOf(ISchemaValidator** subvalidators, SizeType count) = 0; + virtual void MultipleOneOf(SizeType index1, SizeType index2) = 0; virtual void Disallowed() = 0; }; @@ -594,8 +595,9 @@ public: for (ConstMemberIterator itr = v->MemberBegin(); itr != v->MemberEnd(); ++itr) { new (&patternProperties_[patternPropertyCount_]) PatternProperty(); - patternProperties_[patternPropertyCount_].pattern = CreatePattern(itr->name); - schemaDocument->CreateSchema(&patternProperties_[patternPropertyCount_].schema, q.Append(itr->name, allocator_), itr->value, document, id_); + 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_++; } } @@ -675,7 +677,7 @@ public: AssignIfExist(maxLength_, value, GetMaxLengthString()); if (const ValueType* v = GetMember(value, GetPatternString())) - pattern_ = CreatePattern(*v); + pattern_ = CreatePattern(*v, schemaDocument, p.Append(GetPatternString(), allocator_)); // Number if (const ValueType* v = GetMember(value, GetMinimumString())) @@ -828,16 +830,19 @@ public: 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.NotOneOf(&context.validators[oneOf_.begin], oneOf_.count, true); + context.error_handler.MultipleOneOf(firstMatch, i - oneOf_.begin); RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorOneOfMatch); - } else + } else { oneValid = true; + firstMatch = i - oneOf_.begin; + } } if (!oneValid) { - context.error_handler.NotOneOf(&context.validators[oneOf_.begin], oneOf_.count, false); + context.error_handler.NotOneOf(&context.validators[oneOf_.begin], oneOf_.count); RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorOneOf); } } @@ -1247,10 +1252,11 @@ private: #if RAPIDJSON_SCHEMA_USE_INTERNALREGEX template - RegexType* CreatePattern(const ValueType& value) { + 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; @@ -1266,13 +1272,14 @@ private: } #elif RAPIDJSON_SCHEMA_USE_STDREGEX template - RegexType* CreatePattern(const ValueType& value) { + RegexType* CreatePattern(const ValueType& value, SchemaDocumentType* sd, const PointerType& p) { if (value.IsString()) { RegexType *r = static_cast(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&) { + catch (const std::regex_error& e) { + sd->SchemaErrorValue(kSchemaErrorRegexInvalid, p, value.GetString(), value.GetStringLength()); AllocatorType::Free(r); } } @@ -1285,7 +1292,9 @@ private: } #else template - RegexType* CreatePattern(const ValueType&) { return 0; } + RegexType* CreatePattern(const ValueType&) { + return 0; + } static bool IsPatternMatch(const RegexType*, const Ch *, SizeType) { return true; } #endif // RAPIDJSON_SCHEMA_USE_STDREGEX @@ -1632,8 +1641,9 @@ public: typedef typename EncodingType::Ch Ch; typedef internal::Schema SchemaType; typedef GenericPointer PointerType; - typedef GenericValue SValue; + typedef GenericValue GValue; typedef GenericUri UriType; + typedef GenericStringRef StringRefType; friend class internal::Schema; template friend class GenericSchemaValidator; @@ -1658,7 +1668,9 @@ public: root_(), typeless_(), schemaMap_(allocator, kInitialSchemaMapSize), - schemaRef_(allocator, kInitialSchemaRefSize) + schemaRef_(allocator, kInitialSchemaRefSize), + error_(kObjectType), + currentError_() { if (!allocator_) ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator)(); @@ -1680,6 +1692,11 @@ public: else if (const ValueType* v = pointer.Get(document)) { CreateSchema(&root_, pointer, *v, document, docId_); } + else { + GenericStringBuffer sb; + pointer.StringifyUriFragment(sb); + SchemaErrorValue(kSchemaErrorStartUnknown, PointerType(), sb.GetString(), static_cast(sb.GetSize() / sizeof(Ch))); + } RAPIDJSON_ASSERT(root_ != 0); @@ -1697,7 +1714,9 @@ public: schemaMap_(std::move(rhs.schemaMap_)), schemaRef_(std::move(rhs.schemaRef_)), uri_(std::move(rhs.uri_)), - docId_(rhs.docId_) + docId_(rhs.docId_), + error_(std::move(rhs.error_)), + currentError_(std::move(rhs.currentError_)) { rhs.remoteProvider_ = 0; rhs.allocator_ = 0; @@ -1719,12 +1738,52 @@ public: RAPIDJSON_DELETE(ownAllocator_); } - const SValue& GetURI() const { return uri_; } + const GValue& GetURI() const { return uri_; } //! Get the root schema. const SchemaType& GetRoot() const { return *root_; } -private: + //! 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(); + 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(pointer.GetParseErrorOffset() / sizeof(Ch)), *allocator_); + AddCurrentError(code, location); + } + + private: //! Prohibit copying GenericSchemaDocument(const GenericSchemaDocument&); //! Prohibit assignment @@ -1745,6 +1804,58 @@ private: bool owned; }; + void AddErrorInstanceLocation(GValue& result, const PointerType& location) { + GenericStringBuffer sb; + location.StringifyUriFragment(sb); + GValue instanceRef(sb.GetString(), static_cast(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) { + 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(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_(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_(RegexInvalid, 'R', 'e', 'g', 'e', 'x', 'I', 'n', 'v', 'a', 'l', 'i', 'd') + +#undef RAPIDJSON_STRING_ + // 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) { @@ -1795,7 +1906,9 @@ private: if (itr->value.IsString()) { SizeType len = itr->value.GetStringLength(); - if (len > 0) { + 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_); @@ -1805,26 +1918,32 @@ private: const ValueType *base = FindId(document, ref, basePointer, docId_, false); if (!base) { // Remote reference - call the remote document provider - if (remoteProvider_) { + if (!remoteProvider_) + SchemaError(kSchemaErrorRefNoRemoteProvider, source); + else { if (const GenericSchemaDocument* remoteDocument = remoteProvider_->GetRemoteDocument(ref)) { 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()) { + 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(sc)); return true; - } + } else + SchemaErrorValue(kSchemaErrorRefUnknown, source, ref.GetString(), ref.GetStringLength()); } - } else { - // Plain name fragment, not allowed - } - } + } 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 @@ -1833,16 +1952,18 @@ private: if (len <= 1 || s[1] == '/') { // JSON pointer fragment, relative to the resolved URI const PointerType relPointer(s, len, allocator_); - if (relPointer.IsValid()) { + 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); for (SizeType i = 0; i < relPointer.GetTokenCount(); i++) pointer = pointer.Append(relPointer.GetTokens()[i], allocator_); - //GenericStringBuffer sb; - //pointer.StringifyUriFragment(sb); - if (pointer.IsValid() && !IsCyclicRef(pointer)) { + 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; @@ -1850,17 +1971,18 @@ private: 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 + PointerType pointer = PointerType(); // 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. - PointerType pointer = PointerType(); if (const ValueType *pv = FindId(*base, ref, pointer, UriType(ref.GetBaseString(), ref.GetBaseStringLength(), allocator_), true, basePointer)) { - if (!IsCyclicRef(pointer)) { - //GenericStringBuffer sb; - //pointer.StringifyUriFragment(sb); + 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; @@ -1868,7 +1990,8 @@ private: CreateSchema(schema, pointer, *pv, document, scopeId); return true; } - } + } else + SchemaErrorValue(kSchemaErrorRefUnknown, source, ref.GetString(), ref.GetStringLength()); } } } @@ -1965,8 +2088,10 @@ private: SchemaType* typeless_; internal::Stack schemaMap_; // Stores created Pointer -> Schemas internal::Stack schemaRef_; // Stores Pointer(s) from $ref(s) until resolved - SValue uri_; // Schema document URI + GValue uri_; // Schema document URI UriType docId_; + GValue error_; + GValue currentError_; }; //! GenericSchemaDocument using Value type. @@ -2099,13 +2224,12 @@ public: return flags_; } - //! Checks whether the current state is valid. - // Implementation of ISchemaValidator 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_; } @@ -2313,8 +2437,16 @@ public: void NoneOf(ISchemaValidator** subvalidators, SizeType count) { AddErrorArray(kValidateErrorAnyOf, subvalidators, count); } - void NotOneOf(ISchemaValidator** subvalidators, SizeType count, bool matched = false) { - AddErrorArray(matched ? kValidateErrorOneOfMatch : kValidateErrorOneOf, 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(); @@ -2338,6 +2470,7 @@ public: 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_ @@ -2482,6 +2615,7 @@ RAPIDJSON_MULTILINEMACRO_END virtual void FreeState(void* p) { StateAllocator::Free(p); } + // End of implementation of ISchemaStateFactory private: typedef typename SchemaType::Context Context; diff --git a/test/unittest/schematest.cpp b/test/unittest/schematest.cpp index 1b25e2f..f180f3b 100644 --- a/test/unittest/schematest.cpp +++ b/test/unittest/schematest.cpp @@ -112,6 +112,12 @@ TEST(SchemaValidator, Hasher) { #define VALIDATE(schema, json, expected) \ {\ + VALIDATE_(schema, json, expected, true) \ +} + +#define VALIDATE_(schema, json, expected, expected2) \ +{\ + EXPECT_TRUE(expected2 == schema.GetError().ObjectEmpty());\ SchemaValidator validator(schema);\ Document d;\ /*printf("\n%s\n", json);*/\ @@ -149,6 +155,7 @@ TEST(SchemaValidator, Hasher) { #define INVALIDATE_(schema, json, invalidSchemaPointer, invalidSchemaKeyword, invalidDocumentPointer, error, \ flags, SchemaValidatorType, PointerType) \ {\ + EXPECT_TRUE(schema.GetError().ObjectEmpty());\ SchemaValidatorType validator(schema);\ validator.SetValidateFlags(flags);\ Document d;\ @@ -188,6 +195,20 @@ TEST(SchemaValidator, Hasher) { }\ } +// Use for checking whether a compiled schema document contains errors +#define SCHEMAERROR(schema, error) \ +{\ + Document e;\ + e.Parse(error);\ + if (schema.GetError() != e) {\ + StringBuffer sb;\ + Writer w(sb);\ + schema.GetError().Accept(w);\ + printf("GetError() Expected: %s Actual: %s\n", error, sb.GetString());\ + ADD_FAILURE();\ + }\ +} + TEST(SchemaValidator, Typeless) { Document sd; sd.Parse("{}"); @@ -223,7 +244,7 @@ TEST(SchemaValidator, Enum_Typed) { "{ \"enum\": { \"errorCode\": 19, \"instanceRef\": \"#\", \"schemaRef\": \"#\" }}"); } -TEST(SchemaValidator, Enum_Typless) { +TEST(SchemaValidator, Enum_Typeless) { Document sd; sd.Parse("{ \"enum\": [\"red\", \"amber\", \"green\", null, 42] }"); SchemaDocument s(sd); @@ -333,7 +354,7 @@ TEST(SchemaValidator, OneOf) { " ]" "}}"); INVALIDATE(s, "15", "", "oneOf", "", - "{ \"oneOf\": { \"errorCode\": 22, \"instanceRef\": \"#\", \"schemaRef\": \"#\", \"errors\": [{}, {}]}}"); + "{ \"oneOf\": { \"errorCode\": 22, \"instanceRef\": \"#\", \"schemaRef\": \"#\", \"matches\": [0,1]}}"); } TEST(SchemaValidator, Not) { @@ -502,12 +523,13 @@ TEST(SchemaValidator, String_Pattern) { TEST(SchemaValidator, String_Pattern_Invalid) { Document sd; - sd.Parse("{\"type\":\"string\",\"pattern\":\"a{0}\"}"); // TODO: report regex is invalid somehow + sd.Parse("{\"type\":\"string\",\"pattern\":\"a{0}\"}"); SchemaDocument s(sd); + SCHEMAERROR(s, "{\"RegexInvalid\":{\"errorCode\":9,\"instanceRef\":\"#/pattern\",\"value\":\"a{0}\"}}"); - VALIDATE(s, "\"\"", true); - VALIDATE(s, "\"a\"", true); - VALIDATE(s, "\"aa\"", true); + VALIDATE_(s, "\"\"", true, false); + VALIDATE_(s, "\"a\"", true, false); + VALIDATE_(s, "\"aa\"", true, false); } #endif @@ -1886,12 +1908,6 @@ TEST(SchemaValidator, SchemaPointer) { " }," " \"f\": {" " \"type\": \"boolean\"" - " }," - " \"cyclic_source\": {" - " \"$ref\": \"#/definitions/Resp_200/properties/cyclic_target\"" - " }," - " \"cyclic_target\": {" - " \"$ref\": \"#/definitions/Resp_200/properties/cyclic_source\"" " }" " }," " \"type\": \"object\"" @@ -2390,7 +2406,9 @@ TEST(SchemaValidator, Issue728_AllOfRef) { Document sd; sd.Parse("{\"allOf\": [{\"$ref\": \"#/abc\"}]}"); SchemaDocument s(sd); - VALIDATE(s, "{\"key1\": \"abc\", \"key2\": \"def\"}", true); + SCHEMAERROR(s, "{\"RefUnknown\":{\"errorCode\":5,\"instanceRef\":\"#/allOf/0\",\"value\":\"#/abc\"}}"); + + VALIDATE_(s, "{\"key1\": \"abc\", \"key2\": \"def\"}", true, false); } TEST(SchemaValidator, Issue1017_allOfHandler) { @@ -2625,7 +2643,7 @@ TEST(SchemaValidator, Ref_remote_issue1210) { SchemaDocumentProvider(SchemaDocument** collection) : collection(collection) { } virtual const SchemaDocument* GetRemoteDocument(const char* uri, SizeType length) { int i = 0; - while (collection[i] && SchemaDocument::SValue(uri, length) != collection[i]->GetURI()) ++i; + while (collection[i] && SchemaDocument::GValue(uri, length) != collection[i]->GetURI()) ++i; return collection[i]; } }; @@ -2656,7 +2674,7 @@ TEST(SchemaValidator, ContinueOnErrors) { ASSERT_FALSE(sd.HasParseError()); SchemaDocument s(sd); VALIDATE(s, "{\"version\": 1.0, \"address\": {\"number\": 24, \"street1\": \"The Woodlands\", \"street3\": \"Ham\", \"city\": \"Romsey\", \"area\": \"Kent\", \"country\": \"UK\", \"postcode\": \"SO51 0GP\"}, \"phones\": [\"0111-222333\", \"0777-666888\"], \"names\": [\"Fred\", \"Bloggs\"]}", true); - INVALIDATE_(s, "{\"version\": 1.01, \"address\": {\"number\": 0, \"street2\": false, \"street3\": \"Ham\", \"city\": \"RomseyTownFC\", \"area\": \"BC\", \"country\": \"USA\", \"postcode\": \"999ABC\"}, \"phones\": [], \"planet\": \"Earth\", \"extra\": {\"S_xxx\": 123}}", "#", "errors", "#", + INVALIDATE_(s, "{\"version\": 1.01, \"address\": {\"number\": 0, \"street2\": false, \"street3\": \"Ham\", \"city\": \"RomseyTownFC\", \"area\": \"Narnia\", \"country\": \"USA\", \"postcode\": \"999ABC\"}, \"phones\": [], \"planet\": \"Earth\", \"extra\": {\"S_xxx\": 123}}", "#", "errors", "#", "{ \"multipleOf\": {" " \"errorCode\": 1, \"instanceRef\": \"#/version\", \"schemaRef\": \"#/definitions/decimal_type\", \"expected\": 1.0, \"actual\": 1.01" " }," @@ -2691,6 +2709,9 @@ TEST(SchemaValidator, ContinueOnErrors) { " }," " \"required\": {" " \"missing\": [\"street1\"], \"errorCode\": 15, \"instanceRef\": \"#/address\", \"schemaRef\": \"#/definitions/address_type\"" + " }," + " \"oneOf\": {" + " \"matches\": [0, 1], \"errorCode\": 22, \"instanceRef\": \"#/address/area\", \"schemaRef\": \"#/definitions/address_type/properties/area\"" " }" "}", kValidateDefaultFlags | kValidateContinueOnErrorFlag, SchemaValidator, Pointer); @@ -2917,7 +2938,7 @@ TEST(SchemaValidator, ContinueOnErrors_RogueString) { // Test that when kValidateContinueOnErrorFlag is set, an incorrect simple type with a sub-schema is handled correctly. // This tests that we don't blow up when there is a type mismatch but there is a sub-schema present -TEST(SchemaValidator, ContinueOnErrors_Issue2) { +TEST(SchemaValidator, ContinueOnErrors_BadSimpleType) { Document sd; sd.Parse("{\"type\":\"string\", \"anyOf\":[{\"maxLength\":2}]}"); ASSERT_FALSE(sd.HasParseError()); @@ -2943,10 +2964,148 @@ TEST(SchemaValidator, ContinueOnErrors_Issue2) { kValidateDefaultFlags | kValidateContinueOnErrorFlag, SchemaValidator, Pointer); } -TEST(SchemaValidator, Schema_UnknownError) { + +TEST(SchemaValidator, UnknownValidationError) { ASSERT_TRUE(SchemaValidator::SchemaType::GetValidateErrorKeyword(kValidateErrors).GetString() == std::string("null")); } +// The first occurrence of a duplicate keyword is taken +TEST(SchemaValidator, DuplicateKeyword) { + Document sd; + sd.Parse("{ \"title\": \"test\",\"type\": \"number\", \"type\": \"string\" }"); + EXPECT_FALSE(sd.HasParseError()); + SchemaDocument s(sd); + VALIDATE(s, "42", true); + INVALIDATE(s, "\"Life, the universe, and everything\"", "", "type", "", + "{ \"type\": {" + " \"errorCode\": 20," + " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," + " \"expected\": [\"number\"], \"actual\": \"string\"" + "}}"); +} + + +// SchemaDocument tests + +TEST(SchemaValidator, Schema_StartUnknown) { + Document sd; + sd.Parse("{\"type\": \"integer\"}"); + ASSERT_FALSE(sd.HasParseError()); + SchemaDocument s(sd, 0, 0, 0, 0, SchemaDocument::PointerType("/nowhere")); + SCHEMAERROR(s, "{\"StartUnknown\":{\"errorCode\":1,\"instanceRef\":\"#\", \"value\":\"#/nowhere\"}}"); +} + +// $ref is a non-JSON pointer fragment - not allowed when OpenAPI +TEST(SchemaValidator, Schema_RefPlainNameOpenApi) { + typedef GenericSchemaDocument > SchemaDocumentType; + Document sd; + sd.Parse("{\"swagger\": \"2.0\", \"type\": \"object\", \"properties\": {\"myInt1\": {\"$ref\": \"#myId\"}, \"myStr\": {\"type\": \"string\", \"id\": \"#myStrId\"}, \"myInt2\": {\"type\": \"integer\", \"id\": \"#myId\"}}}"); + SchemaDocumentType s(sd); + SCHEMAERROR(s, "{\"RefPlainName\":{\"errorCode\":2,\"instanceRef\":\"#/properties/myInt1\",\"value\":\"#myId\"}}"); +} + +// $ref is a non-JSON pointer fragment - not allowed when remote document +TEST(SchemaValidator, Schema_RefPlainNameRemote) { + typedef GenericSchemaDocument > SchemaDocumentType; + RemoteSchemaDocumentProvider provider; + Document sd; + sd.Parse("{\"type\": \"object\", \"properties\": {\"myInt\": {\"$ref\": \"/subSchemas.json#plainname\"}}}"); + SchemaDocumentType s(sd, "http://localhost:1234/xxxx", 26, &provider); + SCHEMAERROR(s, "{\"RefPlainName\":{\"errorCode\":2,\"instanceRef\":\"#/properties/myInt\",\"value\":\"#plainname\"}}"); +} + +// $ref is an empty string +TEST(SchemaValidator, Schema_RefEmptyString) { + typedef GenericSchemaDocument > SchemaDocumentType; + Document sd; + sd.Parse("{\"type\": \"object\", \"properties\": {\"myInt1\": {\"$ref\": \"\"}}}"); + SchemaDocumentType s(sd); + SCHEMAERROR(s, "{\"RefInvalid\":{\"errorCode\":3,\"instanceRef\":\"#/properties/myInt1\"}}"); +} + +// $ref is remote but no provider +TEST(SchemaValidator, Schema_RefNoRemoteProvider) { + typedef GenericSchemaDocument > SchemaDocumentType; + Document sd; + sd.Parse("{\"type\": \"object\", \"properties\": {\"myInt\": {\"$ref\": \"/subSchemas.json#plainname\"}}}"); + SchemaDocumentType s(sd, "http://localhost:1234/xxxx", 26, 0); + SCHEMAERROR(s, "{\"RefNoRemoteProvider\":{\"errorCode\":7,\"instanceRef\":\"#/properties/myInt\"}}"); +} + +// $ref is remote but no schema returned +TEST(SchemaValidator, Schema_RefNoRemoteSchema) { + typedef GenericSchemaDocument > SchemaDocumentType; + RemoteSchemaDocumentProvider provider; + Document sd; + sd.Parse("{\"type\": \"object\", \"properties\": {\"myInt\": {\"$ref\": \"/will-not-resolve.json\"}}}"); + SchemaDocumentType s(sd, "http://localhost:1234/xxxx", 26, &provider); + SCHEMAERROR(s, "{\"RefNoRemoteSchema\":{\"errorCode\":8,\"instanceRef\":\"#/properties/myInt\",\"value\":\"http://localhost:1234/will-not-resolve.json\"}}"); +} + +// $ref pointer is invalid +TEST(SchemaValidator, Schema_RefPointerInvalid) { + typedef GenericSchemaDocument > SchemaDocumentType; + Document sd; + sd.Parse("{\"type\": \"object\", \"properties\": {\"myInt\": {\"$ref\": \"#/&&&&&\"}}}"); + SchemaDocumentType s(sd); + SCHEMAERROR(s, "{\"RefPointerInvalid\":{\"errorCode\":4,\"instanceRef\":\"#/properties/myInt\",\"value\":\"#/&&&&&\",\"offset\":2}}"); +} + +// $ref is remote and pointer is invalid +TEST(SchemaValidator, Schema_RefPointerInvalidRemote) { + typedef GenericSchemaDocument > SchemaDocumentType; + RemoteSchemaDocumentProvider provider; + Document sd; + sd.Parse("{\"type\": \"object\", \"properties\": {\"myInt\": {\"$ref\": \"/subSchemas.json#/abc&&&&&\"}}}"); + SchemaDocumentType s(sd, "http://localhost:1234/xxxx", 26, &provider); + SCHEMAERROR(s, "{\"RefPointerInvalid\":{\"errorCode\":4,\"instanceRef\":\"#/properties/myInt\",\"value\":\"#/abc&&&&&\",\"offset\":5}}"); +} + +// $ref is unknown non-pointer +TEST(SchemaValidator, Schema_RefUnknownPlainName) { + typedef GenericSchemaDocument > SchemaDocumentType; + Document sd; + sd.Parse("{\"type\": \"object\", \"properties\": {\"myInt\": {\"$ref\": \"#plainname\"}}}"); + SchemaDocumentType s(sd); + SCHEMAERROR(s, "{\"RefUnknown\":{\"errorCode\":5,\"instanceRef\":\"#/properties/myInt\",\"value\":\"#plainname\"}}"); +} + +/// $ref is unknown pointer +TEST(SchemaValidator, Schema_RefUnknownPointer) { + typedef GenericSchemaDocument > SchemaDocumentType; + Document sd; + sd.Parse("{\"type\": \"object\", \"properties\": {\"myInt\": {\"$ref\": \"#/a/b\"}}}"); + SchemaDocumentType s(sd); + SCHEMAERROR(s, "{\"RefUnknown\":{\"errorCode\":5,\"instanceRef\":\"#/properties/myInt\",\"value\":\"#/a/b\"}}"); +} + +// $ref is remote and unknown pointer +TEST(SchemaValidator, Schema_RefUnknownPointerRemote) { + typedef GenericSchemaDocument > SchemaDocumentType; + RemoteSchemaDocumentProvider provider; + Document sd; + sd.Parse("{\"type\": \"object\", \"properties\": {\"myInt\": {\"$ref\": \"/subSchemas.json#/a/b\"}}}"); + SchemaDocumentType s(sd, "http://localhost:1234/xxxx", 26, &provider); + SCHEMAERROR(s, "{\"RefUnknown\":{\"errorCode\":5,\"instanceRef\":\"#/properties/myInt\",\"value\":\"http://localhost:1234/subSchemas.json#/a/b\"}}"); +} + +// $ref is cyclical +TEST(SchemaValidator, Schema_RefCyclical) { + typedef GenericSchemaDocument > SchemaDocumentType; + Document sd; + sd.Parse("{\"type\": \"object\", \"properties\": {" + " \"cyclic_source\": {" + " \"$ref\": \"#/properties/cyclic_target\"" + " }," + " \"cyclic_target\": {" + " \"$ref\": \"#/properties/cyclic_source\"" + " }" + "}}"); + SchemaDocumentType s(sd); + SCHEMAERROR(s, "{\"RefCyclical\":{\"errorCode\":6,\"instanceRef\":\"#/properties/cyclic_target\",\"value\":\"#/properties/cyclic_source\"}}"); +} + + #if defined(_MSC_VER) || defined(__clang__) RAPIDJSON_DIAG_POP #endif From 89f6717f0ba799df8e8da5d95654b944cef04e74 Mon Sep 17 00:00:00 2001 From: Steve Hanson Date: Thu, 17 Jun 2021 18:33:18 +0100 Subject: [PATCH 15/35] corrections --- include/rapidjson/error/en.h | 4 ---- include/rapidjson/error/error.h | 4 ---- test/unittest/schematest.cpp | 11 +---------- 3 files changed, 1 insertion(+), 18 deletions(-) diff --git a/include/rapidjson/error/en.h b/include/rapidjson/error/en.h index cd4a99a..f27d62a 100644 --- a/include/rapidjson/error/en.h +++ b/include/rapidjson/error/en.h @@ -125,9 +125,6 @@ inline const RAPIDJSON_ERROR_CHARTYPE* GetValidateError_En(ValidateErrorCode val switch (schemaErrorCode) { case kSchemaErrorNone: return RAPIDJSON_ERROR_STRING("No error."); - case kSchemaErrorSpecUnknown: return RAPIDJSON_ERROR_STRING("JSON schema draft or OpenAPI version is not recognized."); - case kSchemaErrorSpecUnsupported: return RAPIDJSON_ERROR_STRING("JSON schema draft or OpenAPI version is not supported."); - case kSchemaErrorSpecIllegal: return RAPIDJSON_ERROR_STRING("Both JSON schema draft and OpenAPI version found in document."); case kSchemaErrorStartUnknown: return RAPIDJSON_ERROR_STRING("Pointer '%value' to start of schema does not resolve to a location in the document."); case kSchemaErrorRefPlainName: return RAPIDJSON_ERROR_STRING("$ref fragment '%value' must be a JSON pointer."); case kSchemaErrorRefInvalid: return RAPIDJSON_ERROR_STRING("$ref must not be an empty string."); @@ -136,7 +133,6 @@ inline const RAPIDJSON_ERROR_CHARTYPE* GetValidateError_En(ValidateErrorCode val case kSchemaErrorRefCyclical: return RAPIDJSON_ERROR_STRING("$ref '%value' is cyclical."); case kSchemaErrorRefNoRemoteProvider: return RAPIDJSON_ERROR_STRING("$ref is remote but there is no remote provider."); case kSchemaErrorRefNoRemoteSchema: return RAPIDJSON_ERROR_STRING("$ref '%value' is remote but the remote provider did not return a schema."); - case kSchemaErrorReadOnlyAndWriteOnly: return RAPIDJSON_ERROR_STRING("Property must not be both 'readOnly' and 'writeOnly'."); case kSchemaErrorRegexInvalid: return RAPIDJSON_ERROR_STRING("Invalid regular expression '%value' in 'pattern' or 'patternProperties'."); default: return RAPIDJSON_ERROR_STRING("Unknown error."); diff --git a/include/rapidjson/error/error.h b/include/rapidjson/error/error.h index 64613be..d2a2fc4 100644 --- a/include/rapidjson/error/error.h +++ b/include/rapidjson/error/error.h @@ -217,9 +217,6 @@ typedef const RAPIDJSON_ERROR_CHARTYPE* (*GetValidateErrorFunc)(ValidateErrorCod enum SchemaErrorCode { kSchemaErrorNone = 0, //!< No error. - kSchemaErrorSpecUnknown, //!< JSON schema draft or OpenAPI version is not recognized - kSchemaErrorSpecUnsupported, //!< JSON schema draft or OpenAPI version is not supported - kSchemaErrorSpecIllegal, //!< Both JSON schema draft and OpenAPI version found in document kSchemaErrorStartUnknown, //!< Pointer to start of schema does not resolve to a location in the document kSchemaErrorRefPlainName, //!< $ref fragment must be a JSON pointer kSchemaErrorRefInvalid, //!< $ref must not be an empty string @@ -228,7 +225,6 @@ enum SchemaErrorCode { kSchemaErrorRefCyclical, //!< $ref is cyclical kSchemaErrorRefNoRemoteProvider, //!< $ref is remote but there is no remote provider kSchemaErrorRefNoRemoteSchema, //!< $ref is remote but the remote provider did not return a schema - kSchemaErrorReadOnlyAndWriteOnly, //!< Property must not be both 'readOnly' and 'writeOnly' kSchemaErrorRegexInvalid //!< Invalid regular expression in 'pattern' or 'patternProperties' }; diff --git a/test/unittest/schematest.cpp b/test/unittest/schematest.cpp index f180f3b..3d12398 100644 --- a/test/unittest/schematest.cpp +++ b/test/unittest/schematest.cpp @@ -2152,7 +2152,7 @@ public: virtual const SchemaDocumentType* GetRemoteDocument(const char* uri, SizeType length) { for (size_t i = 0; i < kCount; i++) - if (typename SchemaDocumentType::SValue(uri, length) == sd_[i]->GetURI()) + if (typename SchemaDocumentType::GValue(uri, length) == sd_[i]->GetURI()) return sd_[i]; return 0; } @@ -2995,15 +2995,6 @@ TEST(SchemaValidator, Schema_StartUnknown) { SCHEMAERROR(s, "{\"StartUnknown\":{\"errorCode\":1,\"instanceRef\":\"#\", \"value\":\"#/nowhere\"}}"); } -// $ref is a non-JSON pointer fragment - not allowed when OpenAPI -TEST(SchemaValidator, Schema_RefPlainNameOpenApi) { - typedef GenericSchemaDocument > SchemaDocumentType; - Document sd; - sd.Parse("{\"swagger\": \"2.0\", \"type\": \"object\", \"properties\": {\"myInt1\": {\"$ref\": \"#myId\"}, \"myStr\": {\"type\": \"string\", \"id\": \"#myStrId\"}, \"myInt2\": {\"type\": \"integer\", \"id\": \"#myId\"}}}"); - SchemaDocumentType s(sd); - SCHEMAERROR(s, "{\"RefPlainName\":{\"errorCode\":2,\"instanceRef\":\"#/properties/myInt1\",\"value\":\"#myId\"}}"); -} - // $ref is a non-JSON pointer fragment - not allowed when remote document TEST(SchemaValidator, Schema_RefPlainNameRemote) { typedef GenericSchemaDocument > SchemaDocumentType; From ecb8d9e3a01e4132f66f11583460244797ee1044 Mon Sep 17 00:00:00 2001 From: Steve Hanson Date: Fri, 9 Jul 2021 13:36:10 +0100 Subject: [PATCH 16/35] add dump of unexpected schema errors in schematest.cpp --- test/unittest/schematest.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/unittest/schematest.cpp b/test/unittest/schematest.cpp index 3d12398..733841f 100644 --- a/test/unittest/schematest.cpp +++ b/test/unittest/schematest.cpp @@ -118,6 +118,12 @@ TEST(SchemaValidator, Hasher) { #define VALIDATE_(schema, json, expected, expected2) \ {\ EXPECT_TRUE(expected2 == schema.GetError().ObjectEmpty());\ + if (expected2 && !schema.GetError().ObjectEmpty()) {\ + StringBuffer ssb;\ + Writer ws(ssb);\ + schema.GetError().Accept(ws);\ + printf("Schema error: %s\n", ssb.GetString());\ + }\ SchemaValidator validator(schema);\ Document d;\ /*printf("\n%s\n", json);*/\ @@ -156,6 +162,12 @@ TEST(SchemaValidator, Hasher) { flags, SchemaValidatorType, PointerType) \ {\ EXPECT_TRUE(schema.GetError().ObjectEmpty());\ + if (!schema.GetError().ObjectEmpty()) {\ + StringBuffer ssb;\ + Writer ws(ssb);\ + schema.GetError().Accept(ws);\ + printf("Schema error: %s\n", ssb.GetString());\ + }\ SchemaValidatorType validator(schema);\ validator.SetValidateFlags(flags);\ Document d;\ From aa1f22251f1a85aee52cef14299535712a148496 Mon Sep 17 00:00:00 2001 From: Steve Hanson Date: Fri, 30 Jul 2021 19:33:57 +0100 Subject: [PATCH 17/35] correct address.json so tests pass --- bin/unittestschema/address.json | Bin 3157 -> 3102 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/bin/unittestschema/address.json b/bin/unittestschema/address.json index c3cf6426131c578fa9df93159f1153f0e749d7d7..85c46699d69ec3117b23928c8ac60449d6b14370 100644 GIT binary patch delta 37 pcmcaAF;8N{aSl!$1tq`4qP)z+$;UW?*&xi#qMYxUCj0ZS0Ra7<47>mU delta 43 zcmbOyaaCf&agNCsIf6Fpa=vDo9LLSURFpIMJhv=MOiZxWWIJvJHZBE)TCQ3ETz3tw From 2d87923e91504f16a72d34bd0d6bfef8a6605453 Mon Sep 17 00:00:00 2001 From: Steve Hanson Date: Fri, 6 Aug 2021 16:53:00 +0100 Subject: [PATCH 18/35] remove unnecessary templating from schema tests --- test/unittest/schematest.cpp | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/test/unittest/schematest.cpp b/test/unittest/schematest.cpp index 733841f..a3f5f93 100644 --- a/test/unittest/schematest.cpp +++ b/test/unittest/schematest.cpp @@ -3019,10 +3019,9 @@ TEST(SchemaValidator, Schema_RefPlainNameRemote) { // $ref is an empty string TEST(SchemaValidator, Schema_RefEmptyString) { - typedef GenericSchemaDocument > SchemaDocumentType; Document sd; sd.Parse("{\"type\": \"object\", \"properties\": {\"myInt1\": {\"$ref\": \"\"}}}"); - SchemaDocumentType s(sd); + SchemaDocument s(sd); SCHEMAERROR(s, "{\"RefInvalid\":{\"errorCode\":3,\"instanceRef\":\"#/properties/myInt1\"}}"); } @@ -3047,10 +3046,9 @@ TEST(SchemaValidator, Schema_RefNoRemoteSchema) { // $ref pointer is invalid TEST(SchemaValidator, Schema_RefPointerInvalid) { - typedef GenericSchemaDocument > SchemaDocumentType; Document sd; sd.Parse("{\"type\": \"object\", \"properties\": {\"myInt\": {\"$ref\": \"#/&&&&&\"}}}"); - SchemaDocumentType s(sd); + SchemaDocument s(sd); SCHEMAERROR(s, "{\"RefPointerInvalid\":{\"errorCode\":4,\"instanceRef\":\"#/properties/myInt\",\"value\":\"#/&&&&&\",\"offset\":2}}"); } @@ -3066,19 +3064,17 @@ TEST(SchemaValidator, Schema_RefPointerInvalidRemote) { // $ref is unknown non-pointer TEST(SchemaValidator, Schema_RefUnknownPlainName) { - typedef GenericSchemaDocument > SchemaDocumentType; Document sd; sd.Parse("{\"type\": \"object\", \"properties\": {\"myInt\": {\"$ref\": \"#plainname\"}}}"); - SchemaDocumentType s(sd); + SchemaDocument s(sd); SCHEMAERROR(s, "{\"RefUnknown\":{\"errorCode\":5,\"instanceRef\":\"#/properties/myInt\",\"value\":\"#plainname\"}}"); } /// $ref is unknown pointer TEST(SchemaValidator, Schema_RefUnknownPointer) { - typedef GenericSchemaDocument > SchemaDocumentType; Document sd; sd.Parse("{\"type\": \"object\", \"properties\": {\"myInt\": {\"$ref\": \"#/a/b\"}}}"); - SchemaDocumentType s(sd); + SchemaDocument s(sd); SCHEMAERROR(s, "{\"RefUnknown\":{\"errorCode\":5,\"instanceRef\":\"#/properties/myInt\",\"value\":\"#/a/b\"}}"); } @@ -3094,7 +3090,6 @@ TEST(SchemaValidator, Schema_RefUnknownPointerRemote) { // $ref is cyclical TEST(SchemaValidator, Schema_RefCyclical) { - typedef GenericSchemaDocument > SchemaDocumentType; Document sd; sd.Parse("{\"type\": \"object\", \"properties\": {" " \"cyclic_source\": {" @@ -3104,7 +3099,7 @@ TEST(SchemaValidator, Schema_RefCyclical) { " \"$ref\": \"#/properties/cyclic_source\"" " }" "}}"); - SchemaDocumentType s(sd); + SchemaDocument s(sd); SCHEMAERROR(s, "{\"RefCyclical\":{\"errorCode\":6,\"instanceRef\":\"#/properties/cyclic_target\",\"value\":\"#/properties/cyclic_source\"}}"); } From 794248ee62642a9efc50f22bfcdd1a043451b603 Mon Sep 17 00:00:00 2001 From: Steve Hanson Date: Fri, 7 Oct 2022 08:16:13 +0100 Subject: [PATCH 19/35] fix build break --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index bdfdd67..a5061d4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -128,6 +128,7 @@ elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang") endif() elseif (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") add_definitions(-D_CRT_SECURE_NO_WARNINGS=1) + add_definitions(-DNOMINMAX) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHsc") # CMake >= 3.10 should handle the above CMAKE_CXX_STANDARD fine, otherwise use /std:c++XX with MSVC >= 19.10 if (RAPIDJSON_BUILD_CXX11 AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS "19.10") From 7cad78e236a98b08e1925aba07115f5b2749344c Mon Sep 17 00:00:00 2001 From: Steve Hanson Date: Fri, 25 Nov 2022 14:21:45 +0000 Subject: [PATCH 20/35] tidy up after merge from master --- CMakeLists.txt | 2 +- bin/unittestschema/address.json | Bin 3102 -> 3150 bytes example/schemavalidator/schemavalidator.cpp | 3 +- include/rapidjson/pointer.h | 10 +++--- include/rapidjson/rapidjson.h | 18 +++++------ include/rapidjson/schema.h | 6 ++-- test/unittest/ostreamwrappertest.cpp | 2 +- test/unittest/pointertest.cpp | 4 +-- test/unittest/uritest.cpp | 2 +- test/unittest/valuetest.cpp | 32 ++++++++++---------- 10 files changed, 39 insertions(+), 40 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a5061d4..f8927b4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -CMAKE_MINIMUM_REQUIRED(VERSION 2.8) +CMAKE_MINIMUM_REQUIRED(VERSION 2.8.12) if(POLICY CMP0025) # detect Apple's Clang cmake_policy(SET CMP0025 NEW) diff --git a/bin/unittestschema/address.json b/bin/unittestschema/address.json index 85c46699d69ec3117b23928c8ac60449d6b14370..abec3ec577711a1edf3c5f443a5912682fe47fbe 100644 GIT binary patch delta 29 lcmbOyaZX}`1kdC+ZVslRoXG{;l9L~B^0IL$DAaP*0sx6)2w4CC delta 14 VcmX>nF;8NH1P>dRf > \tparam Allocator The allocator type for allocating memory for internal representation. - + \note GenericPointer uses same encoding of ValueType. However, Allocator of GenericPointer is independent of Allocator of Value. */ @@ -72,7 +72,7 @@ public: typedef GenericUri UriType; - //! A token is the basic units of internal representation. + //! A token is the basic units of internal representation. /*! A JSON pointer string representation "/foo/123" is parsed to two tokens: "foo" and 123. 123 will be represented in both numeric form and string form. @@ -689,7 +689,7 @@ public: ValueType& GetWithDefault(GenericDocument& document, const Ch* defaultValue) const { return GetWithDefault(document, defaultValue, document.GetAllocator()); } - + #if RAPIDJSON_HAS_STDSTRING //! Query a value in a document with default std::basic_string. template @@ -983,7 +983,7 @@ private: } i++; - + // Escaping "~0" -> '~', "~1" -> '/' if (c == '~') { if (i < length) { diff --git a/include/rapidjson/rapidjson.h b/include/rapidjson/rapidjson.h index a4e8953..5ea6947 100644 --- a/include/rapidjson/rapidjson.h +++ b/include/rapidjson/rapidjson.h @@ -1,5 +1,5 @@ // 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. // // Licensed under the MIT License (the "License"); you may not use this file except @@ -7,9 +7,9 @@ // // 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 +// 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_RAPIDJSON_H_ @@ -17,7 +17,7 @@ /*!\file rapidjson.h \brief common definitions and configuration - + \see RAPIDJSON_CONFIG */ @@ -195,7 +195,7 @@ */ #ifndef RAPIDJSON_NO_INT64DEFINE //!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN -#if defined(_MSC_VER) && (_MSC_VER < 1800) // Visual Studio 2013 +#if defined(_MSC_VER) && (_MSC_VER < 1800) // Visual Studio 2013 #include "msinttypes/stdint.h" #include "msinttypes/inttypes.h" #else @@ -277,7 +277,7 @@ # elif defined(RAPIDJSON_DOXYGEN_RUNNING) # define RAPIDJSON_ENDIAN # else -# error Unknown machine endianness detected. User needs to define RAPIDJSON_ENDIAN. +# error Unknown machine endianness detected. User needs to define RAPIDJSON_ENDIAN. # endif #endif // RAPIDJSON_ENDIAN @@ -513,7 +513,7 @@ RAPIDJSON_NAMESPACE_END //!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN -#define RAPIDJSON_MULTILINEMACRO_BEGIN do { +#define RAPIDJSON_MULTILINEMACRO_BEGIN do { #define RAPIDJSON_MULTILINEMACRO_END \ } while((void)0, 0) @@ -731,7 +731,7 @@ enum Type { kFalseType = 1, //!< false kTrueType = 2, //!< true kObjectType = 3, //!< object - kArrayType = 4, //!< array + kArrayType = 4, //!< array kStringType = 5, //!< string kNumberType = 6 //!< number }; diff --git a/include/rapidjson/schema.h b/include/rapidjson/schema.h index c55e6a7..a57a727 100644 --- a/include/rapidjson/schema.h +++ b/include/rapidjson/schema.h @@ -50,10 +50,6 @@ #define RAPIDJSON_SCHEMA_VERBOSE 0 #endif -#if RAPIDJSON_SCHEMA_VERBOSE -#include "stringbuffer.h" -#endif - RAPIDJSON_DIAG_PUSH #if defined(__GNUC__) @@ -1309,6 +1305,8 @@ private: 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. bool CreateParallelValidator(Context& context) const { if (enum_ || context.arrayUniqueness) context.hasher = context.factory.CreateHasher(); diff --git a/test/unittest/ostreamwrappertest.cpp b/test/unittest/ostreamwrappertest.cpp index c9bc5f4..be9e429 100644 --- a/test/unittest/ostreamwrappertest.cpp +++ b/test/unittest/ostreamwrappertest.cpp @@ -77,7 +77,7 @@ static void TestFileStream() { } fp = fopen(filename, "r"); - ASSERT_TRUE( fp != NULL ); + ASSERT_TRUE( fp != NULL ); for (const char* p = s; *p; p++) EXPECT_EQ(*p, static_cast(fgetc(fp))); fclose(fp); diff --git a/test/unittest/pointertest.cpp b/test/unittest/pointertest.cpp index 2c90cae..c35fa8f 100644 --- a/test/unittest/pointertest.cpp +++ b/test/unittest/pointertest.cpp @@ -475,7 +475,7 @@ TEST(Pointer, CopyConstructor) { EXPECT_EQ(1u, q.GetTokens()[1].length); EXPECT_STREQ("0", q.GetTokens()[1].name); EXPECT_EQ(0u, q.GetTokens()[1].index); - + // Copied pointer needs to have its own allocator EXPECT_NE(&p.GetAllocator(), &q.GetAllocator()); } @@ -1727,4 +1727,4 @@ TEST(Pointer, Issue1899) { EXPECT_TRUE(PointerType("/foo/1234") == q); q = q.Append(""); EXPECT_TRUE(PointerType("/foo/1234/") == q); -} \ No newline at end of file +} diff --git a/test/unittest/uritest.cpp b/test/unittest/uritest.cpp index 5506aa1..d6c92a2 100644 --- a/test/unittest/uritest.cpp +++ b/test/unittest/uritest.cpp @@ -191,7 +191,7 @@ TEST(Uri, Parse_UTF16) { #if RAPIDJSON_HAS_STDSTRING typedef std::basic_string String; String str = L"http://auth/path/xxx?query#frag"; - const UriType uri = UriType(str); + const UriType uri = UriType(str, &allocator); EXPECT_TRUE(UriType::GetScheme(uri) == L"http:"); EXPECT_TRUE(UriType::GetAuth(uri) == L"//auth"); EXPECT_TRUE(UriType::GetPath(uri) == L"/path/xxx"); diff --git a/test/unittest/valuetest.cpp b/test/unittest/valuetest.cpp index 9211419..13ae1d4 100644 --- a/test/unittest/valuetest.cpp +++ b/test/unittest/valuetest.cpp @@ -1060,7 +1060,7 @@ static void TestArray(T& x, Allocator& allocator) { x.Clear(); for (unsigned i = 0; i < n; i++) x.PushBack(Value(kArrayType).PushBack(i, allocator).Move(), allocator); - + itr = x.Erase(x.Begin() + first, x.Begin() + last); if (last == n) EXPECT_EQ(x.End(), itr); @@ -1556,7 +1556,7 @@ TEST(Value, ObjectHelper) { EXPECT_STREQ("apple", y["a"].GetString()); EXPECT_TRUE(x.IsObject()); // Invariant } - + { Value x(kObjectType); x.AddMember("a", "apple", allocator); @@ -1674,7 +1674,7 @@ TEST(Value, BigNestedObject) { for (SizeType i = 0; i < n; i++) { char name1[10]; sprintf(name1, "%d", i); - + for (SizeType j = 0; j < n; j++) { char name2[10]; sprintf(name2, "%d", j); @@ -1689,8 +1689,8 @@ TEST(Value, BigNestedObject) { TEST(Value, RemoveLastElement) { rapidjson::Document doc; rapidjson::Document::AllocatorType& allocator = doc.GetAllocator(); - rapidjson::Value objVal(rapidjson::kObjectType); - objVal.AddMember("var1", 123, allocator); + rapidjson::Value objVal(rapidjson::kObjectType); + objVal.AddMember("var1", 123, allocator); objVal.AddMember("var2", "444", allocator); objVal.AddMember("var3", 555, allocator); EXPECT_TRUE(objVal.HasMember("var3")); @@ -1712,22 +1712,22 @@ TEST(Document, CrtAllocator) { static void TestShortStringOptimization(const char* str) { const rapidjson::SizeType len = static_cast(strlen(str)); - + rapidjson::Document doc; rapidjson::Value val; val.SetString(str, len, doc.GetAllocator()); - - EXPECT_EQ(val.GetStringLength(), len); - EXPECT_STREQ(val.GetString(), str); + + EXPECT_EQ(val.GetStringLength(), len); + EXPECT_STREQ(val.GetString(), str); } TEST(Value, AllocateShortString) { - TestShortStringOptimization(""); // edge case: empty string - TestShortStringOptimization("12345678"); // regular case for short strings: 8 chars - TestShortStringOptimization("12345678901"); // edge case: 11 chars in 32-bit mode (=> short string) - TestShortStringOptimization("123456789012"); // edge case: 12 chars in 32-bit mode (=> regular string) - TestShortStringOptimization("123456789012345"); // edge case: 15 chars in 64-bit mode (=> short string) - TestShortStringOptimization("1234567890123456"); // edge case: 16 chars in 64-bit mode (=> regular string) + TestShortStringOptimization(""); // edge case: empty string + TestShortStringOptimization("12345678"); // regular case for short strings: 8 chars + TestShortStringOptimization("12345678901"); // edge case: 11 chars in 32-bit mode (=> short string) + TestShortStringOptimization("123456789012"); // edge case: 12 chars in 32-bit mode (=> regular string) + TestShortStringOptimization("123456789012345"); // edge case: 15 chars in 64-bit mode (=> short string) + TestShortStringOptimization("1234567890123456"); // edge case: 16 chars in 64-bit mode (=> regular string) } template @@ -1802,7 +1802,7 @@ static void MergeDuplicateKey(Value& v, Value::AllocatorType& a) { // Convert all key:value into key:[value] for (Value::MemberIterator itr = v.MemberBegin(); itr != v.MemberEnd(); ++itr) itr->value = Value(kArrayType).Move().PushBack(itr->value, a); - + // Merge arrays if key is duplicated for (Value::MemberIterator itr = v.MemberBegin(); itr != v.MemberEnd();) { Value::MemberIterator itr2 = v.FindMember(itr->name); From 97fd830175933b81fdb59300e657f2ddd73e1623 Mon Sep 17 00:00:00 2001 From: Steve Hanson Date: Wed, 30 Nov 2022 11:24:10 +0000 Subject: [PATCH 21/35] attempt to fix SEH --- include/rapidjson/schema.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/rapidjson/schema.h b/include/rapidjson/schema.h index a57a727..c1bb0a0 100644 --- a/include/rapidjson/schema.h +++ b/include/rapidjson/schema.h @@ -1733,6 +1733,10 @@ public: Allocator::Free(typeless_); } + uri_.SetNull(); + error_.SetNull(); + currentError_.SetNull(); + RAPIDJSON_DELETE(ownAllocator_); } From 80b6d1c83402a5785c486603c5611923159d0894 Mon Sep 17 00:00:00 2001 From: Steve Hanson Date: Wed, 30 Nov 2022 13:56:55 +0000 Subject: [PATCH 22/35] small corrections for schema.h --- include/rapidjson/schema.h | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/include/rapidjson/schema.h b/include/rapidjson/schema.h index c1bb0a0..836c0f1 100644 --- a/include/rapidjson/schema.h +++ b/include/rapidjson/schema.h @@ -442,7 +442,7 @@ public: 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), + id_(id, allocator), pointer_(p, allocator), typeless_(schemaDocument->GetTypeless()), enum_(), @@ -1614,7 +1614,7 @@ public: virtual ~IGenericRemoteSchemaDocumentProvider() {} virtual const SchemaDocumentType* GetRemoteDocument(const Ch* uri, SizeType length) = 0; - virtual const SchemaDocumentType* GetRemoteDocument(GenericUri uri) { return GetRemoteDocument(uri.GetBaseString(), uri.GetBaseStringLength()); } + virtual const SchemaDocumentType* GetRemoteDocument(const GenericUri uri) { return GetRemoteDocument(uri.GetBaseString(), uri.GetBaseStringLength()); } }; /////////////////////////////////////////////////////////////////////////////// @@ -1712,7 +1712,7 @@ public: schemaMap_(std::move(rhs.schemaMap_)), schemaRef_(std::move(rhs.schemaRef_)), uri_(std::move(rhs.uri_)), - docId_(rhs.docId_), + docId_(std::move(rhs.docId_)), error_(std::move(rhs.error_)), currentError_(std::move(rhs.currentError_)) { @@ -1733,6 +1733,7 @@ public: Allocator::Free(typeless_); } + // these may contain some allocator data so clear before deleting ownAllocator_ uri_.SetNull(); error_.SetNull(); currentError_.SetNull(); @@ -1960,7 +1961,7 @@ public: // Get the subschema if (const ValueType *pv = relPointer.Get(*base)) { // Now get the absolute JSON pointer by adding relative to base - PointerType pointer(basePointer); + PointerType pointer(basePointer, allocator_); for (SizeType i = 0; i < relPointer.GetTokenCount(); i++) pointer = pointer.Append(relPointer.GetTokens()[i], allocator_); if (IsCyclicRef(pointer)) @@ -1978,7 +1979,7 @@ public: } } else { // Plain name fragment, relative to the resolved URI - PointerType pointer = PointerType(); + PointerType pointer(allocator_); // 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. if (const ValueType *pv = FindId(*base, ref, pointer, UriType(ref.GetBaseString(), ref.GetBaseStringLength(), allocator_), true, basePointer)) { From 55eca66f3921124436af6ac8b1e16ba7c4048060 Mon Sep 17 00:00:00 2001 From: Steve Hanson Date: Fri, 2 Dec 2022 16:53:27 +0000 Subject: [PATCH 23/35] code & tests for openapi 2.0 & 3.0 suppprt --- include/rapidjson/error/en.h | 15 +- include/rapidjson/error/error.h | 19 +- include/rapidjson/schema.h | 472 ++++++++++++++++++++++++------ test/unittest/schematest.cpp | 502 ++++++++++++++++++++++++++++++-- 4 files changed, 898 insertions(+), 110 deletions(-) diff --git a/include/rapidjson/error/en.h b/include/rapidjson/error/en.h index f27d62a..c87b04e 100644 --- a/include/rapidjson/error/en.h +++ b/include/rapidjson/error/en.h @@ -1,5 +1,5 @@ // 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. // // Licensed under the MIT License (the "License"); you may not use this file except @@ -7,9 +7,9 @@ // // 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 +// 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_ERROR_EN_H_ @@ -109,6 +109,9 @@ inline const RAPIDJSON_ERROR_CHARTYPE* GetValidateError_En(ValidateErrorCode val case kValidateErrorAnyOf: return RAPIDJSON_ERROR_STRING("Property did not match any of the sub-schemas specified by 'anyOf', refer to following errors."); case kValidateErrorNot: return RAPIDJSON_ERROR_STRING("Property matched the sub-schema specified by 'not'."); + case kValidateErrorReadOnly: return RAPIDJSON_ERROR_STRING("Property is read-only but has been provided when validation is for writing."); + case kValidateErrorWriteOnly: return RAPIDJSON_ERROR_STRING("Property is write-only but has been provided when validation is for reading."); + default: return RAPIDJSON_ERROR_STRING("Unknown error."); } } @@ -134,6 +137,10 @@ inline const RAPIDJSON_ERROR_CHARTYPE* GetValidateError_En(ValidateErrorCode val case kSchemaErrorRefNoRemoteProvider: return RAPIDJSON_ERROR_STRING("$ref is remote but there is no remote provider."); case kSchemaErrorRefNoRemoteSchema: return RAPIDJSON_ERROR_STRING("$ref '%value' is remote but the remote provider did not return a schema."); case kSchemaErrorRegexInvalid: return RAPIDJSON_ERROR_STRING("Invalid regular expression '%value' in 'pattern' or 'patternProperties'."); + case kSchemaErrorSpecUnknown: return RAPIDJSON_ERROR_STRING("JSON schema draft or OpenAPI version is not recognized."); + case kSchemaErrorSpecUnsupported: return RAPIDJSON_ERROR_STRING("JSON schema draft or OpenAPI version is not supported."); + case kSchemaErrorSpecIllegal: return RAPIDJSON_ERROR_STRING("Both JSON schema draft and OpenAPI version found in document."); + case kSchemaErrorReadOnlyAndWriteOnly: return RAPIDJSON_ERROR_STRING("Property must not be both 'readOnly' and 'writeOnly'."); default: return RAPIDJSON_ERROR_STRING("Unknown error."); } diff --git a/include/rapidjson/error/error.h b/include/rapidjson/error/error.h index d2a2fc4..8bdc395 100644 --- a/include/rapidjson/error/error.h +++ b/include/rapidjson/error/error.h @@ -1,5 +1,5 @@ // 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. // // Licensed under the MIT License (the "License"); you may not use this file except @@ -7,9 +7,9 @@ // // 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 +// 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_ERROR_ERROR_H_ @@ -192,7 +192,10 @@ enum ValidateErrorCode { kValidateErrorOneOfMatch, //!< Property matched more than one of the sub-schemas specified by 'oneOf'. kValidateErrorAllOf, //!< Property did not match all of the sub-schemas specified by 'allOf'. kValidateErrorAnyOf, //!< Property did not match any of the sub-schemas specified by 'anyOf'. - kValidateErrorNot //!< Property matched the sub-schema specified by 'not'. + kValidateErrorNot, //!< Property matched the sub-schema specified by 'not'. + + kValidateErrorReadOnly, //!< Property is read-only but has been provided when validation is for writing + kValidateErrorWriteOnly //!< Property is write-only but has been provided when validation is for reading }; //! Function pointer type of GetValidateError(). @@ -225,7 +228,11 @@ enum SchemaErrorCode { kSchemaErrorRefCyclical, //!< $ref is cyclical kSchemaErrorRefNoRemoteProvider, //!< $ref is remote but there is no remote provider kSchemaErrorRefNoRemoteSchema, //!< $ref is remote but the remote provider did not return a schema - kSchemaErrorRegexInvalid //!< Invalid regular expression in 'pattern' or 'patternProperties' + kSchemaErrorRegexInvalid, //!< Invalid regular expression in 'pattern' or 'patternProperties' + kSchemaErrorSpecUnknown, //!< JSON schema draft or OpenAPI version is not recognized + kSchemaErrorSpecUnsupported, //!< JSON schema draft or OpenAPI version is not supported + kSchemaErrorSpecIllegal, //!< Both JSON schema draft and OpenAPI version found in document + kSchemaErrorReadOnlyAndWriteOnly //!< Property must not be both 'readOnly' and 'writeOnly' }; //! Function pointer type of GetSchemaError(). diff --git a/include/rapidjson/schema.h b/include/rapidjson/schema.h index 836c0f1..0e45df6 100644 --- a/include/rapidjson/schema.h +++ b/include/rapidjson/schema.h @@ -1,5 +1,5 @@ // 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 @@ -7,9 +7,9 @@ // // 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 +// 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_ @@ -74,48 +74,94 @@ RAPIDJSON_NAMESPACE_BEGIN namespace internal { -inline void PrintInvalidKeyword(const char* keyword) { - printf("Fail keyword: %s\n", keyword); +inline void PrintInvalidKeywordData(const char* keyword) { + printf(" Fail keyword: '%s'\n", keyword); } -inline void PrintInvalidKeyword(const wchar_t* keyword) { - wprintf(L"Fail keyword: %ls\n", keyword); +inline void PrintInvalidKeywordData(const wchar_t* keyword) { + wprintf(L" Fail keyword: '%ls'\n", keyword); } -inline void PrintInvalidDocument(const char* document) { - printf("Fail document: %s\n\n", document); +inline void PrintInvalidDocumentData(const char* document) { + printf(" Fail document: '%s'\n", document); } -inline void PrintInvalidDocument(const wchar_t* document) { - wprintf(L"Fail document: %ls\n\n", document); +inline void PrintInvalidDocumentData(const wchar_t* document) { + wprintf(L" Fail document: '%ls'\n", document); } -inline void PrintValidatorPointers(unsigned depth, const char* s, const char* d) { - printf("S: %*s%s\nD: %*s%s\n\n", depth * 4, " ", s, depth * 4, " ", d); +inline void 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 PrintValidatorPointers(unsigned depth, const wchar_t* s, const wchar_t* d) { - wprintf(L"S: %*ls%ls\nD: %*ls%ls\n\n", depth * 4, L" ", s, depth * 4, L" ", d); +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 -#if RAPIDJSON_SCHEMA_VERBOSE -#define RAPIDJSON_INVALID_KEYWORD_VERBOSE(keyword) internal::PrintInvalidKeyword(keyword) -#else -#define RAPIDJSON_INVALID_KEYWORD_VERBOSE(keyword) -#endif - #define RAPIDJSON_INVALID_KEYWORD_RETURN(code)\ RAPIDJSON_MULTILINEMACRO_BEGIN\ context.invalidCode = code;\ context.invalidKeyword = SchemaType::GetValidateErrorKeyword(code).GetString();\ - RAPIDJSON_INVALID_KEYWORD_VERBOSE(context.invalidKeyword);\ + RAPIDJSON_SCHEMA_PRINT(InvalidKeyword, context.invalidKeyword);\ return false;\ RAPIDJSON_MULTILINEMACRO_END @@ -138,9 +184,53 @@ RAPIDJSON_MULTILINEMACRO_END 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 @@ -233,6 +323,8 @@ public: 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; }; @@ -253,10 +345,10 @@ public: bool Uint(unsigned u) { Number n; n.u.u = u; n.d = static_cast(u); return WriteNumber(n); } bool Int64(int64_t i) { Number n; n.u.i = i; n.d = static_cast(i); return WriteNumber(n); } bool Uint64(uint64_t u) { Number n; n.u.u = u; n.d = static_cast(u); return WriteNumber(n); } - bool Double(double d) { - Number n; + bool Double(double d) { + Number n; if (d < 0) n.u.i = static_cast(d); - else n.u.u = static_cast(d); + else n.u.u = static_cast(d); n.d = d; return WriteNumber(n); } @@ -350,10 +442,11 @@ struct SchemaValidationContext { kPatternValidatorWithAdditionalProperty }; - SchemaValidationContext(SchemaValidatorFactoryType& f, ErrorHandlerType& eh, const SchemaType* s) : + SchemaValidationContext(SchemaValidatorFactoryType& f, ErrorHandlerType& eh, const SchemaType* s, unsigned fl = 0) : factory(f), error_handler(eh), schema(s), + flags(fl), valueSchema(), invalidKeyword(), invalidCode(), @@ -401,6 +494,7 @@ struct SchemaValidationContext { SchemaValidatorFactoryType& factory; ErrorHandlerType& error_handler; const SchemaType* schema; + unsigned flags; const SchemaType* valueSchema; const Ch* invalidKeyword; ValidateErrorCode invalidCode; @@ -443,6 +537,7 @@ public: allocator_(allocator), uri_(schemaDocument->GetURI(), *allocator), id_(id, allocator), + spec_(schemaDocument->GetSpecification()), pointer_(p, allocator), typeless_(schemaDocument->GetTypeless()), enum_(), @@ -475,8 +570,15 @@ public: maxLength_(~SizeType(0)), exclusiveMinimum_(false), exclusiveMaximum_(false), - defaultValueLength_(0) + defaultValueLength_(0), + readOnly_(false), + writeOnly_(false), + nullable_(false) { + GenericStringBuffer sb; + p.StringifyUriFragment(sb); + RAPIDJSON_SCHEMA_PRINT(Method, "Schema::Schema", sb.GetString(), id.GetString()); + typedef typename ValueType::ConstValueIterator ConstValueIterator; typedef typename ValueType::ConstMemberIterator ConstMemberIterator; @@ -495,10 +597,13 @@ public: 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()); } } @@ -525,8 +630,11 @@ public: } } - if (schemaDocument) { + 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); @@ -555,6 +663,8 @@ public: 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); @@ -584,6 +694,8 @@ public: } } + // 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(allocator_->Malloc(sizeof(PatternProperty) * v->MemberCount())); @@ -608,6 +720,8 @@ public: } } + // 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; @@ -659,6 +773,8 @@ public: 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(); @@ -696,6 +812,23 @@ public: 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() { @@ -727,11 +860,16 @@ public: 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; @@ -763,6 +901,7 @@ public: } 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; @@ -853,6 +992,7 @@ public: } bool Null(Context& context) const { + RAPIDJSON_SCHEMA_PRINT(Method, "Schema::Null"); if (!(type_ & (1 << kNullSchemaType))) { DisallowedType(context, GetNullString()); RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorType); @@ -860,39 +1000,43 @@ public: return CreateParallelValidator(context); } - bool Bool(Context& context, bool) const { - if (!(type_ & (1 << kBooleanSchemaType))) { - DisallowedType(context, GetBooleanString()); - RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorType); - } + 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); @@ -911,6 +1055,7 @@ public: } 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); @@ -939,6 +1084,7 @@ public: } bool StartObject(Context& context) const { + RAPIDJSON_SCHEMA_PRINT(Method, "Schema::StartObject"); if (!(type_ & (1 << kObjectSchemaType))) { DisallowedType(context, GetObjectString()); RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorType); @@ -960,6 +1106,8 @@ public: } 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++) @@ -1011,6 +1159,7 @@ public: } 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++) @@ -1058,6 +1207,7 @@ public: } 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 @@ -1070,6 +1220,7 @@ public: } bool EndArray(Context& context, SizeType elementCount) const { + RAPIDJSON_SCHEMA_PRINT(Method, "Schema::EndArray"); context.inArray = false; if (elementCount < minItems_) { @@ -1118,6 +1269,9 @@ public: case kValidateErrorAnyOf: return GetAnyOfString(); case kValidateErrorNot: return GetNotString(); + case kValidateErrorReadOnly: return GetReadOnlyString(); + case kValidateErrorWriteOnly: return GetWriteOnlyString(); + default: return GetNullString(); } } @@ -1165,15 +1319,14 @@ public: 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_(SchemeEnd, ':') - RAPIDJSON_STRING_(AuthStart, '/', '/') - RAPIDJSON_STRING_(QueryStart, '?') - RAPIDJSON_STRING_(FragStart, '#') - RAPIDJSON_STRING_(Slash, '/') - RAPIDJSON_STRING_(Dot, '.') + 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_ @@ -1307,6 +1460,7 @@ private: // 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(); @@ -1337,6 +1491,16 @@ private: } } + // 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; } @@ -1359,6 +1523,14 @@ private: 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()); @@ -1524,6 +1696,7 @@ private: AllocatorType* allocator_; SValue uri_; UriType id_; + Specification spec_; PointerType pointer_; const SchemaType* typeless_; uint64_t* enum_; @@ -1568,6 +1741,10 @@ private: bool exclusiveMaximum_; SizeType defaultValueLength_; + + bool readOnly_; + bool writeOnly_; + bool nullable_; }; template @@ -1614,7 +1791,12 @@ public: virtual ~IGenericRemoteSchemaDocumentProvider() {} virtual const SchemaDocumentType* GetRemoteDocument(const Ch* uri, SizeType length) = 0; - virtual const SchemaDocumentType* GetRemoteDocument(const GenericUri uri) { return GetRemoteDocument(uri.GetBaseString(), uri.GetBaseStringLength()); } + virtual const SchemaDocumentType* GetRemoteDocument(const GenericUri uri, Specification& spec) { + // Default implementation just calls through for compatibility + // Following line suppresses unused parameter warning + if (false) printf("GetRemoteDocument: %d %d\n", spec.draft, spec.oapi); + return GetRemoteDocument(uri.GetBaseString(), uri.GetBaseStringLength()); + } }; /////////////////////////////////////////////////////////////////////////////// @@ -1656,10 +1838,12 @@ public: \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 PointerType& pointer = PointerType(), // PR #1393 + const Specification& spec = Specification(kDraft04)) : remoteProvider_(remoteProvider), allocator_(allocator), ownAllocator_(), @@ -1667,9 +1851,11 @@ public: 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)(); @@ -1680,6 +1866,10 @@ public: typeless_ = static_cast(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 @@ -1713,6 +1903,7 @@ public: 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_)) { @@ -1743,6 +1934,23 @@ public: 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_; } @@ -1761,6 +1969,10 @@ public: 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(); } } @@ -1829,6 +2041,7 @@ public: } 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_); @@ -1847,6 +2060,9 @@ public: 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') @@ -1855,10 +2071,94 @@ public: 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(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) { @@ -1875,6 +2175,9 @@ public: // 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 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) @@ -1904,6 +2207,9 @@ public: if (itr == v.MemberEnd()) return false; + GenericStringBuffer 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(&source); @@ -1915,6 +2221,7 @@ public: // 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(); @@ -1924,7 +2231,7 @@ public: if (!remoteProvider_) SchemaError(kSchemaErrorRefNoRemoteProvider, source); else { - if (const GenericSchemaDocument* remoteDocument = remoteProvider_->GetRemoteDocument(ref)) { + if (const GenericSchemaDocument* remoteDocument = remoteProvider_->GetRemoteDocument(ref, spec_)) { const Ch* s = ref.GetFragString(); len = ref.GetFragStringLength(); if (len <= 1 || s[1] == '/') { @@ -1979,10 +2286,13 @@ public: } } 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. - if (const ValueType *pv = FindId(*base, ref, pointer, UriType(ref.GetBaseString(), ref.GetBaseStringLength(), allocator_), true, basePointer)) { + 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 { @@ -2024,6 +2334,7 @@ public: } // See if it matches if (localuri.Match(finduri, full)) { + RAPIDJSON_SCHEMA_PRINT(Method, "GenericSchemaDocument::FindId (match)", full ? localuri.GetString() : localuri.GetBaseString()); resval = const_cast(&doc); resptr = here; return resval; @@ -2050,6 +2361,7 @@ public: // Added by PR #1393 void AddSchemaRefs(SchemaType* schema) { + RAPIDJSON_SCHEMA_PRINT(Method, "GenericSchemaDocument::AddSchemaRefs"); while (!schemaRef_.Empty()) { SchemaRefPtr *ref = schemaRef_.template Pop(1); SchemaEntry *entry = schemaMap_.template Push(); @@ -2093,6 +2405,7 @@ public: internal::Stack schemaRef_; // Stores Pointer(s) from $ref(s) until resolved GValue uri_; // Schema document URI UriType docId_; + Specification spec_; GValue error_; GValue currentError_; }; @@ -2158,11 +2471,10 @@ public: currentError_(), missingDependents_(), valid_(true), - flags_(kValidateDefaultFlags) -#if RAPIDJSON_SCHEMA_VERBOSE - , depth_(0) -#endif + flags_(kValidateDefaultFlags), + depth_(0) { + RAPIDJSON_SCHEMA_PRINT(Method, "GenericSchemaValidator::GenericSchemaValidator"); } //! Constructor with output handler. @@ -2190,11 +2502,10 @@ public: currentError_(), missingDependents_(), valid_(true), - flags_(kValidateDefaultFlags) -#if RAPIDJSON_SCHEMA_VERBOSE - , depth_(0) -#endif + flags_(kValidateDefaultFlags), + depth_(0) { + RAPIDJSON_SCHEMA_PRINT(Method, "GenericSchemaValidator::GenericSchemaValidator (output handler)"); } //! Destructor. @@ -2455,6 +2766,14 @@ public: 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() {\ @@ -2477,21 +2796,12 @@ public: #undef RAPIDJSON_STRING_ -#if RAPIDJSON_SCHEMA_VERBOSE -#define RAPIDJSON_SCHEMA_HANDLE_BEGIN_VERBOSE_() \ -RAPIDJSON_MULTILINEMACRO_BEGIN\ - *documentStack_.template Push() = '\0';\ - documentStack_.template Pop(1);\ - internal::PrintInvalidDocument(documentStack_.template Bottom());\ -RAPIDJSON_MULTILINEMACRO_END -#else -#define RAPIDJSON_SCHEMA_HANDLE_BEGIN_VERBOSE_() -#endif - #define RAPIDJSON_SCHEMA_HANDLE_BEGIN_(method, arg1)\ if (!valid_) return false; \ if ((!BeginValue() && !GetContinueOnErrors()) || (!CurrentSchema().method arg1 && !GetContinueOnErrors())) {\ - RAPIDJSON_SCHEMA_HANDLE_BEGIN_VERBOSE_();\ + *documentStack_.template Push() = '\0';\ + documentStack_.template Pop(1);\ + RAPIDJSON_SCHEMA_PRINT(InvalidDocument, documentStack_.template Bottom());\ valid_ = false;\ return valid_;\ } @@ -2530,6 +2840,7 @@ RAPIDJSON_MULTILINEMACRO_END { 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(); @@ -2537,6 +2848,7 @@ RAPIDJSON_MULTILINEMACRO_END } 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()) { @@ -2549,6 +2861,7 @@ RAPIDJSON_MULTILINEMACRO_END } 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()) { @@ -2559,6 +2872,7 @@ RAPIDJSON_MULTILINEMACRO_END } bool StartArray() { + RAPIDJSON_SCHEMA_PRINT(Method, "GenericSchemaValidator::StartArray"); RAPIDJSON_SCHEMA_HANDLE_BEGIN_(StartArray, (CurrentContext())); RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(StartArray, ()); valid_ = !outputHandler_ || outputHandler_->StartArray(); @@ -2566,6 +2880,7 @@ RAPIDJSON_MULTILINEMACRO_END } 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()) { @@ -2575,17 +2890,16 @@ RAPIDJSON_MULTILINEMACRO_END RAPIDJSON_SCHEMA_HANDLE_END_(EndArray, (elementCount)); } -#undef RAPIDJSON_SCHEMA_HANDLE_BEGIN_VERBOSE_ #undef RAPIDJSON_SCHEMA_HANDLE_BEGIN_ #undef RAPIDJSON_SCHEMA_HANDLE_PARALLEL_ #undef RAPIDJSON_SCHEMA_HANDLE_VALUE_ // Implementation of ISchemaStateFactory virtual ISchemaValidator* CreateSchemaValidator(const SchemaType& root, const bool inheritContinueOnErrors) { + *documentStack_.template Push() = '\0'; + documentStack_.template Pop(1); ISchemaValidator* sv = new (GetStateAllocator().Malloc(sizeof(GenericSchemaValidator))) GenericSchemaValidator(*schemaDocument_, root, documentStack_.template Bottom(), documentStack_.GetSize(), -#if RAPIDJSON_SCHEMA_VERBOSE depth_ + 1, -#endif &GetStateAllocator()); sv->SetValidateFlags(inheritContinueOnErrors ? GetValidateFlags() : GetValidateFlags() & ~(unsigned)kValidateContinueOnErrorFlag); return sv; @@ -2629,9 +2943,7 @@ private: const SchemaDocumentType& schemaDocument, const SchemaType& root, const char* basePath, size_t basePathSize, -#if RAPIDJSON_SCHEMA_VERBOSE unsigned depth, -#endif StateAllocator* allocator = 0, size_t schemaStackCapacity = kDefaultSchemaStackCapacity, size_t documentStackCapacity = kDefaultDocumentStackCapacity) @@ -2647,11 +2959,10 @@ private: currentError_(), missingDependents_(), valid_(true), - flags_(kValidateDefaultFlags) -#if RAPIDJSON_SCHEMA_VERBOSE - , depth_(depth) -#endif + flags_(kValidateDefaultFlags), + depth_(depth) { + RAPIDJSON_SCHEMA_PRINT(Method, "GenericSchemaValidator::GenericSchemaValidator (internal)", basePath && basePathSize ? basePath : ""); if (basePath && basePathSize) memcpy(documentStack_.template Push(basePathSize), basePath, basePathSize); } @@ -2667,6 +2978,7 @@ private: } bool BeginValue() { + RAPIDJSON_SCHEMA_PRINT(Method, "GenericSchemaValidator::BeginValue"); if (schemaStack_.Empty()) PushSchema(root_); else { @@ -2699,17 +3011,15 @@ private: } bool EndValue() { + RAPIDJSON_SCHEMA_PRINT(Method, "GenericSchemaValidator::EndValue"); if (!CurrentSchema().EndValue(CurrentContext()) && !GetContinueOnErrors()) return false; -#if RAPIDJSON_SCHEMA_VERBOSE GenericStringBuffer sb; - schemaDocument_->GetPointer(&CurrentSchema()).Stringify(sb); - + schemaDocument_->GetPointer(&CurrentSchema()).StringifyUriFragment(sb); *documentStack_.template Push() = '\0'; documentStack_.template Pop(1); - internal::PrintValidatorPointers(depth_, sb.GetString(), documentStack_.template Bottom()); -#endif + RAPIDJSON_SCHEMA_PRINT(ValidatorPointers, sb.GetString(), documentStack_.template Bottom(), depth_); void* hasher = CurrentContext().hasher; uint64_t h = hasher && CurrentContext().arrayUniqueness ? static_cast(hasher)->GetHashCode() : 0; @@ -2760,7 +3070,7 @@ private: } } - RAPIDJSON_FORCEINLINE void PushSchema(const SchemaType& schema) { new (schemaStack_.template Push()) Context(*this, *this, &schema); } + RAPIDJSON_FORCEINLINE void PushSchema(const SchemaType& schema) { new (schemaStack_.template Push()) Context(*this, *this, &schema, flags_); } RAPIDJSON_FORCEINLINE void PopSchema() { Context* c = schemaStack_.template Pop(1); @@ -2862,9 +3172,7 @@ private: ValueType missingDependents_; bool valid_; unsigned flags_; -#if RAPIDJSON_SCHEMA_VERBOSE unsigned depth_; -#endif }; typedef GenericSchemaValidator SchemaValidator; diff --git a/test/unittest/schematest.cpp b/test/unittest/schematest.cpp index a3f5f93..7387c08 100644 --- a/test/unittest/schematest.cpp +++ b/test/unittest/schematest.cpp @@ -118,12 +118,7 @@ TEST(SchemaValidator, Hasher) { #define VALIDATE_(schema, json, expected, expected2) \ {\ EXPECT_TRUE(expected2 == schema.GetError().ObjectEmpty());\ - if (expected2 && !schema.GetError().ObjectEmpty()) {\ - StringBuffer ssb;\ - Writer ws(ssb);\ - schema.GetError().Accept(ws);\ - printf("Schema error: %s\n", ssb.GetString());\ - }\ + EXPECT_TRUE(schema.IsSupportedSpecification());\ SchemaValidator validator(schema);\ Document d;\ /*printf("\n%s\n", json);*/\ @@ -162,12 +157,7 @@ TEST(SchemaValidator, Hasher) { flags, SchemaValidatorType, PointerType) \ {\ EXPECT_TRUE(schema.GetError().ObjectEmpty());\ - if (!schema.GetError().ObjectEmpty()) {\ - StringBuffer ssb;\ - Writer ws(ssb);\ - schema.GetError().Accept(ws);\ - printf("Schema error: %s\n", ssb.GetString());\ - }\ + EXPECT_TRUE(schema.IsSupportedSpecification());\ SchemaValidatorType validator(schema);\ validator.SetValidateFlags(flags);\ Document d;\ @@ -2163,9 +2153,13 @@ public: } virtual const SchemaDocumentType* GetRemoteDocument(const char* uri, SizeType length) { + //printf("GetRemoteDocument : %s\n", uri); for (size_t i = 0; i < kCount; i++) - if (typename SchemaDocumentType::GValue(uri, length) == sd_[i]->GetURI()) + if (typename SchemaDocumentType::GValue(uri, length) == sd_[i]->GetURI()) { + //printf("Matched document"); return sd_[i]; + } + //printf("No matched document"); return 0; } @@ -2999,6 +2993,334 @@ TEST(SchemaValidator, DuplicateKeyword) { // SchemaDocument tests +// Specification (schema draft, open api version) +TEST(SchemaValidator, Schema_SupportedNotObject) { + Document sd; + sd.Parse("true"); + ASSERT_FALSE(sd.HasParseError()); + SchemaDocument s(sd); + ASSERT_TRUE(s.IsSupportedSpecification()); + ASSERT_TRUE(s.GetSpecification().draft == kDraft04); + ASSERT_TRUE(s.GetSpecification().oapi == kVersionNone); + EXPECT_TRUE(s.GetError().ObjectEmpty()); +} + +TEST(SchemaValidator, Schema_SupportedNoSpec) { + Document sd; + sd.Parse("{\"type\": \"integer\"}"); + ASSERT_FALSE(sd.HasParseError()); + SchemaDocument s(sd); + ASSERT_TRUE(s.IsSupportedSpecification()); + ASSERT_TRUE(s.GetSpecification().draft == kDraft04); + ASSERT_TRUE(s.GetSpecification().oapi == kVersionNone); + EXPECT_TRUE(s.GetError().ObjectEmpty()); +} + +TEST(SchemaValidator, Schema_SupportedNoSpecStatic) { + typedef GenericSchemaDocument > SchemaDocumentType; + Document sd; + sd.Parse("{\"type\": \"integer\"}"); + ASSERT_FALSE(sd.HasParseError()); + Specification spec = SchemaDocumentType::GetSpecification(sd); + ASSERT_FALSE(spec.IsSupported()); + ASSERT_TRUE(spec.draft == kDraftNone); + ASSERT_TRUE(spec.oapi == kVersionNone); +} + +TEST(SchemaValidator, Schema_SupportedDraft5Static) { + typedef GenericSchemaDocument > SchemaDocumentType; + Document sd; + sd.Parse("{\"$schema\":\"http://json-schema.org/draft-05/schema#\", \"type\": \"integer\"}"); + ASSERT_FALSE(sd.HasParseError()); + Specification spec = SchemaDocumentType::GetSpecification(sd); + ASSERT_TRUE(spec.IsSupported()); + ASSERT_TRUE(spec.draft == kDraft05); + ASSERT_TRUE(spec.oapi == kVersionNone); +} + +TEST(SchemaValidator, Schema_SupportedDraft4) { + Document sd; + sd.Parse("{\"$schema\":\"http://json-schema.org/draft-04/schema#\", \"type\": \"integer\"}"); + ASSERT_FALSE(sd.HasParseError()); + SchemaDocument s(sd); + ASSERT_TRUE(s.IsSupportedSpecification()); + ASSERT_TRUE(s.GetSpecification().draft == kDraft04); + ASSERT_TRUE(s.GetSpecification().oapi == kVersionNone); + EXPECT_TRUE(s.GetError().ObjectEmpty()); +} + +TEST(SchemaValidator, Schema_SupportedDraft4NoFrag) { + Document sd; + sd.Parse("{\"$schema\":\"http://json-schema.org/draft-04/schema\", \"type\": \"integer\"}"); + ASSERT_FALSE(sd.HasParseError()); + SchemaDocument s(sd); + ASSERT_TRUE(s.IsSupportedSpecification()); + ASSERT_TRUE(s.GetSpecification().draft == kDraft04); + ASSERT_TRUE(s.GetSpecification().oapi == kVersionNone); + EXPECT_TRUE(s.GetError().ObjectEmpty()); +} + +TEST(SchemaValidator, Schema_SupportedDraft5) { + Document sd; + sd.Parse("{\"$schema\":\"http://json-schema.org/draft-05/schema#\", \"type\": \"integer\"}"); + ASSERT_FALSE(sd.HasParseError()); + SchemaDocument s(sd); + ASSERT_TRUE(s.IsSupportedSpecification()); + ASSERT_TRUE(s.GetSpecification().draft == kDraft05); + ASSERT_TRUE(s.GetSpecification().oapi == kVersionNone); + EXPECT_TRUE(s.GetError().ObjectEmpty()); +} + +TEST(SchemaValidator, Schema_SupportedDraft5NoFrag) { + Document sd; + sd.Parse("{\"$schema\":\"http://json-schema.org/draft-05/schema\", \"type\": \"integer\"}"); + ASSERT_FALSE(sd.HasParseError()); + SchemaDocument s(sd); + ASSERT_TRUE(s.IsSupportedSpecification()); + ASSERT_TRUE(s.GetSpecification().draft == kDraft05); + ASSERT_TRUE(s.GetSpecification().oapi == kVersionNone); + EXPECT_TRUE(s.GetError().ObjectEmpty()); +} + +TEST(SchemaValidator, Schema_IgnoreDraftEmbedded) { + Document sd; + sd.Parse("{\"root\": {\"$schema\":\"http://json-schema.org/draft-05/schema#\", \"type\": \"integer\"}}"); + ASSERT_FALSE(sd.HasParseError()); + SchemaDocument s(sd, 0, 0, 0, 0, SchemaDocument::PointerType("/root")); + ASSERT_TRUE(s.IsSupportedSpecification()); + ASSERT_TRUE(s.GetSpecification().draft == kDraft04); + ASSERT_TRUE(s.GetSpecification().oapi == kVersionNone); + EXPECT_TRUE(s.GetError().ObjectEmpty()); +} + +TEST(SchemaValidator, Schema_SupportedDraftOverride) { + Document sd; + sd.Parse("{\"type\": \"integer\"}"); + ASSERT_FALSE(sd.HasParseError()); + SchemaDocument s(sd, 0, 0, 0, 0, 0, Specification(kDraft04)); + ASSERT_TRUE(s.IsSupportedSpecification()); + ASSERT_TRUE(s.GetSpecification().draft == kDraft04); + ASSERT_TRUE(s.GetSpecification().oapi == kVersionNone); + EXPECT_TRUE(s.GetError().ObjectEmpty()); +} + +TEST(SchemaValidator, Schema_UnknownDraftOverride) { + Document sd; + sd.Parse("{\"type\": \"integer\"}"); + ASSERT_FALSE(sd.HasParseError()); + SchemaDocument s(sd, 0, 0, 0, 0, 0, Specification(kDraftUnknown)); + ASSERT_FALSE(s.IsSupportedSpecification()); + ASSERT_TRUE(s.GetSpecification().draft == kDraftUnknown); + ASSERT_TRUE(s.GetSpecification().oapi == kVersionNone); + SCHEMAERROR(s, "{\"SpecUnknown\":{\"errorCode\":10,\"instanceRef\":\"#\"}}"); +} + +TEST(SchemaValidator, Schema_UnsupportedDraftOverride) { + Document sd; + sd.Parse("{\"type\": \"integer\"}"); + ASSERT_FALSE(sd.HasParseError()); + SchemaDocument s(sd, 0, 0, 0, 0, 0, Specification(kDraft03)); + ASSERT_FALSE(s.IsSupportedSpecification()); + ASSERT_TRUE(s.GetSpecification().draft == kDraft03); + ASSERT_TRUE(s.GetSpecification().oapi == kVersionNone); + SCHEMAERROR(s, "{\"SpecUnsupported\":{\"errorCode\":11,\"instanceRef\":\"#\"}}"); +} + +TEST(SchemaValidator, Schema_UnknownDraft) { + Document sd; + sd.Parse("{\"$schema\":\"http://json-schema.org/draft-xxx/schema#\", \"type\": \"integer\"}"); + ASSERT_FALSE(sd.HasParseError()); + SchemaDocument s(sd); + ASSERT_FALSE(s.IsSupportedSpecification()); + ASSERT_TRUE(s.GetSpecification().draft == kDraftUnknown); + ASSERT_TRUE(s.GetSpecification().oapi == kVersionNone); + SCHEMAERROR(s, "{\"SpecUnknown\":{\"errorCode\":10,\"instanceRef\":\"#\"}}"); +} + +TEST(SchemaValidator, Schema_UnknownDraftNotString) { + Document sd; + sd.Parse("{\"$schema\": 4, \"type\": \"integer\"}"); + ASSERT_FALSE(sd.HasParseError()); + SchemaDocument s(sd); + ASSERT_FALSE(s.IsSupportedSpecification()); + ASSERT_TRUE(s.GetSpecification().draft == kDraftUnknown); + ASSERT_TRUE(s.GetSpecification().oapi == kVersionNone); + SCHEMAERROR(s, "{\"SpecUnknown\":{\"errorCode\":10,\"instanceRef\":\"#\"}}"); +} + +TEST(SchemaValidator, Schema_UnsupportedDraft3) { + Document sd; + sd.Parse("{\"$schema\":\"http://json-schema.org/draft-03/schema#\", \"type\": \"integer\"}"); + ASSERT_FALSE(sd.HasParseError()); + SchemaDocument s(sd); + ASSERT_FALSE(s.IsSupportedSpecification()); + ASSERT_TRUE(s.GetSpecification().draft == kDraft03); + ASSERT_TRUE(s.GetSpecification().oapi == kVersionNone); + SCHEMAERROR(s, "{\"SpecUnsupported\":{\"errorCode\":11,\"instanceRef\":\"#\"}}"); +} + +TEST(SchemaValidator, Schema_UnsupportedDraft6) { + Document sd; + sd.Parse("{\"$schema\":\"http://json-schema.org/draft-06/schema#\", \"type\": \"integer\"}"); + ASSERT_FALSE(sd.HasParseError()); + SchemaDocument s(sd); + ASSERT_FALSE(s.IsSupportedSpecification()); + ASSERT_TRUE(s.GetSpecification().draft == kDraft06); + SCHEMAERROR(s, "{\"SpecUnsupported\":{\"errorCode\":11,\"instanceRef\":\"#\"}}"); +} + +TEST(SchemaValidator, Schema_UnsupportedDraft7) { + Document sd; + sd.Parse("{\"$schema\":\"http://json-schema.org/draft-07/schema#\", \"type\": \"integer\"}"); + ASSERT_FALSE(sd.HasParseError()); + SchemaDocument s(sd); + ASSERT_FALSE(s.IsSupportedSpecification()); + ASSERT_TRUE(s.GetSpecification().draft == kDraft07); + ASSERT_TRUE(s.GetSpecification().oapi == kVersionNone); + SCHEMAERROR(s, "{\"SpecUnsupported\":{\"errorCode\":11,\"instanceRef\":\"#\"}}"); +} + +TEST(SchemaValidator, Schema_UnsupportedDraft2019_09) { + Document sd; + sd.Parse("{\"$schema\":\"https://json-schema.org/draft/2019-09/schema\", \"type\": \"integer\"}"); + ASSERT_FALSE(sd.HasParseError()); + SchemaDocument s(sd); + ASSERT_FALSE(s.IsSupportedSpecification()); + ASSERT_TRUE(s.GetSpecification().draft == kDraft2019_09); + ASSERT_TRUE(s.GetSpecification().oapi == kVersionNone); + SCHEMAERROR(s, "{\"SpecUnsupported\":{\"errorCode\":11,\"instanceRef\":\"#\"}}"); +} + +TEST(SchemaValidator, Schema_UnsupportedDraft2020_12) { + Document sd; + sd.Parse("{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\", \"type\": \"integer\"}"); + ASSERT_FALSE(sd.HasParseError()); + SchemaDocument s(sd); + ASSERT_FALSE(s.IsSupportedSpecification()); + ASSERT_TRUE(s.GetSpecification().draft == kDraft2020_12); + ASSERT_TRUE(s.GetSpecification().oapi == kVersionNone); + SCHEMAERROR(s, "{\"SpecUnsupported\":{\"errorCode\":11,\"instanceRef\":\"#\"}}"); +} + +TEST(SchemaValidator, Schema_SupportedVersion20Static) { + typedef GenericSchemaDocument > SchemaDocumentType; + Document sd; + sd.Parse("{\"swagger\":\"2.0\"}"); + ASSERT_FALSE(sd.HasParseError()); + Specification spec = SchemaDocumentType::GetSpecification(sd); + ASSERT_TRUE(spec.IsSupported()); + ASSERT_TRUE(spec.draft == kDraft04); + ASSERT_TRUE(spec.oapi == kVersion20); +} + +TEST(SchemaValidator, Schema_SupportedVersion20) { + Document sd; + sd.Parse("{\"swagger\":\"2.0\"}"); + ASSERT_FALSE(sd.HasParseError()); + SchemaDocument s(sd); + ASSERT_TRUE(s.IsSupportedSpecification()); + ASSERT_TRUE(s.GetSpecification().oapi == kVersion20); + ASSERT_TRUE(s.GetSpecification().draft == kDraft04); + EXPECT_TRUE(s.GetError().ObjectEmpty()); +} + +TEST(SchemaValidator, Schema_SupportedVersion30x) { + Document sd; + sd.Parse("{\"openapi\":\"3.0.0\"}"); + ASSERT_FALSE(sd.HasParseError()); + SchemaDocument s(sd); + ASSERT_TRUE(s.IsSupportedSpecification()); + ASSERT_TRUE(s.GetSpecification().oapi == kVersion30); + ASSERT_TRUE(s.GetSpecification().draft == kDraft05); + EXPECT_TRUE(s.GetError().ObjectEmpty()); +} + +TEST(SchemaValidator, Schema_SupportedVersionOverride) { + Document sd; + sd.Parse("{\"type\": \"integer\"}"); + ASSERT_FALSE(sd.HasParseError()); + SchemaDocument s(sd, 0, 0, 0, 0, 0, Specification(kVersion20)); + ASSERT_TRUE(s.IsSupportedSpecification()); + ASSERT_TRUE(s.GetSpecification().oapi == kVersion20); + ASSERT_TRUE(s.GetSpecification().draft == kDraft04); + EXPECT_TRUE(s.GetError().ObjectEmpty()); +} + +TEST(SchemaValidator, Schema_UnknownVersionOverride) { + Document sd; + sd.Parse("{\"type\": \"integer\"}"); + ASSERT_FALSE(sd.HasParseError()); + SchemaDocument s(sd, 0, 0, 0, 0, 0, Specification(kVersionUnknown)); + ASSERT_FALSE(s.IsSupportedSpecification()); + ASSERT_TRUE(s.GetSpecification().oapi == kVersionUnknown); + ASSERT_TRUE(s.GetSpecification().draft == kDraft04); + SCHEMAERROR(s, "{\"SpecUnknown\":{\"errorCode\":10,\"instanceRef\":\"#\"}}"); +} + +TEST(SchemaValidator, Schema_UnsupportedVersionOverride) { + Document sd; + sd.Parse("{\"type\": \"integer\"}"); + ASSERT_FALSE(sd.HasParseError()); + SchemaDocument s(sd, 0, 0, 0, 0, 0, Specification(kVersion31)); + ASSERT_FALSE(s.IsSupportedSpecification()); + ASSERT_TRUE(s.GetSpecification().oapi == kVersion31); + ASSERT_TRUE(s.GetSpecification().draft == kDraft2020_12); + SCHEMAERROR(s, "{\"SpecUnsupported\":{\"errorCode\":11,\"instanceRef\":\"#\"}}"); +} + +TEST(SchemaValidator, Schema_UnknownVersion) { + Document sd; + sd.Parse("{\"openapi\":\"1.0\"}"); + ASSERT_FALSE(sd.HasParseError()); + SchemaDocument s(sd); + ASSERT_FALSE(s.IsSupportedSpecification()); + ASSERT_TRUE(s.GetSpecification().oapi == kVersionUnknown); + ASSERT_TRUE(s.GetSpecification().draft == kDraft04); + SCHEMAERROR(s, "{\"SpecUnknown\":{\"errorCode\":10,\"instanceRef\":\"#\"}}"); +} + +TEST(SchemaValidator, Schema_UnknownVersionShort) { + Document sd; + sd.Parse("{\"openapi\":\"3.0.\"}"); + ASSERT_FALSE(sd.HasParseError()); + SchemaDocument s(sd); + ASSERT_FALSE(s.IsSupportedSpecification()); + ASSERT_TRUE(s.GetSpecification().oapi == kVersionUnknown); + ASSERT_TRUE(s.GetSpecification().draft == kDraft04); + SCHEMAERROR(s, "{\"SpecUnknown\":{\"errorCode\":10,\"instanceRef\":\"#\"}}"); +} + +TEST(SchemaValidator, Schema_UnknownVersionNotString) { + Document sd; + sd.Parse("{\"swagger\": 2}"); + ASSERT_FALSE(sd.HasParseError()); + SchemaDocument s(sd); + ASSERT_FALSE(s.IsSupportedSpecification()); + ASSERT_TRUE(s.GetSpecification().oapi == kVersionUnknown); + ASSERT_TRUE(s.GetSpecification().draft == kDraft04); + SCHEMAERROR(s, "{\"SpecUnknown\":{\"errorCode\":10,\"instanceRef\":\"#\"}}"); +} + +TEST(SchemaValidator, Schema_UnsupportedVersion31) { + Document sd; + sd.Parse("{\"openapi\":\"3.1.0\"}"); + ASSERT_FALSE(sd.HasParseError()); + SchemaDocument s(sd); + ASSERT_FALSE(s.IsSupportedSpecification()); + ASSERT_TRUE(s.GetSpecification().oapi == kVersion31); + ASSERT_TRUE(s.GetSpecification().draft == kDraft2020_12); + SCHEMAERROR(s, "{\"SpecUnsupported\":{\"errorCode\":11,\"instanceRef\":\"#\"}}"); +} + +TEST(SchemaValidator, Schema_DraftAndVersion) { + Document sd; + sd.Parse("{\"swagger\": \"2.0\", \"$schema\": \"http://json-schema.org/draft-04/schema#\"}"); + ASSERT_FALSE(sd.HasParseError()); + SchemaDocument s(sd); + ASSERT_TRUE(s.IsSupportedSpecification()); + SCHEMAERROR(s, "{\"SpecIllegal\":{\"errorCode\":12,\"instanceRef\":\"#\"}}"); +} + TEST(SchemaValidator, Schema_StartUnknown) { Document sd; sd.Parse("{\"type\": \"integer\"}"); @@ -3007,6 +3329,25 @@ TEST(SchemaValidator, Schema_StartUnknown) { SCHEMAERROR(s, "{\"StartUnknown\":{\"errorCode\":1,\"instanceRef\":\"#\", \"value\":\"#/nowhere\"}}"); } +TEST(SchemaValidator, Schema_MultipleErrors) { + Document sd; + sd.Parse("{\"swagger\": \"foo\", \"$schema\": \"bar\"}"); + ASSERT_FALSE(sd.HasParseError()); + SchemaDocument s(sd); + SCHEMAERROR(s, "{ \"SpecUnknown\": {\"errorCode\":10,\"instanceRef\":\"#\"}," + " \"SpecIllegal\": {\"errorCode\":12,\"instanceRef\":\"#\"}" + "}"); +} + +// $ref is a non-JSON pointer fragment - not allowed when OpenAPI +TEST(SchemaValidator, Schema_RefPlainNameOpenApi) { + typedef GenericSchemaDocument > SchemaDocumentType; + Document sd; + sd.Parse("{\"swagger\": \"2.0\", \"type\": \"object\", \"properties\": {\"myInt1\": {\"$ref\": \"#myId\"}, \"myStr\": {\"type\": \"string\", \"id\": \"#myStrId\"}, \"myInt2\": {\"type\": \"integer\", \"id\": \"#myId\"}}}"); + SchemaDocumentType s(sd); + SCHEMAERROR(s, "{\"RefPlainName\":{\"errorCode\":2,\"instanceRef\":\"#/properties/myInt1\",\"value\":\"#myId\"}}"); +} + // $ref is a non-JSON pointer fragment - not allowed when remote document TEST(SchemaValidator, Schema_RefPlainNameRemote) { typedef GenericSchemaDocument > SchemaDocumentType; @@ -3019,9 +3360,10 @@ TEST(SchemaValidator, Schema_RefPlainNameRemote) { // $ref is an empty string TEST(SchemaValidator, Schema_RefEmptyString) { + typedef GenericSchemaDocument > SchemaDocumentType; Document sd; sd.Parse("{\"type\": \"object\", \"properties\": {\"myInt1\": {\"$ref\": \"\"}}}"); - SchemaDocument s(sd); + SchemaDocumentType s(sd); SCHEMAERROR(s, "{\"RefInvalid\":{\"errorCode\":3,\"instanceRef\":\"#/properties/myInt1\"}}"); } @@ -3046,9 +3388,10 @@ TEST(SchemaValidator, Schema_RefNoRemoteSchema) { // $ref pointer is invalid TEST(SchemaValidator, Schema_RefPointerInvalid) { + typedef GenericSchemaDocument > SchemaDocumentType; Document sd; sd.Parse("{\"type\": \"object\", \"properties\": {\"myInt\": {\"$ref\": \"#/&&&&&\"}}}"); - SchemaDocument s(sd); + SchemaDocumentType s(sd); SCHEMAERROR(s, "{\"RefPointerInvalid\":{\"errorCode\":4,\"instanceRef\":\"#/properties/myInt\",\"value\":\"#/&&&&&\",\"offset\":2}}"); } @@ -3064,17 +3407,19 @@ TEST(SchemaValidator, Schema_RefPointerInvalidRemote) { // $ref is unknown non-pointer TEST(SchemaValidator, Schema_RefUnknownPlainName) { + typedef GenericSchemaDocument > SchemaDocumentType; Document sd; sd.Parse("{\"type\": \"object\", \"properties\": {\"myInt\": {\"$ref\": \"#plainname\"}}}"); - SchemaDocument s(sd); + SchemaDocumentType s(sd); SCHEMAERROR(s, "{\"RefUnknown\":{\"errorCode\":5,\"instanceRef\":\"#/properties/myInt\",\"value\":\"#plainname\"}}"); } /// $ref is unknown pointer TEST(SchemaValidator, Schema_RefUnknownPointer) { + typedef GenericSchemaDocument > SchemaDocumentType; Document sd; sd.Parse("{\"type\": \"object\", \"properties\": {\"myInt\": {\"$ref\": \"#/a/b\"}}}"); - SchemaDocument s(sd); + SchemaDocumentType s(sd); SCHEMAERROR(s, "{\"RefUnknown\":{\"errorCode\":5,\"instanceRef\":\"#/properties/myInt\",\"value\":\"#/a/b\"}}"); } @@ -3090,6 +3435,7 @@ TEST(SchemaValidator, Schema_RefUnknownPointerRemote) { // $ref is cyclical TEST(SchemaValidator, Schema_RefCyclical) { + typedef GenericSchemaDocument > SchemaDocumentType; Document sd; sd.Parse("{\"type\": \"object\", \"properties\": {" " \"cyclic_source\": {" @@ -3099,10 +3445,130 @@ TEST(SchemaValidator, Schema_RefCyclical) { " \"$ref\": \"#/properties/cyclic_source\"" " }" "}}"); - SchemaDocument s(sd); + SchemaDocumentType s(sd); SCHEMAERROR(s, "{\"RefCyclical\":{\"errorCode\":6,\"instanceRef\":\"#/properties/cyclic_target\",\"value\":\"#/properties/cyclic_source\"}}"); } +TEST(SchemaValidator, Schema_ReadOnlyAndWriteOnly) { + Document sd; + sd.Parse("{\"type\": \"integer\", \"readOnly\": true, \"writeOnly\": true}"); + ASSERT_FALSE(sd.HasParseError()); + SchemaDocument s1(sd, 0, 0, 0, 0, 0, Specification(kDraft04)); + EXPECT_TRUE(s1.GetError().ObjectEmpty()); + SchemaDocument s2(sd, 0, 0, 0, 0, 0, Specification(kVersion30)); + SCHEMAERROR(s2, "{\"ReadOnlyAndWriteOnly\":{\"errorCode\":13,\"instanceRef\":\"#\"}}"); +} + +TEST(SchemaValidator, ReadOnlyWhenWriting) { + Document sd; + sd.Parse( + "{" + " \"type\":\"object\"," + " \"properties\": {" + " \"rprop\" : {" + " \"type\": \"string\"," + " \"readOnly\": true" + " }" + " }" + "}"); + SchemaDocument s(sd, 0, 0, 0, 0, 0, Specification(kVersion20)); + VALIDATE(s, "{ \"rprop\": \"hello\" }", true); + INVALIDATE_(s, "{ \"rprop\": \"hello\" }", "/properties/rprop", "readOnly", "/rprop", + "{ \"readOnly\": {" + " \"errorCode\": 26, \"instanceRef\": \"#/rprop\", \"schemaRef\": \"#/properties/rprop\"" + " }" + "}", + kValidateDefaultFlags | kValidateWriteFlag, SchemaValidator, Pointer); +} + +TEST(SchemaValidator, WriteOnlyWhenReading) { + Document sd; + sd.Parse( + "{" + " \"type\":\"object\"," + " \"properties\": {" + " \"wprop\" : {" + " \"type\": \"boolean\"," + " \"writeOnly\": true" + " }" + " }" + "}"); + SchemaDocument s(sd, 0, 0, 0, 0, 0, Specification(kVersion30)); + VALIDATE(s, "{ \"wprop\": true }", true); + INVALIDATE_(s, "{ \"wprop\": true }", "/properties/wprop", "writeOnly", "/wprop", + "{ \"writeOnly\": {" + " \"errorCode\": 27, \"instanceRef\": \"#/wprop\", \"schemaRef\": \"#/properties/wprop\"" + " }" + "}", + kValidateDefaultFlags | kValidateReadFlag, SchemaValidator, Pointer); +} + +TEST(SchemaValidator, NullableTrue) { + Document sd; + sd.Parse("{\"type\": \"string\", \"nullable\": true}"); + SchemaDocument s(sd, 0, 0, 0, 0, 0, kVersion20); + + VALIDATE(s, "\"hello\"", true); + INVALIDATE(s, "null", "", "type", "", + "{ \"type\": {" + " \"errorCode\": 20," + " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," + " \"expected\": [\"string\"], \"actual\": \"null\"" + "}}"); + INVALIDATE(s, "false", "", "type", "", + "{ \"type\": {" + " \"errorCode\": 20," + " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," + " \"expected\": [\"string\"], \"actual\": \"boolean\"" + "}}"); + + SchemaDocument s30(sd, 0, 0, 0, 0, 0, kVersion30); + + VALIDATE(s30, "\"hello\"", true); + VALIDATE(s30, "null", true); + INVALIDATE(s30, "false", "", "type", "", + "{ \"type\": {" + " \"errorCode\": 20," + " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," + " \"expected\": [\"null\", \"string\"], \"actual\": \"boolean\"" + "}}"); +} + +TEST(SchemaValidator, NullableFalse) { + Document sd; + sd.Parse("{\"type\": \"string\", \"nullable\": false}"); + SchemaDocument s(sd, 0, 0, 0, 0, 0, kVersion20); + + VALIDATE(s, "\"hello\"", true); + INVALIDATE(s, "null", "", "type", "", + "{ \"type\": {" + " \"errorCode\": 20," + " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," + " \"expected\": [\"string\"], \"actual\": \"null\"" + "}}"); + INVALIDATE(s, "false", "", "type", "", + "{ \"type\": {" + " \"errorCode\": 20," + " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," + " \"expected\": [\"string\"], \"actual\": \"boolean\"" + "}}"); + + SchemaDocument s30(sd, 0, 0, 0, 0, 0, kVersion30); + + VALIDATE(s30, "\"hello\"", true); + INVALIDATE(s, "null", "", "type", "", + "{ \"type\": {" + " \"errorCode\": 20," + " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," + " \"expected\": [\"string\"], \"actual\": \"null\"" + "}}"); + INVALIDATE(s30, "false", "", "type", "", + "{ \"type\": {" + " \"errorCode\": 20," + " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," + " \"expected\": [\"string\"], \"actual\": \"boolean\"" + "}}"); +} #if defined(_MSC_VER) || defined(__clang__) RAPIDJSON_DIAG_POP From b08672d4699b4c55c70d1a288b87295ee5808e07 Mon Sep 17 00:00:00 2001 From: Steve Hanson Date: Fri, 16 Dec 2022 10:06:12 +0000 Subject: [PATCH 24/35] review comment updates --- doc/features.md | 10 ++++++---- doc/schema.md | 8 ++++++++ include/rapidjson/schema.h | 3 ++- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/doc/features.md b/doc/features.md index 0d79e7f..4d15937 100644 --- a/doc/features.md +++ b/doc/features.md @@ -22,13 +22,15 @@ * RapidJSON should be fully RFC4627/ECMA-404 compliance. * Support JSON Pointer (RFC6901). * Support JSON Schema Draft v4. +* Support Swagger v2 schema. +* Support OpenAPI v3.0.x schema. * Support Unicode surrogate. * Support null character (`"\u0000"`) - * For example, `["Hello\u0000World"]` can be parsed and handled gracefully. There is API for getting/setting lengths of string. +* For example, `["Hello\u0000World"]` can be parsed and handled gracefully. There is API for getting/setting lengths of string. * Support optional relaxed syntax. - * Single line (`// ...`) and multiple line (`/* ... */`) comments (`kParseCommentsFlag`). - * Trailing commas at the end of objects and arrays (`kParseTrailingCommasFlag`). - * `NaN`, `Inf`, `Infinity`, `-Inf` and `-Infinity` as `double` values (`kParseNanAndInfFlag`) +* Single line (`// ...`) and multiple line (`/* ... */`) comments (`kParseCommentsFlag`). +* Trailing commas at the end of objects and arrays (`kParseTrailingCommasFlag`). +* `NaN`, `Inf`, `Infinity`, `-Inf` and `-Infinity` as `double` values (`kParseNanAndInfFlag`) * [NPM compliant](http://github.com/Tencent/rapidjson/blob/master/doc/npm.md). ## Unicode diff --git a/doc/schema.md b/doc/schema.md index 238d7a5..4da4474 100644 --- a/doc/schema.md +++ b/doc/schema.md @@ -24,7 +24,15 @@ if (sd.Parse(schemaJson).HasParseError()) { // the schema is not a valid JSON. // ... } + SchemaDocument schema(sd); // Compile a Document to SchemaDocument +if (!schema.GetError().ObjectEmpty()) { + // there was a problem compiling the schema + StringBuffer sb; + Writer w(sb); + schema.GetError().Accept(w); + printf("Invalid schema: %s\n", sb.GetString()); +} // sd is no longer needed here. Document d; diff --git a/include/rapidjson/schema.h b/include/rapidjson/schema.h index 0e45df6..6e51177 100644 --- a/include/rapidjson/schema.h +++ b/include/rapidjson/schema.h @@ -1794,7 +1794,8 @@ public: virtual const SchemaDocumentType* GetRemoteDocument(const GenericUri uri, Specification& spec) { // Default implementation just calls through for compatibility // Following line suppresses unused parameter warning - if (false) printf("GetRemoteDocument: %d %d\n", spec.draft, spec.oapi); + (void)spec; + // printf("GetRemoteDocument: %d %d\n", spec.draft, spec.oapi); return GetRemoteDocument(uri.GetBaseString(), uri.GetBaseStringLength()); } }; From a98e99992bd633a2736cc41f96ec85ef0c50e44d Mon Sep 17 00:00:00 2001 From: Kent Ross Date: Thu, 3 Nov 2022 20:17:41 -0700 Subject: [PATCH 25/35] do not define operator!= in C++20 A change to the semantics of equality operator rewriting in C++20 (P2468R2: The Equality Operator You Are Looking For) means that operator== may not be rewritten with reversed operands if operator!= is also defined. Since operator!= can normally be synthesized from operator== regardless in this language standard, we can and should avoid defining those when the new language semantics are available. This fixes the compilation of tests (and probably consuming code) in C++20 onwards for compilers that implement this new semantic, including recent nightly builds of clang-16. Reference: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2468r2.html --- include/rapidjson/document.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/rapidjson/document.h b/include/rapidjson/document.h index 4f1e246..2cd9a70 100644 --- a/include/rapidjson/document.h +++ b/include/rapidjson/document.h @@ -1078,6 +1078,7 @@ public: */ template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr,internal::IsGenericValue >), (bool)) operator==(const T& rhs) const { return *this == GenericValue(rhs); } +#ifndef __cpp_impl_three_way_comparison //! Not-equal-to operator /*! \return !(*this == rhs) */ @@ -1092,7 +1093,6 @@ public: */ template RAPIDJSON_DISABLEIF_RETURN((internal::IsGenericValue), (bool)) operator!=(const T& rhs) const { return !(*this == rhs); } -#ifndef __cpp_impl_three_way_comparison //! Equal-to operator with arbitrary types (symmetric version) /*! \return (rhs == lhs) */ From 76281ff388c3dda04196c0586503e9a1396989dd Mon Sep 17 00:00:00 2001 From: Tana0910 <32482176+Tana0910@users.noreply.github.com> Date: Wed, 4 Jan 2023 07:47:06 +0900 Subject: [PATCH 26/35] fix a typo in error.h: literial -> literal --- include/rapidjson/error/error.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/rapidjson/error/error.h b/include/rapidjson/error/error.h index 8bdc395..cae345d 100644 --- a/include/rapidjson/error/error.h +++ b/include/rapidjson/error/error.h @@ -42,7 +42,7 @@ RAPIDJSON_DIAG_OFF(padded) /////////////////////////////////////////////////////////////////////////////// // RAPIDJSON_ERROR_STRING -//! Macro for converting string literial to \ref RAPIDJSON_ERROR_CHARTYPE[]. +//! Macro for converting string literal to \ref RAPIDJSON_ERROR_CHARTYPE[]. /*! \ingroup RAPIDJSON_ERRORS By default this conversion macro does nothing. On Windows, user can define this macro as \c _T(x) for supporting both From 778dc8b03ea582064f6a06a7e11f7319b14e60b5 Mon Sep 17 00:00:00 2001 From: Steve Hanson Date: Thu, 1 Dec 2022 10:58:43 +0000 Subject: [PATCH 27/35] fix #1 --- test/unittest/simdtest.cpp | 12 +++++++----- test/unittest/uritest.cpp | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/test/unittest/simdtest.cpp b/test/unittest/simdtest.cpp index 6fa0d49..570b083 100644 --- a/test/unittest/simdtest.cpp +++ b/test/unittest/simdtest.cpp @@ -49,10 +49,12 @@ using namespace rapidjson_simd; #define SIMD_SUFFIX(name) name #endif +#define SIMD_SIZE_ALIGN(n) ((size_t(n) + 15) & ~size_t(15)) + template void TestSkipWhitespace() { for (size_t step = 1; step < 32; step++) { - char buffer[1025]; + char buffer[SIMD_SIZE_ALIGN(1025)]; for (size_t i = 0; i < 1024; i++) buffer[i] = " \t\r\n"[i % 4]; for (size_t i = 0; i < 1024; i += step) @@ -79,7 +81,7 @@ TEST(SIMD, SIMD_SUFFIX(SkipWhitespace)) { TEST(SIMD, SIMD_SUFFIX(SkipWhitespace_EncodedMemoryStream)) { for (size_t step = 1; step < 32; step++) { - char buffer[1024]; + char buffer[SIMD_SIZE_ALIGN(1024)]; for (size_t i = 0; i < 1024; i++) buffer[i] = " \t\r\n"[i % 4]; for (size_t i = 0; i < 1024; i += step) @@ -107,8 +109,8 @@ struct ScanCopyUnescapedStringHandler : BaseReaderHandler, ScanCopyUnesca template void TestScanCopyUnescapedString() { - char buffer[1024u + 5 + 32]; - char backup[1024u + 5 + 32]; + char buffer[SIMD_SIZE_ALIGN(1024u + 5 + 32)]; + char backup[SIMD_SIZE_ALIGN(1024u + 5 + 32)]; // Test "ABCDABCD...\\" for (size_t offset = 0; offset < 32; offset++) { @@ -165,7 +167,7 @@ TEST(SIMD, SIMD_SUFFIX(ScanCopyUnescapedString)) { } TEST(SIMD, SIMD_SUFFIX(ScanWriteUnescapedString)) { - char buffer[2048 + 1 + 32]; + char buffer[SIMD_SIZE_ALIGN(2048 + 1 + 32)]; for (size_t offset = 0; offset < 32; offset++) { for (size_t step = 0; step < 1024; step++) { char* s = buffer + offset; diff --git a/test/unittest/uritest.cpp b/test/unittest/uritest.cpp index d6c92a2..f49f6c2 100644 --- a/test/unittest/uritest.cpp +++ b/test/unittest/uritest.cpp @@ -69,7 +69,7 @@ TEST(Uri, Parse) { #if RAPIDJSON_HAS_STDSTRING typedef std::basic_string String; String str = "http://auth/path/xxx?query#frag"; - const UriType uri = UriType(str); + const UriType uri = UriType(str, &allocator); EXPECT_TRUE(UriType::GetScheme(uri) == "http:"); EXPECT_TRUE(UriType::GetAuth(uri) == "//auth"); EXPECT_TRUE(UriType::GetPath(uri) == "/path/xxx"); From 1ce516e50bec548eb3273e5b8563d97a18ba233c Mon Sep 17 00:00:00 2001 From: Steve Hanson Date: Fri, 2 Dec 2022 13:42:13 +0000 Subject: [PATCH 28/35] Suppress uritest --- test/unittest/uritest.cpp | 85 +++++++++++++++++++++------------------ test/valgrind.supp | 9 +++++ 2 files changed, 55 insertions(+), 39 deletions(-) diff --git a/test/unittest/uritest.cpp b/test/unittest/uritest.cpp index f49f6c2..789c9dd 100644 --- a/test/unittest/uritest.cpp +++ b/test/unittest/uritest.cpp @@ -48,7 +48,6 @@ TEST(Uri, DefaultConstructor) { EXPECT_TRUE(u.GetStringLength() == 0); } - TEST(Uri, Parse) { typedef GenericUri > UriType; MemoryPoolAllocator allocator; @@ -66,21 +65,8 @@ TEST(Uri, Parse) { u.Get(w, allocator); EXPECT_TRUE(*w.GetString() == *v.GetString()); -#if RAPIDJSON_HAS_STDSTRING - typedef std::basic_string String; - String str = "http://auth/path/xxx?query#frag"; - const UriType uri = UriType(str, &allocator); - EXPECT_TRUE(UriType::GetScheme(uri) == "http:"); - EXPECT_TRUE(UriType::GetAuth(uri) == "//auth"); - EXPECT_TRUE(UriType::GetPath(uri) == "/path/xxx"); - EXPECT_TRUE(UriType::GetBase(uri) == "http://auth/path/xxx?query"); - EXPECT_TRUE(UriType::GetQuery(uri) == "?query"); - EXPECT_TRUE(UriType::GetFrag(uri) == "#frag"); - EXPECT_TRUE(UriType::Get(uri) == str); -#endif - v.SetString("urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f", allocator); - u = UriType(v); + u = UriType(v, &allocator); EXPECT_TRUE(StrCmp(u.GetSchemeString(), "urn:") == 0); EXPECT_TRUE(u.GetAuthStringLength() == 0); EXPECT_TRUE(StrCmp(u.GetPathString(), "uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f") == 0); @@ -91,7 +77,7 @@ TEST(Uri, Parse) { EXPECT_TRUE(*w.GetString() == *v.GetString()); v.SetString("", allocator); - u = UriType(v); + u = UriType(v, &allocator); EXPECT_TRUE(u.GetSchemeStringLength() == 0); EXPECT_TRUE(u.GetAuthStringLength() == 0); EXPECT_TRUE(u.GetPathStringLength() == 0); @@ -100,7 +86,7 @@ TEST(Uri, Parse) { EXPECT_TRUE(u.GetFragStringLength() == 0); v.SetString("http://auth/", allocator); - u = UriType(v); + u = UriType(v, &allocator); EXPECT_TRUE(StrCmp(u.GetSchemeString(), "http:") == 0); EXPECT_TRUE(StrCmp(u.GetAuthString(), "//auth") == 0); EXPECT_TRUE(StrCmp(u.GetPathString(), "/") == 0); @@ -162,12 +148,11 @@ TEST(Uri, Parse) { EXPECT_TRUE(u.GetFragStringLength() == len); // Incomplete auth treated as path - str = "http:/"; - const UriType u2 = UriType(str); - EXPECT_TRUE(StrCmp(u2.GetSchemeString(), "http:") == 0); - EXPECT_TRUE(u2.GetAuthStringLength() == 0); - EXPECT_TRUE(StrCmp(u2.GetPathString(), "/") == 0); - EXPECT_TRUE(StrCmp(u2.GetBaseString(), "http:/") == 0); + u = UriType("http:/"); + EXPECT_TRUE(StrCmp(u.GetSchemeString(), "http:") == 0); + EXPECT_TRUE(u.GetAuthStringLength() == 0); + EXPECT_TRUE(StrCmp(u.GetPathString(), "/") == 0); + EXPECT_TRUE(StrCmp(u.GetBaseString(), "http:/") == 0); } TEST(Uri, Parse_UTF16) { @@ -188,21 +173,8 @@ TEST(Uri, Parse_UTF16) { u.Get(w, allocator); EXPECT_TRUE(*w.GetString() == *v.GetString()); -#if RAPIDJSON_HAS_STDSTRING - typedef std::basic_string String; - String str = L"http://auth/path/xxx?query#frag"; - const UriType uri = UriType(str, &allocator); - EXPECT_TRUE(UriType::GetScheme(uri) == L"http:"); - EXPECT_TRUE(UriType::GetAuth(uri) == L"//auth"); - EXPECT_TRUE(UriType::GetPath(uri) == L"/path/xxx"); - EXPECT_TRUE(UriType::GetBase(uri) == L"http://auth/path/xxx?query"); - EXPECT_TRUE(UriType::GetQuery(uri) == L"?query"); - EXPECT_TRUE(UriType::GetFrag(uri) == L"#frag"); - EXPECT_TRUE(UriType::Get(uri) == str); -#endif - v.SetString(L"urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f", allocator); - u = UriType(v); + u = UriType(v, &allocator); EXPECT_TRUE(StrCmp(u.GetSchemeString(), L"urn:") == 0); EXPECT_TRUE(u.GetAuthStringLength() == 0); EXPECT_TRUE(StrCmp(u.GetPathString(), L"uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f") == 0); @@ -213,7 +185,7 @@ TEST(Uri, Parse_UTF16) { EXPECT_TRUE(*w.GetString() == *v.GetString()); v.SetString(L"", allocator); - u = UriType(v); + u = UriType(v, &allocator); EXPECT_TRUE(u.GetSchemeStringLength() == 0); EXPECT_TRUE(u.GetAuthStringLength() == 0); EXPECT_TRUE(u.GetPathStringLength() == 0); @@ -222,7 +194,7 @@ TEST(Uri, Parse_UTF16) { EXPECT_TRUE(u.GetFragStringLength() == 0); v.SetString(L"http://auth/", allocator); - u = UriType(v); + u = UriType(v, &allocator); EXPECT_TRUE(StrCmp(u.GetSchemeString(), L"http:") == 0); EXPECT_TRUE(StrCmp(u.GetAuthString(), L"//auth") == 0); EXPECT_TRUE(StrCmp(u.GetPathString(), L"/") == 0); @@ -291,6 +263,41 @@ TEST(Uri, Parse_UTF16) { EXPECT_TRUE(StrCmp(u.GetBaseString(), L"http:/") == 0); } +#if RAPIDJSON_HAS_STDSTRING +TEST(Uri, Parse_Std) { + typedef GenericUri > UriType; + MemoryPoolAllocator allocator; + typedef std::basic_string String; + + String str = "http://auth/path/xxx?query#frag"; + const UriType uri = UriType(str, &allocator); + EXPECT_TRUE(UriType::GetScheme(uri) == "http:"); + EXPECT_TRUE(UriType::GetAuth(uri) == "//auth"); + EXPECT_TRUE(UriType::GetPath(uri) == "/path/xxx"); + EXPECT_TRUE(UriType::GetBase(uri) == "http://auth/path/xxx?query"); + EXPECT_TRUE(UriType::GetQuery(uri) == "?query"); + EXPECT_TRUE(UriType::GetFrag(uri) == "#frag"); + EXPECT_TRUE(UriType::Get(uri) == str); +} + +TEST(Uri, Parse_UTF16_Std) { + typedef GenericValue > Value16; + typedef GenericUri > UriType; + MemoryPoolAllocator allocator; + typedef std::basic_string String; + + String str = L"http://auth/path/xxx?query#frag"; + const UriType uri = UriType(str, &allocator); + EXPECT_TRUE(UriType::GetScheme(uri) == L"http:"); + EXPECT_TRUE(UriType::GetAuth(uri) == L"//auth"); + EXPECT_TRUE(UriType::GetPath(uri) == L"/path/xxx"); + EXPECT_TRUE(UriType::GetBase(uri) == L"http://auth/path/xxx?query"); + EXPECT_TRUE(UriType::GetQuery(uri) == L"?query"); + EXPECT_TRUE(UriType::GetFrag(uri) == L"#frag"); + EXPECT_TRUE(UriType::Get(uri) == str); +} +#endif + TEST(Uri, CopyConstructor) { typedef GenericUri UriType; CrtAllocator allocator; diff --git a/test/valgrind.supp b/test/valgrind.supp index 1fed18b..c9d3d22 100644 --- a/test/valgrind.supp +++ b/test/valgrind.supp @@ -15,3 +15,12 @@ Memcheck:Value8 fun:__wcslen_sse2 } + +{ + Suppress wmemcmp valgrind report 4 + Memcheck:Addr32 + fun:__wmemcmp_avx2_movbe + ... + fun:*Uri*Parse_UTF16_Std* +} + From 012be8528783cdbf4b7a9e64f78bd8f056b97e24 Mon Sep 17 00:00:00 2001 From: supperpiccle Date: Wed, 11 Jan 2023 10:00:24 -0600 Subject: [PATCH 29/35] Use passed in allocator. --- include/rapidjson/schema.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/rapidjson/schema.h b/include/rapidjson/schema.h index 6e51177..439133f 100644 --- a/include/rapidjson/schema.h +++ b/include/rapidjson/schema.h @@ -620,9 +620,9 @@ public: if (v->IsArray() && v->Size() > 0) { enum_ = static_cast(allocator_->Malloc(sizeof(uint64_t) * v->Size())); for (ConstValueIterator itr = v->Begin(); itr != v->End(); ++itr) { - typedef Hasher > EnumHasherType; + typedef Hasher > EnumHasherType; char buffer[256u + 24]; - MemoryPoolAllocator<> hasherAllocator(buffer, sizeof(buffer)); + MemoryPoolAllocator hasherAllocator(buffer, sizeof(buffer)); EnumHasherType h(&hasherAllocator, 256); itr->Accept(h); enum_[enumCount_++] = h.GetHashCode(); From 083f359f5c36198accc2b9360ce1e32a333231d9 Mon Sep 17 00:00:00 2001 From: Sergey Fedorov Date: Mon, 6 Mar 2023 07:50:16 +0800 Subject: [PATCH 30/35] CMakeLists: fix optflags for ppc --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f8927b4..dd1f173 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -68,7 +68,7 @@ endif(CCACHE_FOUND) if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") if(RAPIDJSON_ENABLE_INSTRUMENTATION_OPT AND NOT CMAKE_CROSSCOMPILING) - if(CMAKE_SYSTEM_PROCESSOR STREQUAL "powerpc" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "ppc64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "ppc64le") + if(CMAKE_SYSTEM_PROCESSOR STREQUAL "powerpc" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "ppc" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "ppc64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "ppc64le") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mcpu=native") else() #FIXME: x86 is -march=native, but doesn't mean every arch is this option. To keep original project's compatibility, I leave this except POWER. @@ -102,7 +102,7 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") endif() elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang") if(NOT CMAKE_CROSSCOMPILING) - if(CMAKE_SYSTEM_PROCESSOR STREQUAL "powerpc" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "ppc64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "ppc64le") + if(CMAKE_SYSTEM_PROCESSOR STREQUAL "powerpc" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "ppc" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "ppc64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "ppc64le") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mcpu=native") else() #FIXME: x86 is -march=native, but doesn't mean every arch is this option. To keep original project's compatibility, I leave this except POWER. From 949c771b03de448bdedea80c44a4a5f65284bfeb Mon Sep 17 00:00:00 2001 From: Flaviu_ <37621568+flaviu22@users.noreply.github.com> Date: Fri, 7 Apr 2023 12:11:19 +0200 Subject: [PATCH 31/35] Resolve conflict with Windows header about max macro --- include/rapidjson/allocators.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/rapidjson/allocators.h b/include/rapidjson/allocators.h index ddcf478..35650af 100644 --- a/include/rapidjson/allocators.h +++ b/include/rapidjson/allocators.h @@ -434,7 +434,7 @@ namespace internal { template inline T* Realloc(A& a, T* old_p, size_t old_n, size_t new_n) { - RAPIDJSON_NOEXCEPT_ASSERT(old_n <= std::numeric_limits::max() / sizeof(T) && new_n <= std::numeric_limits::max() / sizeof(T)); + RAPIDJSON_NOEXCEPT_ASSERT(old_n <= (std::numeric_limits::max)() / sizeof(T) && new_n <= (std::numeric_limits::max)() / sizeof(T)); return static_cast(a.Realloc(old_p, old_n * sizeof(T), new_n * sizeof(T))); } From 0e88d5e40448616ede258be29e6e337eb99aa104 Mon Sep 17 00:00:00 2001 From: Albert Hung Date: Wed, 3 May 2023 09:54:12 +0800 Subject: [PATCH 32/35] Eliminate missing prototypes warning --- test/unittest/cursorstreamwrappertest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unittest/cursorstreamwrappertest.cpp b/test/unittest/cursorstreamwrappertest.cpp index dad3359..49e3d5e 100644 --- a/test/unittest/cursorstreamwrappertest.cpp +++ b/test/unittest/cursorstreamwrappertest.cpp @@ -20,7 +20,7 @@ using namespace rapidjson; // static const char json[] = "{\"string\"\n\n:\"my string\",\"array\"\n:[\"1\", \"2\", \"3\"]}"; -bool testJson(const char *json, size_t &line, size_t &col) { +static bool testJson(const char *json, size_t &line, size_t &col) { StringStream ss(json); CursorStreamWrapper csw(ss); Document document; From 2a1f586ba692ecbbf6d63c8ffbd4d837b1d4a9a4 Mon Sep 17 00:00:00 2001 From: Albert Hung Date: Tue, 11 Apr 2023 13:47:13 +0800 Subject: [PATCH 33/35] Check for __GNUC__ definition Wrap code checking against __GNUC__ to ensure it is defined. This can cause errors with compilers which do not define this preprocessor value. --- include/rapidjson/internal/biginteger.h | 2 +- include/rapidjson/internal/diyfp.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/rapidjson/internal/biginteger.h b/include/rapidjson/internal/biginteger.h index af48738..4930043 100644 --- a/include/rapidjson/internal/biginteger.h +++ b/include/rapidjson/internal/biginteger.h @@ -259,7 +259,7 @@ private: if (low < k) (*outHigh)++; return low; -#elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) && defined(__x86_64__) +#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) && defined(__x86_64__) __extension__ typedef unsigned __int128 uint128; uint128 p = static_cast(a) * static_cast(b); p += k; diff --git a/include/rapidjson/internal/diyfp.h b/include/rapidjson/internal/diyfp.h index f7d4653..1f60fb6 100644 --- a/include/rapidjson/internal/diyfp.h +++ b/include/rapidjson/internal/diyfp.h @@ -79,7 +79,7 @@ struct DiyFp { if (l & (uint64_t(1) << 63)) // rounding h++; return DiyFp(h, e + rhs.e + 64); -#elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) && defined(__x86_64__) +#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) && defined(__x86_64__) __extension__ typedef unsigned __int128 uint128; uint128 p = static_cast(f) * static_cast(rhs.f); uint64_t h = static_cast(p >> 64); From 973dc9c06dcd3d035ebd039cfb9ea457721ec213 Mon Sep 17 00:00:00 2001 From: Leonard Chan Date: Tue, 9 May 2023 21:31:22 +0000 Subject: [PATCH 34/35] Avoid ptrdiff between pointers to different allocations When using running both Undefined Behavior Sanitizer (UBSan) and Hardware-Assisted Address Sanitizer (HWASan) on Fuchsia, ubsan complained about a pointer overflow when computing the new token->name pointer. This happens because the initial pointer diff takes the offset between two allocations with different tags, so the arithmetic results in a very large diff that gets added to the original token->name ptr which overflows. Any arithmetic between pointers to two allocations is unspecified behavior, so hwasan+ubsan is catching a bug here. It looks like rapidjson is just attempting to update the name pointers to strings copied into the new nameBuffer_ via this arithmetic, but since these strings and the tokens are in the same buffer, the offset between them should be the same. For each token we can just get this offset and adjust the new name pointers accordingly which avoids the bad arithmetic. --- include/rapidjson/pointer.h | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/include/rapidjson/pointer.h b/include/rapidjson/pointer.h index 05b1704..6f4ef38 100644 --- a/include/rapidjson/pointer.h +++ b/include/rapidjson/pointer.h @@ -890,10 +890,16 @@ private: std::memcpy(nameBuffer_, rhs.nameBuffer_, nameBufferSize * sizeof(Ch)); } - // Adjust pointers to name buffer - std::ptrdiff_t diff = nameBuffer_ - rhs.nameBuffer_; - for (Token *t = tokens_; t != tokens_ + rhs.tokenCount_; ++t) - t->name += diff; + // The names of each token point to a string in the nameBuffer_. The + // previous memcpy copied over string pointers into the rhs.nameBuffer_, + // but they should point to the strings in the new nameBuffer_. + for (size_t i = 0; i < rhs.tokenCount_; ++i) { + // The offset between the string address and the name buffer should + // still be constant, so we can just get this offset and set each new + // token name according the new buffer start + the known offset. + std::ptrdiff_t name_offset = rhs.tokens_[i].name - rhs.nameBuffer_; + tokens_[i].name = nameBuffer_ + name_offset; + } return nameBuffer_ + nameBufferSize; } From a95e013b97ca6523f32da23f5095fcc9dd6067e5 Mon Sep 17 00:00:00 2001 From: Jihadist Date: Tue, 13 Sep 2022 17:57:46 +0300 Subject: [PATCH 35/35] Stringify NaN, Inf as null if needs --- include/rapidjson/writer.h | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/include/rapidjson/writer.h b/include/rapidjson/writer.h index 8b38921..142230d 100644 --- a/include/rapidjson/writer.h +++ b/include/rapidjson/writer.h @@ -67,6 +67,7 @@ enum WriteFlag { kWriteNoFlags = 0, //!< No flags are set. kWriteValidateEncodingFlag = 1, //!< Validate encoding of JSON strings. kWriteNanAndInfFlag = 2, //!< Allow writing of Infinity, -Infinity and NaN. + kWriteNanAndInfNullFlag = 4, //!< Allow writing of Infinity, -Infinity and NaN as null. kWriteDefaultFlags = RAPIDJSON_WRITE_DEFAULT_FLAGS //!< Default write flags. Can be customized by defining RAPIDJSON_WRITE_DEFAULT_FLAGS }; @@ -350,6 +351,11 @@ protected: if (internal::Double(d).IsNanOrInf()) { if (!(writeFlags & kWriteNanAndInfFlag)) return false; + if (writeFlags & kWriteNanAndInfNullFlag) { + PutReserve(*os_, 4); + PutUnsafe(*os_, 'n'); PutUnsafe(*os_, 'u'); PutUnsafe(*os_, 'l'); PutUnsafe(*os_, 'l'); + return true; + } if (internal::Double(d).IsNan()) { PutReserve(*os_, 3); PutUnsafe(*os_, 'N'); PutUnsafe(*os_, 'a'); PutUnsafe(*os_, 'N'); @@ -548,6 +554,11 @@ inline bool Writer::WriteDouble(double d) { // Note: This code path can only be reached if (RAPIDJSON_WRITE_DEFAULT_FLAGS & kWriteNanAndInfFlag). if (!(kWriteDefaultFlags & kWriteNanAndInfFlag)) return false; + if (kWriteDefaultFlags & kWriteNanAndInfNullFlag) { + PutReserve(*os_, 4); + PutUnsafe(*os_, 'n'); PutUnsafe(*os_, 'u'); PutUnsafe(*os_, 'l'); PutUnsafe(*os_, 'l'); + return true; + } if (internal::Double(d).IsNan()) { PutReserve(*os_, 3); PutUnsafe(*os_, 'N'); PutUnsafe(*os_, 'a'); PutUnsafe(*os_, 'N');