Merge branch 'master' into master

This commit is contained in:
Jack·Boos·Yu 2022-05-05 08:03:42 +00:00 committed by GitHub
commit 3445e155e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 2199 additions and 165 deletions

View File

@ -247,6 +247,7 @@ INSTALL(FILES "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/${PROJECT_NAM
DESTINATION ${CMAKECONFIG_INSTALL_DIR} ) DESTINATION ${CMAKECONFIG_INSTALL_DIR} )
# Install files # Install files
IF(CMAKE_INSTALL_DIR)
INSTALL(FILES INSTALL(FILES
${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake
${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake
@ -254,4 +255,5 @@ INSTALL(FILES
COMPONENT dev) COMPONENT dev)
INSTALL(TARGETS RapidJSON EXPORT RapidJSON-targets) INSTALL(TARGETS RapidJSON EXPORT RapidJSON-targets)
INSTALL(EXPORT RapidJSON-targets DESTINATION ${CMAKECONFIG_INSTALL_DIR}) INSTALL(EXPORT RapidJSON-targets DESTINATION ${CMAKE_INSTALL_DIR})
ENDIF()

Binary file not shown.

View File

@ -1,6 +1,6 @@
# DOM # DOM
文档对象模型Document Object Model, DOM是一种罝于内存中的 JSON 表示方式,以供查询及操作。我们于 [教程](doc/tutorial.zh-cn.md) 中介绍了 DOM 的基本用法,本节将讲述一些细节及高级用法。 文档对象模型Document Object Model, DOM是一种罝于内存中的 JSON 表示方式,以供查询及操作。我们于 [教程](doc/tutorial.zh-cn.md) 中介绍了 DOM 的基本用法,本节将讲述一些细节及高级用法。
[TOC] [TOC]

View File

@ -2,6 +2,8 @@
// The example validates JSON text from stdin with a JSON schema specified in the argument. // The example validates JSON text from stdin with a JSON schema specified in the argument.
#define RAPIDJSON_HAS_STDSTRING 1
#include "rapidjson/error/en.h" #include "rapidjson/error/en.h"
#include "rapidjson/filereadstream.h" #include "rapidjson/filereadstream.h"
#include "rapidjson/schema.h" #include "rapidjson/schema.h"

View File

@ -75,7 +75,7 @@ class GenericDocument;
User can define this to use CrtAllocator or MemoryPoolAllocator. User can define this to use CrtAllocator or MemoryPoolAllocator.
*/ */
#ifndef RAPIDJSON_DEFAULT_ALLOCATOR #ifndef RAPIDJSON_DEFAULT_ALLOCATOR
#define RAPIDJSON_DEFAULT_ALLOCATOR MemoryPoolAllocator<CrtAllocator> #define RAPIDJSON_DEFAULT_ALLOCATOR ::RAPIDJSON_NAMESPACE::MemoryPoolAllocator<::RAPIDJSON_NAMESPACE::CrtAllocator>
#endif #endif
/*! \def RAPIDJSON_DEFAULT_STACK_ALLOCATOR /*! \def RAPIDJSON_DEFAULT_STACK_ALLOCATOR
@ -85,7 +85,7 @@ class GenericDocument;
User can define this to use CrtAllocator or MemoryPoolAllocator. User can define this to use CrtAllocator or MemoryPoolAllocator.
*/ */
#ifndef RAPIDJSON_DEFAULT_STACK_ALLOCATOR #ifndef RAPIDJSON_DEFAULT_STACK_ALLOCATOR
#define RAPIDJSON_DEFAULT_STACK_ALLOCATOR CrtAllocator #define RAPIDJSON_DEFAULT_STACK_ALLOCATOR ::RAPIDJSON_NAMESPACE::CrtAllocator
#endif #endif
/*! \def RAPIDJSON_VALUE_DEFAULT_OBJECT_CAPACITY /*! \def RAPIDJSON_VALUE_DEFAULT_OBJECT_CAPACITY
@ -1235,8 +1235,8 @@ public:
// return NullValue; // return NullValue;
// Use static buffer and placement-new to prevent destruction // Use static buffer and placement-new to prevent destruction
static char buffer[sizeof(GenericValue)]; static GenericValue buffer;
return *new (buffer) GenericValue(); return *new (reinterpret_cast<char *>(&buffer)) GenericValue();
} }
} }
template <typename SourceAllocator> template <typename SourceAllocator>
@ -1951,7 +1951,7 @@ public:
case kArrayType: case kArrayType:
if (RAPIDJSON_UNLIKELY(!handler.StartArray())) if (RAPIDJSON_UNLIKELY(!handler.StartArray()))
return false; return false;
for (const GenericValue* v = Begin(); v != End(); ++v) for (ConstValueIterator v = Begin(); v != End(); ++v)
if (RAPIDJSON_UNLIKELY(!v->Accept(handler))) if (RAPIDJSON_UNLIKELY(!v->Accept(handler)))
return false; return false;
return handler.EndArray(data_.a.size); return handler.EndArray(data_.a.size);
@ -2486,6 +2486,7 @@ public:
typedef typename Encoding::Ch Ch; //!< Character type derived from Encoding. typedef typename Encoding::Ch Ch; //!< Character type derived from Encoding.
typedef GenericValue<Encoding, Allocator> ValueType; //!< Value type of the document. typedef GenericValue<Encoding, Allocator> ValueType; //!< Value type of the document.
typedef Allocator AllocatorType; //!< Allocator type from template parameter. typedef Allocator AllocatorType; //!< Allocator type from template parameter.
typedef StackAllocator StackAllocatorType; //!< StackAllocator type from template parameter.
//! Constructor //! Constructor
/*! Creates an empty document of specified type. /*! Creates an empty document of specified type.

View File

@ -19,7 +19,11 @@
#if defined(_MSC_VER) && !defined(__INTEL_COMPILER) && defined(_M_AMD64) #if defined(_MSC_VER) && !defined(__INTEL_COMPILER) && defined(_M_AMD64)
#include <intrin.h> // for _umul128 #include <intrin.h> // for _umul128
#if !defined(_ARM64EC_)
#pragma intrinsic(_umul128) #pragma intrinsic(_umul128)
#else
#pragma comment(lib,"softintrin")
#endif
#endif #endif
RAPIDJSON_NAMESPACE_BEGIN RAPIDJSON_NAMESPACE_BEGIN
@ -37,7 +41,8 @@ public:
digits_[0] = u; digits_[0] = u;
} }
BigInteger(const char* decimals, size_t length) : count_(1) { template<typename Ch>
BigInteger(const Ch* decimals, size_t length) : count_(1) {
RAPIDJSON_ASSERT(length > 0); RAPIDJSON_ASSERT(length > 0);
digits_[0] = 0; digits_[0] = 0;
size_t i = 0; size_t i = 0;
@ -221,7 +226,8 @@ public:
bool IsZero() const { return count_ == 1 && digits_[0] == 0; } bool IsZero() const { return count_ == 1 && digits_[0] == 0; }
private: private:
void AppendDecimal64(const char* begin, const char* end) { template<typename Ch>
void AppendDecimal64(const Ch* begin, const Ch* end) {
uint64_t u = ParseUint64(begin, end); uint64_t u = ParseUint64(begin, end);
if (IsZero()) if (IsZero())
*this = u; *this = u;
@ -236,11 +242,12 @@ private:
digits_[count_++] = digit; digits_[count_++] = digit;
} }
static uint64_t ParseUint64(const char* begin, const char* end) { template<typename Ch>
static uint64_t ParseUint64(const Ch* begin, const Ch* end) {
uint64_t r = 0; uint64_t r = 0;
for (const char* p = begin; p != end; ++p) { for (const Ch* p = begin; p != end; ++p) {
RAPIDJSON_ASSERT(*p >= '0' && *p <= '9'); RAPIDJSON_ASSERT(*p >= Ch('0') && *p <= Ch('9'));
r = r * 10u + static_cast<unsigned>(*p - '0'); r = r * 10u + static_cast<unsigned>(*p - Ch('0'));
} }
return r; return r;
} }

View File

@ -25,7 +25,11 @@
#if defined(_MSC_VER) && defined(_M_AMD64) && !defined(__INTEL_COMPILER) #if defined(_MSC_VER) && defined(_M_AMD64) && !defined(__INTEL_COMPILER)
#include <intrin.h> #include <intrin.h>
#if !defined(_ARM64EC_)
#pragma intrinsic(_umul128) #pragma intrinsic(_umul128)
#else
#pragma comment(lib,"softintrin")
#endif
#endif #endif
RAPIDJSON_NAMESPACE_BEGIN RAPIDJSON_NAMESPACE_BEGIN

View File

@ -58,7 +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) { inline void DigitGen(const DiyFp& W, const DiyFp& Mp, uint64_t delta, char* buffer, int* len, int* K) {
static const uint32_t kPow10[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 }; 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 };
const DiyFp one(uint64_t(1) << -Mp.e, Mp.e); const DiyFp one(uint64_t(1) << -Mp.e, Mp.e);
const DiyFp wp_w = Mp - W; const DiyFp wp_w = Mp - W;
uint32_t p1 = static_cast<uint32_t>(Mp.f >> -one.e); uint32_t p1 = static_cast<uint32_t>(Mp.f >> -one.e);
@ -86,7 +90,7 @@ inline void DigitGen(const DiyFp& W, const DiyFp& Mp, uint64_t delta, char* buff
uint64_t tmp = (static_cast<uint64_t>(p1) << -one.e) + p2; uint64_t tmp = (static_cast<uint64_t>(p1) << -one.e) + p2;
if (tmp <= delta) { if (tmp <= delta) {
*K += kappa; *K += kappa;
GrisuRound(buffer, *len, delta, tmp, static_cast<uint64_t>(kPow10[kappa]) << -one.e, wp_w.f); GrisuRound(buffer, *len, delta, tmp, kPow10[kappa] << -one.e, wp_w.f);
return; return;
} }
} }
@ -103,7 +107,7 @@ inline void DigitGen(const DiyFp& W, const DiyFp& Mp, uint64_t delta, char* buff
if (p2 < delta) { if (p2 < delta) {
*K += kappa; *K += kappa;
int index = -kappa; int index = -kappa;
GrisuRound(buffer, *len, delta, p2, one.f, wp_w.f * (index < 9 ? kPow10[index] : 0)); GrisuRound(buffer, *len, delta, p2, one.f, wp_w.f * (index < 20 ? kPow10[index] : 0));
return; return;
} }
} }

View File

@ -45,6 +45,20 @@ inline SizeType StrLen(const wchar_t* s) {
return SizeType(std::wcslen(s)); return SizeType(std::wcslen(s));
} }
//! Custom strcmpn() which works on different character types.
/*! \tparam Ch Character type (e.g. char, wchar_t, short)
\param s1 Null-terminated input string.
\param s2 Null-terminated input string.
\return 0 if equal
*/
template<typename Ch>
inline int StrCmp(const Ch* s1, const Ch* s2) {
RAPIDJSON_ASSERT(s1 != 0);
RAPIDJSON_ASSERT(s2 != 0);
while(*s1 && (*s1 == *s2)) { s1++; s2++; }
return static_cast<unsigned>(*s1) < static_cast<unsigned>(*s2) ? -1 : static_cast<unsigned>(*s1) > static_cast<unsigned>(*s2);
}
//! Returns number of code points in a encoded string. //! Returns number of code points in a encoded string.
template<typename Encoding> template<typename Encoding>
bool CountStringCodePoint(const typename Encoding::Ch* s, SizeType length, SizeType* outCount) { bool CountStringCodePoint(const typename Encoding::Ch* s, SizeType length, SizeType* outCount) {

View File

@ -128,17 +128,18 @@ inline bool StrtodFast(double d, int p, double* result) {
} }
// Compute an approximation and see if it is within 1/2 ULP // Compute an approximation and see if it is within 1/2 ULP
inline bool StrtodDiyFp(const char* decimals, int dLen, int dExp, double* result) { template<typename Ch>
inline bool StrtodDiyFp(const Ch* decimals, int dLen, int dExp, double* result) {
uint64_t significand = 0; uint64_t significand = 0;
int i = 0; // 2^64 - 1 = 18446744073709551615, 1844674407370955161 = 0x1999999999999999 int i = 0; // 2^64 - 1 = 18446744073709551615, 1844674407370955161 = 0x1999999999999999
for (; i < dLen; i++) { for (; i < dLen; i++) {
if (significand > RAPIDJSON_UINT64_C2(0x19999999, 0x99999999) || if (significand > RAPIDJSON_UINT64_C2(0x19999999, 0x99999999) ||
(significand == RAPIDJSON_UINT64_C2(0x19999999, 0x99999999) && decimals[i] > '5')) (significand == RAPIDJSON_UINT64_C2(0x19999999, 0x99999999) && decimals[i] > Ch('5')))
break; break;
significand = significand * 10u + static_cast<unsigned>(decimals[i] - '0'); significand = significand * 10u + static_cast<unsigned>(decimals[i] - Ch('0'));
} }
if (i < dLen && decimals[i] >= '5') // Rounding if (i < dLen && decimals[i] >= Ch('5')) // Rounding
significand++; significand++;
int remaining = dLen - i; int remaining = dLen - i;
@ -205,7 +206,8 @@ inline bool StrtodDiyFp(const char* decimals, int dLen, int dExp, double* result
return halfWay - static_cast<unsigned>(error) >= precisionBits || precisionBits >= halfWay + static_cast<unsigned>(error); return halfWay - static_cast<unsigned>(error) >= precisionBits || precisionBits >= halfWay + static_cast<unsigned>(error);
} }
inline double StrtodBigInteger(double approx, const char* decimals, int dLen, int dExp) { template<typename Ch>
inline double StrtodBigInteger(double approx, const Ch* decimals, int dLen, int dExp) {
RAPIDJSON_ASSERT(dLen >= 0); RAPIDJSON_ASSERT(dLen >= 0);
const BigInteger dInt(decimals, static_cast<unsigned>(dLen)); const BigInteger dInt(decimals, static_cast<unsigned>(dLen));
Double a(approx); Double a(approx);
@ -223,7 +225,8 @@ inline double StrtodBigInteger(double approx, const char* decimals, int dLen, in
return a.NextPositiveDouble(); return a.NextPositiveDouble();
} }
inline double StrtodFullPrecision(double d, int p, const char* decimals, size_t length, size_t decimalPosition, int exp) { template<typename Ch>
inline double StrtodFullPrecision(double d, int p, const Ch* decimals, size_t length, size_t decimalPosition, int exp) {
RAPIDJSON_ASSERT(d >= 0.0); RAPIDJSON_ASSERT(d >= 0.0);
RAPIDJSON_ASSERT(length >= 1); RAPIDJSON_ASSERT(length >= 1);

View File

@ -16,6 +16,7 @@
#define RAPIDJSON_POINTER_H_ #define RAPIDJSON_POINTER_H_
#include "document.h" #include "document.h"
#include "uri.h"
#include "internal/itoa.h" #include "internal/itoa.h"
#ifdef __clang__ #ifdef __clang__
@ -80,6 +81,8 @@ class GenericPointer {
public: public:
typedef typename ValueType::EncodingType EncodingType; //!< Encoding type from Value typedef typename ValueType::EncodingType EncodingType; //!< Encoding type from Value
typedef typename ValueType::Ch Ch; //!< Character type from Value typedef typename ValueType::Ch Ch; //!< Character type from Value
typedef GenericUri<ValueType, Allocator> UriType;
//! A token is the basic units of internal representation. //! A token is the basic units of internal representation.
/*! /*!
@ -163,7 +166,7 @@ public:
GenericPointer(const Token* tokens, size_t tokenCount) : allocator_(), ownAllocator_(), nameBuffer_(), tokens_(const_cast<Token*>(tokens)), tokenCount_(tokenCount), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) {} GenericPointer(const Token* tokens, size_t tokenCount) : allocator_(), ownAllocator_(), nameBuffer_(), tokens_(const_cast<Token*>(tokens)), tokenCount_(tokenCount), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) {}
//! Copy constructor. //! Copy constructor.
GenericPointer(const GenericPointer& rhs) : allocator_(rhs.allocator_), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) { GenericPointer(const GenericPointer& rhs) : allocator_(), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) {
*this = rhs; *this = rhs;
} }
@ -520,6 +523,70 @@ public:
//@} //@}
//!@name Compute URI
//@{
//! Compute the in-scope URI for a subtree.
// For use with JSON pointers into JSON schema documents.
/*!
\param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root.
\param rootUri Root URI
\param unresolvedTokenIndex If the pointer cannot resolve a token in the pointer, this parameter can obtain the index of unresolved token.
\param allocator Allocator for Uris
\return Uri if it can be resolved. Otherwise null.
\note
There are only 3 situations when a URI cannot be resolved:
1. A value in the path is not an array nor object.
2. An object value does not contain the token.
3. A token is out of range of an array value.
Use unresolvedTokenIndex to retrieve the token index.
*/
UriType GetUri(ValueType& root, const UriType& rootUri, size_t* unresolvedTokenIndex = 0, Allocator* allocator = 0) const {
static const Ch kIdString[] = { 'i', 'd', '\0' };
static const ValueType kIdValue(kIdString, 2);
UriType base = UriType(rootUri, allocator);
RAPIDJSON_ASSERT(IsValid());
ValueType* v = &root;
for (const Token *t = tokens_; t != tokens_ + tokenCount_; ++t) {
switch (v->GetType()) {
case kObjectType:
{
// See if we have an id, and if so resolve with the current base
typename ValueType::MemberIterator m = v->FindMember(kIdValue);
if (m != v->MemberEnd() && (m->value).IsString()) {
UriType here = UriType(m->value, allocator).Resolve(base, allocator);
base = here;
}
m = v->FindMember(GenericValue<EncodingType>(GenericStringRef<Ch>(t->name, t->length)));
if (m == v->MemberEnd())
break;
v = &m->value;
}
continue;
case kArrayType:
if (t->index == kPointerInvalidIndex || t->index >= v->Size())
break;
v = &((*v)[t->index]);
continue;
default:
break;
}
// Error: unresolved token
if (unresolvedTokenIndex)
*unresolvedTokenIndex = static_cast<size_t>(t - tokens_);
return UriType(allocator);
}
return base;
}
UriType GetUri(const ValueType& root, const UriType& rootUri, size_t* unresolvedTokenIndex = 0, Allocator* allocator = 0) const {
return GetUri(const_cast<ValueType&>(root), rootUri, unresolvedTokenIndex, allocator);
}
//!@name Query value //!@name Query value
//@{ //@{

View File

@ -1404,11 +1404,11 @@ private:
} }
#endif // RAPIDJSON_NEON #endif // RAPIDJSON_NEON
template<typename InputStream, bool backup, bool pushOnTake> template<typename InputStream, typename StackCharacter, bool backup, bool pushOnTake>
class NumberStream; class NumberStream;
template<typename InputStream> template<typename InputStream, typename StackCharacter>
class NumberStream<InputStream, false, false> { class NumberStream<InputStream, StackCharacter, false, false> {
public: public:
typedef typename InputStream::Ch Ch; typedef typename InputStream::Ch Ch;
@ -1421,7 +1421,7 @@ private:
size_t Tell() { return is.Tell(); } size_t Tell() { return is.Tell(); }
size_t Length() { return 0; } size_t Length() { return 0; }
const char* Pop() { return 0; } const StackCharacter* Pop() { return 0; }
protected: protected:
NumberStream& operator=(const NumberStream&); NumberStream& operator=(const NumberStream&);
@ -1429,45 +1429,47 @@ private:
InputStream& is; InputStream& is;
}; };
template<typename InputStream> template<typename InputStream, typename StackCharacter>
class NumberStream<InputStream, true, false> : public NumberStream<InputStream, false, false> { class NumberStream<InputStream, StackCharacter, true, false> : public NumberStream<InputStream, StackCharacter, false, false> {
typedef NumberStream<InputStream, false, false> Base; typedef NumberStream<InputStream, StackCharacter, false, false> Base;
public: public:
NumberStream(GenericReader& reader, InputStream& is) : Base(reader, is), stackStream(reader.stack_) {} NumberStream(GenericReader& reader, InputStream& s) : Base(reader, s), stackStream(reader.stack_) {}
RAPIDJSON_FORCEINLINE Ch TakePush() { RAPIDJSON_FORCEINLINE Ch TakePush() {
stackStream.Put(static_cast<char>(Base::is.Peek())); stackStream.Put(static_cast<StackCharacter>(Base::is.Peek()));
return Base::is.Take(); return Base::is.Take();
} }
RAPIDJSON_FORCEINLINE void Push(char c) { RAPIDJSON_FORCEINLINE void Push(StackCharacter c) {
stackStream.Put(c); stackStream.Put(c);
} }
size_t Length() { return stackStream.Length(); } size_t Length() { return stackStream.Length(); }
const char* Pop() { const StackCharacter* Pop() {
stackStream.Put('\0'); stackStream.Put('\0');
return stackStream.Pop(); return stackStream.Pop();
} }
private: private:
StackStream<char> stackStream; StackStream<StackCharacter> stackStream;
}; };
template<typename InputStream> template<typename InputStream, typename StackCharacter>
class NumberStream<InputStream, true, true> : public NumberStream<InputStream, true, false> { class NumberStream<InputStream, StackCharacter, true, true> : public NumberStream<InputStream, StackCharacter, true, false> {
typedef NumberStream<InputStream, true, false> Base; typedef NumberStream<InputStream, StackCharacter, true, false> Base;
public: public:
NumberStream(GenericReader& reader, InputStream& is) : Base(reader, is) {} NumberStream(GenericReader& reader, InputStream& s) : Base(reader, s) {}
RAPIDJSON_FORCEINLINE Ch Take() { return Base::TakePush(); } RAPIDJSON_FORCEINLINE Ch Take() { return Base::TakePush(); }
}; };
template<unsigned parseFlags, typename InputStream, typename Handler> template<unsigned parseFlags, typename InputStream, typename Handler>
void ParseNumber(InputStream& is, Handler& handler) { void ParseNumber(InputStream& is, Handler& handler) {
typedef typename internal::SelectIf<internal::BoolType<(parseFlags & kParseNumbersAsStringsFlag) != 0>, typename TargetEncoding::Ch, char>::Type NumberCharacter;
internal::StreamLocalCopy<InputStream> copy(is); internal::StreamLocalCopy<InputStream> copy(is);
NumberStream<InputStream, NumberStream<InputStream, NumberCharacter,
((parseFlags & kParseNumbersAsStringsFlag) != 0) ? ((parseFlags & kParseNumbersAsStringsFlag) != 0) ?
((parseFlags & kParseInsituFlag) == 0) : ((parseFlags & kParseInsituFlag) == 0) :
((parseFlags & kParseFullPrecisionFlag) != 0), ((parseFlags & kParseFullPrecisionFlag) != 0),
@ -1692,10 +1694,10 @@ private:
} }
else { else {
SizeType numCharsToCopy = static_cast<SizeType>(s.Length()); SizeType numCharsToCopy = static_cast<SizeType>(s.Length());
StringStream srcStream(s.Pop()); GenericStringStream<UTF8<NumberCharacter> > srcStream(s.Pop());
StackStream<typename TargetEncoding::Ch> dstStream(stack_); StackStream<typename TargetEncoding::Ch> dstStream(stack_);
while (numCharsToCopy--) { while (numCharsToCopy--) {
Transcoder<UTF8<>, TargetEncoding>::Transcode(srcStream, dstStream); Transcoder<UTF8<typename TargetEncoding::Ch>, TargetEncoding>::Transcode(srcStream, dstStream);
} }
dstStream.Put('\0'); dstStream.Put('\0');
const typename TargetEncoding::Ch* str = dstStream.Pop(); const typename TargetEncoding::Ch* str = dstStream.Pop();
@ -1705,7 +1707,7 @@ private:
} }
else { else {
size_t length = s.Length(); size_t length = s.Length();
const char* decimal = s.Pop(); // Pop stack no matter if it will be used or not. const NumberCharacter* decimal = s.Pop(); // Pop stack no matter if it will be used or not.
if (useDouble) { if (useDouble) {
int p = exp + expFrac; int p = exp + expFrac;

View File

@ -19,6 +19,7 @@
#include "pointer.h" #include "pointer.h"
#include "stringbuffer.h" #include "stringbuffer.h"
#include "error/en.h" #include "error/en.h"
#include "uri.h"
#include <cmath> // abs, floor #include <cmath> // abs, floor
#if !defined(RAPIDJSON_SCHEMA_USE_INTERNALREGEX) #if !defined(RAPIDJSON_SCHEMA_USE_INTERNALREGEX)
@ -432,11 +433,13 @@ public:
typedef Schema<SchemaDocumentType> SchemaType; typedef Schema<SchemaDocumentType> SchemaType;
typedef GenericValue<EncodingType, AllocatorType> SValue; typedef GenericValue<EncodingType, AllocatorType> SValue;
typedef IValidationErrorHandler<Schema> ErrorHandler; typedef IValidationErrorHandler<Schema> ErrorHandler;
typedef GenericUri<ValueType, AllocatorType> UriType;
friend class GenericSchemaDocument<ValueType, AllocatorType>; friend class GenericSchemaDocument<ValueType, AllocatorType>;
Schema(SchemaDocumentType* schemaDocument, const PointerType& p, const ValueType& value, const ValueType& document, AllocatorType* allocator) : Schema(SchemaDocumentType* schemaDocument, const PointerType& p, const ValueType& value, const ValueType& document, AllocatorType* allocator, const UriType& id = UriType()) :
allocator_(allocator), allocator_(allocator),
uri_(schemaDocument->GetURI(), *allocator), uri_(schemaDocument->GetURI(), *allocator),
id_(id),
pointer_(p, allocator), pointer_(p, allocator),
typeless_(schemaDocument->GetTypeless()), typeless_(schemaDocument->GetTypeless()),
enum_(), enum_(),
@ -474,9 +477,28 @@ public:
typedef typename ValueType::ConstValueIterator ConstValueIterator; typedef typename ValueType::ConstValueIterator ConstValueIterator;
typedef typename ValueType::ConstMemberIterator ConstMemberIterator; typedef typename ValueType::ConstMemberIterator ConstMemberIterator;
// PR #1393
// Early add this Schema and its $ref(s) in schemaDocument's map to avoid infinite
// recursion (with recursive schemas), since schemaDocument->getSchema() is always
// checked before creating a new one. Don't cache typeless_, though.
if (this != typeless_) {
typedef typename SchemaDocumentType::SchemaEntry SchemaEntry;
SchemaEntry *entry = schemaDocument->schemaMap_.template Push<SchemaEntry>();
new (entry) SchemaEntry(pointer_, this, true, allocator_);
schemaDocument->AddSchemaRefs(this);
}
if (!value.IsObject()) if (!value.IsObject())
return; return;
// If we have an id property, resolve it with the in-scope id
if (const ValueType* v = GetMember(value, GetIdString())) {
if (v->IsString()) {
UriType local(*v, allocator);
id_ = local.Resolve(id_, allocator);
}
}
if (const ValueType* v = GetMember(value, GetTypeString())) { if (const ValueType* v = GetMember(value, GetTypeString())) {
type_ = 0; type_ = 0;
if (v->IsString()) if (v->IsString())
@ -506,7 +528,7 @@ public:
AssignIfExist(oneOf_, *schemaDocument, p, value, GetOneOfString(), document); AssignIfExist(oneOf_, *schemaDocument, p, value, GetOneOfString(), document);
if (const ValueType* v = GetMember(value, GetNotString())) { if (const ValueType* v = GetMember(value, GetNotString())) {
schemaDocument->CreateSchema(&not_, p.Append(GetNotString(), allocator_), *v, document); schemaDocument->CreateSchema(&not_, p.Append(GetNotString(), allocator_), *v, document, id_);
notValidatorIndex_ = validatorCount_; notValidatorIndex_ = validatorCount_;
validatorCount_++; validatorCount_++;
} }
@ -555,7 +577,7 @@ public:
for (ConstMemberIterator itr = properties->MemberBegin(); itr != properties->MemberEnd(); ++itr) { for (ConstMemberIterator itr = properties->MemberBegin(); itr != properties->MemberEnd(); ++itr) {
SizeType index; SizeType index;
if (FindPropertyIndex(itr->name, &index)) if (FindPropertyIndex(itr->name, &index))
schemaDocument->CreateSchema(&properties_[index].schema, q.Append(itr->name, allocator_), itr->value, document); schemaDocument->CreateSchema(&properties_[index].schema, q.Append(itr->name, allocator_), itr->value, document, id_);
} }
} }
@ -567,7 +589,7 @@ public:
for (ConstMemberIterator itr = v->MemberBegin(); itr != v->MemberEnd(); ++itr) { for (ConstMemberIterator itr = v->MemberBegin(); itr != v->MemberEnd(); ++itr) {
new (&patternProperties_[patternPropertyCount_]) PatternProperty(); new (&patternProperties_[patternPropertyCount_]) PatternProperty();
patternProperties_[patternPropertyCount_].pattern = CreatePattern(itr->name); patternProperties_[patternPropertyCount_].pattern = CreatePattern(itr->name);
schemaDocument->CreateSchema(&patternProperties_[patternPropertyCount_].schema, q.Append(itr->name, allocator_), itr->value, document); schemaDocument->CreateSchema(&patternProperties_[patternPropertyCount_].schema, q.Append(itr->name, allocator_), itr->value, document, id_);
patternPropertyCount_++; patternPropertyCount_++;
} }
} }
@ -599,7 +621,7 @@ public:
} }
else if (itr->value.IsObject()) { else if (itr->value.IsObject()) {
hasSchemaDependencies_ = true; hasSchemaDependencies_ = true;
schemaDocument->CreateSchema(&properties_[sourceIndex].dependenciesSchema, q.Append(itr->name, allocator_), itr->value, document); schemaDocument->CreateSchema(&properties_[sourceIndex].dependenciesSchema, q.Append(itr->name, allocator_), itr->value, document, id_);
properties_[sourceIndex].dependenciesValidatorIndex = validatorCount_; properties_[sourceIndex].dependenciesValidatorIndex = validatorCount_;
validatorCount_++; validatorCount_++;
} }
@ -611,7 +633,7 @@ public:
if (v->IsBool()) if (v->IsBool())
additionalProperties_ = v->GetBool(); additionalProperties_ = v->GetBool();
else if (v->IsObject()) else if (v->IsObject())
schemaDocument->CreateSchema(&additionalPropertiesSchema_, p.Append(GetAdditionalPropertiesString(), allocator_), *v, document); schemaDocument->CreateSchema(&additionalPropertiesSchema_, p.Append(GetAdditionalPropertiesString(), allocator_), *v, document, id_);
} }
AssignIfExist(minProperties_, value, GetMinPropertiesString()); AssignIfExist(minProperties_, value, GetMinPropertiesString());
@ -621,12 +643,12 @@ public:
if (const ValueType* v = GetMember(value, GetItemsString())) { if (const ValueType* v = GetMember(value, GetItemsString())) {
PointerType q = p.Append(GetItemsString(), allocator_); PointerType q = p.Append(GetItemsString(), allocator_);
if (v->IsObject()) // List validation if (v->IsObject()) // List validation
schemaDocument->CreateSchema(&itemsList_, q, *v, document); schemaDocument->CreateSchema(&itemsList_, q, *v, document, id_);
else if (v->IsArray()) { // Tuple validation else if (v->IsArray()) { // Tuple validation
itemsTuple_ = static_cast<const Schema**>(allocator_->Malloc(sizeof(const Schema*) * v->Size())); itemsTuple_ = static_cast<const Schema**>(allocator_->Malloc(sizeof(const Schema*) * v->Size()));
SizeType index = 0; SizeType index = 0;
for (ConstValueIterator itr = v->Begin(); itr != v->End(); ++itr, index++) for (ConstValueIterator itr = v->Begin(); itr != v->End(); ++itr, index++)
schemaDocument->CreateSchema(&itemsTuple_[itemsTupleCount_++], q.Append(index, allocator_), *itr, document); schemaDocument->CreateSchema(&itemsTuple_[itemsTupleCount_++], q.Append(index, allocator_), *itr, document, id_);
} }
} }
@ -637,7 +659,7 @@ public:
if (v->IsBool()) if (v->IsBool())
additionalItems_ = v->GetBool(); additionalItems_ = v->GetBool();
else if (v->IsObject()) else if (v->IsObject())
schemaDocument->CreateSchema(&additionalItemsSchema_, p.Append(GetAdditionalItemsString(), allocator_), *v, document); schemaDocument->CreateSchema(&additionalItemsSchema_, p.Append(GetAdditionalItemsString(), allocator_), *v, document, id_);
} }
AssignIfExist(uniqueItems_, value, GetUniqueItemsString()); AssignIfExist(uniqueItems_, value, GetUniqueItemsString());
@ -697,6 +719,10 @@ public:
return uri_; return uri_;
} }
const UriType& GetId() const {
return id_;
}
const PointerType& GetPointer() const { const PointerType& GetPointer() const {
return pointer_; return pointer_;
} }
@ -1132,6 +1158,15 @@ public:
RAPIDJSON_STRING_(ExclusiveMaximum, 'e', 'x', 'c', 'l', 'u', 's', 'i', 'v', 'e', 'M', 'a', 'x', 'i', 'm', 'u', 'm') RAPIDJSON_STRING_(ExclusiveMaximum, 'e', 'x', 'c', 'l', 'u', 's', 'i', 'v', 'e', 'M', 'a', 'x', 'i', 'm', 'u', 'm')
RAPIDJSON_STRING_(MultipleOf, 'm', 'u', 'l', 't', 'i', 'p', 'l', 'e', 'O', 'f') RAPIDJSON_STRING_(MultipleOf, 'm', 'u', 'l', 't', 'i', 'p', 'l', 'e', 'O', 'f')
RAPIDJSON_STRING_(DefaultValue, 'd', 'e', 'f', 'a', 'u', 'l', 't') RAPIDJSON_STRING_(DefaultValue, 'd', 'e', 'f', 'a', 'u', 'l', 't')
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, '.')
#undef RAPIDJSON_STRING_ #undef RAPIDJSON_STRING_
@ -1197,7 +1232,7 @@ private:
out.schemas = static_cast<const Schema**>(allocator_->Malloc(out.count * sizeof(const Schema*))); out.schemas = static_cast<const Schema**>(allocator_->Malloc(out.count * sizeof(const Schema*)));
memset(out.schemas, 0, sizeof(Schema*)* out.count); memset(out.schemas, 0, sizeof(Schema*)* out.count);
for (SizeType i = 0; i < out.count; i++) for (SizeType i = 0; i < out.count; i++)
schemaDocument.CreateSchema(&out.schemas[i], q.Append(i, allocator_), (*v)[i], document); schemaDocument.CreateSchema(&out.schemas[i], q.Append(i, allocator_), (*v)[i], document, id_);
out.begin = validatorCount_; out.begin = validatorCount_;
validatorCount_ += out.count; validatorCount_ += out.count;
} }
@ -1474,6 +1509,7 @@ private:
AllocatorType* allocator_; AllocatorType* allocator_;
SValue uri_; SValue uri_;
UriType id_;
PointerType pointer_; PointerType pointer_;
const SchemaType* typeless_; const SchemaType* typeless_;
uint64_t* enum_; uint64_t* enum_;
@ -1559,9 +1595,12 @@ template <typename SchemaDocumentType>
class IGenericRemoteSchemaDocumentProvider { class IGenericRemoteSchemaDocumentProvider {
public: public:
typedef typename SchemaDocumentType::Ch Ch; typedef typename SchemaDocumentType::Ch Ch;
typedef typename SchemaDocumentType::ValueType ValueType;
typedef typename SchemaDocumentType::AllocatorType AllocatorType;
virtual ~IGenericRemoteSchemaDocumentProvider() {} virtual ~IGenericRemoteSchemaDocumentProvider() {}
virtual const SchemaDocumentType* GetRemoteDocument(const Ch* uri, SizeType length) = 0; virtual const SchemaDocumentType* GetRemoteDocument(const Ch* uri, SizeType length) = 0;
virtual const SchemaDocumentType* GetRemoteDocument(GenericUri<ValueType, AllocatorType> uri) { return GetRemoteDocument(uri.GetBaseString(), uri.GetBaseStringLength()); }
}; };
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
@ -1586,7 +1625,8 @@ public:
typedef typename EncodingType::Ch Ch; typedef typename EncodingType::Ch Ch;
typedef internal::Schema<GenericSchemaDocument> SchemaType; typedef internal::Schema<GenericSchemaDocument> SchemaType;
typedef GenericPointer<ValueType, Allocator> PointerType; typedef GenericPointer<ValueType, Allocator> PointerType;
typedef GenericValue<EncodingType, Allocator> URIType; typedef GenericValue<EncodingType, AllocatorType> SValue;
typedef GenericUri<ValueType, Allocator> UriType;
friend class internal::Schema<GenericSchemaDocument>; friend class internal::Schema<GenericSchemaDocument>;
template <typename, typename, typename> template <typename, typename, typename>
friend class GenericSchemaValidator; friend class GenericSchemaValidator;
@ -1600,9 +1640,11 @@ public:
\param uriLength Length of \c name, in code points. \param uriLength Length of \c name, in code points.
\param remoteProvider An optional remote schema document provider for resolving remote reference. Can be null. \param 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 allocator An optional allocator instance for allocating memory. Can be null.
\param pointer An optional JSON pointer to the start of the schema document
*/ */
explicit GenericSchemaDocument(const ValueType& document, const Ch* uri = 0, SizeType uriLength = 0, explicit GenericSchemaDocument(const ValueType& document, const Ch* uri = 0, SizeType uriLength = 0,
IRemoteSchemaDocumentProviderType* remoteProvider = 0, Allocator* allocator = 0) : IRemoteSchemaDocumentProviderType* remoteProvider = 0, Allocator* allocator = 0,
const PointerType& pointer = PointerType()) : // PR #1393
remoteProvider_(remoteProvider), remoteProvider_(remoteProvider),
allocator_(allocator), allocator_(allocator),
ownAllocator_(), ownAllocator_(),
@ -1616,30 +1658,20 @@ public:
Ch noUri[1] = {0}; Ch noUri[1] = {0};
uri_.SetString(uri ? uri : noUri, uriLength, *allocator_); uri_.SetString(uri ? uri : noUri, uriLength, *allocator_);
docId_ = UriType(uri_, allocator_);
typeless_ = static_cast<SchemaType*>(allocator_->Malloc(sizeof(SchemaType))); typeless_ = static_cast<SchemaType*>(allocator_->Malloc(sizeof(SchemaType)));
new (typeless_) SchemaType(this, PointerType(), ValueType(kObjectType).Move(), ValueType(kObjectType).Move(), allocator_); new (typeless_) SchemaType(this, PointerType(), ValueType(kObjectType).Move(), ValueType(kObjectType).Move(), allocator_, docId_);
// Generate root schema, it will call CreateSchema() to create sub-schemas, // Generate root schema, it will call CreateSchema() to create sub-schemas,
// And call AddRefSchema() if there are $ref. // And call HandleRefSchema() if there are $ref.
CreateSchemaRecursive(&root_, PointerType(), document, document); // PR #1393 use input pointer if supplied
root_ = typeless_;
// Resolve $ref if (pointer.GetTokenCount() == 0) {
while (!schemaRef_.Empty()) { CreateSchemaRecursive(&root_, pointer, document, document, docId_);
SchemaRefEntry* refEntry = schemaRef_.template Pop<SchemaRefEntry>(1);
if (const SchemaType* s = GetSchema(refEntry->target)) {
if (refEntry->schema)
*refEntry->schema = s;
// Create entry in map if not exist
if (!GetSchema(refEntry->source)) {
new (schemaMap_.template Push<SchemaEntry>()) SchemaEntry(refEntry->source, const_cast<SchemaType*>(s), false, allocator_);
} }
} else if (const ValueType* v = pointer.Get(document)) {
else if (refEntry->schema) CreateSchema(&root_, pointer, *v, document, docId_);
*refEntry->schema = typeless_;
refEntry->~SchemaRefEntry();
} }
RAPIDJSON_ASSERT(root_ != 0); RAPIDJSON_ASSERT(root_ != 0);
@ -1657,7 +1689,8 @@ public:
typeless_(rhs.typeless_), typeless_(rhs.typeless_),
schemaMap_(std::move(rhs.schemaMap_)), schemaMap_(std::move(rhs.schemaMap_)),
schemaRef_(std::move(rhs.schemaRef_)), schemaRef_(std::move(rhs.schemaRef_)),
uri_(std::move(rhs.uri_)) uri_(std::move(rhs.uri_)),
docId_(rhs.docId_)
{ {
rhs.remoteProvider_ = 0; rhs.remoteProvider_ = 0;
rhs.allocator_ = 0; rhs.allocator_ = 0;
@ -1679,7 +1712,7 @@ public:
RAPIDJSON_DELETE(ownAllocator_); RAPIDJSON_DELETE(ownAllocator_);
} }
const URIType& GetURI() const { return uri_; } const SValue& GetURI() const { return uri_; }
//! Get the root schema. //! Get the root schema.
const SchemaType& GetRoot() const { return *root_; } const SchemaType& GetRoot() const { return *root_; }
@ -1690,12 +1723,7 @@ private:
//! Prohibit assignment //! Prohibit assignment
GenericSchemaDocument& operator=(const GenericSchemaDocument&); GenericSchemaDocument& operator=(const GenericSchemaDocument&);
struct SchemaRefEntry { typedef const PointerType* SchemaRefPtr; // PR #1393
SchemaRefEntry(const PointerType& s, const PointerType& t, const SchemaType** outSchema, Allocator *allocator) : source(s, allocator), target(t, allocator), schema(outSchema) {}
PointerType source;
PointerType target;
const SchemaType** schema;
};
struct SchemaEntry { struct SchemaEntry {
SchemaEntry(const PointerType& p, SchemaType* s, bool o, Allocator* allocator) : pointer(p, allocator), schema(s), owned(o) {} SchemaEntry(const PointerType& p, SchemaType* s, bool o, Allocator* allocator) : pointer(p, allocator), schema(s), owned(o) {}
@ -1710,79 +1738,197 @@ private:
bool owned; bool owned;
}; };
void CreateSchemaRecursive(const SchemaType** schema, const PointerType& pointer, const ValueType& v, const ValueType& document) { // Changed by PR #1393
if (schema) void CreateSchemaRecursive(const SchemaType** schema, const PointerType& pointer, const ValueType& v, const ValueType& document, const UriType& id) {
*schema = typeless_;
if (v.GetType() == kObjectType) { if (v.GetType() == kObjectType) {
const SchemaType* s = GetSchema(pointer); UriType newid = UriType(CreateSchema(schema, pointer, v, document, id), allocator_);
if (!s)
CreateSchema(schema, pointer, v, document);
for (typename ValueType::ConstMemberIterator itr = v.MemberBegin(); itr != v.MemberEnd(); ++itr) for (typename ValueType::ConstMemberIterator itr = v.MemberBegin(); itr != v.MemberEnd(); ++itr)
CreateSchemaRecursive(0, pointer.Append(itr->name, allocator_), itr->value, document); CreateSchemaRecursive(0, pointer.Append(itr->name, allocator_), itr->value, document, newid);
} }
else if (v.GetType() == kArrayType) else if (v.GetType() == kArrayType)
for (SizeType i = 0; i < v.Size(); i++) for (SizeType i = 0; i < v.Size(); i++)
CreateSchemaRecursive(0, pointer.Append(i, allocator_), v[i], document); CreateSchemaRecursive(0, pointer.Append(i, allocator_), v[i], document, id);
} }
void CreateSchema(const SchemaType** schema, const PointerType& pointer, const ValueType& v, const ValueType& document) { // 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()); RAPIDJSON_ASSERT(pointer.IsValid());
if (v.IsObject()) { if (v.IsObject()) {
if (!HandleRefSchema(pointer, schema, v, document)) { if (const SchemaType* sc = GetSchema(pointer)) {
SchemaType* s = new (allocator_->Malloc(sizeof(SchemaType))) SchemaType(this, pointer, v, document, allocator_); if (schema)
new (schemaMap_.template Push<SchemaEntry>()) SchemaEntry(pointer, s, true, allocator_); *schema = sc;
AddSchemaRefs(const_cast<SchemaType*>(sc));
}
else if (!HandleRefSchema(pointer, schema, v, document, id)) {
// The new schema constructor adds itself and its $ref(s) to schemaMap_
SchemaType* s = new (allocator_->Malloc(sizeof(SchemaType))) SchemaType(this, pointer, v, document, allocator_, id);
if (schema) if (schema)
*schema = s; *schema = s;
return s->GetId();
} }
} }
else {
if (schema)
*schema = typeless_;
AddSchemaRefs(typeless_);
}
return id;
} }
bool HandleRefSchema(const PointerType& source, const SchemaType** schema, const ValueType& v, const ValueType& document) { // Changed by PR #1393
static const Ch kRefString[] = { '$', 'r', 'e', 'f', '\0' }; // TODO should this return a UriType& ?
static const ValueType kRefValue(kRefString, 4); bool HandleRefSchema(const PointerType& source, const SchemaType** schema, const ValueType& v, const ValueType& document, const UriType& id) {
typename ValueType::ConstMemberIterator itr = v.FindMember(SchemaType::GetRefString());
typename ValueType::ConstMemberIterator itr = v.FindMember(kRefValue);
if (itr == v.MemberEnd()) if (itr == v.MemberEnd())
return false; return false;
// Resolve the source pointer to the $ref'ed schema (finally)
new (schemaRef_.template Push<SchemaRefPtr>()) SchemaRefPtr(&source);
if (itr->value.IsString()) { if (itr->value.IsString()) {
SizeType len = itr->value.GetStringLength(); SizeType len = itr->value.GetStringLength();
if (len > 0) { if (len > 0) {
const Ch* s = itr->value.GetString(); // First resolve $ref against the in-scope id
SizeType i = 0; UriType scopeId = UriType(id, allocator_);
while (i < len && s[i] != '#') // Find the first # UriType ref = UriType(itr->value, allocator_).Resolve(scopeId, allocator_);
i++; // 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.
if (i > 0) { // Remote reference, resolve immediately PointerType basePointer = PointerType();
const ValueType *base = FindId(document, ref, basePointer, docId_, false);
if (!base) {
// Remote reference - call the remote document provider
if (remoteProvider_) { if (remoteProvider_) {
if (const GenericSchemaDocument* remoteDocument = remoteProvider_->GetRemoteDocument(s, i)) { if (const GenericSchemaDocument* remoteDocument = remoteProvider_->GetRemoteDocument(ref)) {
PointerType pointer(&s[i], len - i, allocator_); 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()) {
// Get the subschema
if (const SchemaType *sc = remoteDocument->GetSchema(pointer)) { if (const SchemaType *sc = remoteDocument->GetSchema(pointer)) {
if (schema) if (schema)
*schema = sc; *schema = sc;
new (schemaMap_.template Push<SchemaEntry>()) SchemaEntry(source, const_cast<SchemaType*>(sc), false, allocator_); AddSchemaRefs(const_cast<SchemaType *>(sc));
return true;
}
}
} else {
// Plain name fragment, not allowed
}
}
}
}
else { // Local reference
const Ch* s = ref.GetFragString();
len = ref.GetFragStringLength();
if (len <= 1 || s[1] == '/') {
// JSON pointer fragment, relative to the resolved URI
const PointerType relPointer(s, len, allocator_);
if (relPointer.IsValid()) {
// 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<EncodingType> sb;
//pointer.StringifyUriFragment(sb);
if (pointer.IsValid() && !IsCyclicRef(pointer)) {
// Call CreateSchema recursively, but first compute the in-scope id for the $ref target as we have jumped there
// TODO: cache pointer <-> id mapping
size_t unresolvedTokenIndex;
scopeId = pointer.GetUri(document, docId_, &unresolvedTokenIndex, allocator_);
CreateSchema(schema, pointer, *pv, document, scopeId);
return true;
}
}
}
} else {
// Plain name fragment, relative to the resolved URI
// 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<EncodingType> sb;
//pointer.StringifyUriFragment(sb);
// Call CreateSchema recursively, but first compute the in-scope id for the $ref target as we have jumped there
// TODO: cache pointer <-> id mapping
size_t unresolvedTokenIndex;
scopeId = pointer.GetUri(document, docId_, &unresolvedTokenIndex, allocator_);
CreateSchema(schema, pointer, *pv, document, scopeId);
return true; return true;
} }
} }
} }
} }
} }
else if (s[i] == '#') { // Local reference, defer resolution }
PointerType pointer(&s[i], len - i, allocator_);
if (pointer.IsValid()) {
if (const ValueType* nv = pointer.Get(document))
if (HandleRefSchema(source, schema, *nv, document))
return true;
new (schemaRef_.template Push<SchemaRefEntry>()) SchemaRefEntry(source, pointer, schema, allocator_); // Invalid/Unknown $ref
if (schema)
*schema = typeless_;
AddSchemaRefs(typeless_);
return true; return true;
} }
//! Find the first subschema with a resolved 'id' that matches the specified URI.
// If full specified use all URI else ignore fragment.
// If found, return a pointer to the subschema and its JSON pointer.
// TODO cache pointer <-> id mapping
ValueType* FindId(const ValueType& doc, const UriType& finduri, PointerType& resptr, const UriType& baseuri, bool full, const PointerType& here = PointerType()) const {
SizeType i = 0;
ValueType* resval = 0;
UriType tempuri = UriType(finduri, allocator_);
UriType localuri = UriType(baseuri, allocator_);
if (doc.GetType() == kObjectType) {
// Establish the base URI of this object
typename ValueType::ConstMemberIterator m = doc.FindMember(SchemaType::GetIdString());
if (m != doc.MemberEnd() && m->value.GetType() == kStringType) {
localuri = UriType(m->value, allocator_).Resolve(baseuri, allocator_);
}
// See if it matches
if (localuri.Match(finduri, full)) {
resval = const_cast<ValueType *>(&doc);
resptr = here;
return resval;
}
// No match, continue looking
for (m = doc.MemberBegin(); m != doc.MemberEnd(); ++m) {
if (m->value.GetType() == kObjectType || m->value.GetType() == kArrayType) {
resval = FindId(m->value, finduri, resptr, localuri, full, here.Append(m->name.GetString(), m->name.GetStringLength(), allocator_));
}
if (resval) break;
}
} else if (doc.GetType() == kArrayType) {
// Continue looking
for (typename ValueType::ConstValueIterator v = doc.Begin(); v != doc.End(); ++v) {
if (v->GetType() == kObjectType || v->GetType() == kArrayType) {
resval = FindId(*v, finduri, resptr, localuri, full, here.Append(i, allocator_));
}
if (resval) break;
i++;
} }
} }
return resval;
} }
// Added by PR #1393
void AddSchemaRefs(SchemaType* schema) {
while (!schemaRef_.Empty()) {
SchemaRefPtr *ref = schemaRef_.template Pop<SchemaRefPtr>(1);
SchemaEntry *entry = schemaMap_.template Push<SchemaEntry>();
new (entry) SchemaEntry(**ref, schema, false, allocator_);
}
}
// Added by PR #1393
bool IsCyclicRef(const PointerType& pointer) const {
for (const SchemaRefPtr* ref = schemaRef_.template Bottom<SchemaRefPtr>(); ref != schemaRef_.template End<SchemaRefPtr>(); ++ref)
if (pointer == **ref)
return true;
return false; return false;
} }
@ -1811,8 +1957,9 @@ private:
const SchemaType* root_; //!< Root schema. const SchemaType* root_; //!< Root schema.
SchemaType* typeless_; SchemaType* typeless_;
internal::Stack<Allocator> schemaMap_; // Stores created Pointer -> Schemas internal::Stack<Allocator> schemaMap_; // Stores created Pointer -> Schemas
internal::Stack<Allocator> schemaRef_; // Stores Pointer from $ref and schema which holds the $ref internal::Stack<Allocator> schemaRef_; // Stores Pointer(s) from $ref(s) until resolved
URIType uri_; SValue uri_; // Schema document URI
UriType docId_;
}; };
//! GenericSchemaDocument using Value type. //! GenericSchemaDocument using Value type.
@ -2202,7 +2349,8 @@ RAPIDJSON_MULTILINEMACRO_END
if (!valid_) return false; \ if (!valid_) return false; \
if ((!BeginValue() && !GetContinueOnErrors()) || (!CurrentSchema().method arg1 && !GetContinueOnErrors())) {\ if ((!BeginValue() && !GetContinueOnErrors()) || (!CurrentSchema().method arg1 && !GetContinueOnErrors())) {\
RAPIDJSON_SCHEMA_HANDLE_BEGIN_VERBOSE_();\ RAPIDJSON_SCHEMA_HANDLE_BEGIN_VERBOSE_();\
return valid_ = false;\ valid_ = false;\
return valid_;\
} }
#define RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(method, arg2)\ #define RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(method, arg2)\
@ -2241,34 +2389,46 @@ RAPIDJSON_MULTILINEMACRO_END
bool StartObject() { bool StartObject() {
RAPIDJSON_SCHEMA_HANDLE_BEGIN_(StartObject, (CurrentContext())); RAPIDJSON_SCHEMA_HANDLE_BEGIN_(StartObject, (CurrentContext()));
RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(StartObject, ()); RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(StartObject, ());
return valid_ = !outputHandler_ || outputHandler_->StartObject(); valid_ = !outputHandler_ || outputHandler_->StartObject();
return valid_;
} }
bool Key(const Ch* str, SizeType len, bool copy) { bool Key(const Ch* str, SizeType len, bool copy) {
if (!valid_) return false; if (!valid_) return false;
AppendToken(str, len); AppendToken(str, len);
if (!CurrentSchema().Key(CurrentContext(), str, len, copy) && !GetContinueOnErrors()) return valid_ = false; if (!CurrentSchema().Key(CurrentContext(), str, len, copy) && !GetContinueOnErrors()) {
valid_ = false;
return valid_;
}
RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(Key, (str, len, copy)); RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(Key, (str, len, copy));
return valid_ = !outputHandler_ || outputHandler_->Key(str, len, copy); valid_ = !outputHandler_ || outputHandler_->Key(str, len, copy);
return valid_;
} }
bool EndObject(SizeType memberCount) { bool EndObject(SizeType memberCount) {
if (!valid_) return false; if (!valid_) return false;
RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(EndObject, (memberCount)); RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(EndObject, (memberCount));
if (!CurrentSchema().EndObject(CurrentContext(), memberCount) && !GetContinueOnErrors()) return valid_ = false; if (!CurrentSchema().EndObject(CurrentContext(), memberCount) && !GetContinueOnErrors()) {
valid_ = false;
return valid_;
}
RAPIDJSON_SCHEMA_HANDLE_END_(EndObject, (memberCount)); RAPIDJSON_SCHEMA_HANDLE_END_(EndObject, (memberCount));
} }
bool StartArray() { bool StartArray() {
RAPIDJSON_SCHEMA_HANDLE_BEGIN_(StartArray, (CurrentContext())); RAPIDJSON_SCHEMA_HANDLE_BEGIN_(StartArray, (CurrentContext()));
RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(StartArray, ()); RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(StartArray, ());
return valid_ = !outputHandler_ || outputHandler_->StartArray(); valid_ = !outputHandler_ || outputHandler_->StartArray();
return valid_;
} }
bool EndArray(SizeType elementCount) { bool EndArray(SizeType elementCount) {
if (!valid_) return false; if (!valid_) return false;
RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(EndArray, (elementCount)); RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(EndArray, (elementCount));
if (!CurrentSchema().EndArray(CurrentContext(), elementCount) && !GetContinueOnErrors()) return valid_ = false; if (!CurrentSchema().EndArray(CurrentContext(), elementCount) && !GetContinueOnErrors()) {
valid_ = false;
return valid_;
}
RAPIDJSON_SCHEMA_HANDLE_END_(EndArray, (elementCount)); RAPIDJSON_SCHEMA_HANDLE_END_(EndArray, (elementCount));
} }

481
include/rapidjson/uri.h Normal file
View File

@ -0,0 +1,481 @@
// Tencent is pleased to support the open source community by making RapidJSON available.
//
// (C) Copyright IBM Corporation 2021
//
// Licensed under the MIT License (the "License"); you may not use this file except
// in compliance with the License. You may obtain a copy of the License at
//
// http://opensource.org/licenses/MIT
//
// Unless required by applicable law or agreed to in writing, software distributed
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.
#ifndef RAPIDJSON_URI_H_
#define RAPIDJSON_URI_H_
#include "internal/strfunc.h"
#if defined(__clang__)
RAPIDJSON_DIAG_PUSH
RAPIDJSON_DIAG_OFF(c++98-compat)
#elif defined(_MSC_VER)
RAPIDJSON_DIAG_OFF(4512) // assignment operator could not be generated
#endif
RAPIDJSON_NAMESPACE_BEGIN
///////////////////////////////////////////////////////////////////////////////
// GenericUri
template <typename ValueType, typename Allocator=CrtAllocator>
class GenericUri {
public:
typedef typename ValueType::Ch Ch;
#if RAPIDJSON_HAS_STDSTRING
typedef std::basic_string<Ch> String;
#endif
//! Constructors
GenericUri(Allocator* allocator = 0) : uri_(), base_(), scheme_(), auth_(), path_(), query_(), frag_(), allocator_(allocator), ownAllocator_() {
}
GenericUri(const Ch* uri, SizeType len, Allocator* allocator = 0) : uri_(), base_(), scheme_(), auth_(), path_(), query_(), frag_(), allocator_(allocator), ownAllocator_() {
Parse(uri, len);
}
GenericUri(const Ch* uri, Allocator* allocator = 0) : uri_(), base_(), scheme_(), auth_(), path_(), query_(), frag_(), allocator_(allocator), ownAllocator_() {
Parse(uri, internal::StrLen<Ch>(uri));
}
// Use with specializations of GenericValue
template<typename T> GenericUri(const T& uri, Allocator* allocator = 0) : uri_(), base_(), scheme_(), auth_(), path_(), query_(), frag_(), allocator_(allocator), ownAllocator_() {
const Ch* u = uri.template Get<const Ch*>(); // TypeHelper from document.h
Parse(u, internal::StrLen<Ch>(u));
}
#if RAPIDJSON_HAS_STDSTRING
GenericUri(const String& uri, Allocator* allocator = 0) : uri_(), base_(), scheme_(), auth_(), path_(), query_(), frag_(), allocator_(allocator), ownAllocator_() {
Parse(uri.c_str(), internal::StrLen<Ch>(uri.c_str()));
}
#endif
//! Copy constructor
GenericUri(const GenericUri& rhs) : uri_(), base_(), scheme_(), auth_(), path_(), query_(), frag_(), allocator_(), ownAllocator_() {
*this = rhs;
}
//! Copy constructor
GenericUri(const GenericUri& rhs, Allocator* allocator) : uri_(), base_(), scheme_(), auth_(), path_(), query_(), frag_(), allocator_(allocator), ownAllocator_() {
*this = rhs;
}
//! Destructor.
~GenericUri() {
Free();
RAPIDJSON_DELETE(ownAllocator_);
}
//! Assignment operator
GenericUri& operator=(const GenericUri& rhs) {
if (this != &rhs) {
// Do not delete ownAllocator
Free();
Allocate(rhs.GetStringLength());
auth_ = CopyPart(scheme_, rhs.scheme_, rhs.GetSchemeStringLength());
path_ = CopyPart(auth_, rhs.auth_, rhs.GetAuthStringLength());
query_ = CopyPart(path_, rhs.path_, rhs.GetPathStringLength());
frag_ = CopyPart(query_, rhs.query_, rhs.GetQueryStringLength());
base_ = CopyPart(frag_, rhs.frag_, rhs.GetFragStringLength());
uri_ = CopyPart(base_, rhs.base_, rhs.GetBaseStringLength());
CopyPart(uri_, rhs.uri_, rhs.GetStringLength());
}
return *this;
}
//! Getters
// Use with specializations of GenericValue
template<typename T> void Get(T& uri, Allocator& allocator) {
uri.template Set<const Ch*>(this->GetString(), allocator); // TypeHelper from document.h
}
const Ch* GetString() const { return uri_; }
SizeType GetStringLength() const { return uri_ == 0 ? 0 : internal::StrLen<Ch>(uri_); }
const Ch* GetBaseString() const { return base_; }
SizeType GetBaseStringLength() const { return base_ == 0 ? 0 : internal::StrLen<Ch>(base_); }
const Ch* GetSchemeString() const { return scheme_; }
SizeType GetSchemeStringLength() const { return scheme_ == 0 ? 0 : internal::StrLen<Ch>(scheme_); }
const Ch* GetAuthString() const { return auth_; }
SizeType GetAuthStringLength() const { return auth_ == 0 ? 0 : internal::StrLen<Ch>(auth_); }
const Ch* GetPathString() const { return path_; }
SizeType GetPathStringLength() const { return path_ == 0 ? 0 : internal::StrLen<Ch>(path_); }
const Ch* GetQueryString() const { return query_; }
SizeType GetQueryStringLength() const { return query_ == 0 ? 0 : internal::StrLen<Ch>(query_); }
const Ch* GetFragString() const { return frag_; }
SizeType GetFragStringLength() const { return frag_ == 0 ? 0 : internal::StrLen<Ch>(frag_); }
#if RAPIDJSON_HAS_STDSTRING
static String Get(const GenericUri& uri) { return String(uri.GetString(), uri.GetStringLength()); }
static String GetBase(const GenericUri& uri) { return String(uri.GetBaseString(), uri.GetBaseStringLength()); }
static String GetScheme(const GenericUri& uri) { return String(uri.GetSchemeString(), uri.GetSchemeStringLength()); }
static String GetAuth(const GenericUri& uri) { return String(uri.GetAuthString(), uri.GetAuthStringLength()); }
static String GetPath(const GenericUri& uri) { return String(uri.GetPathString(), uri.GetPathStringLength()); }
static String GetQuery(const GenericUri& uri) { return String(uri.GetQueryString(), uri.GetQueryStringLength()); }
static String GetFrag(const GenericUri& uri) { return String(uri.GetFragString(), uri.GetFragStringLength()); }
#endif
//! Equality operators
bool operator==(const GenericUri& rhs) const {
return Match(rhs, true);
}
bool operator!=(const GenericUri& rhs) const {
return !Match(rhs, true);
}
bool Match(const GenericUri& uri, bool full = true) const {
Ch* s1;
Ch* s2;
if (full) {
s1 = uri_;
s2 = uri.uri_;
} else {
s1 = base_;
s2 = uri.base_;
}
if (s1 == s2) return true;
if (s1 == 0 || s2 == 0) return false;
return internal::StrCmp<Ch>(s1, s2) == 0;
}
//! Resolve this URI against another (base) URI in accordance with URI resolution rules.
// See https://tools.ietf.org/html/rfc3986
// Use for resolving an id or $ref with an in-scope id.
// Returns a new GenericUri for the resolved URI.
GenericUri Resolve(const GenericUri& baseuri, Allocator* allocator = 0) {
GenericUri resuri;
resuri.allocator_ = allocator;
// Ensure enough space for combining paths
resuri.Allocate(GetStringLength() + baseuri.GetStringLength() + 1); // + 1 for joining slash
if (!(GetSchemeStringLength() == 0)) {
// Use all of this URI
resuri.auth_ = CopyPart(resuri.scheme_, scheme_, GetSchemeStringLength());
resuri.path_ = CopyPart(resuri.auth_, auth_, GetAuthStringLength());
resuri.query_ = CopyPart(resuri.path_, path_, GetPathStringLength());
resuri.frag_ = CopyPart(resuri.query_, query_, GetQueryStringLength());
resuri.RemoveDotSegments();
} else {
// Use the base scheme
resuri.auth_ = CopyPart(resuri.scheme_, baseuri.scheme_, baseuri.GetSchemeStringLength());
if (!(GetAuthStringLength() == 0)) {
// Use this auth, path, query
resuri.path_ = CopyPart(resuri.auth_, auth_, GetAuthStringLength());
resuri.query_ = CopyPart(resuri.path_, path_, GetPathStringLength());
resuri.frag_ = CopyPart(resuri.query_, query_, GetQueryStringLength());
resuri.RemoveDotSegments();
} else {
// Use the base auth
resuri.path_ = CopyPart(resuri.auth_, baseuri.auth_, baseuri.GetAuthStringLength());
if (GetPathStringLength() == 0) {
// Use the base path
resuri.query_ = CopyPart(resuri.path_, baseuri.path_, baseuri.GetPathStringLength());
if (GetQueryStringLength() == 0) {
// Use the base query
resuri.frag_ = CopyPart(resuri.query_, baseuri.query_, baseuri.GetQueryStringLength());
} else {
// Use this query
resuri.frag_ = CopyPart(resuri.query_, query_, GetQueryStringLength());
}
} else {
if (path_[0] == '/') {
// Absolute path - use all of this path
resuri.query_ = CopyPart(resuri.path_, path_, GetPathStringLength());
resuri.RemoveDotSegments();
} else {
// Relative path - append this path to base path after base path's last slash
size_t pos = 0;
if (!(baseuri.GetAuthStringLength() == 0) && baseuri.GetPathStringLength() == 0) {
resuri.path_[pos] = '/';
pos++;
}
size_t lastslashpos = baseuri.GetPathStringLength();
while (lastslashpos > 0) {
if (baseuri.path_[lastslashpos - 1] == '/') break;
lastslashpos--;
}
std::memcpy(&resuri.path_[pos], baseuri.path_, lastslashpos * sizeof(Ch));
pos += lastslashpos;
resuri.query_ = CopyPart(&resuri.path_[pos], path_, GetPathStringLength());
resuri.RemoveDotSegments();
}
// Use this query
resuri.frag_ = CopyPart(resuri.query_, query_, GetQueryStringLength());
}
}
}
// Always use this frag
resuri.base_ = CopyPart(resuri.frag_, frag_, GetFragStringLength());
// Re-constitute base_ and uri_
resuri.SetBase();
resuri.uri_ = resuri.base_ + resuri.GetBaseStringLength() + 1;
resuri.SetUri();
return resuri;
}
//! Get the allocator of this GenericUri.
Allocator& GetAllocator() { return *allocator_; }
private:
// Allocate memory for a URI
// Returns total amount allocated
std::size_t Allocate(std::size_t len) {
// Create own allocator if user did not supply.
if (!allocator_)
ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator)();
// Allocate one block containing each part of the URI (5) plus base plus full URI, all null terminated.
// Order: scheme, auth, path, query, frag, base, uri
// Note need to set, increment, assign in 3 stages to avoid compiler warning bug.
size_t total = (3 * len + 7) * sizeof(Ch);
scheme_ = static_cast<Ch*>(allocator_->Malloc(total));
*scheme_ = '\0';
auth_ = scheme_;
auth_++;
*auth_ = '\0';
path_ = auth_;
path_++;
*path_ = '\0';
query_ = path_;
query_++;
*query_ = '\0';
frag_ = query_;
frag_++;
*frag_ = '\0';
base_ = frag_;
base_++;
*base_ = '\0';
uri_ = base_;
uri_++;
*uri_ = '\0';
return total;
}
// Free memory for a URI
void Free() {
if (scheme_) {
Allocator::Free(scheme_);
scheme_ = 0;
}
}
// Parse a URI into constituent scheme, authority, path, query, & fragment parts
// Supports URIs that match regex ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))? as per
// https://tools.ietf.org/html/rfc3986
void Parse(const Ch* uri, std::size_t len) {
std::size_t start = 0, pos1 = 0, pos2 = 0;
Allocate(len);
// Look for scheme ([^:/?#]+):)?
if (start < len) {
while (pos1 < len) {
if (uri[pos1] == ':') break;
pos1++;
}
if (pos1 != len) {
while (pos2 < len) {
if (uri[pos2] == '/') break;
if (uri[pos2] == '?') break;
if (uri[pos2] == '#') break;
pos2++;
}
if (pos1 < pos2) {
pos1++;
std::memcpy(scheme_, &uri[start], pos1 * sizeof(Ch));
scheme_[pos1] = '\0';
start = pos1;
}
}
}
// Look for auth (//([^/?#]*))?
// Note need to set, increment, assign in 3 stages to avoid compiler warning bug.
auth_ = scheme_ + GetSchemeStringLength();
auth_++;
*auth_ = '\0';
if (start < len - 1 && uri[start] == '/' && uri[start + 1] == '/') {
pos2 = start + 2;
while (pos2 < len) {
if (uri[pos2] == '/') break;
if (uri[pos2] == '?') break;
if (uri[pos2] == '#') break;
pos2++;
}
std::memcpy(auth_, &uri[start], (pos2 - start) * sizeof(Ch));
auth_[pos2 - start] = '\0';
start = pos2;
}
// Look for path ([^?#]*)
// Note need to set, increment, assign in 3 stages to avoid compiler warning bug.
path_ = auth_ + GetAuthStringLength();
path_++;
*path_ = '\0';
if (start < len) {
pos2 = start;
while (pos2 < len) {
if (uri[pos2] == '?') break;
if (uri[pos2] == '#') break;
pos2++;
}
if (start != pos2) {
std::memcpy(path_, &uri[start], (pos2 - start) * sizeof(Ch));
path_[pos2 - start] = '\0';
if (path_[0] == '/')
RemoveDotSegments(); // absolute path - normalize
start = pos2;
}
}
// Look for query (\?([^#]*))?
// Note need to set, increment, assign in 3 stages to avoid compiler warning bug.
query_ = path_ + GetPathStringLength();
query_++;
*query_ = '\0';
if (start < len && uri[start] == '?') {
pos2 = start + 1;
while (pos2 < len) {
if (uri[pos2] == '#') break;
pos2++;
}
if (start != pos2) {
std::memcpy(query_, &uri[start], (pos2 - start) * sizeof(Ch));
query_[pos2 - start] = '\0';
start = pos2;
}
}
// Look for fragment (#(.*))?
// Note need to set, increment, assign in 3 stages to avoid compiler warning bug.
frag_ = query_ + GetQueryStringLength();
frag_++;
*frag_ = '\0';
if (start < len && uri[start] == '#') {
std::memcpy(frag_, &uri[start], (len - start) * sizeof(Ch));
frag_[len - start] = '\0';
}
// Re-constitute base_ and uri_
base_ = frag_ + GetFragStringLength() + 1;
SetBase();
uri_ = base_ + GetBaseStringLength() + 1;
SetUri();
}
// Reconstitute base
void SetBase() {
Ch* next = base_;
std::memcpy(next, scheme_, GetSchemeStringLength() * sizeof(Ch));
next+= GetSchemeStringLength();
std::memcpy(next, auth_, GetAuthStringLength() * sizeof(Ch));
next+= GetAuthStringLength();
std::memcpy(next, path_, GetPathStringLength() * sizeof(Ch));
next+= GetPathStringLength();
std::memcpy(next, query_, GetQueryStringLength() * sizeof(Ch));
next+= GetQueryStringLength();
*next = '\0';
}
// Reconstitute uri
void SetUri() {
Ch* next = uri_;
std::memcpy(next, base_, GetBaseStringLength() * sizeof(Ch));
next+= GetBaseStringLength();
std::memcpy(next, frag_, GetFragStringLength() * sizeof(Ch));
next+= GetFragStringLength();
*next = '\0';
}
// Copy a part from one GenericUri to another
// Return the pointer to the next part to be copied to
Ch* CopyPart(Ch* to, Ch* from, std::size_t len) {
RAPIDJSON_ASSERT(to != 0);
RAPIDJSON_ASSERT(from != 0);
std::memcpy(to, from, len * sizeof(Ch));
to[len] = '\0';
Ch* next = to + len + 1;
return next;
}
// Remove . and .. segments from the path_ member.
// https://tools.ietf.org/html/rfc3986
// This is done in place as we are only removing segments.
void RemoveDotSegments() {
std::size_t pathlen = GetPathStringLength();
std::size_t pathpos = 0; // Position in path_
std::size_t newpos = 0; // Position in new path_
// Loop through each segment in original path_
while (pathpos < pathlen) {
// Get next segment, bounded by '/' or end
size_t slashpos = 0;
while ((pathpos + slashpos) < pathlen) {
if (path_[pathpos + slashpos] == '/') break;
slashpos++;
}
// Check for .. and . segments
if (slashpos == 2 && path_[pathpos] == '.' && path_[pathpos + 1] == '.') {
// Backup a .. segment in the new path_
// We expect to find a previously added slash at the end or nothing
RAPIDJSON_ASSERT(newpos == 0 || path_[newpos - 1] == '/');
size_t lastslashpos = newpos;
// Make sure we don't go beyond the start segment
if (lastslashpos > 1) {
// Find the next to last slash and back up to it
lastslashpos--;
while (lastslashpos > 0) {
if (path_[lastslashpos - 1] == '/') break;
lastslashpos--;
}
// Set the new path_ position
newpos = lastslashpos;
}
} else if (slashpos == 1 && path_[pathpos] == '.') {
// Discard . segment, leaves new path_ unchanged
} else {
// Move any other kind of segment to the new path_
RAPIDJSON_ASSERT(newpos <= pathpos);
std::memmove(&path_[newpos], &path_[pathpos], slashpos * sizeof(Ch));
newpos += slashpos;
// Add slash if not at end
if ((pathpos + slashpos) < pathlen) {
path_[newpos] = '/';
newpos++;
}
}
// Move to next segment
pathpos += slashpos + 1;
}
path_[newpos] = '\0';
}
Ch* uri_; // Everything
Ch* base_; // Everything except fragment
Ch* scheme_; // Includes the :
Ch* auth_; // Includes the //
Ch* path_; // Absolute if starts with /
Ch* query_; // Includes the ?
Ch* frag_; // Includes the #
Allocator* allocator_; //!< The current allocator. It is either user-supplied or equal to ownAllocator_.
Allocator* ownAllocator_; //!< Allocator owned by this Uri.
};
//! GenericUri for Value (UTF-8, default allocator).
typedef GenericUri<Value> Uri;
RAPIDJSON_NAMESPACE_END
#if defined(__clang__)
RAPIDJSON_DIAG_POP
#endif
#endif // RAPIDJSON_URI_H_

View File

@ -27,6 +27,7 @@ set(UNITTEST_SOURCES
stringbuffertest.cpp stringbuffertest.cpp
strtodtest.cpp strtodtest.cpp
unittest.cpp unittest.cpp
uritest.cpp
valuetest.cpp valuetest.cpp
writertest.cpp) writertest.cpp)

View File

@ -38,6 +38,7 @@ TEST(dtoa, normal) {
TEST_DTOA(0.123456789012, "0.123456789012"); TEST_DTOA(0.123456789012, "0.123456789012");
TEST_DTOA(1234567.8, "1234567.8"); TEST_DTOA(1234567.8, "1234567.8");
TEST_DTOA(-79.39773355813419, "-79.39773355813419"); TEST_DTOA(-79.39773355813419, "-79.39773355813419");
TEST_DTOA(-36.973846435546875, "-36.973846435546875");
TEST_DTOA(0.000001, "0.000001"); TEST_DTOA(0.000001, "0.000001");
TEST_DTOA(0.0000001, "1e-7"); TEST_DTOA(0.0000001, "1e-7");
TEST_DTOA(1e30, "1e30"); TEST_DTOA(1e30, "1e30");

View File

@ -475,7 +475,9 @@ TEST(Pointer, CopyConstructor) {
EXPECT_EQ(1u, q.GetTokens()[1].length); EXPECT_EQ(1u, q.GetTokens()[1].length);
EXPECT_STREQ("0", q.GetTokens()[1].name); EXPECT_STREQ("0", q.GetTokens()[1].name);
EXPECT_EQ(0u, q.GetTokens()[1].index); EXPECT_EQ(0u, q.GetTokens()[1].index);
EXPECT_EQ(&p.GetAllocator(), &q.GetAllocator());
// Copied pointer needs to have its own allocator
EXPECT_NE(&p.GetAllocator(), &q.GetAllocator());
} }
// Static tokens // Static tokens
@ -648,6 +650,52 @@ TEST(Pointer, Create) {
} }
} }
static const char kJsonIds[] = "{\n"
" \"id\": \"/root/\","
" \"foo\":[\"bar\", \"baz\", {\"id\": \"inarray\", \"child\": 1}],\n"
" \"int\" : 2,\n"
" \"str\" : \"val\",\n"
" \"obj\": {\"id\": \"inobj\", \"child\": 3},\n"
" \"jbo\": {\"id\": true, \"child\": 4}\n"
"}";
TEST(Pointer, GetUri) {
CrtAllocator allocator;
Document d;
d.Parse(kJsonIds);
Pointer::UriType doc("http://doc");
Pointer::UriType root("http://doc/root/");
Pointer::UriType empty = Pointer::UriType();
EXPECT_TRUE(Pointer("").GetUri(d, doc) == doc);
EXPECT_TRUE(Pointer("/foo").GetUri(d, doc) == root);
EXPECT_TRUE(Pointer("/foo/0").GetUri(d, doc) == root);
EXPECT_TRUE(Pointer("/foo/2").GetUri(d, doc) == root);
EXPECT_TRUE(Pointer("/foo/2/child").GetUri(d, doc) == Pointer::UriType("http://doc/root/inarray"));
EXPECT_TRUE(Pointer("/int").GetUri(d, doc) == root);
EXPECT_TRUE(Pointer("/str").GetUri(d, doc) == root);
EXPECT_TRUE(Pointer("/obj").GetUri(d, doc) == root);
EXPECT_TRUE(Pointer("/obj/child").GetUri(d, doc) == Pointer::UriType("http://doc/root/inobj"));
EXPECT_TRUE(Pointer("/jbo").GetUri(d, doc) == root);
EXPECT_TRUE(Pointer("/jbo/child").GetUri(d, doc) == root); // id not string
size_t unresolvedTokenIndex;
EXPECT_TRUE(Pointer("/abc").GetUri(d, doc, &unresolvedTokenIndex, &allocator) == empty); // Out of boundary
EXPECT_EQ(0u, unresolvedTokenIndex);
EXPECT_TRUE(Pointer("/foo/3").GetUri(d, doc, &unresolvedTokenIndex, &allocator) == empty); // Out of boundary
EXPECT_EQ(1u, unresolvedTokenIndex);
EXPECT_TRUE(Pointer("/foo/a").GetUri(d, doc, &unresolvedTokenIndex, &allocator) == empty); // "/foo" is an array, cannot query by "a"
EXPECT_EQ(1u, unresolvedTokenIndex);
EXPECT_TRUE(Pointer("/foo/0/0").GetUri(d, doc, &unresolvedTokenIndex, &allocator) == empty); // "/foo/0" is an string, cannot further query
EXPECT_EQ(2u, unresolvedTokenIndex);
EXPECT_TRUE(Pointer("/foo/0/a").GetUri(d, doc, &unresolvedTokenIndex, &allocator) == empty); // "/foo/0" is an string, cannot further query
EXPECT_EQ(2u, unresolvedTokenIndex);
Pointer::Token tokens[] = { { "foo ...", 3, kPointerInvalidIndex } };
EXPECT_TRUE(Pointer(tokens, 1).GetUri(d, doc) == root);
}
TEST(Pointer, Get) { TEST(Pointer, Get) {
Document d; Document d;
d.Parse(kJson); d.Parse(kJson);
@ -664,7 +712,8 @@ TEST(Pointer, Get) {
EXPECT_EQ(&d["k\"l"], Pointer("/k\"l").Get(d)); EXPECT_EQ(&d["k\"l"], Pointer("/k\"l").Get(d));
EXPECT_EQ(&d[" "], Pointer("/ ").Get(d)); EXPECT_EQ(&d[" "], Pointer("/ ").Get(d));
EXPECT_EQ(&d["m~n"], Pointer("/m~0n").Get(d)); EXPECT_EQ(&d["m~n"], Pointer("/m~0n").Get(d));
EXPECT_TRUE(Pointer("/abc").Get(d) == 0);
EXPECT_TRUE(Pointer("/abc").Get(d) == 0); // Out of boundary
size_t unresolvedTokenIndex; size_t unresolvedTokenIndex;
EXPECT_TRUE(Pointer("/foo/2").Get(d, &unresolvedTokenIndex) == 0); // Out of boundary EXPECT_TRUE(Pointer("/foo/2").Get(d, &unresolvedTokenIndex) == 0); // Out of boundary
EXPECT_EQ(1u, unresolvedTokenIndex); EXPECT_EQ(1u, unresolvedTokenIndex);
@ -1668,3 +1717,14 @@ TEST(Pointer, Issue483) {
value.SetString(mystr.c_str(), static_cast<SizeType>(mystr.length()), document.GetAllocator()); value.SetString(mystr.c_str(), static_cast<SizeType>(mystr.length()), document.GetAllocator());
myjson::Pointer(path.c_str()).Set(document, value, document.GetAllocator()); myjson::Pointer(path.c_str()).Set(document, value, document.GetAllocator());
} }
TEST(Pointer, Issue1899) {
typedef GenericPointer<Value, MemoryPoolAllocator<> > PointerType;
PointerType p;
PointerType q = p.Append("foo");
EXPECT_TRUE(PointerType("/foo") == q);
q = q.Append(1234);
EXPECT_TRUE(PointerType("/foo/1234") == q);
q = q.Append("");
EXPECT_TRUE(PointerType("/foo/1234/") == q);
}

View File

@ -1392,6 +1392,36 @@ private:
std::istream& is_; std::istream& is_;
}; };
class WIStreamWrapper {
public:
typedef wchar_t Ch;
WIStreamWrapper(std::wistream& is) : is_(is) {}
Ch Peek() const {
unsigned c = is_.peek();
return c == std::char_traits<wchar_t>::eof() ? Ch('\0') : static_cast<Ch>(c);
}
Ch Take() {
unsigned c = is_.get();
return c == std::char_traits<wchar_t>::eof() ? Ch('\0') : static_cast<Ch>(c);
}
size_t Tell() const { return static_cast<size_t>(is_.tellg()); }
Ch* PutBegin() { assert(false); return 0; }
void Put(Ch) { assert(false); }
void Flush() { assert(false); }
size_t PutEnd(Ch*) { assert(false); return 0; }
private:
WIStreamWrapper(const WIStreamWrapper&);
WIStreamWrapper& operator=(const WIStreamWrapper&);
std::wistream& is_;
};
TEST(Reader, Parse_IStreamWrapper_StringStream) { TEST(Reader, Parse_IStreamWrapper_StringStream) {
const char* json = "[1,2,3,4]"; const char* json = "[1,2,3,4]";
@ -1991,6 +2021,118 @@ TEST(Reader, NumbersAsStrings) {
} }
} }
struct NumbersAsStringsHandlerWChar_t {
bool Null() { return true; }
bool Bool(bool) { return true; }
bool Int(int) { return true; }
bool Uint(unsigned) { return true; }
bool Int64(int64_t) { return true; }
bool Uint64(uint64_t) { return true; }
bool Double(double) { return true; }
// 'str' is not null-terminated
bool RawNumber(const wchar_t* str, SizeType length, bool) {
EXPECT_TRUE(str != 0);
EXPECT_TRUE(expected_len_ == length);
EXPECT_TRUE(wcsncmp(str, expected_, length) == 0);
return true;
}
bool String(const wchar_t*, SizeType, bool) { return true; }
bool StartObject() { return true; }
bool Key(const wchar_t*, SizeType, bool) { return true; }
bool EndObject(SizeType) { return true; }
bool StartArray() { return true; }
bool EndArray(SizeType) { return true; }
NumbersAsStringsHandlerWChar_t(const wchar_t* expected)
: expected_(expected)
, expected_len_(wcslen(expected)) {}
const wchar_t* expected_;
size_t expected_len_;
};
TEST(Reader, NumbersAsStringsWChar_t) {
{
const wchar_t* json = L"{ \"pi\": 3.1416 } ";
GenericStringStream<UTF16<> > s(json);
NumbersAsStringsHandlerWChar_t h(L"3.1416");
GenericReader<UTF16<>, UTF16<> > reader;
EXPECT_TRUE(reader.Parse<kParseNumbersAsStringsFlag>(s, h));
}
{
wchar_t* json = StrDup(L"{ \"pi\": 3.1416 } ");
GenericInsituStringStream<UTF16<> > s(json);
NumbersAsStringsHandlerWChar_t h(L"3.1416");
GenericReader<UTF16<>, UTF16<> > reader;
EXPECT_TRUE(reader.Parse<kParseInsituFlag | kParseNumbersAsStringsFlag>(s, h));
free(json);
}
{
const wchar_t* json = L"{ \"gigabyte\": 1.0e9 } ";
GenericStringStream<UTF16<> > s(json);
NumbersAsStringsHandlerWChar_t h(L"1.0e9");
GenericReader<UTF16<>, UTF16<> > reader;
EXPECT_TRUE(reader.Parse<kParseNumbersAsStringsFlag>(s, h));
}
{
wchar_t* json = StrDup(L"{ \"gigabyte\": 1.0e9 } ");
GenericInsituStringStream<UTF16<> > s(json);
NumbersAsStringsHandlerWChar_t h(L"1.0e9");
GenericReader<UTF16<>, UTF16<> > reader;
EXPECT_TRUE(reader.Parse<kParseInsituFlag | kParseNumbersAsStringsFlag>(s, h));
free(json);
}
{
const wchar_t* json = L"{ \"pi\": 314.159e-2 } ";
GenericStringStream<UTF16<> > s(json);
NumbersAsStringsHandlerWChar_t h(L"314.159e-2");
GenericReader<UTF16<>, UTF16<> > reader;
EXPECT_TRUE(reader.Parse<kParseNumbersAsStringsFlag>(s, h));
}
{
wchar_t* json = StrDup(L"{ \"gigabyte\": 314.159e-2 } ");
GenericInsituStringStream<UTF16<> > s(json);
NumbersAsStringsHandlerWChar_t h(L"314.159e-2");
GenericReader<UTF16<>, UTF16<> > reader;
EXPECT_TRUE(reader.Parse<kParseInsituFlag | kParseNumbersAsStringsFlag>(s, h));
free(json);
}
{
const wchar_t* json = L"{ \"negative\": -1.54321 } ";
GenericStringStream<UTF16<> > s(json);
NumbersAsStringsHandlerWChar_t h(L"-1.54321");
GenericReader<UTF16<>, UTF16<> > reader;
EXPECT_TRUE(reader.Parse<kParseNumbersAsStringsFlag>(s, h));
}
{
wchar_t* json = StrDup(L"{ \"negative\": -1.54321 } ");
GenericInsituStringStream<UTF16<> > s(json);
NumbersAsStringsHandlerWChar_t h(L"-1.54321");
GenericReader<UTF16<>, UTF16<> > reader;
EXPECT_TRUE(reader.Parse<kParseInsituFlag | kParseNumbersAsStringsFlag>(s, h));
free(json);
}
{
const wchar_t* json = L"{ \"pi\": 314.159e-2 } ";
std::wstringstream ss(json);
WIStreamWrapper s(ss);
NumbersAsStringsHandlerWChar_t h(L"314.159e-2");
GenericReader<UTF16<>, UTF16<> > reader;
EXPECT_TRUE(reader.Parse<kParseNumbersAsStringsFlag>(s, h));
}
{
wchar_t n1e319[321]; // '1' followed by 319 '0'
n1e319[0] = L'1';
for(int i = 1; i < 320; i++)
n1e319[i] = L'0';
n1e319[320] = L'\0';
GenericStringStream<UTF16<> > s(n1e319);
NumbersAsStringsHandlerWChar_t h(n1e319);
GenericReader<UTF16<>, UTF16<> > reader;
EXPECT_TRUE(reader.Parse<kParseNumbersAsStringsFlag>(s, h));
}
}
template <unsigned extraFlags> template <unsigned extraFlags>
void TestTrailingCommas() { void TestTrailingCommas() {
{ {

View File

@ -13,6 +13,7 @@
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
#define RAPIDJSON_SCHEMA_VERBOSE 0 #define RAPIDJSON_SCHEMA_VERBOSE 0
#define RAPIDJSON_HAS_STDSTRING 1
#include "unittest.h" #include "unittest.h"
#include "rapidjson/schema.h" #include "rapidjson/schema.h"
@ -1811,6 +1812,189 @@ TEST(SchemaValidator, EscapedPointer) {
"}}"); "}}");
} }
TEST(SchemaValidator, SchemaPointer) {
Document sd;
sd.Parse(
"{"
" \"swagger\": \"2.0\","
" \"paths\": {"
" \"/some/path\": {"
" \"post\": {"
" \"parameters\": ["
" {"
" \"in\": \"body\","
" \"name\": \"body\","
" \"schema\": {"
" \"properties\": {"
" \"a\": {"
" \"$ref\": \"#/definitions/Prop_a\""
" },"
" \"b\": {"
" \"type\": \"integer\""
" }"
" },"
" \"type\": \"object\""
" }"
" }"
" ],"
" \"responses\": {"
" \"200\": {"
" \"schema\": {"
" \"$ref\": \"#/definitions/Resp_200\""
" }"
" }"
" }"
" }"
" }"
" },"
" \"definitions\": {"
" \"Prop_a\": {"
" \"properties\": {"
" \"c\": {"
" \"enum\": ["
" \"C1\","
" \"C2\","
" \"C3\""
" ],"
" \"type\": \"string\""
" },"
" \"d\": {"
" \"$ref\": \"#/definitions/Prop_d\""
" },"
" \"s\": {"
" \"type\": \"string\""
" }"
" },"
" \"required\": [\"c\"],"
" \"type\": \"object\""
" },"
" \"Prop_d\": {"
" \"properties\": {"
" \"a\": {"
" \"$ref\": \"#/definitions/Prop_a\""
" },"
" \"c\": {"
" \"$ref\": \"#/definitions/Prop_a/properties/c\""
" }"
" },"
" \"type\": \"object\""
" },"
" \"Resp_200\": {"
" \"properties\": {"
" \"e\": {"
" \"type\": \"string\""
" },"
" \"f\": {"
" \"type\": \"boolean\""
" },"
" \"cyclic_source\": {"
" \"$ref\": \"#/definitions/Resp_200/properties/cyclic_target\""
" },"
" \"cyclic_target\": {"
" \"$ref\": \"#/definitions/Resp_200/properties/cyclic_source\""
" }"
" },"
" \"type\": \"object\""
" }"
" }"
"}");
SchemaDocument s1(sd, NULL, 0, NULL, NULL, Pointer("#/paths/~1some~1path/post/parameters/0/schema"));
VALIDATE(s1,
"{"
" \"a\": {"
" \"c\": \"C1\","
" \"d\": {"
" \"a\": {"
" \"c\": \"C2\""
" },"
" \"c\": \"C3\""
" }"
" },"
" \"b\": 123"
"}",
true);
INVALIDATE(s1,
"{"
" \"a\": {"
" \"c\": \"C1\","
" \"d\": {"
" \"a\": {"
" \"c\": \"C2\""
" },"
" \"c\": \"C3\""
" }"
" },"
" \"b\": \"should be an int\""
"}",
"#/paths/~1some~1path/post/parameters/0/schema/properties/b", "type", "#/b",
"{ \"type\": {"
" \"errorCode\": 20,"
" \"instanceRef\":\"#/b\","
" \"schemaRef\":\"#/paths/~1some~1path/post/parameters/0/schema/properties/b\","
" \"expected\": [\"integer\"], \"actual\":\"string\""
"}}");
INVALIDATE(s1,
"{"
" \"a\": {"
" \"c\": \"C1\","
" \"d\": {"
" \"a\": {"
" \"c\": \"should be within enum\""
" },"
" \"c\": \"C3\""
" }"
" },"
" \"b\": 123"
"}",
"#/definitions/Prop_a/properties/c", "enum", "#/a/d/a/c",
"{ \"enum\": {"
" \"errorCode\": 19,"
" \"instanceRef\":\"#/a/d/a/c\","
" \"schemaRef\":\"#/definitions/Prop_a/properties/c\""
"}}");
INVALIDATE(s1,
"{"
" \"a\": {"
" \"c\": \"C1\","
" \"d\": {"
" \"a\": {"
" \"s\": \"required 'c' is missing\""
" }"
" }"
" },"
" \"b\": 123"
"}",
"#/definitions/Prop_a", "required", "#/a/d/a",
"{ \"required\": {"
" \"errorCode\": 15,"
" \"missing\":[\"c\"],"
" \"instanceRef\":\"#/a/d/a\","
" \"schemaRef\":\"#/definitions/Prop_a\""
"}}");
SchemaDocument s2(sd, NULL, 0, NULL, NULL, Pointer("#/paths/~1some~1path/post/responses/200/schema"));
VALIDATE(s2,
"{ \"e\": \"some string\", \"f\": false }",
true);
INVALIDATE(s2,
"{ \"e\": true, \"f\": false }",
"#/definitions/Resp_200/properties/e", "type", "#/e",
"{ \"type\": {"
" \"errorCode\": 20,"
" \"instanceRef\":\"#/e\","
" \"schemaRef\":\"#/definitions/Resp_200/properties/e\","
" \"expected\": [\"string\"], \"actual\":\"boolean\""
"}}");
INVALIDATE(s2,
"{ \"e\": \"some string\", \"f\": 123 }",
"#/definitions/Resp_200/properties/f", "type", "#/f",
"{ \"type\": {"
" \"errorCode\": 20,"
" \"instanceRef\":\"#/f\","
" \"schemaRef\":\"#/definitions/Resp_200/properties/f\","
" \"expected\": [\"boolean\"], \"actual\":\"integer\""
"}}");
}
template <typename Allocator> template <typename Allocator>
static char* ReadFile(const char* filename, Allocator& allocator) { static char* ReadFile(const char* filename, Allocator& allocator) {
const char *paths[] = { const char *paths[] = {
@ -1952,7 +2136,7 @@ public:
virtual const SchemaDocumentType* GetRemoteDocument(const char* uri, SizeType length) { virtual const SchemaDocumentType* GetRemoteDocument(const char* uri, SizeType length) {
for (size_t i = 0; i < kCount; i++) for (size_t i = 0; i < kCount; i++)
if (typename SchemaDocumentType::URIType(uri, length) == sd_[i]->GetURI()) if (typename SchemaDocumentType::SValue(uri, length) == sd_[i]->GetURI())
return sd_[i]; return sd_[i];
return 0; return 0;
} }
@ -2032,7 +2216,7 @@ TEST(SchemaValidator, TestSuite) {
ADD_FAILURE(); ADD_FAILURE();
} }
else { else {
//printf("json test suite file %s parsed ok\n", filename); //printf("\njson test suite file %s parsed ok\n", filename);
GenericDocument<UTF8<>, MemoryPoolAllocator<>, MemoryPoolAllocator<> > d(&documentAllocator, 1024, &documentStackAllocator); GenericDocument<UTF8<>, MemoryPoolAllocator<>, MemoryPoolAllocator<> > d(&documentAllocator, 1024, &documentStackAllocator);
d.Parse(json); d.Parse(json);
if (d.HasParseError()) { if (d.HasParseError()) {
@ -2042,12 +2226,14 @@ TEST(SchemaValidator, TestSuite) {
else { else {
for (Value::ConstValueIterator schemaItr = d.Begin(); schemaItr != d.End(); ++schemaItr) { for (Value::ConstValueIterator schemaItr = d.Begin(); schemaItr != d.End(); ++schemaItr) {
{ {
const char* description1 = (*schemaItr)["description"].GetString();
//printf("\ncompiling schema for json test %s \n", description1);
SchemaDocumentType schema((*schemaItr)["schema"], filenames[i], static_cast<SizeType>(strlen(filenames[i])), &provider, &schemaAllocator); SchemaDocumentType schema((*schemaItr)["schema"], filenames[i], static_cast<SizeType>(strlen(filenames[i])), &provider, &schemaAllocator);
GenericSchemaValidator<SchemaDocumentType, BaseReaderHandler<UTF8<> >, MemoryPoolAllocator<> > validator(schema, &validatorAllocator); GenericSchemaValidator<SchemaDocumentType, BaseReaderHandler<UTF8<> >, MemoryPoolAllocator<> > validator(schema, &validatorAllocator);
const char* description1 = (*schemaItr)["description"].GetString();
const Value& tests = (*schemaItr)["tests"]; const Value& tests = (*schemaItr)["tests"];
for (Value::ConstValueIterator testItr = tests.Begin(); testItr != tests.End(); ++testItr) { for (Value::ConstValueIterator testItr = tests.Begin(); testItr != tests.End(); ++testItr) {
const char* description2 = (*testItr)["description"].GetString(); const char* description2 = (*testItr)["description"].GetString();
//printf("running json test %s \n", description2);
if (!onlyRunDescription || strcmp(description2, onlyRunDescription) == 0) { if (!onlyRunDescription || strcmp(description2, onlyRunDescription) == 0) {
const Value& data = (*testItr)["data"]; const Value& data = (*testItr)["data"];
bool expected = (*testItr)["valid"].GetBool(); bool expected = (*testItr)["valid"].GetBool();
@ -2075,8 +2261,8 @@ TEST(SchemaValidator, TestSuite) {
jsonAllocator.Clear(); jsonAllocator.Clear();
} }
printf("%d / %d passed (%2d%%)\n", passCount, testCount, passCount * 100 / testCount); printf("%d / %d passed (%2d%%)\n", passCount, testCount, passCount * 100 / testCount);
// if (passCount != testCount) if (passCount != testCount)
// ADD_FAILURE(); ADD_FAILURE();
} }
TEST(SchemaValidatingReader, Simple) { TEST(SchemaValidatingReader, Simple) {
@ -2244,6 +2430,185 @@ TEST(SchemaValidator, Ref_remote) {
kValidateDefaultFlags, SchemaValidatorType, PointerType); kValidateDefaultFlags, SchemaValidatorType, PointerType);
} }
// Merge with id where $ref is full URI
TEST(SchemaValidator, Ref_remote_change_resolution_scope_uri) {
typedef GenericSchemaDocument<Value, MemoryPoolAllocator<> > SchemaDocumentType;
RemoteSchemaDocumentProvider<SchemaDocumentType> provider;
Document sd;
sd.Parse("{\"id\": \"http://ignore/blah#/ref\", \"type\": \"object\", \"properties\": {\"myInt\": {\"$ref\": \"http://localhost:1234/subSchemas.json#/integer\"}}}");
SchemaDocumentType s(sd, 0, 0, &provider);
typedef GenericSchemaValidator<SchemaDocumentType, BaseReaderHandler<UTF8<> >, MemoryPoolAllocator<> > SchemaValidatorType;
typedef GenericPointer<Value, MemoryPoolAllocator<> > PointerType;
INVALIDATE_(s, "{\"myInt\": null}", "/integer", "type", "/myInt",
"{ \"type\": {"
" \"errorCode\": 20,"
" \"instanceRef\": \"#/myInt\","
" \"schemaRef\": \"http://localhost:1234/subSchemas.json#/integer\","
" \"expected\": [\"integer\"], \"actual\": \"null\""
"}}",
kValidateDefaultFlags, SchemaValidatorType, PointerType);
}
// Merge with id where $ref is a relative path
TEST(SchemaValidator, Ref_remote_change_resolution_scope_relative_path) {
typedef GenericSchemaDocument<Value, MemoryPoolAllocator<> > SchemaDocumentType;
RemoteSchemaDocumentProvider<SchemaDocumentType> provider;
Document sd;
sd.Parse("{\"id\": \"http://localhost:1234/\", \"type\": \"object\", \"properties\": {\"myInt\": {\"$ref\": \"subSchemas.json#/integer\"}}}");
SchemaDocumentType s(sd, 0, 0, &provider);
typedef GenericSchemaValidator<SchemaDocumentType, BaseReaderHandler<UTF8<> >, MemoryPoolAllocator<> > SchemaValidatorType;
typedef GenericPointer<Value, MemoryPoolAllocator<> > PointerType;
INVALIDATE_(s, "{\"myInt\": null}", "/integer", "type", "/myInt",
"{ \"type\": {"
" \"errorCode\": 20,"
" \"instanceRef\": \"#/myInt\","
" \"schemaRef\": \"http://localhost:1234/subSchemas.json#/integer\","
" \"expected\": [\"integer\"], \"actual\": \"null\""
"}}",
kValidateDefaultFlags, SchemaValidatorType, PointerType);
}
// Merge with id where $ref is an absolute path
TEST(SchemaValidator, Ref_remote_change_resolution_scope_absolute_path) {
typedef GenericSchemaDocument<Value, MemoryPoolAllocator<> > SchemaDocumentType;
RemoteSchemaDocumentProvider<SchemaDocumentType> provider;
Document sd;
sd.Parse("{\"id\": \"http://localhost:1234/xxxx\", \"type\": \"object\", \"properties\": {\"myInt\": {\"$ref\": \"/subSchemas.json#/integer\"}}}");
SchemaDocumentType s(sd, 0, 0, &provider);
typedef GenericSchemaValidator<SchemaDocumentType, BaseReaderHandler<UTF8<> >, MemoryPoolAllocator<> > SchemaValidatorType;
typedef GenericPointer<Value, MemoryPoolAllocator<> > PointerType;
INVALIDATE_(s, "{\"myInt\": null}", "/integer", "type", "/myInt",
"{ \"type\": {"
" \"errorCode\": 20,"
" \"instanceRef\": \"#/myInt\","
" \"schemaRef\": \"http://localhost:1234/subSchemas.json#/integer\","
" \"expected\": [\"integer\"], \"actual\": \"null\""
"}}",
kValidateDefaultFlags, SchemaValidatorType, PointerType);
}
// Merge with id where $ref is an absolute path, and the document has a base URI
TEST(SchemaValidator, Ref_remote_change_resolution_scope_absolute_path_document) {
typedef GenericSchemaDocument<Value, MemoryPoolAllocator<> > SchemaDocumentType;
RemoteSchemaDocumentProvider<SchemaDocumentType> provider;
Document sd;
sd.Parse("{\"type\": \"object\", \"properties\": {\"myInt\": {\"$ref\": \"/subSchemas.json#/integer\"}}}");
SchemaDocumentType s(sd, "http://localhost:1234/xxxx", 26, &provider);
typedef GenericSchemaValidator<SchemaDocumentType, BaseReaderHandler<UTF8<> >, MemoryPoolAllocator<> > SchemaValidatorType;
typedef GenericPointer<Value, MemoryPoolAllocator<> > PointerType;
INVALIDATE_(s, "{\"myInt\": null}", "/integer", "type", "/myInt",
"{ \"type\": {"
" \"errorCode\": 20,"
" \"instanceRef\": \"#/myInt\","
" \"schemaRef\": \"http://localhost:1234/subSchemas.json#/integer\","
" \"expected\": [\"integer\"], \"actual\": \"null\""
"}}",
kValidateDefaultFlags, SchemaValidatorType, PointerType);
}
// $ref is a non-JSON pointer fragment and there a matching id
TEST(SchemaValidator, Ref_internal_id_1) {
typedef GenericSchemaDocument<Value, MemoryPoolAllocator<> > SchemaDocumentType;
Document sd;
sd.Parse("{\"type\": \"object\", \"properties\": {\"myInt1\": {\"$ref\": \"#myId\"}, \"myStr\": {\"type\": \"string\", \"id\": \"#myStrId\"}, \"myInt2\": {\"type\": \"integer\", \"id\": \"#myId\"}}}");
SchemaDocumentType s(sd);
typedef GenericSchemaValidator<SchemaDocumentType, BaseReaderHandler<UTF8<> >, MemoryPoolAllocator<> > SchemaValidatorType;
typedef GenericPointer<Value, MemoryPoolAllocator<> > PointerType;
INVALIDATE_(s, "{\"myInt1\": null}", "/properties/myInt2", "type", "/myInt1",
"{ \"type\": {"
" \"errorCode\": 20,"
" \"instanceRef\": \"#/myInt1\","
" \"schemaRef\": \"#/properties/myInt2\","
" \"expected\": [\"integer\"], \"actual\": \"null\""
"}}",
kValidateDefaultFlags, SchemaValidatorType, PointerType);
}
// $ref is a non-JSON pointer fragment and there are two matching ids so we take the first
TEST(SchemaValidator, Ref_internal_id_2) {
typedef GenericSchemaDocument<Value, MemoryPoolAllocator<> > SchemaDocumentType;
Document sd;
sd.Parse("{\"type\": \"object\", \"properties\": {\"myInt1\": {\"$ref\": \"#myId\"}, \"myInt2\": {\"type\": \"integer\", \"id\": \"#myId\"}, \"myStr\": {\"type\": \"string\", \"id\": \"#myId\"}}}");
SchemaDocumentType s(sd);
typedef GenericSchemaValidator<SchemaDocumentType, BaseReaderHandler<UTF8<> >, MemoryPoolAllocator<> > SchemaValidatorType;
typedef GenericPointer<Value, MemoryPoolAllocator<> > PointerType;
INVALIDATE_(s, "{\"myInt1\": null}", "/properties/myInt2", "type", "/myInt1",
"{ \"type\": {"
" \"errorCode\": 20,"
" \"instanceRef\": \"#/myInt1\","
" \"schemaRef\": \"#/properties/myInt2\","
" \"expected\": [\"integer\"], \"actual\": \"null\""
"}}",
kValidateDefaultFlags, SchemaValidatorType, PointerType);
}
// $ref is a non-JSON pointer fragment and there is a matching id within array
TEST(SchemaValidator, Ref_internal_id_in_array) {
typedef GenericSchemaDocument<Value, MemoryPoolAllocator<> > SchemaDocumentType;
Document sd;
sd.Parse("{\"type\": \"object\", \"properties\": {\"myInt1\": {\"$ref\": \"#myId\"}, \"myInt2\": {\"anyOf\": [{\"type\": \"string\", \"id\": \"#myStrId\"}, {\"type\": \"integer\", \"id\": \"#myId\"}]}}}");
SchemaDocumentType s(sd);
typedef GenericSchemaValidator<SchemaDocumentType, BaseReaderHandler<UTF8<> >, MemoryPoolAllocator<> > SchemaValidatorType;
typedef GenericPointer<Value, MemoryPoolAllocator<> > PointerType;
INVALIDATE_(s, "{\"myInt1\": null}", "/properties/myInt2/anyOf/1", "type", "/myInt1",
"{ \"type\": {"
" \"errorCode\": 20,"
" \"instanceRef\": \"#/myInt1\","
" \"schemaRef\": \"#/properties/myInt2/anyOf/1\","
" \"expected\": [\"integer\"], \"actual\": \"null\""
"}}",
kValidateDefaultFlags, SchemaValidatorType, PointerType);
}
// $ref is a non-JSON pointer fragment and there is a matching id, and the schema is embedded in the document
TEST(SchemaValidator, Ref_internal_id_and_schema_pointer) {
typedef GenericSchemaDocument<Value, MemoryPoolAllocator<> > SchemaDocumentType;
Document sd;
sd.Parse("{ \"schema\": {\"type\": \"object\", \"properties\": {\"myInt1\": {\"$ref\": \"#myId\"}, \"myInt2\": {\"anyOf\": [{\"type\": \"integer\", \"id\": \"#myId\"}]}}}}");
typedef GenericPointer<Value, MemoryPoolAllocator<> > PointerType;
SchemaDocumentType s(sd, 0, 0, 0, 0, PointerType("/schema"));
typedef GenericSchemaValidator<SchemaDocumentType, BaseReaderHandler<UTF8<> >, MemoryPoolAllocator<> > SchemaValidatorType;
INVALIDATE_(s, "{\"myInt1\": null}", "/schema/properties/myInt2/anyOf/0", "type", "/myInt1",
"{ \"type\": {"
" \"errorCode\": 20,"
" \"instanceRef\": \"#/myInt1\","
" \"schemaRef\": \"#/schema/properties/myInt2/anyOf/0\","
" \"expected\": [\"integer\"], \"actual\": \"null\""
"}}",
kValidateDefaultFlags, SchemaValidatorType, PointerType);
}
// Test that $refs are correctly resolved when intermediate multiple ids are present
// Includes $ref to a part of the document with a different in-scope id, which also contains $ref..
TEST(SchemaValidator, Ref_internal_multiple_ids) {
typedef GenericSchemaDocument<Value, MemoryPoolAllocator<> > SchemaDocumentType;
//RemoteSchemaDocumentProvider<SchemaDocumentType> provider;
CrtAllocator allocator;
char* schema = ReadFile("unittestschema/idandref.json", allocator);
Document sd;
sd.Parse(schema);
ASSERT_FALSE(sd.HasParseError());
SchemaDocumentType s(sd, "http://xyz", 10/*, &provider*/);
typedef GenericSchemaValidator<SchemaDocumentType, BaseReaderHandler<UTF8<> >, MemoryPoolAllocator<> > SchemaValidatorType;
typedef GenericPointer<Value, MemoryPoolAllocator<> > PointerType;
INVALIDATE_(s, "{\"PA1\": \"s\", \"PA2\": \"t\", \"PA3\": \"r\", \"PX1\": 1, \"PX2Y\": 2, \"PX3Z\": 3, \"PX4\": 4, \"PX5\": 5, \"PX6\": 6, \"PX7W\": 7, \"PX8N\": { \"NX\": 8}}", "#", "errors", "#",
"{ \"type\": ["
" {\"errorCode\": 20, \"instanceRef\": \"#/PA1\", \"schemaRef\": \"http://xyz#/definitions/A\", \"expected\": [\"integer\"], \"actual\": \"string\"},"
" {\"errorCode\": 20, \"instanceRef\": \"#/PA2\", \"schemaRef\": \"http://xyz#/definitions/A\", \"expected\": [\"integer\"], \"actual\": \"string\"},"
" {\"errorCode\": 20, \"instanceRef\": \"#/PA3\", \"schemaRef\": \"http://xyz#/definitions/A\", \"expected\": [\"integer\"], \"actual\": \"string\"},"
" {\"errorCode\": 20, \"instanceRef\": \"#/PX1\", \"schemaRef\": \"http://xyz#/definitions/B/definitions/X\", \"expected\": [\"boolean\"], \"actual\": \"integer\"},"
" {\"errorCode\": 20, \"instanceRef\": \"#/PX2Y\", \"schemaRef\": \"http://xyz#/definitions/B/definitions/X\", \"expected\": [\"boolean\"], \"actual\": \"integer\"},"
" {\"errorCode\": 20, \"instanceRef\": \"#/PX3Z\", \"schemaRef\": \"http://xyz#/definitions/B/definitions/X\", \"expected\": [\"boolean\"], \"actual\": \"integer\"},"
" {\"errorCode\": 20, \"instanceRef\": \"#/PX4\", \"schemaRef\": \"http://xyz#/definitions/B/definitions/X\", \"expected\": [\"boolean\"], \"actual\": \"integer\"},"
" {\"errorCode\": 20, \"instanceRef\": \"#/PX5\", \"schemaRef\": \"http://xyz#/definitions/B/definitions/X\", \"expected\": [\"boolean\"], \"actual\": \"integer\"},"
" {\"errorCode\": 20, \"instanceRef\": \"#/PX6\", \"schemaRef\": \"http://xyz#/definitions/B/definitions/X\", \"expected\": [\"boolean\"], \"actual\": \"integer\"},"
" {\"errorCode\": 20, \"instanceRef\": \"#/PX7W\", \"schemaRef\": \"http://xyz#/definitions/B/definitions/X\", \"expected\": [\"boolean\"], \"actual\": \"integer\"},"
" {\"errorCode\": 20, \"instanceRef\": \"#/PX8N/NX\", \"schemaRef\": \"http://xyz#/definitions/B/definitions/X\", \"expected\": [\"boolean\"], \"actual\": \"integer\"}"
"]}",
kValidateDefaultFlags | kValidateContinueOnErrorFlag, SchemaValidatorType, PointerType);
CrtAllocator::Free(schema);
}
TEST(SchemaValidator, Ref_remote_issue1210) { TEST(SchemaValidator, Ref_remote_issue1210) {
class SchemaDocumentProvider : public IRemoteSchemaDocumentProvider { class SchemaDocumentProvider : public IRemoteSchemaDocumentProvider {
SchemaDocument** collection; SchemaDocument** collection;
@ -2260,7 +2625,7 @@ TEST(SchemaValidator, Ref_remote_issue1210) {
SchemaDocumentProvider(SchemaDocument** collection) : collection(collection) { } SchemaDocumentProvider(SchemaDocument** collection) : collection(collection) { }
virtual const SchemaDocument* GetRemoteDocument(const char* uri, SizeType length) { virtual const SchemaDocument* GetRemoteDocument(const char* uri, SizeType length) {
int i = 0; int i = 0;
while (collection[i] && SchemaDocument::URIType(uri, length) != collection[i]->GetURI()) ++i; while (collection[i] && SchemaDocument::SValue(uri, length) != collection[i]->GetURI()) ++i;
return collection[i]; return collection[i];
} }
}; };

718
test/unittest/uritest.cpp Normal file
View File

@ -0,0 +1,718 @@
// Tencent is pleased to support the open source community by making RapidJSON available.
//
// (C) Copyright IBM Corporation 2021
//
// Licensed under the MIT License (the "License"); you may not use this file except
// in compliance with the License. You may obtain a copy of the License at
//
// http://opensource.org/licenses/MIT
//
// Unless required by applicable law or agreed to in writing, software distributed
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.
#define RAPIDJSON_SCHEMA_VERBOSE 0
#define RAPIDJSON_HAS_STDSTRING 1
#include "unittest.h"
#include "rapidjson/document.h"
#include "rapidjson/uri.h"
#ifdef __clang__
RAPIDJSON_DIAG_PUSH
RAPIDJSON_DIAG_OFF(variadic-macros)
#elif defined(_MSC_VER)
RAPIDJSON_DIAG_PUSH
RAPIDJSON_DIAG_OFF(4822) // local class member function does not have a body
#endif
using namespace rapidjson;
TEST(Uri, DefaultConstructor) {
typedef GenericUri<Value> UriType;
UriType u;
EXPECT_TRUE(u.GetSchemeString() == 0);
EXPECT_TRUE(u.GetAuthString() == 0);
EXPECT_TRUE(u.GetPathString() == 0);
EXPECT_TRUE(u.GetBaseString() == 0);
EXPECT_TRUE(u.GetQueryString() == 0);
EXPECT_TRUE(u.GetFragString() == 0);
EXPECT_TRUE(u.GetString() == 0);
EXPECT_TRUE(u.GetSchemeStringLength() == 0);
EXPECT_TRUE(u.GetAuthStringLength() == 0);
EXPECT_TRUE(u.GetPathStringLength() == 0);
EXPECT_TRUE(u.GetBaseStringLength() == 0);
EXPECT_TRUE(u.GetQueryStringLength() == 0);
EXPECT_TRUE(u.GetFragStringLength() == 0);
EXPECT_TRUE(u.GetStringLength() == 0);
}
TEST(Uri, Parse) {
typedef GenericUri<Value, MemoryPoolAllocator<> > UriType;
MemoryPoolAllocator<CrtAllocator> allocator;
Value v;
Value w;
v.SetString("http://auth/path/xxx?query#frag", allocator);
UriType u = UriType(v, &allocator);
EXPECT_TRUE(StrCmp(u.GetSchemeString(), "http:") == 0);
EXPECT_TRUE(StrCmp(u.GetAuthString(), "//auth") == 0);
EXPECT_TRUE(StrCmp(u.GetPathString(), "/path/xxx") == 0);
EXPECT_TRUE(StrCmp(u.GetBaseString(), "http://auth/path/xxx?query") == 0);
EXPECT_TRUE(StrCmp(u.GetQueryString(), "?query") == 0);
EXPECT_TRUE(StrCmp(u.GetFragString(), "#frag") == 0);
u.Get(w, allocator);
EXPECT_TRUE(*w.GetString() == *v.GetString());
#if RAPIDJSON_HAS_STDSTRING
typedef std::basic_string<Value::Ch> String;
String str = "http://auth/path/xxx?query#frag";
const UriType uri = UriType(str);
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);
EXPECT_TRUE(StrCmp(u.GetSchemeString(), "urn:") == 0);
EXPECT_TRUE(u.GetAuthStringLength() == 0);
EXPECT_TRUE(StrCmp(u.GetPathString(), "uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f") == 0);
EXPECT_TRUE(StrCmp(u.GetBaseString(), "urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f") == 0);
EXPECT_TRUE(u.GetQueryStringLength() == 0);
EXPECT_TRUE(u.GetFragStringLength() == 0);
u.Get(w, allocator);
EXPECT_TRUE(*w.GetString() == *v.GetString());
v.SetString("", allocator);
u = UriType(v);
EXPECT_TRUE(u.GetSchemeStringLength() == 0);
EXPECT_TRUE(u.GetAuthStringLength() == 0);
EXPECT_TRUE(u.GetPathStringLength() == 0);
EXPECT_TRUE(u.GetBaseStringLength() == 0);
EXPECT_TRUE(u.GetQueryStringLength() == 0);
EXPECT_TRUE(u.GetFragStringLength() == 0);
v.SetString("http://auth/", allocator);
u = UriType(v);
EXPECT_TRUE(StrCmp(u.GetSchemeString(), "http:") == 0);
EXPECT_TRUE(StrCmp(u.GetAuthString(), "//auth") == 0);
EXPECT_TRUE(StrCmp(u.GetPathString(), "/") == 0);
EXPECT_TRUE(StrCmp(u.GetBaseString(), "http://auth/") == 0);
EXPECT_TRUE(u.GetQueryStringLength() == 0);
EXPECT_TRUE(u.GetFragStringLength() == 0);
u = UriType("/path/sub");
EXPECT_TRUE(u.GetSchemeStringLength() == 0);
EXPECT_TRUE(u.GetAuthStringLength() == 0);
EXPECT_TRUE(StrCmp(u.GetPathString(), "/path/sub") == 0);
EXPECT_TRUE(StrCmp(u.GetBaseString(), "/path/sub") == 0);
EXPECT_TRUE(u.GetQueryStringLength() == 0);
EXPECT_TRUE(u.GetFragStringLength() == 0);
// absolute path gets normalized
u = UriType("/path/../sub/");
EXPECT_TRUE(u.GetSchemeStringLength() == 0);
EXPECT_TRUE(u.GetAuthStringLength() == 0);
EXPECT_TRUE(StrCmp(u.GetPathString(), "/sub/") == 0);
EXPECT_TRUE(StrCmp(u.GetBaseString(), "/sub/") == 0);
EXPECT_TRUE(u.GetQueryStringLength() == 0);
EXPECT_TRUE(u.GetFragStringLength() == 0);
// relative path does not
u = UriType("path/../sub");
EXPECT_TRUE(u.GetSchemeStringLength() == 0);
EXPECT_TRUE(u.GetAuthStringLength() == 0);
EXPECT_TRUE(StrCmp(u.GetPathString(), "path/../sub") == 0);
EXPECT_TRUE(StrCmp(u.GetBaseString(), "path/../sub") == 0);
EXPECT_TRUE(u.GetQueryStringLength() == 0);
EXPECT_TRUE(u.GetFragStringLength() == 0);
u = UriType("http://auth#frag/stuff");
EXPECT_TRUE(StrCmp(u.GetSchemeString(), "http:") == 0);
EXPECT_TRUE(StrCmp(u.GetAuthString(), "//auth") == 0);
EXPECT_TRUE(u.GetPathStringLength() == 0);
EXPECT_TRUE(StrCmp(u.GetBaseString(), "http://auth") == 0);
EXPECT_TRUE(u.GetQueryStringLength() == 0);
EXPECT_TRUE(StrCmp(u.GetFragString(), "#frag/stuff") == 0);
EXPECT_TRUE(StrCmp(u.GetString(), "http://auth#frag/stuff") == 0);
const Value::Ch c[] = { '#', 'f', 'r', 'a', 'g', '/', 's', 't', 'u', 'f', 'f', '\0'};
SizeType len = internal::StrLen<Value::Ch>(c);
u = UriType(c, len);
EXPECT_TRUE(StrCmp(u.GetString(), "#frag/stuff") == 0);
EXPECT_TRUE(u.GetStringLength() == len);
EXPECT_TRUE(StrCmp(u.GetBaseString(), "") == 0);
EXPECT_TRUE(u.GetBaseStringLength() == 0);
EXPECT_TRUE(StrCmp(u.GetFragString(), "#frag/stuff") == 0);
EXPECT_TRUE(u.GetFragStringLength() == len);
u = UriType(c);
EXPECT_TRUE(StrCmp(u.GetString(), "#frag/stuff") == 0);
EXPECT_TRUE(u.GetStringLength() == len);
EXPECT_TRUE(StrCmp(u.GetBaseString(), "") == 0);
EXPECT_TRUE(u.GetBaseStringLength() == 0);
EXPECT_TRUE(StrCmp(u.GetFragString(), "#frag/stuff") == 0);
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);
}
TEST(Uri, Parse_UTF16) {
typedef GenericValue<UTF16<> > Value16;
typedef GenericUri<Value16, MemoryPoolAllocator<> > UriType;
MemoryPoolAllocator<CrtAllocator> allocator;
Value16 v;
Value16 w;
v.SetString(L"http://auth/path/xxx?query#frag", allocator);
UriType 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"/path/xxx") == 0);
EXPECT_TRUE(StrCmp(u.GetBaseString(), L"http://auth/path/xxx?query") == 0);
EXPECT_TRUE(StrCmp(u.GetQueryString(), L"?query") == 0);
EXPECT_TRUE(StrCmp(u.GetFragString(), L"#frag") == 0);
u.Get(w, allocator);
EXPECT_TRUE(*w.GetString() == *v.GetString());
#if RAPIDJSON_HAS_STDSTRING
typedef std::basic_string<Value16::Ch> String;
String str = L"http://auth/path/xxx?query#frag";
const UriType uri = UriType(str);
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);
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);
EXPECT_TRUE(StrCmp(u.GetBaseString(), L"urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f") == 0);
EXPECT_TRUE(u.GetQueryStringLength() == 0);
EXPECT_TRUE(u.GetFragStringLength() == 0);
u.Get(w, allocator);
EXPECT_TRUE(*w.GetString() == *v.GetString());
v.SetString(L"", allocator);
u = UriType(v);
EXPECT_TRUE(u.GetSchemeStringLength() == 0);
EXPECT_TRUE(u.GetAuthStringLength() == 0);
EXPECT_TRUE(u.GetPathStringLength() == 0);
EXPECT_TRUE(u.GetBaseStringLength() == 0);
EXPECT_TRUE(u.GetQueryStringLength() == 0);
EXPECT_TRUE(u.GetFragStringLength() == 0);
v.SetString(L"http://auth/", allocator);
u = UriType(v);
EXPECT_TRUE(StrCmp(u.GetSchemeString(), L"http:") == 0);
EXPECT_TRUE(StrCmp(u.GetAuthString(), L"//auth") == 0);
EXPECT_TRUE(StrCmp(u.GetPathString(), L"/") == 0);
EXPECT_TRUE(StrCmp(u.GetBaseString(), L"http://auth/") == 0);
EXPECT_TRUE(u.GetQueryStringLength() == 0);
EXPECT_TRUE(u.GetFragStringLength() == 0);
u = UriType(L"/path/sub");
EXPECT_TRUE(u.GetSchemeStringLength() == 0);
EXPECT_TRUE(u.GetAuthStringLength() == 0);
EXPECT_TRUE(StrCmp(u.GetPathString(), L"/path/sub") == 0);
EXPECT_TRUE(StrCmp(u.GetBaseString(), L"/path/sub") == 0);
EXPECT_TRUE(u.GetQueryStringLength() == 0);
EXPECT_TRUE(u.GetFragStringLength() == 0);
// absolute path gets normalized
u = UriType(L"/path/../sub/");
EXPECT_TRUE(u.GetSchemeStringLength() == 0);
EXPECT_TRUE(u.GetAuthStringLength() == 0);
EXPECT_TRUE(StrCmp(u.GetPathString(), L"/sub/") == 0);
EXPECT_TRUE(StrCmp(u.GetBaseString(), L"/sub/") == 0);
EXPECT_TRUE(u.GetQueryStringLength() == 0);
EXPECT_TRUE(u.GetFragStringLength() == 0);
// relative path does not
u = UriType(L"path/../sub");
EXPECT_TRUE(u.GetSchemeStringLength() == 0);
EXPECT_TRUE(u.GetAuthStringLength() == 0);
EXPECT_TRUE(StrCmp(u.GetPathString(), L"path/../sub") == 0);
EXPECT_TRUE(StrCmp(u.GetBaseString(), L"path/../sub") == 0);
EXPECT_TRUE(u.GetQueryStringLength() == 0);
EXPECT_TRUE(u.GetFragStringLength() == 0);
u = UriType(L"http://auth#frag/stuff");
EXPECT_TRUE(StrCmp(u.GetSchemeString(), L"http:") == 0);
EXPECT_TRUE(StrCmp(u.GetAuthString(), L"//auth") == 0);
EXPECT_TRUE(u.GetPathStringLength() == 0);
EXPECT_TRUE(StrCmp(u.GetBaseString(), L"http://auth") == 0);
EXPECT_TRUE(u.GetQueryStringLength() == 0);
EXPECT_TRUE(StrCmp(u.GetFragString(), L"#frag/stuff") == 0);
EXPECT_TRUE(StrCmp(u.GetString(), L"http://auth#frag/stuff") == 0);
const Value16::Ch c[] = { '#', 'f', 'r', 'a', 'g', '/', 's', 't', 'u', 'f', 'f', '\0'};
SizeType len = internal::StrLen<Value16::Ch>(c);
u = UriType(c, len);
EXPECT_TRUE(StrCmp(u.GetString(), L"#frag/stuff") == 0);
EXPECT_TRUE(u.GetStringLength() == len);
EXPECT_TRUE(StrCmp(u.GetBaseString(), L"") == 0);
EXPECT_TRUE(u.GetBaseStringLength() == 0);
EXPECT_TRUE(StrCmp(u.GetFragString(), L"#frag/stuff") == 0);
EXPECT_TRUE(u.GetFragStringLength() == len);
u = UriType(c);
EXPECT_TRUE(StrCmp(u.GetString(), L"#frag/stuff") == 0);
EXPECT_TRUE(u.GetStringLength() == len);
EXPECT_TRUE(StrCmp(u.GetBaseString(), L"") == 0);
EXPECT_TRUE(u.GetBaseStringLength() == 0);
EXPECT_TRUE(StrCmp(u.GetFragString(), L"#frag/stuff") == 0);
EXPECT_TRUE(u.GetFragStringLength() == len);
// Incomplete auth treated as path
u = UriType(L"http:/");
EXPECT_TRUE(StrCmp(u.GetSchemeString(), L"http:") == 0);
EXPECT_TRUE(u.GetAuthStringLength() == 0);
EXPECT_TRUE(StrCmp(u.GetPathString(), L"/") == 0);
EXPECT_TRUE(StrCmp(u.GetBaseString(), L"http:/") == 0);
}
TEST(Uri, CopyConstructor) {
typedef GenericUri<Value> UriType;
CrtAllocator allocator;
UriType u("http://auth/path/xxx?query#frag", &allocator);
UriType u2(u);
EXPECT_TRUE(u == u2);
EXPECT_NE(&u.GetAllocator(), &u2.GetAllocator());
}
TEST(Uri, Assignment) {
typedef GenericUri<Value> UriType;
CrtAllocator allocator;
UriType u("http://auth/path/xxx?query#frag", &allocator);
UriType u2;
u2 = u;
EXPECT_TRUE(u == u2);
EXPECT_NE(&u.GetAllocator(), &u2.GetAllocator());
}
TEST(Uri, Resolve) {
typedef GenericUri<Value> UriType;
CrtAllocator allocator;
// ref is full uri
UriType base = UriType("http://auth/path/#frag");
UriType ref = UriType("http://newauth/newpath#newfrag");
UriType res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), "http://newauth/newpath#newfrag") == 0);
base = UriType("/path/#frag", &allocator);
ref = UriType("http://newauth/newpath#newfrag", &allocator);
res = ref.Resolve(base, &allocator);
EXPECT_TRUE(StrCmp(res.GetString(), "http://newauth/newpath#newfrag") == 0);
// ref is alternate uri
base = UriType("http://auth/path/#frag");
ref = UriType("urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), "urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f") == 0);
// ref is absolute path
base = UriType("http://auth/path/#");
ref = UriType("/newpath#newfrag");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), "http://auth/newpath#newfrag") == 0);
// ref is relative path
base = UriType("http://auth/path/file.json#frag");
ref = UriType("newfile.json#");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), "http://auth/path/newfile.json#") == 0);
base = UriType("http://auth/path/file.json#frag/stuff");
ref = UriType("newfile.json#newfrag/newstuff");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), "http://auth/path/newfile.json#newfrag/newstuff") == 0);
base = UriType("file.json", &allocator);
ref = UriType("newfile.json", &base.GetAllocator());
res = ref.Resolve(base, &ref.GetAllocator());
EXPECT_TRUE(StrCmp(res.GetString(), "newfile.json") == 0);
base = UriType("file.json", &allocator);
ref = UriType("./newfile.json", &allocator);
res = ref.Resolve(base, &allocator);
EXPECT_TRUE(StrCmp(res.GetString(), "newfile.json") == 0);
base = UriType("file.json");
ref = UriType("parent/../newfile.json");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), "newfile.json") == 0);
base = UriType("file.json");
ref = UriType("parent/./newfile.json");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), "parent/newfile.json") == 0);
base = UriType("file.json");
ref = UriType("../../parent/.././newfile.json");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), "newfile.json") == 0);
// This adds a joining slash so resolved length is base length + ref length + 1
base = UriType("http://auth");
ref = UriType("newfile.json");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), "http://auth/newfile.json") == 0);
// ref is fragment
base = UriType("#frag/stuff");
ref = UriType("#newfrag/newstuff");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), "#newfrag/newstuff") == 0);
// test ref fragment always wins
base = UriType("/path#frag");
ref = UriType("");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), "/path") == 0);
// Examples from RFC3896
base = UriType("http://a/b/c/d;p?q");
ref = UriType("g:h");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), "g:h") == 0);
ref = UriType("g");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/g") == 0);
ref = UriType("./g");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/g") == 0);
ref = UriType("g/");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/g/") == 0);
ref = UriType("/g");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), "http://a/g") == 0);
ref = UriType("//g");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), "http://g") == 0);
ref = UriType("?y");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/d;p?y") == 0);
ref = UriType("g?y");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/g?y") == 0);
ref = UriType("#s");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/d;p?q#s") == 0);
ref = UriType("g#s");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/g#s") == 0);
ref = UriType("g?y#s");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/g?y#s") == 0);
ref = UriType(";x");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/;x") == 0);
ref = UriType("g;x");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/g;x") == 0);
ref = UriType("g;x?y#s");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/g;x?y#s") == 0);
ref = UriType("");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/d;p?q") == 0);
ref = UriType(".");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/") == 0);
ref = UriType("./");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/") == 0);
ref = UriType("..");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/") == 0);
ref = UriType("../");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/") == 0);
ref = UriType("../g");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/g") == 0);
ref = UriType("../..");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), "http://a/") == 0);
ref = UriType("../../");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), "http://a/") == 0);
ref = UriType("../../g");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), "http://a/g") == 0);
ref = UriType("../../../g");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), "http://a/g") == 0);
ref = UriType("../../../../g");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), "http://a/g") == 0);
ref = UriType("/./g");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), "http://a/g") == 0);
ref = UriType("/../g");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), "http://a/g") == 0);
ref = UriType("g.");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/g.") == 0);
ref = UriType(".g");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/.g") == 0);
ref = UriType("g..");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/g..") == 0);
ref = UriType("..g");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/..g") == 0);
ref = UriType("g#s/../x");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), "http://a/b/c/g#s/../x") == 0);
}
TEST(Uri, Resolve_UTF16) {
typedef GenericValue<UTF16<> > Value16;
typedef GenericUri<Value16> UriType;
CrtAllocator allocator;
// ref is full uri
UriType base = UriType(L"http://auth/path/#frag");
UriType ref = UriType(L"http://newauth/newpath#newfrag");
UriType res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), L"http://newauth/newpath#newfrag") == 0);
base = UriType(L"/path/#frag");
ref = UriType(L"http://newauth/newpath#newfrag");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), L"http://newauth/newpath#newfrag") == 0);
// ref is alternate uri
base = UriType(L"http://auth/path/#frag");
ref = UriType(L"urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), L"urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f") == 0);
// ref is absolute path
base = UriType(L"http://auth/path/#");
ref = UriType(L"/newpath#newfrag");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), L"http://auth/newpath#newfrag") == 0);
// ref is relative path
base = UriType(L"http://auth/path/file.json#frag");
ref = UriType(L"newfile.json#");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), L"http://auth/path/newfile.json#") == 0);
base = UriType(L"http://auth/path/file.json#frag/stuff");
ref = UriType(L"newfile.json#newfrag/newstuff");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), L"http://auth/path/newfile.json#newfrag/newstuff") == 0);
base = UriType(L"file.json", &allocator);
ref = UriType(L"newfile.json", &base.GetAllocator());
res = ref.Resolve(base, &ref.GetAllocator());
EXPECT_TRUE(StrCmp(res.GetString(), L"newfile.json") == 0);
base = UriType(L"file.json", &allocator);
ref = UriType(L"./newfile.json", &allocator);
res = ref.Resolve(base, &allocator);
EXPECT_TRUE(StrCmp(res.GetString(), L"newfile.json") == 0);
base = UriType(L"file.json");
ref = UriType(L"parent/../newfile.json");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), L"newfile.json") == 0);
base = UriType(L"file.json");
ref = UriType(L"parent/./newfile.json");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), L"parent/newfile.json") == 0);
base = UriType(L"file.json");
ref = UriType(L"../../parent/.././newfile.json");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), L"newfile.json") == 0);
// This adds a joining slash so resolved length is base length + ref length + 1
base = UriType(L"http://auth");
ref = UriType(L"newfile.json");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), L"http://auth/newfile.json") == 0);
// ref is fragment
base = UriType(L"#frag/stuff");
ref = UriType(L"#newfrag/newstuff");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), L"#newfrag/newstuff") == 0);
// test ref fragment always wins
base = UriType(L"/path#frag");
ref = UriType(L"");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), L"/path") == 0);
// Examples from RFC3896
base = UriType(L"http://a/b/c/d;p?q");
ref = UriType(L"g:h");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), L"g:h") == 0);
ref = UriType(L"g");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/g") == 0);
ref = UriType(L"./g");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/g") == 0);
ref = UriType(L"g/");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/g/") == 0);
ref = UriType(L"/g");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/g") == 0);
ref = UriType(L"//g");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), L"http://g") == 0);
ref = UriType(L"?y");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/d;p?y") == 0);
ref = UriType(L"g?y");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/g?y") == 0);
ref = UriType(L"#s");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/d;p?q#s") == 0);
ref = UriType(L"g#s");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/g#s") == 0);
ref = UriType(L"g?y#s");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/g?y#s") == 0);
ref = UriType(L";x");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/;x") == 0);
ref = UriType(L"g;x");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/g;x") == 0);
ref = UriType(L"g;x?y#s");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/g;x?y#s") == 0);
ref = UriType(L"");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/d;p?q") == 0);
ref = UriType(L".");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/") == 0);
ref = UriType(L"./");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/") == 0);
ref = UriType(L"..");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/") == 0);
ref = UriType(L"../");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/") == 0);
ref = UriType(L"../g");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/g") == 0);
ref = UriType(L"../..");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/") == 0);
ref = UriType(L"../../");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/") == 0);
ref = UriType(L"../../g");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/g") == 0);
ref = UriType(L"../../../g");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/g") == 0);
ref = UriType(L"../../../../g");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/g") == 0);
ref = UriType(L"/./g");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/g") == 0);
ref = UriType(L"/../g");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/g") == 0);
ref = UriType(L"g.");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/g.") == 0);
ref = UriType(L".g");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/.g") == 0);
ref = UriType(L"g..");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/g..") == 0);
ref = UriType(L"..g");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/..g") == 0);
ref = UriType(L"g#s/../x");
res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), L"http://a/b/c/g#s/../x") == 0);
}
TEST(Uri, Equals) {
typedef GenericUri<Value> UriType;
UriType a = UriType("http://a/a#a");
UriType b = UriType("http://a/a#b");
UriType c = a;
EXPECT_TRUE(a == a);
EXPECT_TRUE(a == c);
EXPECT_TRUE(a != b);
}
TEST(Uri, Match) {
typedef GenericUri<Value> UriType;
UriType a = UriType("http://a/a#a");
UriType b = UriType("http://a/a#b");
UriType c = a;
UriType d;
EXPECT_TRUE(a.Match(a));
EXPECT_TRUE(a.Match(c));
EXPECT_FALSE(a.Match(b));
EXPECT_FALSE(a.Match(b, true));
EXPECT_TRUE(a.Match(b, false)); // Base Uri same
EXPECT_FALSE(a.Match(d));
EXPECT_FALSE(d.Match(a));
}
TEST(Uri, Issue1899) {
typedef GenericUri<Value, MemoryPoolAllocator<> > UriType;
UriType base = UriType("http://auth/path/#frag");
UriType ref = UriType("http://newauth/newpath#newfrag");
UriType res = ref.Resolve(base);
EXPECT_TRUE(StrCmp(res.GetString(), "http://newauth/newpath#newfrag") == 0);
}
#if defined(_MSC_VER) || defined(__clang__)
RAPIDJSON_DIAG_POP
#endif