code and tests

This commit is contained in:
Steve Hanson 2021-02-25 21:45:29 +00:00
parent dad85cab9d
commit 7698b3cd48
3 changed files with 981 additions and 97 deletions

View File

@ -1967,7 +1967,7 @@ public:
case kArrayType: case kArrayType:
if (RAPIDJSON_UNLIKELY(!handler.StartArray())) if (RAPIDJSON_UNLIKELY(!handler.StartArray()))
return false; return false;
for (const GenericValue* v = Begin(); v != End(); ++v) for (ConstValueIterator v = Begin(); v != End(); ++v)
if (RAPIDJSON_UNLIKELY(!v->Accept(handler))) if (RAPIDJSON_UNLIKELY(!v->Accept(handler)))
return false; return false;
return handler.EndArray(data_.a.size); return handler.EndArray(data_.a.size);

View File

@ -150,6 +150,9 @@ enum ValidateFlag {
template <typename ValueType, typename Allocator> template <typename ValueType, typename Allocator>
class GenericSchemaDocument; class GenericSchemaDocument;
template <typename SchemaDocumentType>
class Uri;
namespace internal { namespace internal {
template <typename SchemaDocumentType> template <typename SchemaDocumentType>
@ -432,11 +435,13 @@ public:
typedef Schema<SchemaDocumentType> SchemaType; typedef Schema<SchemaDocumentType> SchemaType;
typedef GenericValue<EncodingType, AllocatorType> SValue; typedef GenericValue<EncodingType, AllocatorType> SValue;
typedef IValidationErrorHandler<Schema> ErrorHandler; typedef IValidationErrorHandler<Schema> ErrorHandler;
typedef Uri<SchemaDocumentType> UriType;
friend class GenericSchemaDocument<ValueType, AllocatorType>; friend class GenericSchemaDocument<ValueType, AllocatorType>;
Schema(SchemaDocumentType* schemaDocument, const PointerType& p, const ValueType& value, const ValueType& document, AllocatorType* allocator) : Schema(SchemaDocumentType* schemaDocument, const PointerType& p, const ValueType& value, const ValueType& document, AllocatorType* allocator, const UriType& id = UriType()) :
allocator_(allocator), allocator_(allocator),
uri_(schemaDocument->GetURI(), *allocator), uri_(schemaDocument->GetURI(), *allocator),
id_(id),
pointer_(p, allocator), pointer_(p, allocator),
typeless_(schemaDocument->GetTypeless()), typeless_(schemaDocument->GetTypeless()),
enum_(), enum_(),
@ -474,9 +479,30 @@ public:
typedef typename ValueType::ConstValueIterator ConstValueIterator; typedef typename ValueType::ConstValueIterator ConstValueIterator;
typedef typename ValueType::ConstMemberIterator ConstMemberIterator; typedef typename ValueType::ConstMemberIterator ConstMemberIterator;
// PR #1393
// Early add this Schema and its $ref(s) in schemaDocument's map to avoid infinite
// recursion (with recursive schemas), since schemaDocument->getSchema() is always
// checked before creating a new one. Don't cache typeless_, though.
if (this != typeless_) {
typedef typename SchemaDocumentType::SchemaEntry SchemaEntry;
SchemaEntry *entry = schemaDocument->schemaMap_.template Push<SchemaEntry>();
new (entry) SchemaEntry(pointer_, this, true, allocator_);
schemaDocument->AddSchemaRefs(this);
}
if (!value.IsObject()) if (!value.IsObject())
return; return;
// If we have an id property, resolve it with the in-scope id
if (const ValueType* v = GetMember(value, GetIdString())) {
if (v->IsString()) {
//std::cout << "Resolving local id '" << v->.GetString() << "' with in-scope id '" << id.GetString() << "'" << std::endl;
UriType local = UriType(*v);
local.Resolve(id_);
id_ = local;
}
}
if (const ValueType* v = GetMember(value, GetTypeString())) { if (const ValueType* v = GetMember(value, GetTypeString())) {
type_ = 0; type_ = 0;
if (v->IsString()) if (v->IsString())
@ -507,7 +533,7 @@ public:
} }
if (const ValueType* v = GetMember(value, GetNotString())) { if (const ValueType* v = GetMember(value, GetNotString())) {
schemaDocument->CreateSchema(&not_, p.Append(GetNotString(), allocator_), *v, document); schemaDocument->CreateSchema(&not_, p.Append(GetNotString(), allocator_), *v, document, id_);
notValidatorIndex_ = validatorCount_; notValidatorIndex_ = validatorCount_;
validatorCount_++; validatorCount_++;
} }
@ -524,7 +550,7 @@ public:
if (properties && properties->IsObject()) if (properties && properties->IsObject())
for (ConstMemberIterator itr = properties->MemberBegin(); itr != properties->MemberEnd(); ++itr) for (ConstMemberIterator itr = properties->MemberBegin(); itr != properties->MemberEnd(); ++itr)
AddUniqueElement(allProperties, itr->name); AddUniqueElement(allProperties, itr->name);
if (required && required->IsArray()) if (required && required->IsArray())
for (ConstValueIterator itr = required->Begin(); itr != required->End(); ++itr) for (ConstValueIterator itr = required->Begin(); itr != required->End(); ++itr)
if (itr->IsString()) if (itr->IsString())
@ -555,7 +581,7 @@ public:
for (ConstMemberIterator itr = properties->MemberBegin(); itr != properties->MemberEnd(); ++itr) { for (ConstMemberIterator itr = properties->MemberBegin(); itr != properties->MemberEnd(); ++itr) {
SizeType index; SizeType index;
if (FindPropertyIndex(itr->name, &index)) if (FindPropertyIndex(itr->name, &index))
schemaDocument->CreateSchema(&properties_[index].schema, q.Append(itr->name, allocator_), itr->value, document); schemaDocument->CreateSchema(&properties_[index].schema, q.Append(itr->name, allocator_), itr->value, document, id_);
} }
} }
@ -567,7 +593,7 @@ public:
for (ConstMemberIterator itr = v->MemberBegin(); itr != v->MemberEnd(); ++itr) { for (ConstMemberIterator itr = v->MemberBegin(); itr != v->MemberEnd(); ++itr) {
new (&patternProperties_[patternPropertyCount_]) PatternProperty(); new (&patternProperties_[patternPropertyCount_]) PatternProperty();
patternProperties_[patternPropertyCount_].pattern = CreatePattern(itr->name); patternProperties_[patternPropertyCount_].pattern = CreatePattern(itr->name);
schemaDocument->CreateSchema(&patternProperties_[patternPropertyCount_].schema, q.Append(itr->name, allocator_), itr->value, document); schemaDocument->CreateSchema(&patternProperties_[patternPropertyCount_].schema, q.Append(itr->name, allocator_), itr->value, document, id_);
patternPropertyCount_++; patternPropertyCount_++;
} }
} }
@ -599,7 +625,7 @@ public:
} }
else if (itr->value.IsObject()) { else if (itr->value.IsObject()) {
hasSchemaDependencies_ = true; hasSchemaDependencies_ = true;
schemaDocument->CreateSchema(&properties_[sourceIndex].dependenciesSchema, q.Append(itr->name, allocator_), itr->value, document); schemaDocument->CreateSchema(&properties_[sourceIndex].dependenciesSchema, q.Append(itr->name, allocator_), itr->value, document, id_);
properties_[sourceIndex].dependenciesValidatorIndex = validatorCount_; properties_[sourceIndex].dependenciesValidatorIndex = validatorCount_;
validatorCount_++; validatorCount_++;
} }
@ -611,7 +637,7 @@ public:
if (v->IsBool()) if (v->IsBool())
additionalProperties_ = v->GetBool(); additionalProperties_ = v->GetBool();
else if (v->IsObject()) else if (v->IsObject())
schemaDocument->CreateSchema(&additionalPropertiesSchema_, p.Append(GetAdditionalPropertiesString(), allocator_), *v, document); schemaDocument->CreateSchema(&additionalPropertiesSchema_, p.Append(GetAdditionalPropertiesString(), allocator_), *v, document, id_);
} }
AssignIfExist(minProperties_, value, GetMinPropertiesString()); AssignIfExist(minProperties_, value, GetMinPropertiesString());
@ -621,12 +647,12 @@ public:
if (const ValueType* v = GetMember(value, GetItemsString())) { if (const ValueType* v = GetMember(value, GetItemsString())) {
PointerType q = p.Append(GetItemsString(), allocator_); PointerType q = p.Append(GetItemsString(), allocator_);
if (v->IsObject()) // List validation if (v->IsObject()) // List validation
schemaDocument->CreateSchema(&itemsList_, q, *v, document); schemaDocument->CreateSchema(&itemsList_, q, *v, document, id_);
else if (v->IsArray()) { // Tuple validation else if (v->IsArray()) { // Tuple validation
itemsTuple_ = static_cast<const Schema**>(allocator_->Malloc(sizeof(const Schema*) * v->Size())); itemsTuple_ = static_cast<const Schema**>(allocator_->Malloc(sizeof(const Schema*) * v->Size()));
SizeType index = 0; SizeType index = 0;
for (ConstValueIterator itr = v->Begin(); itr != v->End(); ++itr, index++) for (ConstValueIterator itr = v->Begin(); itr != v->End(); ++itr, index++)
schemaDocument->CreateSchema(&itemsTuple_[itemsTupleCount_++], q.Append(index, allocator_), *itr, document); schemaDocument->CreateSchema(&itemsTuple_[itemsTupleCount_++], q.Append(index, allocator_), *itr, document, id_);
} }
} }
@ -637,7 +663,7 @@ public:
if (v->IsBool()) if (v->IsBool())
additionalItems_ = v->GetBool(); additionalItems_ = v->GetBool();
else if (v->IsObject()) else if (v->IsObject())
schemaDocument->CreateSchema(&additionalItemsSchema_, p.Append(GetAdditionalItemsString(), allocator_), *v, document); schemaDocument->CreateSchema(&additionalItemsSchema_, p.Append(GetAdditionalItemsString(), allocator_), *v, document, id_);
} }
AssignIfExist(uniqueItems_, value, GetUniqueItemsString()); AssignIfExist(uniqueItems_, value, GetUniqueItemsString());
@ -697,7 +723,11 @@ public:
return uri_; return uri_;
} }
const PointerType& GetPointer() const { const UriType& GetId() const {
return id_;
}
const PointerType& GetPointer() const {
return pointer_; return pointer_;
} }
@ -826,7 +856,7 @@ public:
} }
return CreateParallelValidator(context); return CreateParallelValidator(context);
} }
bool Bool(Context& context, bool) const { bool Bool(Context& context, bool) const {
if (!(type_ & (1 << kBooleanSchemaType))) { if (!(type_ & (1 << kBooleanSchemaType))) {
DisallowedType(context, GetBooleanString()); DisallowedType(context, GetBooleanString());
@ -870,13 +900,13 @@ public:
if (!maximum_.IsNull() && !CheckDoubleMaximum(context, d)) if (!maximum_.IsNull() && !CheckDoubleMaximum(context, d))
return false; return false;
if (!multipleOf_.IsNull() && !CheckDoubleMultipleOf(context, d)) if (!multipleOf_.IsNull() && !CheckDoubleMultipleOf(context, d))
return false; return false;
return CreateParallelValidator(context); return CreateParallelValidator(context);
} }
bool String(Context& context, const Ch* str, SizeType length, bool) const { bool String(Context& context, const Ch* str, SizeType length, bool) const {
if (!(type_ & (1 << kStringSchemaType))) { if (!(type_ & (1 << kStringSchemaType))) {
DisallowedType(context, GetStringString()); DisallowedType(context, GetStringString());
@ -925,7 +955,7 @@ public:
return CreateParallelValidator(context); return CreateParallelValidator(context);
} }
bool Key(Context& context, const Ch* str, SizeType len, bool) const { bool Key(Context& context, const Ch* str, SizeType len, bool) const {
if (patternProperties_) { if (patternProperties_) {
context.patternPropertiesSchemaCount = 0; context.patternPropertiesSchemaCount = 0;
@ -1018,7 +1048,7 @@ public:
} }
} }
if (context.error_handler.EndDependencyErrors()) if (context.error_handler.EndDependencyErrors())
RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorDependencies); RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorDependencies);
} }
return true; return true;
@ -1038,12 +1068,12 @@ public:
bool EndArray(Context& context, SizeType elementCount) const { bool EndArray(Context& context, SizeType elementCount) const {
context.inArray = false; context.inArray = false;
if (elementCount < minItems_) { if (elementCount < minItems_) {
context.error_handler.TooFewItems(elementCount, minItems_); context.error_handler.TooFewItems(elementCount, minItems_);
RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorMinItems); RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorMinItems);
} }
if (elementCount > maxItems_) { if (elementCount > maxItems_) {
context.error_handler.TooManyItems(elementCount, maxItems_); context.error_handler.TooManyItems(elementCount, maxItems_);
RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorMaxItems); RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorMaxItems);
@ -1132,6 +1162,15 @@ public:
RAPIDJSON_STRING_(ExclusiveMaximum, 'e', 'x', 'c', 'l', 'u', 's', 'i', 'v', 'e', 'M', 'a', 'x', 'i', 'm', 'u', 'm') RAPIDJSON_STRING_(ExclusiveMaximum, 'e', 'x', 'c', 'l', 'u', 's', 'i', 'v', 'e', 'M', 'a', 'x', 'i', 'm', 'u', 'm')
RAPIDJSON_STRING_(MultipleOf, 'm', 'u', 'l', 't', 'i', 'p', 'l', 'e', 'O', 'f') RAPIDJSON_STRING_(MultipleOf, 'm', 'u', 'l', 't', 'i', 'p', 'l', 'e', 'O', 'f')
RAPIDJSON_STRING_(DefaultValue, 'd', 'e', 'f', 'a', 'u', 'l', 't') RAPIDJSON_STRING_(DefaultValue, 'd', 'e', 'f', 'a', 'u', 'l', 't')
RAPIDJSON_STRING_(Ref, '$', 'r', 'e', 'f')
RAPIDJSON_STRING_(Id, 'i', 'd')
RAPIDJSON_STRING_(SchemeEnd, ':')
RAPIDJSON_STRING_(AuthStart, '/', '/')
RAPIDJSON_STRING_(QueryStart, '?')
RAPIDJSON_STRING_(FragStart, '#')
RAPIDJSON_STRING_(Slash, '/')
RAPIDJSON_STRING_(Dot, '.')
#undef RAPIDJSON_STRING_ #undef RAPIDJSON_STRING_
@ -1197,7 +1236,7 @@ private:
out.schemas = static_cast<const Schema**>(allocator_->Malloc(out.count * sizeof(const Schema*))); out.schemas = static_cast<const Schema**>(allocator_->Malloc(out.count * sizeof(const Schema*)));
memset(out.schemas, 0, sizeof(Schema*)* out.count); memset(out.schemas, 0, sizeof(Schema*)* out.count);
for (SizeType i = 0; i < out.count; i++) for (SizeType i = 0; i < out.count; i++)
schemaDocument.CreateSchema(&out.schemas[i], q.Append(i, allocator_), (*v)[i], document); schemaDocument.CreateSchema(&out.schemas[i], q.Append(i, allocator_), (*v)[i], document, id_);
out.begin = validatorCount_; out.begin = validatorCount_;
validatorCount_ += out.count; validatorCount_ += out.count;
} }
@ -1274,10 +1313,10 @@ private:
if (anyOf_.schemas) if (anyOf_.schemas)
CreateSchemaValidators(context, anyOf_, false); CreateSchemaValidators(context, anyOf_, false);
if (oneOf_.schemas) if (oneOf_.schemas)
CreateSchemaValidators(context, oneOf_, false); CreateSchemaValidators(context, oneOf_, false);
if (not_) if (not_)
context.validators[notValidatorIndex_] = context.factory.CreateSchemaValidator(*not_, false); context.validators[notValidatorIndex_] = context.factory.CreateSchemaValidator(*not_, false);
@ -1301,7 +1340,7 @@ private:
SizeType len = name.GetStringLength(); SizeType len = name.GetStringLength();
const Ch* str = name.GetString(); const Ch* str = name.GetString();
for (SizeType index = 0; index < propertyCount_; index++) 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)) (std::memcmp(properties_[index].name.GetString(), str, sizeof(Ch) * len) == 0))
{ {
*outIndex = index; *outIndex = index;
@ -1462,7 +1501,7 @@ private:
struct PatternProperty { struct PatternProperty {
PatternProperty() : schema(), pattern() {} PatternProperty() : schema(), pattern() {}
~PatternProperty() { ~PatternProperty() {
if (pattern) { if (pattern) {
pattern->~RegexType(); pattern->~RegexType();
AllocatorType::Free(pattern); AllocatorType::Free(pattern);
@ -1474,6 +1513,7 @@ private:
AllocatorType* allocator_; AllocatorType* allocator_;
SValue uri_; SValue uri_;
UriType id_;
PointerType pointer_; PointerType pointer_;
const SchemaType* typeless_; const SchemaType* typeless_;
uint64_t* enum_; uint64_t* enum_;
@ -1516,7 +1556,7 @@ private:
SValue multipleOf_; SValue multipleOf_;
bool exclusiveMinimum_; bool exclusiveMinimum_;
bool exclusiveMaximum_; bool exclusiveMaximum_;
SizeType defaultValueLength_; SizeType defaultValueLength_;
}; };
@ -1552,6 +1592,209 @@ struct TokenHelper<Stack, char> {
} // namespace internal } // namespace internal
///////////////////////////////////////////////////////////////////////////////
// Uri
template <typename SchemaDocumentType>
class Uri {
public:
typedef typename SchemaDocumentType::Ch Ch;
typedef typename SchemaDocumentType::AllocatorType AllocatorType;
typedef internal::Schema<SchemaDocumentType> SchemaType;
typedef std::basic_string<Ch> String;
// Constructors
Uri() {}
Uri(const String& uri) {
Parse(uri);
}
Uri(const Ch* uri, SizeType len) {
Parse(String(uri, len));
}
// Use with specializations of GenericValue
template<typename T> Uri(const T& uri) {
Parse(uri.template Get<String>());
}
// Getters
const String& Get() {
// Create uri_ on-demand
if (uri_.empty()) uri_ = this->GetDoc() + frag_;
return uri_; }
// Use with specializations of GenericValue
template<typename T> void Get(T& uri, AllocatorType& allocator) {
uri.template Set<String>(this->Get(), allocator);
}
const String& GetDoc() {
// Create doc_ on-demand
if (doc_.empty()) doc_ = scheme_ + auth_ + path_ + query_;
return doc_;
}
const String& GetScheme() const { return scheme_; }
const String& GetAuth() const { return auth_; }
const String& GetPath() const { return path_; }
const String& GetQuery() const { return query_; }
const String& GetFrag() const { return frag_; }
const Ch* GetString() { return this->Get().c_str(); }
SizeType GetStringLength() { return static_cast<SizeType>(this->Get().length()); }
const Ch* GetDocString() { return this->GetDoc().c_str(); }
SizeType GetDocStringLength() { return static_cast<SizeType>(this->GetDoc().length()); }
const Ch* GetFragString() const { return frag_.c_str(); }
SizeType GetFragStringLength() const { return static_cast<SizeType>(frag_.length()); }
// Resolve this URI against a base URI in accordance with URI resolution rules at
// https://tools.ietf.org/html/rfc3986
// Use for resolving an id or $ref with an in-scope id.
// This URI is updated in place where needed from the base URI.
Uri& Resolve(const Uri& base)
{
if (!scheme_.empty()) {
// Use all of this URI
RemoveDotSegments(path_);
} else {
if (!auth_.empty()) {
RemoveDotSegments(path_);
} else {
if (path_.empty()) {
path_ = base.GetPath();
if (query_.empty()) {
query_ = base.GetQuery();
}
} else {
static const String slash = SchemaType::GetSlashString().GetString();
if (path_.find(slash) == 0) {
// Absolute path - replace all the path
RemoveDotSegments(path_);
} else {
// Relative path - append to path after last slash
String p;
if (!base.GetAuth().empty() && base.GetPath().empty()) p = slash;
std::size_t lastslashpos = base.GetPath().find_last_of(slash);
path_ = p + base.GetPath().substr(0, lastslashpos + 1) + path_;
RemoveDotSegments(path_);
}
}
auth_ = base.GetAuth();
}
scheme_ = base.GetScheme();
}
//std::cout << " Resolved uri: " << this->GetString() << std::endl;
return *this;
}
private:
// Parse a URI into constituent scheme, authority, path, query, fragment
// Supports URIs that match regex ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))? as per
// https://tools.ietf.org/html/rfc3986
void Parse(const String& uri) {
std::size_t start = 0, pos1 = 0, pos2 = 0;
const std::size_t len = uri.length();
static const String schemeEnd = SchemaType::GetSchemeEndString().GetString();
static const String authStart = SchemaType::GetAuthStartString().GetString();
static const String pathStart = SchemaType::GetSlashString().GetString();
static const String queryStart = SchemaType::GetQueryStartString().GetString();
static const String fragStart = SchemaType::GetFragStartString().GetString();
// Look for scheme ([^:/?#]+):)?
if (start < len) {
pos1 = uri.find(schemeEnd);
if (pos1 != std::string::npos) {
pos2 = uri.find_first_of(pathStart + queryStart + fragStart);
if (pos1 < pos2) {
pos1 += schemeEnd.length();
scheme_ = uri.substr(start, pos1);
start = pos1;
}
}
}
// Look for auth (//([^/?#]*))?
if (start < len) {
pos1 = uri.find(authStart, start);
if (pos1 == start) {
pos2 = uri.find_first_of(pathStart + queryStart + fragStart, start + authStart.length());
auth_ = uri.substr(start, pos2 - start);
start = pos2;
}
}
// Look for path ([^?#]*)
if (start < len) {
pos2 = uri.find_first_of(queryStart + fragStart, start);
if (start != pos2) {
path_ = uri.substr(start, pos2 - start);
if (path_.find(pathStart) == 0) { // absolute path - normalize
RemoveDotSegments(path_);
}
start = pos2;
}
}
// Look for query (\?([^#]*))?
if (start < len) {
pos2 = uri.find(fragStart, start);
if (start != pos2) {
query_ = uri.substr(start, pos2 - start);
start = pos2;
}
}
// Look for fragment (#(.*))?
if (start < len) {
frag_ = uri.substr(start);
}
//std::cout << " Parsed uri: " << "s: " << scheme_.c_str() << " a: " << auth_.c_str() << " p: " << path_.c_str() << " q: " << query_.c_str() << " f: " << frag_.c_str() << std::endl;
}
// Remove . and .. segments from a path
// https://tools.ietf.org/html/rfc3986
void RemoveDotSegments(String& path) {
String temp = path;
path.clear();
static const String slash = SchemaType::GetSlashString().GetString();
static const String dot = SchemaType::GetDotString().GetString();
std::size_t pos = 0;
// Loop through each path segment
while (pos != std::string::npos) {
//std::cout << "Temp: '" << temp.c_str() << "' Path: '" << path.c_str() << "'" << std::endl;
pos = temp.find_first_of(slash);
// Get next segment
String seg = temp.substr(0, pos);
if (seg == dot) {
// Discard . segment
} else if (seg == dot + dot) {
// Backup a .. segment
// We expect to find a previously added slash at the end or nothing
std::size_t pos1 = path.find_last_of(slash);
// Make sure we don't go beyond the start
if (pos1 != std::string::npos && pos1 != 0) {
// Find the next to last slash and back up to it
pos1 = path.find_last_of(slash, pos1 - 1);
path = path.substr(0, pos1 + 1);
}
} else {
// Copy segment and add slash if not at end
path += seg;
if (pos != std::string::npos) path += slash;
}
// Move to next segment if not at end
if (pos != std::string::npos) temp = temp.substr(pos + 1);
}
//std::cout << "Final Temp: '" << temp.c_str() << "' Final Path: '" << path.c_str() << "'" << std::endl;
}
String uri_; // Created on-demand
String doc_; // Created on-demand
String scheme_; // Includes the :
String auth_; // Includes the //
String path_; // Absolute if starts with /
String query_; // Includes the ?
String frag_; // Includes the #
};
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// IGenericRemoteSchemaDocumentProvider // IGenericRemoteSchemaDocumentProvider
@ -1562,6 +1805,7 @@ public:
virtual ~IGenericRemoteSchemaDocumentProvider() {} virtual ~IGenericRemoteSchemaDocumentProvider() {}
virtual const SchemaDocumentType* GetRemoteDocument(const Ch* uri, SizeType length) = 0; virtual const SchemaDocumentType* GetRemoteDocument(const Ch* uri, SizeType length) = 0;
virtual const SchemaDocumentType* GetRemoteDocument(Uri<SchemaDocumentType> uri) { return GetRemoteDocument(uri.GetDocString(), uri.GetDocStringLength()); }
}; };
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
@ -1586,7 +1830,8 @@ public:
typedef typename EncodingType::Ch Ch; typedef typename EncodingType::Ch Ch;
typedef internal::Schema<GenericSchemaDocument> SchemaType; typedef internal::Schema<GenericSchemaDocument> SchemaType;
typedef GenericPointer<ValueType, Allocator> PointerType; typedef GenericPointer<ValueType, Allocator> PointerType;
typedef GenericValue<EncodingType, Allocator> URIType; typedef GenericValue<EncodingType, AllocatorType> SValue;
typedef Uri<GenericSchemaDocument> UriType;
friend class internal::Schema<GenericSchemaDocument>; friend class internal::Schema<GenericSchemaDocument>;
template <typename, typename, typename> template <typename, typename, typename>
friend class GenericSchemaValidator; friend class GenericSchemaValidator;
@ -1600,9 +1845,11 @@ public:
\param uriLength Length of \c name, in code points. \param uriLength Length of \c name, in code points.
\param remoteProvider An optional remote schema document provider for resolving remote reference. Can be null. \param remoteProvider An optional remote schema document provider for resolving remote reference. Can be null.
\param allocator An optional allocator instance for allocating memory. Can be null. \param allocator An optional allocator instance for allocating memory. Can be null.
\param pointer An optional JSON pointer to the start of the schema document
*/ */
explicit GenericSchemaDocument(const ValueType& document, const Ch* uri = 0, SizeType uriLength = 0, explicit GenericSchemaDocument(const ValueType& document, const Ch* uri = 0, SizeType uriLength = 0,
IRemoteSchemaDocumentProviderType* remoteProvider = 0, Allocator* allocator = 0) : IRemoteSchemaDocumentProviderType* remoteProvider = 0, Allocator* allocator = 0,
const PointerType& pointer = PointerType() : // PR #1393
remoteProvider_(remoteProvider), remoteProvider_(remoteProvider),
allocator_(allocator), allocator_(allocator),
ownAllocator_(), ownAllocator_(),
@ -1616,30 +1863,20 @@ public:
Ch noUri[1] = {0}; Ch noUri[1] = {0};
uri_.SetString(uri ? uri : noUri, uriLength, *allocator_); uri_.SetString(uri ? uri : noUri, uriLength, *allocator_);
UriType baseId(uri_);
typeless_ = static_cast<SchemaType*>(allocator_->Malloc(sizeof(SchemaType))); typeless_ = static_cast<SchemaType*>(allocator_->Malloc(sizeof(SchemaType)));
new (typeless_) SchemaType(this, PointerType(), ValueType(kObjectType).Move(), ValueType(kObjectType).Move(), allocator_); new (typeless_) SchemaType(this, PointerType(), ValueType(kObjectType).Move(), ValueType(kObjectType).Move(), allocator_, baseId);
// Generate root schema, it will call CreateSchema() to create sub-schemas, // Generate root schema, it will call CreateSchema() to create sub-schemas,
// And call AddRefSchema() if there are $ref. // And call HandleRefSchema() if there are $ref.
CreateSchemaRecursive(&root_, PointerType(), document, document); // PR #1393 use input pointer if supplied
root_ = typeless_;
// Resolve $ref if (pointer.GetTokenCount() == 0) {
while (!schemaRef_.Empty()) { CreateSchemaRecursive(&root_, pointer, document, document, baseId);
SchemaRefEntry* refEntry = schemaRef_.template Pop<SchemaRefEntry>(1); }
if (const SchemaType* s = GetSchema(refEntry->target)) { else if (const ValueType* v = pointer.Get(document)) {
if (refEntry->schema) CreateSchema(&root_, pointer, *v, document, baseId);
*refEntry->schema = s;
// Create entry in map if not exist
if (!GetSchema(refEntry->source)) {
new (schemaMap_.template Push<SchemaEntry>()) SchemaEntry(refEntry->source, const_cast<SchemaType*>(s), false, allocator_);
}
}
else if (refEntry->schema)
*refEntry->schema = typeless_;
refEntry->~SchemaRefEntry();
} }
RAPIDJSON_ASSERT(root_ != 0); RAPIDJSON_ASSERT(root_ != 0);
@ -1679,7 +1916,7 @@ public:
RAPIDJSON_DELETE(ownAllocator_); RAPIDJSON_DELETE(ownAllocator_);
} }
const URIType& GetURI() const { return uri_; } const SValue& GetURI() const { return uri_; }
//! Get the root schema. //! Get the root schema.
const SchemaType& GetRoot() const { return *root_; } const SchemaType& GetRoot() const { return *root_; }
@ -1690,12 +1927,7 @@ private:
//! Prohibit assignment //! Prohibit assignment
GenericSchemaDocument& operator=(const GenericSchemaDocument&); GenericSchemaDocument& operator=(const GenericSchemaDocument&);
struct SchemaRefEntry { typedef const PointerType* SchemaRefPtr; // PR #1393
SchemaRefEntry(const PointerType& s, const PointerType& t, const SchemaType** outSchema, Allocator *allocator) : source(s, allocator), target(t, allocator), schema(outSchema) {}
PointerType source;
PointerType target;
const SchemaType** schema;
};
struct SchemaEntry { struct SchemaEntry {
SchemaEntry(const PointerType& p, SchemaType* s, bool o, Allocator* allocator) : pointer(p, allocator), schema(s), owned(o) {} SchemaEntry(const PointerType& p, SchemaType* s, bool o, Allocator* allocator) : pointer(p, allocator), schema(s), owned(o) {}
@ -1710,79 +1942,153 @@ private:
bool owned; bool owned;
}; };
void CreateSchemaRecursive(const SchemaType** schema, const PointerType& pointer, const ValueType& v, const ValueType& document) { // Changed by PR #1393
if (schema) void CreateSchemaRecursive(const SchemaType** schema, const PointerType& pointer, const ValueType& v, const ValueType& document, const UriType& id) {
*schema = typeless_;
if (v.GetType() == kObjectType) { if (v.GetType() == kObjectType) {
const SchemaType* s = GetSchema(pointer); CreateSchema(schema, pointer, v, document, id);
if (!s)
CreateSchema(schema, pointer, v, document);
for (typename ValueType::ConstMemberIterator itr = v.MemberBegin(); itr != v.MemberEnd(); ++itr) for (typename ValueType::ConstMemberIterator itr = v.MemberBegin(); itr != v.MemberEnd(); ++itr)
CreateSchemaRecursive(0, pointer.Append(itr->name, allocator_), itr->value, document); CreateSchemaRecursive(0, pointer.Append(itr->name, allocator_), itr->value, document, id);
} }
else if (v.GetType() == kArrayType) else if (v.GetType() == kArrayType)
for (SizeType i = 0; i < v.Size(); i++) for (SizeType i = 0; i < v.Size(); i++)
CreateSchemaRecursive(0, pointer.Append(i, allocator_), v[i], document); CreateSchemaRecursive(0, pointer.Append(i, allocator_), v[i], document, id);
} }
void CreateSchema(const SchemaType** schema, const PointerType& pointer, const ValueType& v, const ValueType& document) { // Changed by PR #1393
void CreateSchema(const SchemaType** schema, const PointerType& pointer, const ValueType& v, const ValueType& document, const UriType& id) {
RAPIDJSON_ASSERT(pointer.IsValid()); RAPIDJSON_ASSERT(pointer.IsValid());
if (v.IsObject()) { if (v.IsObject()) {
if (!HandleRefSchema(pointer, schema, v, document)) { if (const SchemaType* sc = GetSchema(pointer)) {
SchemaType* s = new (allocator_->Malloc(sizeof(SchemaType))) SchemaType(this, pointer, v, document, allocator_); if (schema)
new (schemaMap_.template Push<SchemaEntry>()) SchemaEntry(pointer, s, true, allocator_); *schema = sc;
//std::cout << "Using Schema with id " << sc->GetId().GetString() << std::endl;
AddSchemaRefs(const_cast<SchemaType*>(sc));
}
else if (!HandleRefSchema(pointer, schema, v, document, id)) {
// The new schema adds itself and its $ref(s) to schemaMap_
SchemaType* s = new (allocator_->Malloc(sizeof(SchemaType))) SchemaType(this, pointer, v, document, allocator_, id);
if (schema) if (schema)
*schema = s; *schema = s;
} }
} }
else {
if (schema)
*schema = typeless_;
AddSchemaRefs(typeless_);
}
} }
bool HandleRefSchema(const PointerType& source, const SchemaType** schema, const ValueType& v, const ValueType& document) { // Changed by PR #1393
static const Ch kRefString[] = { '$', 'r', 'e', 'f', '\0' }; bool HandleRefSchema(const PointerType& source, const SchemaType** schema, const ValueType& v, const ValueType& document, const UriType& id) {
static const ValueType kRefValue(kRefString, 4); //std::cout << "HandleRefSchema called with id " << id.GetString() << std::endl;
typename ValueType::ConstMemberIterator itr = v.FindMember(SchemaType::GetRefString());
typename ValueType::ConstMemberIterator itr = v.FindMember(kRefValue);
if (itr == v.MemberEnd()) if (itr == v.MemberEnd())
return false; return false;
// Resolve the source pointer to the $ref'ed schema (finally)
new (schemaRef_.template Push<SchemaRefPtr>()) SchemaRefPtr(&source);
if (itr->value.IsString()) { if (itr->value.IsString()) {
SizeType len = itr->value.GetStringLength(); SizeType len = itr->value.GetStringLength();
if (len > 0) { if (len > 0) {
const Ch* s = itr->value.GetString(); const Ch* s = itr->value.GetString();
SizeType i = 0; if (s[0] != '#') { // Remote reference - resolve $ref against the in-scope id
while (i < len && s[i] != '#') // Find the first #
i++;
if (i > 0) { // Remote reference, resolve immediately
if (remoteProvider_) { if (remoteProvider_) {
if (const GenericSchemaDocument* remoteDocument = remoteProvider_->GetRemoteDocument(s, i)) { UriType ref = UriType(itr->value);
PointerType pointer(&s[i], len - i, allocator_); ref.Resolve(id);
//std::cout << "Resolved $ref '" << s << "' against in-scope id '" << id.GetString() << "' giving '" << ref.GetDocString() << "'" << std::endl;
if (const GenericSchemaDocument* remoteDocument = remoteProvider_->GetRemoteDocument(ref)) {
// Create a pointer from the # onwards
const PointerType pointer(ref.GetFragString(), ref.GetFragStringLength(), allocator_);
if (pointer.IsValid()) { if (pointer.IsValid()) {
if (const SchemaType* sc = remoteDocument->GetSchema(pointer)) { if (const SchemaType* sc = remoteDocument->GetSchema(pointer)) {
if (schema) if (schema)
*schema = sc; *schema = sc;
new (schemaMap_.template Push<SchemaEntry>()) SchemaEntry(source, const_cast<SchemaType*>(sc), false, allocator_); AddSchemaRefs(const_cast<SchemaType*>(sc));
return true; return true;
} }
} }
} }
} }
} }
else if (s[i] == '#') { // Local reference, defer resolution else { // Local reference
PointerType pointer(&s[i], len - i, allocator_); if (len == 1 || s[1] == '/') {
if (pointer.IsValid()) { // JSON pointer
if (const ValueType* nv = pointer.Get(document)) const PointerType pointer(s, len, allocator_);
if (HandleRefSchema(source, schema, *nv, document)) if (pointer.IsValid() && !IsCyclicRef(pointer)) {
return true; if (const ValueType *nv = pointer.Get(document)) {
CreateSchema(schema, pointer, *nv, document, id);
new (schemaRef_.template Push<SchemaRefEntry>()) SchemaRefEntry(source, pointer, schema, allocator_);
return true; return true;
}
} }
} else {
// Internal reference to an id
const ValueType val(s, len);
PointerType pointer = PointerType();
ValueType *nv = FindId(document, val, pointer);
if (nv && !IsCyclicRef(pointer)) {
CreateSchema(schema, pointer, *nv, document, id);
return true;
}
}
} }
} }
} }
// Invalid/Unknown $ref
if (schema)
*schema = typeless_;
AddSchemaRefs(typeless_);
return true;
}
//! Find the first subschema with 'id' string property matching the specified value.
// Return a pointer to the subschema and its JSON pointer.
ValueType* FindId(const ValueType& doc, const ValueType& findval, PointerType& resptr, const PointerType& here = PointerType()) const {
SizeType i = 0;
ValueType* resval = 0;
switch(doc.GetType()) {
case kObjectType:
for (typename ValueType::ConstMemberIterator m = doc.MemberBegin(); m != doc.MemberEnd(); ++m) {
if (m->name == SchemaType::GetIdString() && m->value.GetType() == kStringType && m->value == findval) {
// Found the 'id' with the value
resval = const_cast<ValueType*>(&doc);
resptr = here;
} else if (m->value.GetType() == kObjectType || m->value.GetType() == kArrayType) {
resval = FindId(m->value, findval, resptr, here.Append(m->name.GetString(), m->name.GetStringLength(), allocator_));
}
if (resval) break;
}
return resval;
case kArrayType:
for (typename ValueType::ConstValueIterator v = doc.Begin(); v != doc.End(); ++v) {
if (v->GetType() == kObjectType || v->GetType() == kArrayType) {
resval = FindId(*v, findval, resptr, here.Append(i, allocator_));
}
if (resval) break;
i++;
}
return resval;
default:
return resval;
}
}
// Added by PR #1393
void AddSchemaRefs(SchemaType* schema) {
while (!schemaRef_.Empty()) {
SchemaRefPtr *ref = schemaRef_.template Pop<SchemaRefPtr>(1);
SchemaEntry *entry = schemaMap_.template Push<SchemaEntry>();
new (entry) SchemaEntry(**ref, schema, false, allocator_);
}
}
// Added by PR #1393
bool IsCyclicRef(const PointerType& pointer) const {
for (const SchemaRefPtr* ref = schemaRef_.template Bottom<SchemaRefPtr>(); ref != schemaRef_.template End<SchemaRefPtr>(); ++ref)
if (pointer == **ref)
return true;
return false; return false;
} }
@ -1811,8 +2117,8 @@ private:
const SchemaType* root_; //!< Root schema. const SchemaType* root_; //!< Root schema.
SchemaType* typeless_; SchemaType* typeless_;
internal::Stack<Allocator> schemaMap_; // Stores created Pointer -> Schemas internal::Stack<Allocator> schemaMap_; // Stores created Pointer -> Schemas
internal::Stack<Allocator> schemaRef_; // Stores Pointer from $ref and schema which holds the $ref internal::Stack<Allocator> schemaRef_; // Stores Pointer(s) from $ref(s) until resolved
URIType uri_; SValue uri_;
}; };
//! GenericSchemaDocument using Value type. //! GenericSchemaDocument using Value type.

View File

@ -13,6 +13,7 @@
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
#define RAPIDJSON_SCHEMA_VERBOSE 0 #define RAPIDJSON_SCHEMA_VERBOSE 0
#define RAPIDJSON_HAS_STDSTRING 1
#include "unittest.h" #include "unittest.h"
#include "rapidjson/schema.h" #include "rapidjson/schema.h"
@ -1811,6 +1812,189 @@ TEST(SchemaValidator, EscapedPointer) {
"}}"); "}}");
} }
TEST(SchemaValidator, SchemaPointer) {
Document sd;
sd.Parse(
"{"
" \"swagger\": \"2.0\","
" \"paths\": {"
" \"/some/path\": {"
" \"post\": {"
" \"parameters\": ["
" {"
" \"in\": \"body\","
" \"name\": \"body\","
" \"schema\": {"
" \"properties\": {"
" \"a\": {"
" \"$ref\": \"#/definitions/Prop_a\""
" },"
" \"b\": {"
" \"type\": \"integer\""
" }"
" },"
" \"type\": \"object\""
" }"
" }"
" ],"
" \"responses\": {"
" \"200\": {"
" \"schema\": {"
" \"$ref\": \"#/definitions/Resp_200\""
" }"
" }"
" }"
" }"
" }"
" },"
" \"definitions\": {"
" \"Prop_a\": {"
" \"properties\": {"
" \"c\": {"
" \"enum\": ["
" \"C1\","
" \"C2\","
" \"C3\""
" ],"
" \"type\": \"string\""
" },"
" \"d\": {"
" \"$ref\": \"#/definitions/Prop_d\""
" },"
" \"s\": {"
" \"type\": \"string\""
" }"
" },"
" \"required\": [\"c\"],"
" \"type\": \"object\""
" },"
" \"Prop_d\": {"
" \"properties\": {"
" \"a\": {"
" \"$ref\": \"#/definitions/Prop_a\""
" },"
" \"c\": {"
" \"$ref\": \"#/definitions/Prop_a/properties/c\""
" }"
" },"
" \"type\": \"object\""
" },"
" \"Resp_200\": {"
" \"properties\": {"
" \"e\": {"
" \"type\": \"string\""
" },"
" \"f\": {"
" \"type\": \"boolean\""
" },"
" \"cyclic_source\": {"
" \"$ref\": \"#/definitions/Resp_200/properties/cyclic_target\""
" },"
" \"cyclic_target\": {"
" \"$ref\": \"#/definitions/Resp_200/properties/cyclic_source\""
" }"
" },"
" \"type\": \"object\""
" }"
" }"
"}");
SchemaDocument s1(sd, NULL, 0, NULL, NULL, Pointer("#/paths/~1some~1path/post/parameters/0/schema"));
VALIDATE(s1,
"{"
" \"a\": {"
" \"c\": \"C1\","
" \"d\": {"
" \"a\": {"
" \"c\": \"C2\""
" },"
" \"c\": \"C3\""
" }"
" },"
" \"b\": 123"
"}",
true);
INVALIDATE(s1,
"{"
" \"a\": {"
" \"c\": \"C1\","
" \"d\": {"
" \"a\": {"
" \"c\": \"C2\""
" },"
" \"c\": \"C3\""
" }"
" },"
" \"b\": \"should be an int\""
"}",
"#/paths/~1some~1path/post/parameters/0/schema/properties/b", "type", "#/b",
"{ \"type\": {"
" \"errorCode\": 20,"
" \"instanceRef\":\"#/b\","
" \"schemaRef\":\"#/paths/~1some~1path/post/parameters/0/schema/properties/b\","
" \"expected\": [\"integer\"], \"actual\":\"string\""
"}}");
INVALIDATE(s1,
"{"
" \"a\": {"
" \"c\": \"C1\","
" \"d\": {"
" \"a\": {"
" \"c\": \"should be within enum\""
" },"
" \"c\": \"C3\""
" }"
" },"
" \"b\": 123"
"}",
"#/definitions/Prop_a/properties/c", "enum", "#/a/d/a/c",
"{ \"enum\": {"
" \"errorCode\": 19,"
" \"instanceRef\":\"#/a/d/a/c\","
" \"schemaRef\":\"#/definitions/Prop_a/properties/c\""
"}}");
INVALIDATE(s1,
"{"
" \"a\": {"
" \"c\": \"C1\","
" \"d\": {"
" \"a\": {"
" \"s\": \"required 'c' is missing\""
" }"
" }"
" },"
" \"b\": 123"
"}",
"#/definitions/Prop_a", "required", "#/a/d/a",
"{ \"required\": {"
" \"errorCode\": 15,"
" \"missing\":[\"c\"],"
" \"instanceRef\":\"#/a/d/a\","
" \"schemaRef\":\"#/definitions/Prop_a\""
"}}");
SchemaDocument s2(sd, NULL, 0, NULL, NULL, Pointer("#/paths/~1some~1path/post/responses/200/schema"));
VALIDATE(s2,
"{ \"e\": \"some string\", \"f\": false }",
true);
INVALIDATE(s2,
"{ \"e\": true, \"f\": false }",
"#/definitions/Resp_200/properties/e", "type", "#/e",
"{ \"type\": {"
" \"errorCode\": 20,"
" \"instanceRef\":\"#/e\","
" \"schemaRef\":\"#/definitions/Resp_200/properties/e\","
" \"expected\": [\"string\"], \"actual\":\"boolean\""
"}}");
INVALIDATE(s2,
"{ \"e\": \"some string\", \"f\": 123 }",
"#/definitions/Resp_200/properties/f", "type", "#/f",
"{ \"type\": {"
" \"errorCode\": 20,"
" \"instanceRef\":\"#/f\","
" \"schemaRef\":\"#/definitions/Resp_200/properties/f\","
" \"expected\": [\"boolean\"], \"actual\":\"integer\""
"}}");
}
template <typename Allocator> template <typename Allocator>
static char* ReadFile(const char* filename, Allocator& allocator) { static char* ReadFile(const char* filename, Allocator& allocator) {
const char *paths[] = { const char *paths[] = {
@ -1952,7 +2136,7 @@ public:
virtual const SchemaDocumentType* GetRemoteDocument(const char* uri, SizeType length) { virtual const SchemaDocumentType* GetRemoteDocument(const char* uri, SizeType length) {
for (size_t i = 0; i < kCount; i++) for (size_t i = 0; i < kCount; i++)
if (typename SchemaDocumentType::URIType(uri, length) == sd_[i]->GetURI()) if (typename SchemaDocumentType::SType(uri, length) == sd_[i]->GetURI())
return sd_[i]; return sd_[i];
return 0; return 0;
} }
@ -2032,7 +2216,7 @@ TEST(SchemaValidator, TestSuite) {
ADD_FAILURE(); ADD_FAILURE();
} }
else { else {
//printf("json test suite file %s parsed ok\n", filename); //printf("\njson test suite file %s parsed ok\n", filename);
GenericDocument<UTF8<>, MemoryPoolAllocator<>, MemoryPoolAllocator<> > d(&documentAllocator, 1024, &documentStackAllocator); GenericDocument<UTF8<>, MemoryPoolAllocator<>, MemoryPoolAllocator<> > d(&documentAllocator, 1024, &documentStackAllocator);
d.Parse(json); d.Parse(json);
if (d.HasParseError()) { if (d.HasParseError()) {
@ -2042,12 +2226,14 @@ TEST(SchemaValidator, TestSuite) {
else { else {
for (Value::ConstValueIterator schemaItr = d.Begin(); schemaItr != d.End(); ++schemaItr) { for (Value::ConstValueIterator schemaItr = d.Begin(); schemaItr != d.End(); ++schemaItr) {
{ {
const char* description1 = (*schemaItr)["description"].GetString();
//printf("\ncompiling schema for json test %s \n", description1);
SchemaDocumentType schema((*schemaItr)["schema"], filenames[i], static_cast<SizeType>(strlen(filenames[i])), &provider, &schemaAllocator); SchemaDocumentType schema((*schemaItr)["schema"], filenames[i], static_cast<SizeType>(strlen(filenames[i])), &provider, &schemaAllocator);
GenericSchemaValidator<SchemaDocumentType, BaseReaderHandler<UTF8<> >, MemoryPoolAllocator<> > validator(schema, &validatorAllocator); GenericSchemaValidator<SchemaDocumentType, BaseReaderHandler<UTF8<> >, MemoryPoolAllocator<> > validator(schema, &validatorAllocator);
const char* description1 = (*schemaItr)["description"].GetString();
const Value& tests = (*schemaItr)["tests"]; const Value& tests = (*schemaItr)["tests"];
for (Value::ConstValueIterator testItr = tests.Begin(); testItr != tests.End(); ++testItr) { for (Value::ConstValueIterator testItr = tests.Begin(); testItr != tests.End(); ++testItr) {
const char* description2 = (*testItr)["description"].GetString(); const char* description2 = (*testItr)["description"].GetString();
//printf("running json test %s \n", description2);
if (!onlyRunDescription || strcmp(description2, onlyRunDescription) == 0) { if (!onlyRunDescription || strcmp(description2, onlyRunDescription) == 0) {
const Value& data = (*testItr)["data"]; const Value& data = (*testItr)["data"];
bool expected = (*testItr)["valid"].GetBool(); bool expected = (*testItr)["valid"].GetBool();
@ -2075,8 +2261,8 @@ TEST(SchemaValidator, TestSuite) {
jsonAllocator.Clear(); jsonAllocator.Clear();
} }
printf("%d / %d passed (%2d%%)\n", passCount, testCount, passCount * 100 / testCount); printf("%d / %d passed (%2d%%)\n", passCount, testCount, passCount * 100 / testCount);
// if (passCount != testCount) if (passCount != testCount)
// ADD_FAILURE(); ADD_FAILURE();
} }
TEST(SchemaValidatingReader, Simple) { TEST(SchemaValidatingReader, Simple) {
@ -2244,6 +2430,154 @@ TEST(SchemaValidator, Ref_remote) {
kValidateDefaultFlags, SchemaValidatorType, PointerType); kValidateDefaultFlags, SchemaValidatorType, PointerType);
} }
// Merge with id where $ref is full URI
TEST(SchemaValidator, Ref_remote_change_resolution_scope_uri) {
typedef GenericSchemaDocument<Value, MemoryPoolAllocator<> > SchemaDocumentType;
RemoteSchemaDocumentProvider<SchemaDocumentType> provider;
Document sd;
sd.Parse("{\"id\": \"http://ignore/blah#/ref\", \"type\": \"object\", \"properties\": {\"myInt\": {\"$ref\": \"http://localhost:1234/subSchemas.json#/integer\"}}}");
SchemaDocumentType s(sd, 0, 0, &provider);
typedef GenericSchemaValidator<SchemaDocumentType, BaseReaderHandler<UTF8<> >, MemoryPoolAllocator<> > SchemaValidatorType;
typedef GenericPointer<Value, MemoryPoolAllocator<> > PointerType;
INVALIDATE_(s, "{\"myInt\": null}", "/integer", "type", "/myInt",
"{ \"type\": {"
" \"errorCode\": 20,"
" \"instanceRef\": \"#/myInt\","
" \"schemaRef\": \"http://localhost:1234/subSchemas.json#/integer\","
" \"expected\": [\"integer\"], \"actual\": \"null\""
"}}",
kValidateDefaultFlags, SchemaValidatorType, PointerType);
}
// Merge with id where $ref is a relative path
TEST(SchemaValidator, Ref_remote_change_resolution_scope_relative_path) {
typedef GenericSchemaDocument<Value, MemoryPoolAllocator<> > SchemaDocumentType;
RemoteSchemaDocumentProvider<SchemaDocumentType> provider;
Document sd;
sd.Parse("{\"id\": \"http://localhost:1234/\", \"type\": \"object\", \"properties\": {\"myInt\": {\"$ref\": \"subSchemas.json#/integer\"}}}");
SchemaDocumentType s(sd, 0, 0, &provider);
typedef GenericSchemaValidator<SchemaDocumentType, BaseReaderHandler<UTF8<> >, MemoryPoolAllocator<> > SchemaValidatorType;
typedef GenericPointer<Value, MemoryPoolAllocator<> > PointerType;
INVALIDATE_(s, "{\"myInt\": null}", "/integer", "type", "/myInt",
"{ \"type\": {"
" \"errorCode\": 20,"
" \"instanceRef\": \"#/myInt\","
" \"schemaRef\": \"http://localhost:1234/subSchemas.json#/integer\","
" \"expected\": [\"integer\"], \"actual\": \"null\""
"}}",
kValidateDefaultFlags, SchemaValidatorType, PointerType);
}
// Merge with id where $ref is an absolute path
TEST(SchemaValidator, Ref_remote_change_resolution_scope_absolute_path) {
typedef GenericSchemaDocument<Value, MemoryPoolAllocator<> > SchemaDocumentType;
RemoteSchemaDocumentProvider<SchemaDocumentType> provider;
Document sd;
sd.Parse("{\"id\": \"http://localhost:1234/xxxx\", \"type\": \"object\", \"properties\": {\"myInt\": {\"$ref\": \"/subSchemas.json#/integer\"}}}");
SchemaDocumentType s(sd, 0, 0, &provider);
typedef GenericSchemaValidator<SchemaDocumentType, BaseReaderHandler<UTF8<> >, MemoryPoolAllocator<> > SchemaValidatorType;
typedef GenericPointer<Value, MemoryPoolAllocator<> > PointerType;
INVALIDATE_(s, "{\"myInt\": null}", "/integer", "type", "/myInt",
"{ \"type\": {"
" \"errorCode\": 20,"
" \"instanceRef\": \"#/myInt\","
" \"schemaRef\": \"http://localhost:1234/subSchemas.json#/integer\","
" \"expected\": [\"integer\"], \"actual\": \"null\""
"}}",
kValidateDefaultFlags, SchemaValidatorType, PointerType);
}
// Merge with id where $ref is an absolute path, and the document has a base URI
TEST(SchemaValidator, Ref_remote_change_resolution_scope_absolute_path_document) {
typedef GenericSchemaDocument<Value, MemoryPoolAllocator<> > SchemaDocumentType;
RemoteSchemaDocumentProvider<SchemaDocumentType> provider;
Document sd;
sd.Parse("{\"type\": \"object\", \"properties\": {\"myInt\": {\"$ref\": \"/subSchemas.json#/integer\"}}}");
SchemaDocumentType s(sd, "http://localhost:1234/xxxx", 26, &provider);
typedef GenericSchemaValidator<SchemaDocumentType, BaseReaderHandler<UTF8<> >, MemoryPoolAllocator<> > SchemaValidatorType;
typedef GenericPointer<Value, MemoryPoolAllocator<> > PointerType;
INVALIDATE_(s, "{\"myInt\": null}", "/integer", "type", "/myInt",
"{ \"type\": {"
" \"errorCode\": 20,"
" \"instanceRef\": \"#/myInt\","
" \"schemaRef\": \"http://localhost:1234/subSchemas.json#/integer\","
" \"expected\": [\"integer\"], \"actual\": \"null\""
"}}",
kValidateDefaultFlags, SchemaValidatorType, PointerType);
}
// $ref is a non-JSON pointer fragment and there a matching id
TEST(SchemaValidator, Ref_internal_id_1) {
typedef GenericSchemaDocument<Value, MemoryPoolAllocator<> > SchemaDocumentType;
Document sd;
sd.Parse("{\"type\": \"object\", \"properties\": {\"myInt1\": {\"$ref\": \"#myId\"}, \"myStr\": {\"type\": \"string\", \"id\": \"#myStrId\"}, \"myInt2\": {\"type\": \"integer\", \"id\": \"#myId\"}}}");
SchemaDocumentType s(sd);
typedef GenericSchemaValidator<SchemaDocumentType, BaseReaderHandler<UTF8<> >, MemoryPoolAllocator<> > SchemaValidatorType;
typedef GenericPointer<Value, MemoryPoolAllocator<> > PointerType;
INVALIDATE_(s, "{\"myInt1\": null}", "/properties/myInt2", "type", "/myInt1",
"{ \"type\": {"
" \"errorCode\": 20,"
" \"instanceRef\": \"#/myInt1\","
" \"schemaRef\": \"#/properties/myInt2\","
" \"expected\": [\"integer\"], \"actual\": \"null\""
"}}",
kValidateDefaultFlags, SchemaValidatorType, PointerType);
}
// $ref is a non-JSON pointer fragment and there are two matching ids so we take the first
TEST(SchemaValidator, Ref_internal_id_2) {
typedef GenericSchemaDocument<Value, MemoryPoolAllocator<> > SchemaDocumentType;
Document sd;
sd.Parse("{\"type\": \"object\", \"properties\": {\"myInt1\": {\"$ref\": \"#myId\"}, \"myInt2\": {\"type\": \"integer\", \"id\": \"#myId\"}, \"myStr\": {\"type\": \"string\", \"id\": \"#myId\"}}}");
SchemaDocumentType s(sd);
typedef GenericSchemaValidator<SchemaDocumentType, BaseReaderHandler<UTF8<> >, MemoryPoolAllocator<> > SchemaValidatorType;
typedef GenericPointer<Value, MemoryPoolAllocator<> > PointerType;
INVALIDATE_(s, "{\"myInt1\": null}", "/properties/myInt2", "type", "/myInt1",
"{ \"type\": {"
" \"errorCode\": 20,"
" \"instanceRef\": \"#/myInt1\","
" \"schemaRef\": \"#/properties/myInt2\","
" \"expected\": [\"integer\"], \"actual\": \"null\""
"}}",
kValidateDefaultFlags, SchemaValidatorType, PointerType);
}
// $ref is a non-JSON pointer fragment and there is a matching id within array
TEST(SchemaValidator, Ref_internal_id_in_array) {
typedef GenericSchemaDocument<Value, MemoryPoolAllocator<> > SchemaDocumentType;
Document sd;
sd.Parse("{\"type\": \"object\", \"properties\": {\"myInt1\": {\"$ref\": \"#myId\"}, \"myInt2\": {\"anyOf\": [{\"type\": \"string\", \"id\": \"#myStrId\"}, {\"type\": \"integer\", \"id\": \"#myId\"}]}}}");
SchemaDocumentType s(sd);
typedef GenericSchemaValidator<SchemaDocumentType, BaseReaderHandler<UTF8<> >, MemoryPoolAllocator<> > SchemaValidatorType;
typedef GenericPointer<Value, MemoryPoolAllocator<> > PointerType;
INVALIDATE_(s, "{\"myInt1\": null}", "/properties/myInt2/anyOf/1", "type", "/myInt1",
"{ \"type\": {"
" \"errorCode\": 20,"
" \"instanceRef\": \"#/myInt1\","
" \"schemaRef\": \"#/properties/myInt2/anyOf/1\","
" \"expected\": [\"integer\"], \"actual\": \"null\""
"}}",
kValidateDefaultFlags, SchemaValidatorType, PointerType);
}
// $ref is a non-JSON pointer fragment and there is a matching id, and the schema is embedded in the document
TEST(SchemaValidator, Ref_internal_id_and_schema_pointer) {
typedef GenericSchemaDocument<Value, MemoryPoolAllocator<> > SchemaDocumentType;
Document sd;
sd.Parse("{ \"schema\": {\"type\": \"object\", \"properties\": {\"myInt1\": {\"$ref\": \"#myId\"}, \"myInt2\": {\"anyOf\": [{\"type\": \"integer\", \"id\": \"#myId\"}]}}}}");
typedef GenericPointer<Value, MemoryPoolAllocator<> > PointerType;
SchemaDocumentType s(sd, 0, 0, 0, 0, PointerType("/schema"));
typedef GenericSchemaValidator<SchemaDocumentType, BaseReaderHandler<UTF8<> >, MemoryPoolAllocator<> > SchemaValidatorType;
INVALIDATE_(s, "{\"myInt1\": null}", "/schema/properties/myInt2/anyOf/0", "type", "/myInt1",
"{ \"type\": {"
" \"errorCode\": 20,"
" \"instanceRef\": \"#/myInt1\","
" \"schemaRef\": \"#/schema/properties/myInt2/anyOf/0\","
" \"expected\": [\"integer\"], \"actual\": \"null\""
"}}",
kValidateDefaultFlags, SchemaValidatorType, PointerType);
}
TEST(SchemaValidator, Ref_remote_issue1210) { TEST(SchemaValidator, Ref_remote_issue1210) {
class SchemaDocumentProvider : public IRemoteSchemaDocumentProvider { class SchemaDocumentProvider : public IRemoteSchemaDocumentProvider {
SchemaDocument** collection; SchemaDocument** collection;
@ -2260,7 +2594,7 @@ TEST(SchemaValidator, Ref_remote_issue1210) {
SchemaDocumentProvider(SchemaDocument** collection) : collection(collection) { } SchemaDocumentProvider(SchemaDocument** collection) : collection(collection) { }
virtual const SchemaDocument* GetRemoteDocument(const char* uri, SizeType length) { virtual const SchemaDocument* GetRemoteDocument(const char* uri, SizeType length) {
int i = 0; int i = 0;
while (collection[i] && SchemaDocument::URIType(uri, length) != collection[i]->GetURI()) ++i; while (collection[i] && SchemaDocument::SValue(uri, length) != collection[i]->GetURI()) ++i;
return collection[i]; return collection[i];
} }
}; };
@ -2582,6 +2916,250 @@ TEST(SchemaValidator, Schema_UnknownError) {
ASSERT_TRUE(SchemaValidator::SchemaType::GetValidateErrorKeyword(kValidateErrors).GetString() == std::string("null")); ASSERT_TRUE(SchemaValidator::SchemaType::GetValidateErrorKeyword(kValidateErrors).GetString() == std::string("null"));
} }
TEST(SchemaValidator, Uri_Parse) {
typedef std::basic_string<Value::Ch> String;
typedef Uri<GenericSchemaDocument<Value, MemoryPoolAllocator<> >> Uri;
MemoryPoolAllocator<CrtAllocator> allocator;
String s = "http://auth/path?query#frag";
Value v;
v.SetString(s, allocator);
Uri u = Uri(v);
EXPECT_TRUE(u.GetScheme() == "http:");
EXPECT_TRUE(u.GetAuth() == "//auth");
EXPECT_TRUE(u.GetPath() == "/path");
EXPECT_TRUE(u.GetDoc() == "http://auth/path?query");
EXPECT_TRUE(u.GetQuery() == "?query");
EXPECT_TRUE(u.GetFrag() == "#frag");
Value w;
u.Get(w, allocator);
EXPECT_TRUE(*w.GetString() == *v.GetString());
s = "urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f";
v.SetString(s, allocator);
u = Uri(v);
EXPECT_TRUE(u.GetScheme() == "urn:");
EXPECT_TRUE(u.GetAuth() == "");
EXPECT_TRUE(u.GetPath() == "uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f");
EXPECT_TRUE(u.GetDoc() == "urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f");
EXPECT_TRUE(u.GetQuery() == "");
EXPECT_TRUE(u.GetFrag() == "");
u.Get(w, allocator);
EXPECT_TRUE(*w.GetString() == *v.GetString());
s = "";
v.SetString(s, allocator);
u = Uri(v);
EXPECT_TRUE(u.GetScheme() == "");
EXPECT_TRUE(u.GetAuth() == "");
EXPECT_TRUE(u.GetPath() == "");
EXPECT_TRUE(u.GetDoc() == "");
EXPECT_TRUE(u.GetQuery() == "");
EXPECT_TRUE(u.GetFrag() == "");
s = "http://auth/";
v.SetString(s, allocator);
u = Uri(v);
EXPECT_TRUE(u.GetScheme() == "http:");
EXPECT_TRUE(u.GetAuth() == "//auth");
EXPECT_TRUE(u.GetPath() == "/");
EXPECT_TRUE(u.GetDoc() == "http://auth/");
EXPECT_TRUE(u.GetQuery() == "");
EXPECT_TRUE(u.GetFrag() == "");
s = "/path/sub";
u = Uri(s);
EXPECT_TRUE(u.GetScheme() == "");
EXPECT_TRUE(u.GetAuth() == "");
EXPECT_TRUE(u.GetPath() == "/path/sub");
EXPECT_TRUE(u.GetDoc() == "/path/sub");
EXPECT_TRUE(u.GetQuery() == "");
EXPECT_TRUE(u.GetFrag() == "");
// absolute path gets normalized
s = "/path/../sub/";
u = Uri(s);
EXPECT_TRUE(u.GetScheme() == "");
EXPECT_TRUE(u.GetAuth() == "");
EXPECT_TRUE(u.GetPath() == "/sub/");
EXPECT_TRUE(u.GetDoc() == "/sub/");
EXPECT_TRUE(u.GetQuery() == "");
EXPECT_TRUE(u.GetFrag() == "");
// relative path does not
s = "path/../sub";
u = Uri(s);
EXPECT_TRUE(u.GetScheme() == "");
EXPECT_TRUE(u.GetAuth() == "");
EXPECT_TRUE(u.GetPath() == "path/../sub");
EXPECT_TRUE(u.GetDoc() == "path/../sub");
EXPECT_TRUE(u.GetQuery() == "");
EXPECT_TRUE(u.GetFrag() == "");
s = "http://auth#frag/stuff";
u = Uri(s);
EXPECT_TRUE(u.GetScheme() == "http:");
EXPECT_TRUE(u.GetAuth() == "//auth");
EXPECT_TRUE(u.GetPath() == "");
EXPECT_TRUE(u.GetDoc() == "http://auth");
EXPECT_TRUE(u.GetQuery() == "");
EXPECT_TRUE(u.GetFrag() == "#frag/stuff");
EXPECT_TRUE(u.Get() == s);
s = "#frag/stuff";
u = Uri(s);
EXPECT_TRUE(u.GetScheme() == "");
EXPECT_TRUE(u.GetAuth() == "");
EXPECT_TRUE(u.GetPath() == "");
EXPECT_TRUE(u.GetDoc() == "");
EXPECT_TRUE(u.GetQuery() == "");
EXPECT_TRUE(u.GetFrag() == "#frag/stuff");
EXPECT_TRUE(u.Get() == s);
Value::Ch c[] = { '#', 'f', 'r', 'a', 'g', '/', 's', 't', 'u', 'f', 'f', '\0'};
u = Uri(c, 11);
EXPECT_TRUE(String(u.GetString()) == "#frag/stuff");
EXPECT_TRUE(u.GetStringLength() == 11);
EXPECT_TRUE(String(u.GetDocString()) == "");
EXPECT_TRUE(u.GetDocStringLength() == 0);
EXPECT_TRUE(String(u.GetFragString()) == "#frag/stuff");
EXPECT_TRUE(u.GetFragStringLength() == 11);
}
TEST(SchemaValidator, Uri_Resolve) {
typedef std::basic_string<Value::Ch> String;
typedef Uri<GenericSchemaDocument<Value, MemoryPoolAllocator<> >> Uri;
// ref is full uri
Uri base = Uri(String("http://auth/path/#frag"));
Uri ref = Uri(String("http://newauth/newpath#newfrag"));
EXPECT_TRUE(ref.Resolve(base).Get() == "http://newauth/newpath#newfrag");
base = Uri(String("/path/#frag"));
ref = Uri(String("http://newauth/newpath#newfrag"));
EXPECT_TRUE(ref.Resolve(base).Get() == "http://newauth/newpath#newfrag");
// ref is alternate uri
base = Uri(String("http://auth/path/#frag"));
ref = Uri(String("urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f"));
EXPECT_TRUE(ref.Resolve(base).Get() == "urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f");
// ref is absolute path
base = Uri(String("http://auth/path/#"));
ref = Uri(String("/newpath#newfrag"));
EXPECT_TRUE(ref.Resolve(base).Get() == "http://auth/newpath#newfrag");
// ref is relative path
base = Uri(String("http://auth/path/file.json#frag"));
ref = Uri(String("newfile.json#"));
EXPECT_TRUE(ref.Resolve(base).Get() == "http://auth/path/newfile.json#");
base = Uri(String("http://auth/path/file.json#frag/stuff"));
ref = Uri(String("newfile.json#newfrag/newstuff"));
EXPECT_TRUE(ref.Resolve(base).Get() == "http://auth/path/newfile.json#newfrag/newstuff");
base = Uri(String("file.json"));
ref = Uri(String("newfile.json"));
EXPECT_TRUE(ref.Resolve(base).Get() == "newfile.json");
base = Uri(String("file.json"));
ref = Uri(String("./newfile.json"));
EXPECT_TRUE(ref.Resolve(base).Get() == "newfile.json");
base = Uri(String("file.json"));
ref = Uri(String("parent/../newfile.json"));
EXPECT_TRUE(ref.Resolve(base).Get() == "newfile.json");
base = Uri(String("file.json"));
ref = Uri(String("parent/./newfile.json"));
EXPECT_TRUE(ref.Resolve(base).Get() == "parent/newfile.json");
base = Uri(String("file.json"));
ref = Uri(String("../../parent/.././newfile.json"));
EXPECT_TRUE(ref.Resolve(base).Get() == "newfile.json");
base = Uri(String("http://auth"));
ref = Uri(String("newfile.json"));
EXPECT_TRUE(ref.Resolve(base).Get() == "http://auth/newfile.json");
// ref is fragment
base = Uri(String("#frag/stuff"));
ref = Uri(String("#newfrag/newstuff"));
EXPECT_TRUE(ref.Resolve(base).Get() == "#newfrag/newstuff");
// test ref fragment always wins
base = Uri(String("/path#frag"));
ref = Uri(String(""));
EXPECT_TRUE(ref.Resolve(base).Get() == "/path");
// Examples from RFC3896
base = Uri(String("http://a/b/c/d;p?q"));
ref = Uri(String("g:h"));
EXPECT_TRUE(ref.Resolve(base).Get() == "g:h");
ref = Uri(String("g"));
EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g");
ref = Uri(String("./g"));
EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g");
ref = Uri(String("g/"));
EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g/");
ref = Uri(String("/g"));
EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/g");
ref = Uri(String("//g"));
EXPECT_TRUE(ref.Resolve(base).Get() == "http://g");
ref = Uri(String("?y"));
EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/d;p?y");
ref = Uri(String("g?y"));
EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g?y");
ref = Uri(String("#s"));
EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/d;p?q#s");
ref = Uri(String("g#s"));
EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g#s");
ref = Uri(String("g?y#s"));
EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g?y#s");
ref = Uri(String(";x"));
EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/;x");
ref = Uri(String("g;x"));
EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g;x");
ref = Uri(String("g;x?y#s"));
EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g;x?y#s");
ref = Uri(String(""));
EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/d;p?q");
ref = Uri(String("."));
EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/");
ref = Uri(String("./"));
EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/");
ref = Uri(String(".."));
EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/");
ref = Uri(String("../"));
EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/");
ref = Uri(String("../g"));
EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/g");
ref = Uri(String("../.."));
EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/");
ref = Uri(String("../../"));
EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/");
ref = Uri(String("../../g"));
EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/g");
ref = Uri(String("../../../g"));
EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/g");
ref = Uri(String("../../../../g"));
EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/g");
ref = Uri(String("/./g"));
EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/g");
ref = Uri(String("/../g"));
EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/g");
ref = Uri(String("g."));
EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g.");
ref = Uri(String(".g"));
EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/.g");
ref = Uri(String("g.."));
EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g..");
ref = Uri(String("..g"));
EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/..g");
ref = Uri(String("g#s/../x"));
EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g#s/../x");
}
#if defined(_MSC_VER) || defined(__clang__) #if defined(_MSC_VER) || defined(__clang__)
RAPIDJSON_DIAG_POP RAPIDJSON_DIAG_POP
#endif #endif