This commit is contained in:
Steve Hanson 2021-06-17 08:58:37 +01:00 committed by Milo Yip
parent 06d58b9e84
commit 338d8defdb
5 changed files with 473 additions and 75 deletions

View File

@ -104,7 +104,7 @@ inline const RAPIDJSON_ERROR_CHARTYPE* GetValidateError_En(ValidateErrorCode val
case kValidateErrorType: return RAPIDJSON_ERROR_STRING("Property has a type '%actual' that is not in the following list: '%expected'."); case kValidateErrorType: return RAPIDJSON_ERROR_STRING("Property has a type '%actual' that is not in the following list: '%expected'.");
case kValidateErrorOneOf: return RAPIDJSON_ERROR_STRING("Property did not match any of the sub-schemas specified by 'oneOf', refer to following errors."); case kValidateErrorOneOf: return RAPIDJSON_ERROR_STRING("Property did not match any of the sub-schemas specified by 'oneOf', refer to following errors.");
case kValidateErrorOneOfMatch: return RAPIDJSON_ERROR_STRING("Property matched more than one of the sub-schemas specified by 'oneOf'."); case kValidateErrorOneOfMatch: return RAPIDJSON_ERROR_STRING("Property matched more than one of the sub-schemas specified by 'oneOf', indices '%matches'.");
case kValidateErrorAllOf: return RAPIDJSON_ERROR_STRING("Property did not match all of the sub-schemas specified by 'allOf', refer to following errors."); case kValidateErrorAllOf: return RAPIDJSON_ERROR_STRING("Property did not match all of the sub-schemas specified by 'allOf', refer to following errors.");
case kValidateErrorAnyOf: return RAPIDJSON_ERROR_STRING("Property did not match any of the sub-schemas specified by 'anyOf', refer to following errors."); case kValidateErrorAnyOf: return RAPIDJSON_ERROR_STRING("Property did not match any of the sub-schemas specified by 'anyOf', refer to following errors.");
case kValidateErrorNot: return RAPIDJSON_ERROR_STRING("Property matched the sub-schema specified by 'not'."); case kValidateErrorNot: return RAPIDJSON_ERROR_STRING("Property matched the sub-schema specified by 'not'.");
@ -113,6 +113,57 @@ inline const RAPIDJSON_ERROR_CHARTYPE* GetValidateError_En(ValidateErrorCode val
} }
} }
//! Maps error code of schema document compilation into error message.
/*!
\ingroup RAPIDJSON_ERRORS
\param schemaErrorCode Error code obtained from compiling the schema document.
\return the error message.
\note User can make a copy of this function for localization.
Using switch-case is safer for future modification of error codes.
*/
inline const RAPIDJSON_ERROR_CHARTYPE* GetSchemaError_En(SchemaErrorCode schemaErrorCode) {
switch (schemaErrorCode) {
case kSchemaErrorNone: return RAPIDJSON_ERROR_STRING("No error.");
case kSchemaErrorSpecUnknown: return RAPIDJSON_ERROR_STRING("JSON schema draft or OpenAPI version is not recognized.");
case kSchemaErrorSpecUnsupported: return RAPIDJSON_ERROR_STRING("JSON schema draft or OpenAPI version is not supported.");
case kSchemaErrorSpecIllegal: return RAPIDJSON_ERROR_STRING("Both JSON schema draft and OpenAPI version found in document.");
case kSchemaErrorStartUnknown: return RAPIDJSON_ERROR_STRING("Pointer '%value' to start of schema does not resolve to a location in the document.");
case kSchemaErrorRefPlainName: return RAPIDJSON_ERROR_STRING("$ref fragment '%value' must be a JSON pointer.");
case kSchemaErrorRefInvalid: return RAPIDJSON_ERROR_STRING("$ref must not be an empty string.");
case kSchemaErrorRefPointerInvalid: return RAPIDJSON_ERROR_STRING("$ref fragment '%value' is not a valid JSON pointer at offset '%offset'.");
case kSchemaErrorRefUnknown: return RAPIDJSON_ERROR_STRING("$ref '%value' does not resolve to a location in the target document.");
case kSchemaErrorRefCyclical: return RAPIDJSON_ERROR_STRING("$ref '%value' is cyclical.");
case kSchemaErrorRefNoRemoteProvider: return RAPIDJSON_ERROR_STRING("$ref is remote but there is no remote provider.");
case kSchemaErrorRefNoRemoteSchema: return RAPIDJSON_ERROR_STRING("$ref '%value' is remote but the remote provider did not return a schema.");
case kSchemaErrorReadOnlyAndWriteOnly: return RAPIDJSON_ERROR_STRING("Property must not be both 'readOnly' and 'writeOnly'.");
case kSchemaErrorRegexInvalid: return RAPIDJSON_ERROR_STRING("Invalid regular expression '%value' in 'pattern' or 'patternProperties'.");
default: return RAPIDJSON_ERROR_STRING("Unknown error.");
}
}
//! Maps error code of pointer parse into error message.
/*!
\ingroup RAPIDJSON_ERRORS
\param pointerParseErrorCode Error code obtained from pointer parse.
\return the error message.
\note User can make a copy of this function for localization.
Using switch-case is safer for future modification of error codes.
*/
inline const RAPIDJSON_ERROR_CHARTYPE* GetPointerParseError_En(PointerParseErrorCode pointerParseErrorCode) {
switch (pointerParseErrorCode) {
case kPointerParseErrorNone: return RAPIDJSON_ERROR_STRING("No error.");
case kPointerParseErrorTokenMustBeginWithSolidus: return RAPIDJSON_ERROR_STRING("A token must begin with a '/'.");
case kPointerParseErrorInvalidEscape: return RAPIDJSON_ERROR_STRING("Invalid escape.");
case kPointerParseErrorInvalidPercentEncoding: return RAPIDJSON_ERROR_STRING("Invalid percent encoding in URI fragment.");
case kPointerParseErrorCharacterMustPercentEncode: return RAPIDJSON_ERROR_STRING("A character must be percent encoded in a URI fragment.");
default: return RAPIDJSON_ERROR_STRING("Unknown error.");
}
}
RAPIDJSON_NAMESPACE_END RAPIDJSON_NAMESPACE_END
#ifdef __clang__ #ifdef __clang__

View File

@ -185,8 +185,8 @@ enum ValidateErrorCode {
kValidateErrorPatternProperties, //!< See other errors. kValidateErrorPatternProperties, //!< See other errors.
kValidateErrorDependencies, //!< Object has missing property or schema dependencies. kValidateErrorDependencies, //!< Object has missing property or schema dependencies.
kValidateErrorEnum, //!< Property has a value that is not one of its allowed enumerated values kValidateErrorEnum, //!< Property has a value that is not one of its allowed enumerated values.
kValidateErrorType, //!< Property has a type that is not allowed by the schema.. kValidateErrorType, //!< Property has a type that is not allowed by the schema.
kValidateErrorOneOf, //!< Property did not match any of the sub-schemas specified by 'oneOf'. kValidateErrorOneOf, //!< Property did not match any of the sub-schemas specified by 'oneOf'.
kValidateErrorOneOfMatch, //!< Property matched more than one of the sub-schemas specified by 'oneOf'. kValidateErrorOneOfMatch, //!< Property matched more than one of the sub-schemas specified by 'oneOf'.
@ -207,6 +207,72 @@ enum ValidateErrorCode {
*/ */
typedef const RAPIDJSON_ERROR_CHARTYPE* (*GetValidateErrorFunc)(ValidateErrorCode); typedef const RAPIDJSON_ERROR_CHARTYPE* (*GetValidateErrorFunc)(ValidateErrorCode);
///////////////////////////////////////////////////////////////////////////////
// SchemaErrorCode
//! Error codes when validating.
/*! \ingroup RAPIDJSON_ERRORS
\see GenericSchemaValidator
*/
enum SchemaErrorCode {
kSchemaErrorNone = 0, //!< No error.
kSchemaErrorSpecUnknown, //!< JSON schema draft or OpenAPI version is not recognized
kSchemaErrorSpecUnsupported, //!< JSON schema draft or OpenAPI version is not supported
kSchemaErrorSpecIllegal, //!< Both JSON schema draft and OpenAPI version found in document
kSchemaErrorStartUnknown, //!< Pointer to start of schema does not resolve to a location in the document
kSchemaErrorRefPlainName, //!< $ref fragment must be a JSON pointer
kSchemaErrorRefInvalid, //!< $ref must not be an empty string
kSchemaErrorRefPointerInvalid, //!< $ref fragment is not a valid JSON pointer at offset
kSchemaErrorRefUnknown, //!< $ref does not resolve to a location in the target document
kSchemaErrorRefCyclical, //!< $ref is cyclical
kSchemaErrorRefNoRemoteProvider, //!< $ref is remote but there is no remote provider
kSchemaErrorRefNoRemoteSchema, //!< $ref is remote but the remote provider did not return a schema
kSchemaErrorReadOnlyAndWriteOnly, //!< Property must not be both 'readOnly' and 'writeOnly'
kSchemaErrorRegexInvalid //!< Invalid regular expression in 'pattern' or 'patternProperties'
};
//! Function pointer type of GetSchemaError().
/*! \ingroup RAPIDJSON_ERRORS
This is the prototype for \c GetSchemaError_X(), where \c X is a locale.
User can dynamically change locale in runtime, e.g.:
\code
GetSchemaErrorFunc GetSchemaError = GetSchemaError_En; // or whatever
const RAPIDJSON_ERROR_CHARTYPE* s = GetSchemaError(validator.GetInvalidSchemaCode());
\endcode
*/
typedef const RAPIDJSON_ERROR_CHARTYPE* (*GetSchemaErrorFunc)(SchemaErrorCode);
///////////////////////////////////////////////////////////////////////////////
// PointerParseErrorCode
//! Error code of JSON pointer parsing.
/*! \ingroup RAPIDJSON_ERRORS
\see GenericPointer::GenericPointer, GenericPointer::GetParseErrorCode
*/
enum PointerParseErrorCode {
kPointerParseErrorNone = 0, //!< The parse is successful
kPointerParseErrorTokenMustBeginWithSolidus, //!< A token must begin with a '/'
kPointerParseErrorInvalidEscape, //!< Invalid escape
kPointerParseErrorInvalidPercentEncoding, //!< Invalid percent encoding in URI fragment
kPointerParseErrorCharacterMustPercentEncode //!< A character must percent encoded in URI fragment
};
//! Function pointer type of GetPointerParseError().
/*! \ingroup RAPIDJSON_ERRORS
This is the prototype for \c GetPointerParseError_X(), where \c X is a locale.
User can dynamically change locale in runtime, e.g.:
\code
GetPointerParseErrorFunc GetPointerParseError = GetPointerParseError_En; // or whatever
const RAPIDJSON_ERROR_CHARTYPE* s = GetPointerParseError(pointer.GetParseErrorCode());
\endcode
*/
typedef const RAPIDJSON_ERROR_CHARTYPE* (*GetPointerParseErrorFunc)(PointerParseErrorCode);
RAPIDJSON_NAMESPACE_END RAPIDJSON_NAMESPACE_END
#ifdef __clang__ #ifdef __clang__

View File

@ -18,6 +18,7 @@
#include "document.h" #include "document.h"
#include "uri.h" #include "uri.h"
#include "internal/itoa.h" #include "internal/itoa.h"
#include "error/error.h" // PointerParseErrorCode
#ifdef __clang__ #ifdef __clang__
RAPIDJSON_DIAG_PUSH RAPIDJSON_DIAG_PUSH
@ -31,19 +32,6 @@ RAPIDJSON_NAMESPACE_BEGIN
static const SizeType kPointerInvalidIndex = ~SizeType(0); //!< Represents an invalid index in GenericPointer::Token static const SizeType kPointerInvalidIndex = ~SizeType(0); //!< Represents an invalid index in GenericPointer::Token
//! Error code of parsing.
/*! \ingroup RAPIDJSON_ERRORS
\see GenericPointer::GenericPointer, GenericPointer::GetParseErrorCode
*/
enum PointerParseErrorCode {
kPointerParseErrorNone = 0, //!< The parse is successful
kPointerParseErrorTokenMustBeginWithSolidus, //!< A token must begin with a '/'
kPointerParseErrorInvalidEscape, //!< Invalid escape
kPointerParseErrorInvalidPercentEncoding, //!< Invalid percent encoding in URI fragment
kPointerParseErrorCharacterMustPercentEncode //!< A character must percent encoded in URI fragment
};
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// GenericPointer // GenericPointer

View File

@ -234,7 +234,8 @@ public:
virtual void EndDisallowedType(const typename SchemaType::ValueType& actualType) = 0; virtual void EndDisallowedType(const typename SchemaType::ValueType& actualType) = 0;
virtual void NotAllOf(ISchemaValidator** subvalidators, SizeType count) = 0; virtual void NotAllOf(ISchemaValidator** subvalidators, SizeType count) = 0;
virtual void NoneOf(ISchemaValidator** subvalidators, SizeType count) = 0; virtual void NoneOf(ISchemaValidator** subvalidators, SizeType count) = 0;
virtual void NotOneOf(ISchemaValidator** subvalidators, SizeType count, bool matched) = 0; virtual void NotOneOf(ISchemaValidator** subvalidators, SizeType count) = 0;
virtual void MultipleOneOf(SizeType index1, SizeType index2) = 0;
virtual void Disallowed() = 0; virtual void Disallowed() = 0;
}; };
@ -594,8 +595,9 @@ 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); PointerType r = q.Append(itr->name, allocator_);
schemaDocument->CreateSchema(&patternProperties_[patternPropertyCount_].schema, q.Append(itr->name, allocator_), itr->value, document, id_); patternProperties_[patternPropertyCount_].pattern = CreatePattern(itr->name, schemaDocument, r);
schemaDocument->CreateSchema(&patternProperties_[patternPropertyCount_].schema, r, itr->value, document, id_);
patternPropertyCount_++; patternPropertyCount_++;
} }
} }
@ -675,7 +677,7 @@ public:
AssignIfExist(maxLength_, value, GetMaxLengthString()); AssignIfExist(maxLength_, value, GetMaxLengthString());
if (const ValueType* v = GetMember(value, GetPatternString())) if (const ValueType* v = GetMember(value, GetPatternString()))
pattern_ = CreatePattern(*v); pattern_ = CreatePattern(*v, schemaDocument, p.Append(GetPatternString(), allocator_));
// Number // Number
if (const ValueType* v = GetMember(value, GetMinimumString())) if (const ValueType* v = GetMember(value, GetMinimumString()))
@ -828,16 +830,19 @@ public:
if (oneOf_.schemas) { if (oneOf_.schemas) {
bool oneValid = false; bool oneValid = false;
SizeType firstMatch = 0;
for (SizeType i = oneOf_.begin; i < oneOf_.begin + oneOf_.count; i++) for (SizeType i = oneOf_.begin; i < oneOf_.begin + oneOf_.count; i++)
if (context.validators[i]->IsValid()) { if (context.validators[i]->IsValid()) {
if (oneValid) { if (oneValid) {
context.error_handler.NotOneOf(&context.validators[oneOf_.begin], oneOf_.count, true); context.error_handler.MultipleOneOf(firstMatch, i - oneOf_.begin);
RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorOneOfMatch); RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorOneOfMatch);
} else } else {
oneValid = true; oneValid = true;
firstMatch = i - oneOf_.begin;
}
} }
if (!oneValid) { if (!oneValid) {
context.error_handler.NotOneOf(&context.validators[oneOf_.begin], oneOf_.count, false); context.error_handler.NotOneOf(&context.validators[oneOf_.begin], oneOf_.count);
RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorOneOf); RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorOneOf);
} }
} }
@ -1247,10 +1252,11 @@ private:
#if RAPIDJSON_SCHEMA_USE_INTERNALREGEX #if RAPIDJSON_SCHEMA_USE_INTERNALREGEX
template <typename ValueType> template <typename ValueType>
RegexType* CreatePattern(const ValueType& value) { RegexType* CreatePattern(const ValueType& value, SchemaDocumentType* sd, const PointerType& p) {
if (value.IsString()) { if (value.IsString()) {
RegexType* r = new (allocator_->Malloc(sizeof(RegexType))) RegexType(value.GetString(), allocator_); RegexType* r = new (allocator_->Malloc(sizeof(RegexType))) RegexType(value.GetString(), allocator_);
if (!r->IsValid()) { if (!r->IsValid()) {
sd->SchemaErrorValue(kSchemaErrorRegexInvalid, p, value.GetString(), value.GetStringLength());
r->~RegexType(); r->~RegexType();
AllocatorType::Free(r); AllocatorType::Free(r);
r = 0; r = 0;
@ -1266,13 +1272,14 @@ private:
} }
#elif RAPIDJSON_SCHEMA_USE_STDREGEX #elif RAPIDJSON_SCHEMA_USE_STDREGEX
template <typename ValueType> template <typename ValueType>
RegexType* CreatePattern(const ValueType& value) { RegexType* CreatePattern(const ValueType& value, SchemaDocumentType* sd, const PointerType& p) {
if (value.IsString()) { if (value.IsString()) {
RegexType *r = static_cast<RegexType*>(allocator_->Malloc(sizeof(RegexType))); RegexType *r = static_cast<RegexType*>(allocator_->Malloc(sizeof(RegexType)));
try { try {
return new (r) RegexType(value.GetString(), std::size_t(value.GetStringLength()), std::regex_constants::ECMAScript); return new (r) RegexType(value.GetString(), std::size_t(value.GetStringLength()), std::regex_constants::ECMAScript);
} }
catch (const std::regex_error&) { catch (const std::regex_error& e) {
sd->SchemaErrorValue(kSchemaErrorRegexInvalid, p, value.GetString(), value.GetStringLength());
AllocatorType::Free(r); AllocatorType::Free(r);
} }
} }
@ -1285,7 +1292,9 @@ private:
} }
#else #else
template <typename ValueType> template <typename ValueType>
RegexType* CreatePattern(const ValueType&) { return 0; } RegexType* CreatePattern(const ValueType&) {
return 0;
}
static bool IsPatternMatch(const RegexType*, const Ch *, SizeType) { return true; } static bool IsPatternMatch(const RegexType*, const Ch *, SizeType) { return true; }
#endif // RAPIDJSON_SCHEMA_USE_STDREGEX #endif // RAPIDJSON_SCHEMA_USE_STDREGEX
@ -1632,8 +1641,9 @@ 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, AllocatorType> SValue; typedef GenericValue<EncodingType, AllocatorType> GValue;
typedef GenericUri<ValueType, Allocator> UriType; typedef GenericUri<ValueType, Allocator> UriType;
typedef GenericStringRef<Ch> StringRefType;
friend class internal::Schema<GenericSchemaDocument>; friend class internal::Schema<GenericSchemaDocument>;
template <typename, typename, typename> template <typename, typename, typename>
friend class GenericSchemaValidator; friend class GenericSchemaValidator;
@ -1658,7 +1668,9 @@ public:
root_(), root_(),
typeless_(), typeless_(),
schemaMap_(allocator, kInitialSchemaMapSize), schemaMap_(allocator, kInitialSchemaMapSize),
schemaRef_(allocator, kInitialSchemaRefSize) schemaRef_(allocator, kInitialSchemaRefSize),
error_(kObjectType),
currentError_()
{ {
if (!allocator_) if (!allocator_)
ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator)(); ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator)();
@ -1680,6 +1692,11 @@ public:
else if (const ValueType* v = pointer.Get(document)) { else if (const ValueType* v = pointer.Get(document)) {
CreateSchema(&root_, pointer, *v, document, docId_); CreateSchema(&root_, pointer, *v, document, docId_);
} }
else {
GenericStringBuffer<EncodingType> sb;
pointer.StringifyUriFragment(sb);
SchemaErrorValue(kSchemaErrorStartUnknown, PointerType(), sb.GetString(), static_cast<SizeType>(sb.GetSize() / sizeof(Ch)));
}
RAPIDJSON_ASSERT(root_ != 0); RAPIDJSON_ASSERT(root_ != 0);
@ -1697,7 +1714,9 @@ public:
schemaMap_(std::move(rhs.schemaMap_)), schemaMap_(std::move(rhs.schemaMap_)),
schemaRef_(std::move(rhs.schemaRef_)), schemaRef_(std::move(rhs.schemaRef_)),
uri_(std::move(rhs.uri_)), uri_(std::move(rhs.uri_)),
docId_(rhs.docId_) docId_(rhs.docId_),
error_(std::move(rhs.error_)),
currentError_(std::move(rhs.currentError_))
{ {
rhs.remoteProvider_ = 0; rhs.remoteProvider_ = 0;
rhs.allocator_ = 0; rhs.allocator_ = 0;
@ -1719,12 +1738,52 @@ public:
RAPIDJSON_DELETE(ownAllocator_); RAPIDJSON_DELETE(ownAllocator_);
} }
const SValue& GetURI() const { return uri_; } const GValue& GetURI() const { return uri_; }
//! Get the root schema. //! Get the root schema.
const SchemaType& GetRoot() const { return *root_; } const SchemaType& GetRoot() const { return *root_; }
private: //! Gets the error object.
GValue& GetError() { return error_; }
const GValue& GetError() const { return error_; }
static const StringRefType& GetSchemaErrorKeyword(SchemaErrorCode schemaErrorCode) {
switch (schemaErrorCode) {
case kSchemaErrorStartUnknown: return GetStartUnknownString();
case kSchemaErrorRefPlainName: return GetRefPlainNameString();
case kSchemaErrorRefInvalid: return GetRefInvalidString();
case kSchemaErrorRefPointerInvalid: return GetRefPointerInvalidString();
case kSchemaErrorRefUnknown: return GetRefUnknownString();
case kSchemaErrorRefCyclical: return GetRefCyclicalString();
case kSchemaErrorRefNoRemoteProvider: return GetRefNoRemoteProviderString();
case kSchemaErrorRefNoRemoteSchema: return GetRefNoRemoteSchemaString();
case kSchemaErrorRegexInvalid: return GetRegexInvalidString();
default: return GetNullString();
}
}
//! Default error method
void SchemaError(const SchemaErrorCode code, const PointerType& location) {
currentError_ = GValue(kObjectType);
AddCurrentError(code, location);
}
//! Method for error with single string value insert
void SchemaErrorValue(const SchemaErrorCode code, const PointerType& location, const Ch* value, SizeType length) {
currentError_ = GValue(kObjectType);
currentError_.AddMember(GetValueString(), GValue(value, length, *allocator_).Move(), *allocator_);
AddCurrentError(code, location);
}
//! Method for error with invalid pointer
void SchemaErrorPointer(const SchemaErrorCode code, const PointerType& location, const Ch* value, SizeType length, const PointerType& pointer) {
currentError_ = GValue(kObjectType);
currentError_.AddMember(GetValueString(), GValue(value, length, *allocator_).Move(), *allocator_);
currentError_.AddMember(GetOffsetString(), static_cast<SizeType>(pointer.GetParseErrorOffset() / sizeof(Ch)), *allocator_);
AddCurrentError(code, location);
}
private:
//! Prohibit copying //! Prohibit copying
GenericSchemaDocument(const GenericSchemaDocument&); GenericSchemaDocument(const GenericSchemaDocument&);
//! Prohibit assignment //! Prohibit assignment
@ -1745,6 +1804,58 @@ private:
bool owned; bool owned;
}; };
void AddErrorInstanceLocation(GValue& result, const PointerType& location) {
GenericStringBuffer<EncodingType> sb;
location.StringifyUriFragment(sb);
GValue instanceRef(sb.GetString(), static_cast<SizeType>(sb.GetSize() / sizeof(Ch)), *allocator_);
result.AddMember(GetInstanceRefString(), instanceRef, *allocator_);
}
void AddError(GValue& keyword, GValue& error) {
typename GValue::MemberIterator member = error_.FindMember(keyword);
if (member == error_.MemberEnd())
error_.AddMember(keyword, error, *allocator_);
else {
if (member->value.IsObject()) {
GValue errors(kArrayType);
errors.PushBack(member->value, *allocator_);
member->value = errors;
}
member->value.PushBack(error, *allocator_);
}
}
void AddCurrentError(const SchemaErrorCode code, const PointerType& location) {
currentError_.AddMember(GetErrorCodeString(), code, *allocator_);
AddErrorInstanceLocation(currentError_, location);
AddError(GValue(GetSchemaErrorKeyword(code)).Move(), currentError_);
}
#define RAPIDJSON_STRING_(name, ...) \
static const StringRefType& Get##name##String() {\
static const Ch s[] = { __VA_ARGS__, '\0' };\
static const StringRefType v(s, static_cast<SizeType>(sizeof(s) / sizeof(Ch) - 1)); \
return v;\
}
RAPIDJSON_STRING_(InstanceRef, 'i', 'n', 's', 't', 'a', 'n', 'c', 'e', 'R', 'e', 'f')
RAPIDJSON_STRING_(ErrorCode, 'e', 'r', 'r', 'o', 'r', 'C', 'o', 'd', 'e')
RAPIDJSON_STRING_(Value, 'v', 'a', 'l', 'u', 'e')
RAPIDJSON_STRING_(Offset, 'o', 'f', 'f', 's', 'e', 't')
RAPIDJSON_STRING_(Null, 'n', 'u', 'l', 'l')
RAPIDJSON_STRING_(StartUnknown, 'S', 't', 'a', 'r', 't', 'U', 'n', 'k', 'n', 'o', 'w', 'n')
RAPIDJSON_STRING_(RefPlainName, 'R', 'e', 'f', 'P', 'l', 'a', 'i', 'n', 'N', 'a', 'm', 'e')
RAPIDJSON_STRING_(RefInvalid, 'R', 'e', 'f', 'I', 'n', 'v', 'a', 'l', 'i', 'd')
RAPIDJSON_STRING_(RefPointerInvalid, 'R', 'e', 'f', 'P', 'o', 'i', 'n', 't', 'e', 'r', 'I', 'n', 'v', 'a', 'l', 'i', 'd')
RAPIDJSON_STRING_(RefUnknown, 'R', 'e', 'f', 'U', 'n', 'k', 'n', 'o', 'w', 'n')
RAPIDJSON_STRING_(RefCyclical, 'R', 'e', 'f', 'C', 'y', 'c', 'l', 'i', 'c', 'a', 'l')
RAPIDJSON_STRING_(RefNoRemoteProvider, 'R', 'e', 'f', 'N', 'o', 'R', 'e', 'm', 'o', 't', 'e', 'P', 'r', 'o', 'v', 'i', 'd', 'e', 'r')
RAPIDJSON_STRING_(RefNoRemoteSchema, 'R', 'e', 'f', 'N', 'o', 'R', 'e', 'm', 'o', 't', 'e', 'S', 'c', 'h', 'e', 'm', 'a')
RAPIDJSON_STRING_(RegexInvalid, 'R', 'e', 'g', 'e', 'x', 'I', 'n', 'v', 'a', 'l', 'i', 'd')
#undef RAPIDJSON_STRING_
// Changed by PR #1393 // Changed by PR #1393
void CreateSchemaRecursive(const SchemaType** schema, const PointerType& pointer, const ValueType& v, const ValueType& document, const UriType& id) { void CreateSchemaRecursive(const SchemaType** schema, const PointerType& pointer, const ValueType& v, const ValueType& document, const UriType& id) {
if (v.GetType() == kObjectType) { if (v.GetType() == kObjectType) {
@ -1795,7 +1906,9 @@ private:
if (itr->value.IsString()) { if (itr->value.IsString()) {
SizeType len = itr->value.GetStringLength(); SizeType len = itr->value.GetStringLength();
if (len > 0) { if (len == 0)
SchemaError(kSchemaErrorRefInvalid, source);
else {
// First resolve $ref against the in-scope id // First resolve $ref against the in-scope id
UriType scopeId = UriType(id, allocator_); UriType scopeId = UriType(id, allocator_);
UriType ref = UriType(itr->value, allocator_).Resolve(scopeId, allocator_); UriType ref = UriType(itr->value, allocator_).Resolve(scopeId, allocator_);
@ -1805,26 +1918,32 @@ private:
const ValueType *base = FindId(document, ref, basePointer, docId_, false); const ValueType *base = FindId(document, ref, basePointer, docId_, false);
if (!base) { if (!base) {
// Remote reference - call the remote document provider // Remote reference - call the remote document provider
if (remoteProvider_) { if (!remoteProvider_)
SchemaError(kSchemaErrorRefNoRemoteProvider, source);
else {
if (const GenericSchemaDocument* remoteDocument = remoteProvider_->GetRemoteDocument(ref)) { if (const GenericSchemaDocument* remoteDocument = remoteProvider_->GetRemoteDocument(ref)) {
const Ch* s = ref.GetFragString(); const Ch* s = ref.GetFragString();
len = ref.GetFragStringLength(); len = ref.GetFragStringLength();
if (len <= 1 || s[1] == '/') { if (len <= 1 || s[1] == '/') {
// JSON pointer fragment, absolute in the remote schema // JSON pointer fragment, absolute in the remote schema
const PointerType pointer(s, len, allocator_); const PointerType pointer(s, len, allocator_);
if (pointer.IsValid()) { if (!pointer.IsValid())
SchemaErrorPointer(kSchemaErrorRefPointerInvalid, source, s, len, pointer);
else {
// Get the subschema // Get the subschema
if (const SchemaType *sc = remoteDocument->GetSchema(pointer)) { if (const SchemaType *sc = remoteDocument->GetSchema(pointer)) {
if (schema) if (schema)
*schema = sc; *schema = sc;
AddSchemaRefs(const_cast<SchemaType *>(sc)); AddSchemaRefs(const_cast<SchemaType *>(sc));
return true; return true;
} } else
SchemaErrorValue(kSchemaErrorRefUnknown, source, ref.GetString(), ref.GetStringLength());
} }
} else { } else
// Plain name fragment, not allowed // Plain name fragment, not allowed in remote schema
} SchemaErrorValue(kSchemaErrorRefPlainName, source, s, len);
} } else
SchemaErrorValue(kSchemaErrorRefNoRemoteSchema, source, ref.GetString(), ref.GetStringLength());
} }
} }
else { // Local reference else { // Local reference
@ -1833,16 +1952,18 @@ private:
if (len <= 1 || s[1] == '/') { if (len <= 1 || s[1] == '/') {
// JSON pointer fragment, relative to the resolved URI // JSON pointer fragment, relative to the resolved URI
const PointerType relPointer(s, len, allocator_); const PointerType relPointer(s, len, allocator_);
if (relPointer.IsValid()) { if (!relPointer.IsValid())
SchemaErrorPointer(kSchemaErrorRefPointerInvalid, source, s, len, relPointer);
else {
// Get the subschema // Get the subschema
if (const ValueType *pv = relPointer.Get(*base)) { if (const ValueType *pv = relPointer.Get(*base)) {
// Now get the absolute JSON pointer by adding relative to base // Now get the absolute JSON pointer by adding relative to base
PointerType pointer(basePointer); PointerType pointer(basePointer);
for (SizeType i = 0; i < relPointer.GetTokenCount(); i++) for (SizeType i = 0; i < relPointer.GetTokenCount(); i++)
pointer = pointer.Append(relPointer.GetTokens()[i], allocator_); pointer = pointer.Append(relPointer.GetTokens()[i], allocator_);
//GenericStringBuffer<EncodingType> sb; if (IsCyclicRef(pointer))
//pointer.StringifyUriFragment(sb); SchemaErrorValue(kSchemaErrorRefCyclical, source, ref.GetString(), ref.GetStringLength());
if (pointer.IsValid() && !IsCyclicRef(pointer)) { else {
// Call CreateSchema recursively, but first compute the in-scope id for the $ref target as we have jumped there // Call CreateSchema recursively, but first compute the in-scope id for the $ref target as we have jumped there
// TODO: cache pointer <-> id mapping // TODO: cache pointer <-> id mapping
size_t unresolvedTokenIndex; size_t unresolvedTokenIndex;
@ -1850,17 +1971,18 @@ private:
CreateSchema(schema, pointer, *pv, document, scopeId); CreateSchema(schema, pointer, *pv, document, scopeId);
return true; return true;
} }
} } else
SchemaErrorValue(kSchemaErrorRefUnknown, source, ref.GetString(), ref.GetStringLength());
} }
} else { } else {
// Plain name fragment, relative to the resolved URI // Plain name fragment, relative to the resolved URI
PointerType pointer = PointerType();
// See if the fragment matches an id in this document. // 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. // 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 (const ValueType *pv = FindId(*base, ref, pointer, UriType(ref.GetBaseString(), ref.GetBaseStringLength(), allocator_), true, basePointer)) {
if (!IsCyclicRef(pointer)) { if (IsCyclicRef(pointer))
//GenericStringBuffer<EncodingType> sb; SchemaErrorValue(kSchemaErrorRefCyclical, source, ref.GetString(), ref.GetStringLength());
//pointer.StringifyUriFragment(sb); else {
// Call CreateSchema recursively, but first compute the in-scope id for the $ref target as we have jumped there // Call CreateSchema recursively, but first compute the in-scope id for the $ref target as we have jumped there
// TODO: cache pointer <-> id mapping // TODO: cache pointer <-> id mapping
size_t unresolvedTokenIndex; size_t unresolvedTokenIndex;
@ -1868,7 +1990,8 @@ private:
CreateSchema(schema, pointer, *pv, document, scopeId); CreateSchema(schema, pointer, *pv, document, scopeId);
return true; return true;
} }
} } else
SchemaErrorValue(kSchemaErrorRefUnknown, source, ref.GetString(), ref.GetStringLength());
} }
} }
} }
@ -1965,8 +2088,10 @@ private:
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(s) from $ref(s) until resolved internal::Stack<Allocator> schemaRef_; // Stores Pointer(s) from $ref(s) until resolved
SValue uri_; // Schema document URI GValue uri_; // Schema document URI
UriType docId_; UriType docId_;
GValue error_;
GValue currentError_;
}; };
//! GenericSchemaDocument using Value type. //! GenericSchemaDocument using Value type.
@ -2099,13 +2224,12 @@ public:
return flags_; return flags_;
} }
//! Checks whether the current state is valid.
// Implementation of ISchemaValidator
virtual bool IsValid() const { virtual bool IsValid() const {
if (!valid_) return false; if (!valid_) return false;
if (GetContinueOnErrors() && !error_.ObjectEmpty()) return false; if (GetContinueOnErrors() && !error_.ObjectEmpty()) return false;
return true; return true;
} }
//! End of Implementation of ISchemaValidator
//! Gets the error object. //! Gets the error object.
ValueType& GetError() { return error_; } ValueType& GetError() { return error_; }
@ -2313,8 +2437,16 @@ public:
void NoneOf(ISchemaValidator** subvalidators, SizeType count) { void NoneOf(ISchemaValidator** subvalidators, SizeType count) {
AddErrorArray(kValidateErrorAnyOf, subvalidators, count); AddErrorArray(kValidateErrorAnyOf, subvalidators, count);
} }
void NotOneOf(ISchemaValidator** subvalidators, SizeType count, bool matched = false) { void NotOneOf(ISchemaValidator** subvalidators, SizeType count) {
AddErrorArray(matched ? kValidateErrorOneOfMatch : kValidateErrorOneOf, subvalidators, count); AddErrorArray(kValidateErrorOneOf, subvalidators, count);
}
void MultipleOneOf(SizeType index1, SizeType index2) {
ValueType matches(kArrayType);
matches.PushBack(index1, GetStateAllocator());
matches.PushBack(index2, GetStateAllocator());
currentError_.SetObject();
currentError_.AddMember(GetMatchesString(), matches, GetStateAllocator());
AddCurrentError(kValidateErrorOneOfMatch);
} }
void Disallowed() { void Disallowed() {
currentError_.SetObject(); currentError_.SetObject();
@ -2338,6 +2470,7 @@ public:
RAPIDJSON_STRING_(ErrorCode, 'e', 'r', 'r', 'o', 'r', 'C', 'o', 'd', 'e') RAPIDJSON_STRING_(ErrorCode, 'e', 'r', 'r', 'o', 'r', 'C', 'o', 'd', 'e')
RAPIDJSON_STRING_(ErrorMessage, 'e', 'r', 'r', 'o', 'r', 'M', 'e', 's', 's', 'a', 'g', 'e') RAPIDJSON_STRING_(ErrorMessage, 'e', 'r', 'r', 'o', 'r', 'M', 'e', 's', 's', 'a', 'g', 'e')
RAPIDJSON_STRING_(Duplicates, 'd', 'u', 'p', 'l', 'i', 'c', 'a', 't', 'e', 's') RAPIDJSON_STRING_(Duplicates, 'd', 'u', 'p', 'l', 'i', 'c', 'a', 't', 'e', 's')
RAPIDJSON_STRING_(Matches, 'm', 'a', 't', 'c', 'h', 'e', 's')
#undef RAPIDJSON_STRING_ #undef RAPIDJSON_STRING_
@ -2482,6 +2615,7 @@ RAPIDJSON_MULTILINEMACRO_END
virtual void FreeState(void* p) { virtual void FreeState(void* p) {
StateAllocator::Free(p); StateAllocator::Free(p);
} }
// End of implementation of ISchemaStateFactory<SchemaType>
private: private:
typedef typename SchemaType::Context Context; typedef typename SchemaType::Context Context;

View File

@ -112,6 +112,12 @@ TEST(SchemaValidator, Hasher) {
#define VALIDATE(schema, json, expected) \ #define VALIDATE(schema, json, expected) \
{\ {\
VALIDATE_(schema, json, expected, true) \
}
#define VALIDATE_(schema, json, expected, expected2) \
{\
EXPECT_TRUE(expected2 == schema.GetError().ObjectEmpty());\
SchemaValidator validator(schema);\ SchemaValidator validator(schema);\
Document d;\ Document d;\
/*printf("\n%s\n", json);*/\ /*printf("\n%s\n", json);*/\
@ -149,6 +155,7 @@ TEST(SchemaValidator, Hasher) {
#define INVALIDATE_(schema, json, invalidSchemaPointer, invalidSchemaKeyword, invalidDocumentPointer, error, \ #define INVALIDATE_(schema, json, invalidSchemaPointer, invalidSchemaKeyword, invalidDocumentPointer, error, \
flags, SchemaValidatorType, PointerType) \ flags, SchemaValidatorType, PointerType) \
{\ {\
EXPECT_TRUE(schema.GetError().ObjectEmpty());\
SchemaValidatorType validator(schema);\ SchemaValidatorType validator(schema);\
validator.SetValidateFlags(flags);\ validator.SetValidateFlags(flags);\
Document d;\ Document d;\
@ -188,6 +195,20 @@ TEST(SchemaValidator, Hasher) {
}\ }\
} }
// Use for checking whether a compiled schema document contains errors
#define SCHEMAERROR(schema, error) \
{\
Document e;\
e.Parse(error);\
if (schema.GetError() != e) {\
StringBuffer sb;\
Writer<StringBuffer> w(sb);\
schema.GetError().Accept(w);\
printf("GetError() Expected: %s Actual: %s\n", error, sb.GetString());\
ADD_FAILURE();\
}\
}
TEST(SchemaValidator, Typeless) { TEST(SchemaValidator, Typeless) {
Document sd; Document sd;
sd.Parse("{}"); sd.Parse("{}");
@ -223,7 +244,7 @@ TEST(SchemaValidator, Enum_Typed) {
"{ \"enum\": { \"errorCode\": 19, \"instanceRef\": \"#\", \"schemaRef\": \"#\" }}"); "{ \"enum\": { \"errorCode\": 19, \"instanceRef\": \"#\", \"schemaRef\": \"#\" }}");
} }
TEST(SchemaValidator, Enum_Typless) { TEST(SchemaValidator, Enum_Typeless) {
Document sd; Document sd;
sd.Parse("{ \"enum\": [\"red\", \"amber\", \"green\", null, 42] }"); sd.Parse("{ \"enum\": [\"red\", \"amber\", \"green\", null, 42] }");
SchemaDocument s(sd); SchemaDocument s(sd);
@ -333,7 +354,7 @@ TEST(SchemaValidator, OneOf) {
" ]" " ]"
"}}"); "}}");
INVALIDATE(s, "15", "", "oneOf", "", INVALIDATE(s, "15", "", "oneOf", "",
"{ \"oneOf\": { \"errorCode\": 22, \"instanceRef\": \"#\", \"schemaRef\": \"#\", \"errors\": [{}, {}]}}"); "{ \"oneOf\": { \"errorCode\": 22, \"instanceRef\": \"#\", \"schemaRef\": \"#\", \"matches\": [0,1]}}");
} }
TEST(SchemaValidator, Not) { TEST(SchemaValidator, Not) {
@ -502,12 +523,13 @@ TEST(SchemaValidator, String_Pattern) {
TEST(SchemaValidator, String_Pattern_Invalid) { TEST(SchemaValidator, String_Pattern_Invalid) {
Document sd; Document sd;
sd.Parse("{\"type\":\"string\",\"pattern\":\"a{0}\"}"); // TODO: report regex is invalid somehow sd.Parse("{\"type\":\"string\",\"pattern\":\"a{0}\"}");
SchemaDocument s(sd); SchemaDocument s(sd);
SCHEMAERROR(s, "{\"RegexInvalid\":{\"errorCode\":9,\"instanceRef\":\"#/pattern\",\"value\":\"a{0}\"}}");
VALIDATE(s, "\"\"", true); VALIDATE_(s, "\"\"", true, false);
VALIDATE(s, "\"a\"", true); VALIDATE_(s, "\"a\"", true, false);
VALIDATE(s, "\"aa\"", true); VALIDATE_(s, "\"aa\"", true, false);
} }
#endif #endif
@ -1886,12 +1908,6 @@ TEST(SchemaValidator, SchemaPointer) {
" }," " },"
" \"f\": {" " \"f\": {"
" \"type\": \"boolean\"" " \"type\": \"boolean\""
" },"
" \"cyclic_source\": {"
" \"$ref\": \"#/definitions/Resp_200/properties/cyclic_target\""
" },"
" \"cyclic_target\": {"
" \"$ref\": \"#/definitions/Resp_200/properties/cyclic_source\""
" }" " }"
" }," " },"
" \"type\": \"object\"" " \"type\": \"object\""
@ -2390,7 +2406,9 @@ TEST(SchemaValidator, Issue728_AllOfRef) {
Document sd; Document sd;
sd.Parse("{\"allOf\": [{\"$ref\": \"#/abc\"}]}"); sd.Parse("{\"allOf\": [{\"$ref\": \"#/abc\"}]}");
SchemaDocument s(sd); SchemaDocument s(sd);
VALIDATE(s, "{\"key1\": \"abc\", \"key2\": \"def\"}", true); SCHEMAERROR(s, "{\"RefUnknown\":{\"errorCode\":5,\"instanceRef\":\"#/allOf/0\",\"value\":\"#/abc\"}}");
VALIDATE_(s, "{\"key1\": \"abc\", \"key2\": \"def\"}", true, false);
} }
TEST(SchemaValidator, Issue1017_allOfHandler) { TEST(SchemaValidator, Issue1017_allOfHandler) {
@ -2625,7 +2643,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::SValue(uri, length) != collection[i]->GetURI()) ++i; while (collection[i] && SchemaDocument::GValue(uri, length) != collection[i]->GetURI()) ++i;
return collection[i]; return collection[i];
} }
}; };
@ -2656,7 +2674,7 @@ TEST(SchemaValidator, ContinueOnErrors) {
ASSERT_FALSE(sd.HasParseError()); ASSERT_FALSE(sd.HasParseError());
SchemaDocument s(sd); SchemaDocument s(sd);
VALIDATE(s, "{\"version\": 1.0, \"address\": {\"number\": 24, \"street1\": \"The Woodlands\", \"street3\": \"Ham\", \"city\": \"Romsey\", \"area\": \"Kent\", \"country\": \"UK\", \"postcode\": \"SO51 0GP\"}, \"phones\": [\"0111-222333\", \"0777-666888\"], \"names\": [\"Fred\", \"Bloggs\"]}", true); VALIDATE(s, "{\"version\": 1.0, \"address\": {\"number\": 24, \"street1\": \"The Woodlands\", \"street3\": \"Ham\", \"city\": \"Romsey\", \"area\": \"Kent\", \"country\": \"UK\", \"postcode\": \"SO51 0GP\"}, \"phones\": [\"0111-222333\", \"0777-666888\"], \"names\": [\"Fred\", \"Bloggs\"]}", true);
INVALIDATE_(s, "{\"version\": 1.01, \"address\": {\"number\": 0, \"street2\": false, \"street3\": \"Ham\", \"city\": \"RomseyTownFC\", \"area\": \"BC\", \"country\": \"USA\", \"postcode\": \"999ABC\"}, \"phones\": [], \"planet\": \"Earth\", \"extra\": {\"S_xxx\": 123}}", "#", "errors", "#", INVALIDATE_(s, "{\"version\": 1.01, \"address\": {\"number\": 0, \"street2\": false, \"street3\": \"Ham\", \"city\": \"RomseyTownFC\", \"area\": \"Narnia\", \"country\": \"USA\", \"postcode\": \"999ABC\"}, \"phones\": [], \"planet\": \"Earth\", \"extra\": {\"S_xxx\": 123}}", "#", "errors", "#",
"{ \"multipleOf\": {" "{ \"multipleOf\": {"
" \"errorCode\": 1, \"instanceRef\": \"#/version\", \"schemaRef\": \"#/definitions/decimal_type\", \"expected\": 1.0, \"actual\": 1.01" " \"errorCode\": 1, \"instanceRef\": \"#/version\", \"schemaRef\": \"#/definitions/decimal_type\", \"expected\": 1.0, \"actual\": 1.01"
" }," " },"
@ -2691,6 +2709,9 @@ TEST(SchemaValidator, ContinueOnErrors) {
" }," " },"
" \"required\": {" " \"required\": {"
" \"missing\": [\"street1\"], \"errorCode\": 15, \"instanceRef\": \"#/address\", \"schemaRef\": \"#/definitions/address_type\"" " \"missing\": [\"street1\"], \"errorCode\": 15, \"instanceRef\": \"#/address\", \"schemaRef\": \"#/definitions/address_type\""
" },"
" \"oneOf\": {"
" \"matches\": [0, 1], \"errorCode\": 22, \"instanceRef\": \"#/address/area\", \"schemaRef\": \"#/definitions/address_type/properties/area\""
" }" " }"
"}", "}",
kValidateDefaultFlags | kValidateContinueOnErrorFlag, SchemaValidator, Pointer); kValidateDefaultFlags | kValidateContinueOnErrorFlag, SchemaValidator, Pointer);
@ -2917,7 +2938,7 @@ TEST(SchemaValidator, ContinueOnErrors_RogueString) {
// Test that when kValidateContinueOnErrorFlag is set, an incorrect simple type with a sub-schema is handled correctly. // Test that when kValidateContinueOnErrorFlag is set, an incorrect simple type with a sub-schema is handled correctly.
// This tests that we don't blow up when there is a type mismatch but there is a sub-schema present // This tests that we don't blow up when there is a type mismatch but there is a sub-schema present
TEST(SchemaValidator, ContinueOnErrors_Issue2) { TEST(SchemaValidator, ContinueOnErrors_BadSimpleType) {
Document sd; Document sd;
sd.Parse("{\"type\":\"string\", \"anyOf\":[{\"maxLength\":2}]}"); sd.Parse("{\"type\":\"string\", \"anyOf\":[{\"maxLength\":2}]}");
ASSERT_FALSE(sd.HasParseError()); ASSERT_FALSE(sd.HasParseError());
@ -2943,10 +2964,148 @@ TEST(SchemaValidator, ContinueOnErrors_Issue2) {
kValidateDefaultFlags | kValidateContinueOnErrorFlag, SchemaValidator, Pointer); kValidateDefaultFlags | kValidateContinueOnErrorFlag, SchemaValidator, Pointer);
} }
TEST(SchemaValidator, Schema_UnknownError) {
TEST(SchemaValidator, UnknownValidationError) {
ASSERT_TRUE(SchemaValidator::SchemaType::GetValidateErrorKeyword(kValidateErrors).GetString() == std::string("null")); ASSERT_TRUE(SchemaValidator::SchemaType::GetValidateErrorKeyword(kValidateErrors).GetString() == std::string("null"));
} }
// The first occurrence of a duplicate keyword is taken
TEST(SchemaValidator, DuplicateKeyword) {
Document sd;
sd.Parse("{ \"title\": \"test\",\"type\": \"number\", \"type\": \"string\" }");
EXPECT_FALSE(sd.HasParseError());
SchemaDocument s(sd);
VALIDATE(s, "42", true);
INVALIDATE(s, "\"Life, the universe, and everything\"", "", "type", "",
"{ \"type\": {"
" \"errorCode\": 20,"
" \"instanceRef\": \"#\", \"schemaRef\": \"#\","
" \"expected\": [\"number\"], \"actual\": \"string\""
"}}");
}
// SchemaDocument tests
TEST(SchemaValidator, Schema_StartUnknown) {
Document sd;
sd.Parse("{\"type\": \"integer\"}");
ASSERT_FALSE(sd.HasParseError());
SchemaDocument s(sd, 0, 0, 0, 0, SchemaDocument::PointerType("/nowhere"));
SCHEMAERROR(s, "{\"StartUnknown\":{\"errorCode\":1,\"instanceRef\":\"#\", \"value\":\"#/nowhere\"}}");
}
// $ref is a non-JSON pointer fragment - not allowed when OpenAPI
TEST(SchemaValidator, Schema_RefPlainNameOpenApi) {
typedef GenericSchemaDocument<Value, MemoryPoolAllocator<> > SchemaDocumentType;
Document sd;
sd.Parse("{\"swagger\": \"2.0\", \"type\": \"object\", \"properties\": {\"myInt1\": {\"$ref\": \"#myId\"}, \"myStr\": {\"type\": \"string\", \"id\": \"#myStrId\"}, \"myInt2\": {\"type\": \"integer\", \"id\": \"#myId\"}}}");
SchemaDocumentType s(sd);
SCHEMAERROR(s, "{\"RefPlainName\":{\"errorCode\":2,\"instanceRef\":\"#/properties/myInt1\",\"value\":\"#myId\"}}");
}
// $ref is a non-JSON pointer fragment - not allowed when remote document
TEST(SchemaValidator, Schema_RefPlainNameRemote) {
typedef GenericSchemaDocument<Value, MemoryPoolAllocator<> > SchemaDocumentType;
RemoteSchemaDocumentProvider<SchemaDocumentType> provider;
Document sd;
sd.Parse("{\"type\": \"object\", \"properties\": {\"myInt\": {\"$ref\": \"/subSchemas.json#plainname\"}}}");
SchemaDocumentType s(sd, "http://localhost:1234/xxxx", 26, &provider);
SCHEMAERROR(s, "{\"RefPlainName\":{\"errorCode\":2,\"instanceRef\":\"#/properties/myInt\",\"value\":\"#plainname\"}}");
}
// $ref is an empty string
TEST(SchemaValidator, Schema_RefEmptyString) {
typedef GenericSchemaDocument<Value, MemoryPoolAllocator<> > SchemaDocumentType;
Document sd;
sd.Parse("{\"type\": \"object\", \"properties\": {\"myInt1\": {\"$ref\": \"\"}}}");
SchemaDocumentType s(sd);
SCHEMAERROR(s, "{\"RefInvalid\":{\"errorCode\":3,\"instanceRef\":\"#/properties/myInt1\"}}");
}
// $ref is remote but no provider
TEST(SchemaValidator, Schema_RefNoRemoteProvider) {
typedef GenericSchemaDocument<Value, MemoryPoolAllocator<> > SchemaDocumentType;
Document sd;
sd.Parse("{\"type\": \"object\", \"properties\": {\"myInt\": {\"$ref\": \"/subSchemas.json#plainname\"}}}");
SchemaDocumentType s(sd, "http://localhost:1234/xxxx", 26, 0);
SCHEMAERROR(s, "{\"RefNoRemoteProvider\":{\"errorCode\":7,\"instanceRef\":\"#/properties/myInt\"}}");
}
// $ref is remote but no schema returned
TEST(SchemaValidator, Schema_RefNoRemoteSchema) {
typedef GenericSchemaDocument<Value, MemoryPoolAllocator<> > SchemaDocumentType;
RemoteSchemaDocumentProvider<SchemaDocumentType> provider;
Document sd;
sd.Parse("{\"type\": \"object\", \"properties\": {\"myInt\": {\"$ref\": \"/will-not-resolve.json\"}}}");
SchemaDocumentType s(sd, "http://localhost:1234/xxxx", 26, &provider);
SCHEMAERROR(s, "{\"RefNoRemoteSchema\":{\"errorCode\":8,\"instanceRef\":\"#/properties/myInt\",\"value\":\"http://localhost:1234/will-not-resolve.json\"}}");
}
// $ref pointer is invalid
TEST(SchemaValidator, Schema_RefPointerInvalid) {
typedef GenericSchemaDocument<Value, MemoryPoolAllocator<> > SchemaDocumentType;
Document sd;
sd.Parse("{\"type\": \"object\", \"properties\": {\"myInt\": {\"$ref\": \"#/&&&&&\"}}}");
SchemaDocumentType s(sd);
SCHEMAERROR(s, "{\"RefPointerInvalid\":{\"errorCode\":4,\"instanceRef\":\"#/properties/myInt\",\"value\":\"#/&&&&&\",\"offset\":2}}");
}
// $ref is remote and pointer is invalid
TEST(SchemaValidator, Schema_RefPointerInvalidRemote) {
typedef GenericSchemaDocument<Value, MemoryPoolAllocator<> > SchemaDocumentType;
RemoteSchemaDocumentProvider<SchemaDocumentType> provider;
Document sd;
sd.Parse("{\"type\": \"object\", \"properties\": {\"myInt\": {\"$ref\": \"/subSchemas.json#/abc&&&&&\"}}}");
SchemaDocumentType s(sd, "http://localhost:1234/xxxx", 26, &provider);
SCHEMAERROR(s, "{\"RefPointerInvalid\":{\"errorCode\":4,\"instanceRef\":\"#/properties/myInt\",\"value\":\"#/abc&&&&&\",\"offset\":5}}");
}
// $ref is unknown non-pointer
TEST(SchemaValidator, Schema_RefUnknownPlainName) {
typedef GenericSchemaDocument<Value, MemoryPoolAllocator<> > SchemaDocumentType;
Document sd;
sd.Parse("{\"type\": \"object\", \"properties\": {\"myInt\": {\"$ref\": \"#plainname\"}}}");
SchemaDocumentType s(sd);
SCHEMAERROR(s, "{\"RefUnknown\":{\"errorCode\":5,\"instanceRef\":\"#/properties/myInt\",\"value\":\"#plainname\"}}");
}
/// $ref is unknown pointer
TEST(SchemaValidator, Schema_RefUnknownPointer) {
typedef GenericSchemaDocument<Value, MemoryPoolAllocator<> > SchemaDocumentType;
Document sd;
sd.Parse("{\"type\": \"object\", \"properties\": {\"myInt\": {\"$ref\": \"#/a/b\"}}}");
SchemaDocumentType s(sd);
SCHEMAERROR(s, "{\"RefUnknown\":{\"errorCode\":5,\"instanceRef\":\"#/properties/myInt\",\"value\":\"#/a/b\"}}");
}
// $ref is remote and unknown pointer
TEST(SchemaValidator, Schema_RefUnknownPointerRemote) {
typedef GenericSchemaDocument<Value, MemoryPoolAllocator<> > SchemaDocumentType;
RemoteSchemaDocumentProvider<SchemaDocumentType> provider;
Document sd;
sd.Parse("{\"type\": \"object\", \"properties\": {\"myInt\": {\"$ref\": \"/subSchemas.json#/a/b\"}}}");
SchemaDocumentType s(sd, "http://localhost:1234/xxxx", 26, &provider);
SCHEMAERROR(s, "{\"RefUnknown\":{\"errorCode\":5,\"instanceRef\":\"#/properties/myInt\",\"value\":\"http://localhost:1234/subSchemas.json#/a/b\"}}");
}
// $ref is cyclical
TEST(SchemaValidator, Schema_RefCyclical) {
typedef GenericSchemaDocument<Value, MemoryPoolAllocator<> > SchemaDocumentType;
Document sd;
sd.Parse("{\"type\": \"object\", \"properties\": {"
" \"cyclic_source\": {"
" \"$ref\": \"#/properties/cyclic_target\""
" },"
" \"cyclic_target\": {"
" \"$ref\": \"#/properties/cyclic_source\""
" }"
"}}");
SchemaDocumentType s(sd);
SCHEMAERROR(s, "{\"RefCyclical\":{\"errorCode\":6,\"instanceRef\":\"#/properties/cyclic_target\",\"value\":\"#/properties/cyclic_source\"}}");
}
#if defined(_MSC_VER) || defined(__clang__) #if defined(_MSC_VER) || defined(__clang__)
RAPIDJSON_DIAG_POP RAPIDJSON_DIAG_POP
#endif #endif