Merge pull request #1848 from smhdfdl/id-and-ref
Fix issue 1843 - support Id keyword
This commit is contained in:
commit
48fbd8cd20
BIN
bin/unittestschema/idandref.json
Normal file
BIN
bin/unittestschema/idandref.json
Normal file
Binary file not shown.
@ -2,6 +2,8 @@
|
||||
|
||||
// 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/filereadstream.h"
|
||||
#include "rapidjson/schema.h"
|
||||
|
@ -1951,7 +1951,7 @@ public:
|
||||
case kArrayType:
|
||||
if (RAPIDJSON_UNLIKELY(!handler.StartArray()))
|
||||
return false;
|
||||
for (const GenericValue* v = Begin(); v != End(); ++v)
|
||||
for (ConstValueIterator v = Begin(); v != End(); ++v)
|
||||
if (RAPIDJSON_UNLIKELY(!v->Accept(handler)))
|
||||
return false;
|
||||
return handler.EndArray(data_.a.size);
|
||||
|
@ -45,6 +45,20 @@ inline SizeType StrLen(const wchar_t* 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.
|
||||
template<typename Encoding>
|
||||
bool CountStringCodePoint(const typename Encoding::Ch* s, SizeType length, SizeType* outCount) {
|
||||
|
@ -16,6 +16,7 @@
|
||||
#define RAPIDJSON_POINTER_H_
|
||||
|
||||
#include "document.h"
|
||||
#include "uri.h"
|
||||
#include "internal/itoa.h"
|
||||
|
||||
#ifdef __clang__
|
||||
@ -80,6 +81,8 @@ class GenericPointer {
|
||||
public:
|
||||
typedef typename ValueType::EncodingType EncodingType; //!< Encoding 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.
|
||||
/*!
|
||||
@ -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
|
||||
//@{
|
||||
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include "pointer.h"
|
||||
#include "stringbuffer.h"
|
||||
#include "error/en.h"
|
||||
#include "uri.h"
|
||||
#include <cmath> // abs, floor
|
||||
|
||||
#if !defined(RAPIDJSON_SCHEMA_USE_INTERNALREGEX)
|
||||
@ -432,11 +433,13 @@ public:
|
||||
typedef Schema<SchemaDocumentType> SchemaType;
|
||||
typedef GenericValue<EncodingType, AllocatorType> SValue;
|
||||
typedef IValidationErrorHandler<Schema> ErrorHandler;
|
||||
typedef GenericUri<ValueType, AllocatorType> UriType;
|
||||
friend class GenericSchemaDocument<ValueType, AllocatorType>;
|
||||
|
||||
Schema(SchemaDocumentType* schemaDocument, const PointerType& p, const ValueType& value, const ValueType& document, AllocatorType* allocator) :
|
||||
Schema(SchemaDocumentType* schemaDocument, const PointerType& p, const ValueType& value, const ValueType& document, AllocatorType* allocator, const UriType& id = UriType()) :
|
||||
allocator_(allocator),
|
||||
uri_(schemaDocument->GetURI(), *allocator),
|
||||
id_(id),
|
||||
pointer_(p, allocator),
|
||||
typeless_(schemaDocument->GetTypeless()),
|
||||
enum_(),
|
||||
@ -474,9 +477,28 @@ public:
|
||||
typedef typename ValueType::ConstValueIterator ConstValueIterator;
|
||||
typedef typename ValueType::ConstMemberIterator ConstMemberIterator;
|
||||
|
||||
// PR #1393
|
||||
// Early add this Schema and its $ref(s) in schemaDocument's map to avoid infinite
|
||||
// recursion (with recursive schemas), since schemaDocument->getSchema() is always
|
||||
// checked before creating a new one. Don't cache typeless_, though.
|
||||
if (this != typeless_) {
|
||||
typedef typename SchemaDocumentType::SchemaEntry SchemaEntry;
|
||||
SchemaEntry *entry = schemaDocument->schemaMap_.template Push<SchemaEntry>();
|
||||
new (entry) SchemaEntry(pointer_, this, true, allocator_);
|
||||
schemaDocument->AddSchemaRefs(this);
|
||||
}
|
||||
|
||||
if (!value.IsObject())
|
||||
return;
|
||||
|
||||
// If we have an id property, resolve it with the in-scope id
|
||||
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())) {
|
||||
type_ = 0;
|
||||
if (v->IsString())
|
||||
@ -506,9 +528,9 @@ public:
|
||||
AssignIfExist(oneOf_, *schemaDocument, p, value, GetOneOfString(), document);
|
||||
|
||||
if (const ValueType* v = GetMember(value, GetNotString())) {
|
||||
schemaDocument->CreateSchema(¬_, p.Append(GetNotString(), allocator_), *v, document);
|
||||
notValidatorIndex_ = validatorCount_;
|
||||
validatorCount_++;
|
||||
schemaDocument->CreateSchema(¬_, p.Append(GetNotString(), allocator_), *v, document, id_);
|
||||
notValidatorIndex_ = validatorCount_;
|
||||
validatorCount_++;
|
||||
}
|
||||
}
|
||||
|
||||
@ -524,7 +546,7 @@ public:
|
||||
if (properties && properties->IsObject())
|
||||
for (ConstMemberIterator itr = properties->MemberBegin(); itr != properties->MemberEnd(); ++itr)
|
||||
AddUniqueElement(allProperties, itr->name);
|
||||
|
||||
|
||||
if (required && required->IsArray())
|
||||
for (ConstValueIterator itr = required->Begin(); itr != required->End(); ++itr)
|
||||
if (itr->IsString())
|
||||
@ -555,7 +577,7 @@ public:
|
||||
for (ConstMemberIterator itr = properties->MemberBegin(); itr != properties->MemberEnd(); ++itr) {
|
||||
SizeType index;
|
||||
if (FindPropertyIndex(itr->name, &index))
|
||||
schemaDocument->CreateSchema(&properties_[index].schema, q.Append(itr->name, allocator_), itr->value, document);
|
||||
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) {
|
||||
new (&patternProperties_[patternPropertyCount_]) PatternProperty();
|
||||
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_++;
|
||||
}
|
||||
}
|
||||
@ -599,7 +621,7 @@ public:
|
||||
}
|
||||
else if (itr->value.IsObject()) {
|
||||
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_;
|
||||
validatorCount_++;
|
||||
}
|
||||
@ -611,7 +633,7 @@ public:
|
||||
if (v->IsBool())
|
||||
additionalProperties_ = v->GetBool();
|
||||
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());
|
||||
@ -621,12 +643,12 @@ public:
|
||||
if (const ValueType* v = GetMember(value, GetItemsString())) {
|
||||
PointerType q = p.Append(GetItemsString(), allocator_);
|
||||
if (v->IsObject()) // List validation
|
||||
schemaDocument->CreateSchema(&itemsList_, q, *v, document);
|
||||
schemaDocument->CreateSchema(&itemsList_, q, *v, document, id_);
|
||||
else if (v->IsArray()) { // Tuple validation
|
||||
itemsTuple_ = static_cast<const Schema**>(allocator_->Malloc(sizeof(const Schema*) * v->Size()));
|
||||
SizeType index = 0;
|
||||
for (ConstValueIterator itr = v->Begin(); itr != v->End(); ++itr, index++)
|
||||
schemaDocument->CreateSchema(&itemsTuple_[itemsTupleCount_++], q.Append(index, allocator_), *itr, document);
|
||||
schemaDocument->CreateSchema(&itemsTuple_[itemsTupleCount_++], q.Append(index, allocator_), *itr, document, id_);
|
||||
}
|
||||
}
|
||||
|
||||
@ -637,7 +659,7 @@ public:
|
||||
if (v->IsBool())
|
||||
additionalItems_ = v->GetBool();
|
||||
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());
|
||||
@ -697,6 +719,10 @@ public:
|
||||
return uri_;
|
||||
}
|
||||
|
||||
const UriType& GetId() const {
|
||||
return id_;
|
||||
}
|
||||
|
||||
const PointerType& GetPointer() const {
|
||||
return pointer_;
|
||||
}
|
||||
@ -776,7 +802,7 @@ public:
|
||||
foundEnum:;
|
||||
}
|
||||
|
||||
// Only check allOf etc if we have validators
|
||||
// Only check allOf etc if we have validators
|
||||
if (context.validatorCount > 0) {
|
||||
if (allOf_.schemas)
|
||||
for (SizeType i = allOf_.begin; i < allOf_.begin + allOf_.count; i++)
|
||||
@ -826,7 +852,7 @@ public:
|
||||
}
|
||||
return CreateParallelValidator(context);
|
||||
}
|
||||
|
||||
|
||||
bool Bool(Context& context, bool) const {
|
||||
if (!(type_ & (1 << kBooleanSchemaType))) {
|
||||
DisallowedType(context, GetBooleanString());
|
||||
@ -870,13 +896,13 @@ public:
|
||||
|
||||
if (!maximum_.IsNull() && !CheckDoubleMaximum(context, d))
|
||||
return false;
|
||||
|
||||
|
||||
if (!multipleOf_.IsNull() && !CheckDoubleMultipleOf(context, d))
|
||||
return false;
|
||||
|
||||
|
||||
return CreateParallelValidator(context);
|
||||
}
|
||||
|
||||
|
||||
bool String(Context& context, const Ch* str, SizeType length, bool) const {
|
||||
if (!(type_ & (1 << kStringSchemaType))) {
|
||||
DisallowedType(context, GetStringString());
|
||||
@ -925,7 +951,7 @@ public:
|
||||
|
||||
return CreateParallelValidator(context);
|
||||
}
|
||||
|
||||
|
||||
bool Key(Context& context, const Ch* str, SizeType len, bool) const {
|
||||
if (patternProperties_) {
|
||||
context.patternPropertiesSchemaCount = 0;
|
||||
@ -1018,7 +1044,7 @@ public:
|
||||
}
|
||||
}
|
||||
if (context.error_handler.EndDependencyErrors())
|
||||
RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorDependencies);
|
||||
RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorDependencies);
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -1038,12 +1064,12 @@ public:
|
||||
|
||||
bool EndArray(Context& context, SizeType elementCount) const {
|
||||
context.inArray = false;
|
||||
|
||||
|
||||
if (elementCount < minItems_) {
|
||||
context.error_handler.TooFewItems(elementCount, minItems_);
|
||||
RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorMinItems);
|
||||
}
|
||||
|
||||
|
||||
if (elementCount > maxItems_) {
|
||||
context.error_handler.TooManyItems(elementCount, maxItems_);
|
||||
RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorMaxItems);
|
||||
@ -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_(MultipleOf, 'm', 'u', 'l', 't', 'i', 'p', 'l', 'e', 'O', 'f')
|
||||
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_
|
||||
|
||||
@ -1197,7 +1232,7 @@ private:
|
||||
out.schemas = static_cast<const Schema**>(allocator_->Malloc(out.count * sizeof(const Schema*)));
|
||||
memset(out.schemas, 0, sizeof(Schema*)* out.count);
|
||||
for (SizeType i = 0; i < out.count; i++)
|
||||
schemaDocument.CreateSchema(&out.schemas[i], q.Append(i, allocator_), (*v)[i], document);
|
||||
schemaDocument.CreateSchema(&out.schemas[i], q.Append(i, allocator_), (*v)[i], document, id_);
|
||||
out.begin = validatorCount_;
|
||||
validatorCount_ += out.count;
|
||||
}
|
||||
@ -1274,10 +1309,10 @@ private:
|
||||
|
||||
if (anyOf_.schemas)
|
||||
CreateSchemaValidators(context, anyOf_, false);
|
||||
|
||||
|
||||
if (oneOf_.schemas)
|
||||
CreateSchemaValidators(context, oneOf_, false);
|
||||
|
||||
|
||||
if (not_)
|
||||
context.validators[notValidatorIndex_] = context.factory.CreateSchemaValidator(*not_, false);
|
||||
|
||||
@ -1301,7 +1336,7 @@ private:
|
||||
SizeType len = name.GetStringLength();
|
||||
const Ch* str = name.GetString();
|
||||
for (SizeType index = 0; index < propertyCount_; index++)
|
||||
if (properties_[index].name.GetStringLength() == len &&
|
||||
if (properties_[index].name.GetStringLength() == len &&
|
||||
(std::memcmp(properties_[index].name.GetString(), str, sizeof(Ch) * len) == 0))
|
||||
{
|
||||
*outIndex = index;
|
||||
@ -1462,7 +1497,7 @@ private:
|
||||
|
||||
struct PatternProperty {
|
||||
PatternProperty() : schema(), pattern() {}
|
||||
~PatternProperty() {
|
||||
~PatternProperty() {
|
||||
if (pattern) {
|
||||
pattern->~RegexType();
|
||||
AllocatorType::Free(pattern);
|
||||
@ -1474,6 +1509,7 @@ private:
|
||||
|
||||
AllocatorType* allocator_;
|
||||
SValue uri_;
|
||||
UriType id_;
|
||||
PointerType pointer_;
|
||||
const SchemaType* typeless_;
|
||||
uint64_t* enum_;
|
||||
@ -1516,7 +1552,7 @@ private:
|
||||
SValue multipleOf_;
|
||||
bool exclusiveMinimum_;
|
||||
bool exclusiveMaximum_;
|
||||
|
||||
|
||||
SizeType defaultValueLength_;
|
||||
};
|
||||
|
||||
@ -1559,9 +1595,12 @@ template <typename SchemaDocumentType>
|
||||
class IGenericRemoteSchemaDocumentProvider {
|
||||
public:
|
||||
typedef typename SchemaDocumentType::Ch Ch;
|
||||
typedef typename SchemaDocumentType::ValueType ValueType;
|
||||
typedef typename SchemaDocumentType::AllocatorType AllocatorType;
|
||||
|
||||
virtual ~IGenericRemoteSchemaDocumentProvider() {}
|
||||
virtual const SchemaDocumentType* GetRemoteDocument(const Ch* uri, SizeType length) = 0;
|
||||
virtual const SchemaDocumentType* GetRemoteDocument(GenericUri<ValueType, AllocatorType> uri) { return GetRemoteDocument(uri.GetBaseString(), uri.GetBaseStringLength()); }
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
@ -1586,7 +1625,8 @@ public:
|
||||
typedef typename EncodingType::Ch Ch;
|
||||
typedef internal::Schema<GenericSchemaDocument> SchemaType;
|
||||
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>;
|
||||
template <typename, typename, typename>
|
||||
friend class GenericSchemaValidator;
|
||||
@ -1600,9 +1640,11 @@ public:
|
||||
\param uriLength Length of \c name, in code points.
|
||||
\param remoteProvider An optional remote schema document provider for resolving remote reference. Can be null.
|
||||
\param allocator An optional allocator instance for allocating memory. Can be null.
|
||||
\param pointer An optional JSON pointer to the start of the schema document
|
||||
*/
|
||||
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),
|
||||
allocator_(allocator),
|
||||
ownAllocator_(),
|
||||
@ -1616,30 +1658,20 @@ public:
|
||||
|
||||
Ch noUri[1] = {0};
|
||||
uri_.SetString(uri ? uri : noUri, uriLength, *allocator_);
|
||||
docId_ = UriType(uri_, allocator_);
|
||||
|
||||
typeless_ = static_cast<SchemaType*>(allocator_->Malloc(sizeof(SchemaType)));
|
||||
new (typeless_) SchemaType(this, PointerType(), ValueType(kObjectType).Move(), ValueType(kObjectType).Move(), allocator_);
|
||||
new (typeless_) SchemaType(this, PointerType(), ValueType(kObjectType).Move(), ValueType(kObjectType).Move(), allocator_, docId_);
|
||||
|
||||
// Generate root schema, it will call CreateSchema() to create sub-schemas,
|
||||
// And call AddRefSchema() if there are $ref.
|
||||
CreateSchemaRecursive(&root_, PointerType(), document, document);
|
||||
|
||||
// Resolve $ref
|
||||
while (!schemaRef_.Empty()) {
|
||||
SchemaRefEntry* refEntry = schemaRef_.template Pop<SchemaRefEntry>(1);
|
||||
if (const SchemaType* s = GetSchema(refEntry->target)) {
|
||||
if (refEntry->schema)
|
||||
*refEntry->schema = s;
|
||||
|
||||
// Create entry in map if not exist
|
||||
if (!GetSchema(refEntry->source)) {
|
||||
new (schemaMap_.template Push<SchemaEntry>()) SchemaEntry(refEntry->source, const_cast<SchemaType*>(s), false, allocator_);
|
||||
}
|
||||
}
|
||||
else if (refEntry->schema)
|
||||
*refEntry->schema = typeless_;
|
||||
|
||||
refEntry->~SchemaRefEntry();
|
||||
// And call HandleRefSchema() if there are $ref.
|
||||
// PR #1393 use input pointer if supplied
|
||||
root_ = typeless_;
|
||||
if (pointer.GetTokenCount() == 0) {
|
||||
CreateSchemaRecursive(&root_, pointer, document, document, docId_);
|
||||
}
|
||||
else if (const ValueType* v = pointer.Get(document)) {
|
||||
CreateSchema(&root_, pointer, *v, document, docId_);
|
||||
}
|
||||
|
||||
RAPIDJSON_ASSERT(root_ != 0);
|
||||
@ -1657,7 +1689,8 @@ public:
|
||||
typeless_(rhs.typeless_),
|
||||
schemaMap_(std::move(rhs.schemaMap_)),
|
||||
schemaRef_(std::move(rhs.schemaRef_)),
|
||||
uri_(std::move(rhs.uri_))
|
||||
uri_(std::move(rhs.uri_)),
|
||||
docId_(rhs.docId_)
|
||||
{
|
||||
rhs.remoteProvider_ = 0;
|
||||
rhs.allocator_ = 0;
|
||||
@ -1679,7 +1712,7 @@ public:
|
||||
RAPIDJSON_DELETE(ownAllocator_);
|
||||
}
|
||||
|
||||
const URIType& GetURI() const { return uri_; }
|
||||
const SValue& GetURI() const { return uri_; }
|
||||
|
||||
//! Get the root schema.
|
||||
const SchemaType& GetRoot() const { return *root_; }
|
||||
@ -1690,12 +1723,7 @@ private:
|
||||
//! Prohibit assignment
|
||||
GenericSchemaDocument& operator=(const GenericSchemaDocument&);
|
||||
|
||||
struct SchemaRefEntry {
|
||||
SchemaRefEntry(const PointerType& s, const PointerType& t, const SchemaType** outSchema, Allocator *allocator) : source(s, allocator), target(t, allocator), schema(outSchema) {}
|
||||
PointerType source;
|
||||
PointerType target;
|
||||
const SchemaType** schema;
|
||||
};
|
||||
typedef const PointerType* SchemaRefPtr; // PR #1393
|
||||
|
||||
struct SchemaEntry {
|
||||
SchemaEntry(const PointerType& p, SchemaType* s, bool o, Allocator* allocator) : pointer(p, allocator), schema(s), owned(o) {}
|
||||
@ -1710,79 +1738,197 @@ private:
|
||||
bool owned;
|
||||
};
|
||||
|
||||
void CreateSchemaRecursive(const SchemaType** schema, const PointerType& pointer, const ValueType& v, const ValueType& document) {
|
||||
if (schema)
|
||||
*schema = typeless_;
|
||||
|
||||
// Changed by PR #1393
|
||||
void CreateSchemaRecursive(const SchemaType** schema, const PointerType& pointer, const ValueType& v, const ValueType& document, const UriType& id) {
|
||||
if (v.GetType() == kObjectType) {
|
||||
const SchemaType* s = GetSchema(pointer);
|
||||
if (!s)
|
||||
CreateSchema(schema, pointer, v, document);
|
||||
UriType newid = UriType(CreateSchema(schema, pointer, v, document, id), allocator_);
|
||||
|
||||
for (typename ValueType::ConstMemberIterator itr = v.MemberBegin(); itr != v.MemberEnd(); ++itr)
|
||||
CreateSchemaRecursive(0, pointer.Append(itr->name, allocator_), itr->value, document);
|
||||
CreateSchemaRecursive(0, pointer.Append(itr->name, allocator_), itr->value, document, newid);
|
||||
}
|
||||
else if (v.GetType() == kArrayType)
|
||||
for (SizeType i = 0; i < v.Size(); i++)
|
||||
CreateSchemaRecursive(0, pointer.Append(i, allocator_), v[i], document);
|
||||
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());
|
||||
if (v.IsObject()) {
|
||||
if (!HandleRefSchema(pointer, schema, v, document)) {
|
||||
SchemaType* s = new (allocator_->Malloc(sizeof(SchemaType))) SchemaType(this, pointer, v, document, allocator_);
|
||||
new (schemaMap_.template Push<SchemaEntry>()) SchemaEntry(pointer, s, true, allocator_);
|
||||
if (const SchemaType* sc = GetSchema(pointer)) {
|
||||
if (schema)
|
||||
*schema = sc;
|
||||
AddSchemaRefs(const_cast<SchemaType*>(sc));
|
||||
}
|
||||
else if (!HandleRefSchema(pointer, schema, v, document, id)) {
|
||||
// The new schema constructor adds itself and its $ref(s) to schemaMap_
|
||||
SchemaType* s = new (allocator_->Malloc(sizeof(SchemaType))) SchemaType(this, pointer, v, document, allocator_, id);
|
||||
if (schema)
|
||||
*schema = s;
|
||||
return s->GetId();
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (schema)
|
||||
*schema = typeless_;
|
||||
AddSchemaRefs(typeless_);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
bool HandleRefSchema(const PointerType& source, const SchemaType** schema, const ValueType& v, const ValueType& document) {
|
||||
static const Ch kRefString[] = { '$', 'r', 'e', 'f', '\0' };
|
||||
static const ValueType kRefValue(kRefString, 4);
|
||||
|
||||
typename ValueType::ConstMemberIterator itr = v.FindMember(kRefValue);
|
||||
// Changed by PR #1393
|
||||
// TODO should this return a UriType& ?
|
||||
bool HandleRefSchema(const PointerType& source, const SchemaType** schema, const ValueType& v, const ValueType& document, const UriType& id) {
|
||||
typename ValueType::ConstMemberIterator itr = v.FindMember(SchemaType::GetRefString());
|
||||
if (itr == v.MemberEnd())
|
||||
return false;
|
||||
|
||||
// Resolve the source pointer to the $ref'ed schema (finally)
|
||||
new (schemaRef_.template Push<SchemaRefPtr>()) SchemaRefPtr(&source);
|
||||
|
||||
if (itr->value.IsString()) {
|
||||
SizeType len = itr->value.GetStringLength();
|
||||
if (len > 0) {
|
||||
const Ch* s = itr->value.GetString();
|
||||
SizeType i = 0;
|
||||
while (i < len && s[i] != '#') // Find the first #
|
||||
i++;
|
||||
|
||||
if (i > 0) { // Remote reference, resolve immediately
|
||||
// First resolve $ref against the in-scope id
|
||||
UriType scopeId = UriType(id, allocator_);
|
||||
UriType ref = UriType(itr->value, allocator_).Resolve(scopeId, allocator_);
|
||||
// See if the resolved $ref minus the fragment matches a resolved id in this document
|
||||
// Search from the root. Returns the subschema in the document and its absolute JSON pointer.
|
||||
PointerType basePointer = PointerType();
|
||||
const ValueType *base = FindId(document, ref, basePointer, docId_, false);
|
||||
if (!base) {
|
||||
// Remote reference - call the remote document provider
|
||||
if (remoteProvider_) {
|
||||
if (const GenericSchemaDocument* remoteDocument = remoteProvider_->GetRemoteDocument(s, i)) {
|
||||
PointerType pointer(&s[i], len - i, allocator_);
|
||||
if (pointer.IsValid()) {
|
||||
if (const SchemaType* sc = remoteDocument->GetSchema(pointer)) {
|
||||
if (schema)
|
||||
*schema = sc;
|
||||
new (schemaMap_.template Push<SchemaEntry>()) SchemaEntry(source, const_cast<SchemaType*>(sc), false, allocator_);
|
||||
if (const GenericSchemaDocument* remoteDocument = remoteProvider_->GetRemoteDocument(ref)) {
|
||||
const Ch* s = ref.GetFragString();
|
||||
len = ref.GetFragStringLength();
|
||||
if (len <= 1 || s[1] == '/') {
|
||||
// JSON pointer fragment, absolute in the remote schema
|
||||
const PointerType pointer(s, len, allocator_);
|
||||
if (pointer.IsValid()) {
|
||||
// Get the subschema
|
||||
if (const SchemaType *sc = remoteDocument->GetSchema(pointer)) {
|
||||
if (schema)
|
||||
*schema = sc;
|
||||
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 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))
|
||||
} 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;
|
||||
|
||||
new (schemaRef_.template Push<SchemaRefEntry>()) SchemaRefEntry(source, pointer, schema, allocator_);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Invalid/Unknown $ref
|
||||
if (schema)
|
||||
*schema = typeless_;
|
||||
AddSchemaRefs(typeless_);
|
||||
return true;
|
||||
}
|
||||
|
||||
//! Find the first subschema with a resolved 'id' that matches the specified URI.
|
||||
// If full specified use all URI else ignore fragment.
|
||||
// If found, return a pointer to the subschema and its JSON pointer.
|
||||
// TODO cache pointer <-> id mapping
|
||||
ValueType* FindId(const ValueType& doc, const UriType& finduri, PointerType& resptr, const UriType& baseuri, bool full, const PointerType& here = PointerType()) const {
|
||||
SizeType i = 0;
|
||||
ValueType* resval = 0;
|
||||
UriType tempuri = UriType(finduri, allocator_);
|
||||
UriType localuri = UriType(baseuri, allocator_);
|
||||
if (doc.GetType() == kObjectType) {
|
||||
// Establish the base URI of this object
|
||||
typename ValueType::ConstMemberIterator m = doc.FindMember(SchemaType::GetIdString());
|
||||
if (m != doc.MemberEnd() && m->value.GetType() == kStringType) {
|
||||
localuri = UriType(m->value, allocator_).Resolve(baseuri, allocator_);
|
||||
}
|
||||
// See if it matches
|
||||
if (localuri.Match(finduri, full)) {
|
||||
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;
|
||||
}
|
||||
|
||||
@ -1811,8 +1957,9 @@ private:
|
||||
const SchemaType* root_; //!< Root schema.
|
||||
SchemaType* typeless_;
|
||||
internal::Stack<Allocator> schemaMap_; // Stores created Pointer -> Schemas
|
||||
internal::Stack<Allocator> schemaRef_; // Stores Pointer from $ref and schema which holds the $ref
|
||||
URIType uri_;
|
||||
internal::Stack<Allocator> schemaRef_; // Stores Pointer(s) from $ref(s) until resolved
|
||||
SValue uri_; // Schema document URI
|
||||
UriType docId_;
|
||||
};
|
||||
|
||||
//! GenericSchemaDocument using Value type.
|
||||
|
466
include/rapidjson/uri.h
Normal file
466
include/rapidjson/uri.h
Normal file
@ -0,0 +1,466 @@
|
||||
// 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
|
||||
size_t total = (3 * len + 7) * sizeof(Ch);
|
||||
scheme_ = static_cast<Ch*>(allocator_->Malloc(total));
|
||||
*scheme_ = '\0';
|
||||
auth_ = scheme_ + 1;
|
||||
*auth_ = '\0';
|
||||
path_ = auth_ + 1;
|
||||
*path_ = '\0';
|
||||
query_ = path_ + 1;
|
||||
*query_ = '\0';
|
||||
frag_ = query_ + 1;
|
||||
*frag_ = '\0';
|
||||
base_ = frag_ + 1;
|
||||
*base_ = '\0';
|
||||
uri_ = base_ + 1;
|
||||
*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 (//([^/?#]*))?
|
||||
auth_ = scheme_ + GetSchemeStringLength() + 1;
|
||||
*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 ([^?#]*)
|
||||
path_ = auth_ + GetAuthStringLength() + 1;
|
||||
*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 (\?([^#]*))?
|
||||
query_ = path_ + GetPathStringLength() + 1;
|
||||
*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 (#(.*))?
|
||||
frag_ = query_ + GetQueryStringLength() + 1;
|
||||
*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_
|
@ -27,6 +27,7 @@ set(UNITTEST_SOURCES
|
||||
stringbuffertest.cpp
|
||||
strtodtest.cpp
|
||||
unittest.cpp
|
||||
uritest.cpp
|
||||
valuetest.cpp
|
||||
writertest.cpp)
|
||||
|
||||
|
@ -650,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) {
|
||||
Document d;
|
||||
d.Parse(kJson);
|
||||
@ -666,7 +712,8 @@ TEST(Pointer, Get) {
|
||||
EXPECT_EQ(&d["k\"l"], Pointer("/k\"l").Get(d));
|
||||
EXPECT_EQ(&d[" "], Pointer("/ ").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;
|
||||
EXPECT_TRUE(Pointer("/foo/2").Get(d, &unresolvedTokenIndex) == 0); // Out of boundary
|
||||
EXPECT_EQ(1u, unresolvedTokenIndex);
|
||||
|
@ -13,6 +13,7 @@
|
||||
// specific language governing permissions and limitations under the License.
|
||||
|
||||
#define RAPIDJSON_SCHEMA_VERBOSE 0
|
||||
#define RAPIDJSON_HAS_STDSTRING 1
|
||||
|
||||
#include "unittest.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>
|
||||
static char* ReadFile(const char* filename, Allocator& allocator) {
|
||||
const char *paths[] = {
|
||||
@ -1952,7 +2136,7 @@ public:
|
||||
|
||||
virtual const SchemaDocumentType* GetRemoteDocument(const char* uri, SizeType length) {
|
||||
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 0;
|
||||
}
|
||||
@ -2032,7 +2216,7 @@ TEST(SchemaValidator, TestSuite) {
|
||||
ADD_FAILURE();
|
||||
}
|
||||
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);
|
||||
d.Parse(json);
|
||||
if (d.HasParseError()) {
|
||||
@ -2042,12 +2226,14 @@ TEST(SchemaValidator, TestSuite) {
|
||||
else {
|
||||
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);
|
||||
GenericSchemaValidator<SchemaDocumentType, BaseReaderHandler<UTF8<> >, MemoryPoolAllocator<> > validator(schema, &validatorAllocator);
|
||||
const char* description1 = (*schemaItr)["description"].GetString();
|
||||
const Value& tests = (*schemaItr)["tests"];
|
||||
for (Value::ConstValueIterator testItr = tests.Begin(); testItr != tests.End(); ++testItr) {
|
||||
const char* description2 = (*testItr)["description"].GetString();
|
||||
//printf("running json test %s \n", description2);
|
||||
if (!onlyRunDescription || strcmp(description2, onlyRunDescription) == 0) {
|
||||
const Value& data = (*testItr)["data"];
|
||||
bool expected = (*testItr)["valid"].GetBool();
|
||||
@ -2075,8 +2261,8 @@ TEST(SchemaValidator, TestSuite) {
|
||||
jsonAllocator.Clear();
|
||||
}
|
||||
printf("%d / %d passed (%2d%%)\n", passCount, testCount, passCount * 100 / testCount);
|
||||
// if (passCount != testCount)
|
||||
// ADD_FAILURE();
|
||||
if (passCount != testCount)
|
||||
ADD_FAILURE();
|
||||
}
|
||||
|
||||
TEST(SchemaValidatingReader, Simple) {
|
||||
@ -2114,7 +2300,7 @@ TEST(SchemaValidatingReader, Invalid) {
|
||||
Document e;
|
||||
e.Parse(
|
||||
"{ \"maxLength\": {"
|
||||
" \"errorCode\": 6,"
|
||||
" \"errorCode\": 6,"
|
||||
" \"instanceRef\": \"#\", \"schemaRef\": \"#\","
|
||||
" \"expected\": 3, \"actual\": \"ABCD\""
|
||||
"}}");
|
||||
@ -2244,6 +2430,185 @@ TEST(SchemaValidator, Ref_remote) {
|
||||
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) {
|
||||
class SchemaDocumentProvider : public IRemoteSchemaDocumentProvider {
|
||||
SchemaDocument** collection;
|
||||
@ -2260,7 +2625,7 @@ TEST(SchemaValidator, Ref_remote_issue1210) {
|
||||
SchemaDocumentProvider(SchemaDocument** collection) : collection(collection) { }
|
||||
virtual const SchemaDocument* GetRemoteDocument(const char* uri, SizeType length) {
|
||||
int i = 0;
|
||||
while (collection[i] && SchemaDocument::URIType(uri, length) != collection[i]->GetURI()) ++i;
|
||||
while (collection[i] && SchemaDocument::SValue(uri, length) != collection[i]->GetURI()) ++i;
|
||||
return collection[i];
|
||||
}
|
||||
};
|
||||
|
718
test/unittest/uritest.cpp
Normal file
718
test/unittest/uritest.cpp
Normal 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
|
Loading…
x
Reference in New Issue
Block a user