code & tests for openapi 2.0 & 3.0 suppprt

This commit is contained in:
Steve Hanson 2022-12-02 16:53:27 +00:00 committed by Milo Yip
parent 80b6d1c834
commit 55eca66f39
4 changed files with 898 additions and 110 deletions

View File

@ -1,5 +1,5 @@
// Tencent is pleased to support the open source community by making RapidJSON available. // Tencent is pleased to support the open source community by making RapidJSON available.
// //
// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. // Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip.
// //
// Licensed under the MIT License (the "License"); you may not use this file except // Licensed under the MIT License (the "License"); you may not use this file except
@ -7,9 +7,9 @@
// //
// http://opensource.org/licenses/MIT // http://opensource.org/licenses/MIT
// //
// Unless required by applicable law or agreed to in writing, software distributed // Unless required by applicable law or agreed to in writing, software distributed
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
// CONDITIONS OF ANY KIND, either express or implied. See the License for the // CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
#ifndef RAPIDJSON_ERROR_EN_H_ #ifndef RAPIDJSON_ERROR_EN_H_
@ -109,6 +109,9 @@ inline const RAPIDJSON_ERROR_CHARTYPE* GetValidateError_En(ValidateErrorCode val
case kValidateErrorAnyOf: return RAPIDJSON_ERROR_STRING("Property did not match any of the sub-schemas specified by 'anyOf', refer to following errors."); case 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'.");
case kValidateErrorReadOnly: return RAPIDJSON_ERROR_STRING("Property is read-only but has been provided when validation is for writing.");
case kValidateErrorWriteOnly: return RAPIDJSON_ERROR_STRING("Property is write-only but has been provided when validation is for reading.");
default: return RAPIDJSON_ERROR_STRING("Unknown error."); default: return RAPIDJSON_ERROR_STRING("Unknown error.");
} }
} }
@ -134,6 +137,10 @@ inline const RAPIDJSON_ERROR_CHARTYPE* GetValidateError_En(ValidateErrorCode val
case kSchemaErrorRefNoRemoteProvider: return RAPIDJSON_ERROR_STRING("$ref is remote but there is no remote provider."); case 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 kSchemaErrorRefNoRemoteSchema: return RAPIDJSON_ERROR_STRING("$ref '%value' is remote but the remote provider did not return a schema.");
case kSchemaErrorRegexInvalid: return RAPIDJSON_ERROR_STRING("Invalid regular expression '%value' in 'pattern' or 'patternProperties'."); case kSchemaErrorRegexInvalid: return RAPIDJSON_ERROR_STRING("Invalid regular expression '%value' in 'pattern' or 'patternProperties'.");
case kSchemaErrorSpecUnknown: return RAPIDJSON_ERROR_STRING("JSON schema draft or OpenAPI version is not recognized.");
case kSchemaErrorSpecUnsupported: return RAPIDJSON_ERROR_STRING("JSON schema draft or OpenAPI version is not supported.");
case kSchemaErrorSpecIllegal: return RAPIDJSON_ERROR_STRING("Both JSON schema draft and OpenAPI version found in document.");
case kSchemaErrorReadOnlyAndWriteOnly: return RAPIDJSON_ERROR_STRING("Property must not be both 'readOnly' and 'writeOnly'.");
default: return RAPIDJSON_ERROR_STRING("Unknown error."); default: return RAPIDJSON_ERROR_STRING("Unknown error.");
} }

View File

@ -1,5 +1,5 @@
// Tencent is pleased to support the open source community by making RapidJSON available. // Tencent is pleased to support the open source community by making RapidJSON available.
// //
// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. // Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip.
// //
// Licensed under the MIT License (the "License"); you may not use this file except // Licensed under the MIT License (the "License"); you may not use this file except
@ -7,9 +7,9 @@
// //
// http://opensource.org/licenses/MIT // http://opensource.org/licenses/MIT
// //
// Unless required by applicable law or agreed to in writing, software distributed // Unless required by applicable law or agreed to in writing, software distributed
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
// CONDITIONS OF ANY KIND, either express or implied. See the License for the // CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
#ifndef RAPIDJSON_ERROR_ERROR_H_ #ifndef RAPIDJSON_ERROR_ERROR_H_
@ -192,7 +192,10 @@ enum ValidateErrorCode {
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'.
kValidateErrorAllOf, //!< Property did not match all of the sub-schemas specified by 'allOf'. kValidateErrorAllOf, //!< Property did not match all of the sub-schemas specified by 'allOf'.
kValidateErrorAnyOf, //!< Property did not match any of the sub-schemas specified by 'anyOf'. kValidateErrorAnyOf, //!< Property did not match any of the sub-schemas specified by 'anyOf'.
kValidateErrorNot //!< Property matched the sub-schema specified by 'not'. kValidateErrorNot, //!< Property matched the sub-schema specified by 'not'.
kValidateErrorReadOnly, //!< Property is read-only but has been provided when validation is for writing
kValidateErrorWriteOnly //!< Property is write-only but has been provided when validation is for reading
}; };
//! Function pointer type of GetValidateError(). //! Function pointer type of GetValidateError().
@ -225,7 +228,11 @@ enum SchemaErrorCode {
kSchemaErrorRefCyclical, //!< $ref is cyclical kSchemaErrorRefCyclical, //!< $ref is cyclical
kSchemaErrorRefNoRemoteProvider, //!< $ref is remote but there is no remote provider kSchemaErrorRefNoRemoteProvider, //!< $ref is remote but there is no remote provider
kSchemaErrorRefNoRemoteSchema, //!< $ref is remote but the remote provider did not return a schema kSchemaErrorRefNoRemoteSchema, //!< $ref is remote but the remote provider did not return a schema
kSchemaErrorRegexInvalid //!< Invalid regular expression in 'pattern' or 'patternProperties' kSchemaErrorRegexInvalid, //!< Invalid regular expression in 'pattern' or 'patternProperties'
kSchemaErrorSpecUnknown, //!< JSON schema draft or OpenAPI version is not recognized
kSchemaErrorSpecUnsupported, //!< JSON schema draft or OpenAPI version is not supported
kSchemaErrorSpecIllegal, //!< Both JSON schema draft and OpenAPI version found in document
kSchemaErrorReadOnlyAndWriteOnly //!< Property must not be both 'readOnly' and 'writeOnly'
}; };
//! Function pointer type of GetSchemaError(). //! Function pointer type of GetSchemaError().

View File

@ -1,5 +1,5 @@
// Tencent is pleased to support the open source community by making RapidJSON available-> // Tencent is pleased to support the open source community by making RapidJSON available->
// //
// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip-> All rights reserved-> // Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip-> All rights reserved->
// //
// Licensed under the MIT License (the "License"); you may not use this file except // Licensed under the MIT License (the "License"); you may not use this file except
@ -7,9 +7,9 @@
// //
// http://opensource->org/licenses/MIT // http://opensource->org/licenses/MIT
// //
// Unless required by applicable law or agreed to in writing, software distributed // Unless required by applicable law or agreed to in writing, software distributed
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
// CONDITIONS OF ANY KIND, either express or implied-> See the License for the // CONDITIONS OF ANY KIND, either express or implied-> See the License for the
// specific language governing permissions and limitations under the License-> // specific language governing permissions and limitations under the License->
#ifndef RAPIDJSON_SCHEMA_H_ #ifndef RAPIDJSON_SCHEMA_H_
@ -74,48 +74,94 @@ RAPIDJSON_NAMESPACE_BEGIN
namespace internal { namespace internal {
inline void PrintInvalidKeyword(const char* keyword) { inline void PrintInvalidKeywordData(const char* keyword) {
printf("Fail keyword: %s\n", keyword); printf(" Fail keyword: '%s'\n", keyword);
} }
inline void PrintInvalidKeyword(const wchar_t* keyword) { inline void PrintInvalidKeywordData(const wchar_t* keyword) {
wprintf(L"Fail keyword: %ls\n", keyword); wprintf(L" Fail keyword: '%ls'\n", keyword);
} }
inline void PrintInvalidDocument(const char* document) { inline void PrintInvalidDocumentData(const char* document) {
printf("Fail document: %s\n\n", document); printf(" Fail document: '%s'\n", document);
} }
inline void PrintInvalidDocument(const wchar_t* document) { inline void PrintInvalidDocumentData(const wchar_t* document) {
wprintf(L"Fail document: %ls\n\n", document); wprintf(L" Fail document: '%ls'\n", document);
} }
inline void PrintValidatorPointers(unsigned depth, const char* s, const char* d) { inline void PrintValidatorPointersData(const char* s, const char* d, unsigned depth) {
printf("S: %*s%s\nD: %*s%s\n\n", depth * 4, " ", s, depth * 4, " ", d); printf(" Sch: %*s'%s'\n Doc: %*s'%s'\n", depth * 4, " ", s, depth * 4, " ", d);
} }
inline void PrintValidatorPointers(unsigned depth, const wchar_t* s, const wchar_t* d) { inline void PrintValidatorPointersData(const wchar_t* s, const wchar_t* d, unsigned depth) {
wprintf(L"S: %*ls%ls\nD: %*ls%ls\n\n", depth * 4, L" ", s, depth * 4, L" ", d); wprintf(L" Sch: %*ls'%ls'\n Doc: %*ls'%ls'\n", depth * 4, L" ", s, depth * 4, L" ", d);
}
inline void PrintSchemaIdsData(const char* base, const char* local, const char* resolved) {
printf(" Resolving id: Base: '%s', Local: '%s', Resolved: '%s'\n", base, local, resolved);
}
inline void PrintSchemaIdsData(const wchar_t* base, const wchar_t* local, const wchar_t* resolved) {
wprintf(L" Resolving id: Base: '%ls', Local: '%ls', Resolved: '%ls'\n", base, local, resolved);
}
inline void PrintMethodData(const char* method) {
printf("%s\n", method);
}
inline void PrintMethodData(const char* method, bool b) {
printf("%s, Data: '%s'\n", method, b ? "true" : "false");
}
inline void PrintMethodData(const char* method, int64_t i) {
printf("%s, Data: '%" PRId64 "'\n", method, i);
}
inline void PrintMethodData(const char* method, uint64_t u) {
printf("%s, Data: '%" PRIu64 "'\n", method, u);
}
inline void PrintMethodData(const char* method, double d) {
printf("%s, Data: '%lf'\n", method, d);
}
inline void PrintMethodData(const char* method, const char* s) {
printf("%s, Data: '%s'\n", method, s);
}
inline void PrintMethodData(const char* method, const wchar_t* s) {
wprintf(L"%hs, Data: '%ls'\n", method, s);
}
inline void PrintMethodData(const char* method, const char* s1, const char* s2) {
printf("%s, Data: '%s', '%s'\n", method, s1, s2);
}
inline void PrintMethodData(const char* method, const wchar_t* s1, const wchar_t* s2) {
wprintf(L"%hs, Data: '%ls', '%ls'\n", method, s1, s2);
} }
} // namespace internal } // namespace internal
#endif // RAPIDJSON_SCHEMA_VERBOSE #endif // RAPIDJSON_SCHEMA_VERBOSE
#ifndef RAPIDJSON_SCHEMA_PRINT
#if RAPIDJSON_SCHEMA_VERBOSE
#define RAPIDJSON_SCHEMA_PRINT(name, ...) internal::Print##name##Data(__VA_ARGS__)
#else
#define RAPIDJSON_SCHEMA_PRINT(name, ...)
#endif
#endif
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// RAPIDJSON_INVALID_KEYWORD_RETURN // RAPIDJSON_INVALID_KEYWORD_RETURN
#if RAPIDJSON_SCHEMA_VERBOSE
#define RAPIDJSON_INVALID_KEYWORD_VERBOSE(keyword) internal::PrintInvalidKeyword(keyword)
#else
#define RAPIDJSON_INVALID_KEYWORD_VERBOSE(keyword)
#endif
#define RAPIDJSON_INVALID_KEYWORD_RETURN(code)\ #define RAPIDJSON_INVALID_KEYWORD_RETURN(code)\
RAPIDJSON_MULTILINEMACRO_BEGIN\ RAPIDJSON_MULTILINEMACRO_BEGIN\
context.invalidCode = code;\ context.invalidCode = code;\
context.invalidKeyword = SchemaType::GetValidateErrorKeyword(code).GetString();\ context.invalidKeyword = SchemaType::GetValidateErrorKeyword(code).GetString();\
RAPIDJSON_INVALID_KEYWORD_VERBOSE(context.invalidKeyword);\ RAPIDJSON_SCHEMA_PRINT(InvalidKeyword, context.invalidKeyword);\
return false;\ return false;\
RAPIDJSON_MULTILINEMACRO_END RAPIDJSON_MULTILINEMACRO_END
@ -138,9 +184,53 @@ RAPIDJSON_MULTILINEMACRO_END
enum ValidateFlag { enum ValidateFlag {
kValidateNoFlags = 0, //!< No flags are set. kValidateNoFlags = 0, //!< No flags are set.
kValidateContinueOnErrorFlag = 1, //!< Don't stop after first validation error. kValidateContinueOnErrorFlag = 1, //!< Don't stop after first validation error.
kValidateReadFlag = 2, //!< Validation is for a read semantic.
kValidateWriteFlag = 4, //!< Validation is for a write semantic.
kValidateDefaultFlags = RAPIDJSON_VALIDATE_DEFAULT_FLAGS //!< Default validate flags. Can be customized by defining RAPIDJSON_VALIDATE_DEFAULT_FLAGS kValidateDefaultFlags = RAPIDJSON_VALIDATE_DEFAULT_FLAGS //!< Default validate flags. Can be customized by defining RAPIDJSON_VALIDATE_DEFAULT_FLAGS
}; };
///////////////////////////////////////////////////////////////////////////////
// Specification
enum SchemaDraft {
kDraftUnknown = -1,
kDraftNone = 0,
kDraft03 = 3,
kDraftMin = 4, //!< Current minimum supported draft
kDraft04 = 4,
kDraft05 = 5,
kDraftMax = 5, //!< Current maximum supported draft
kDraft06 = 6,
kDraft07 = 7,
kDraft2019_09 = 8,
kDraft2020_12 = 9
};
enum OpenApiVersion {
kVersionUnknown = -1,
kVersionNone = 0,
kVersionMin = 2, //!< Current minimum supported version
kVersion20 = 2,
kVersion30 = 3,
kVersionMax = 3, //!< Current maximum supported version
kVersion31 = 4,
};
struct Specification {
Specification(SchemaDraft d) : draft(d), oapi(kVersionNone) {}
Specification(OpenApiVersion o) : oapi(o) {
if (oapi == kVersion20) draft = kDraft04;
else if (oapi == kVersion30) draft = kDraft05;
else if (oapi == kVersion31) draft = kDraft2020_12;
else draft = kDraft04;
}
~Specification() {}
bool IsSupported() const {
return ((draft >= kDraftMin && draft <= kDraftMax) && ((oapi == kVersionNone) || (oapi >= kVersionMin && oapi <= kVersionMax)));
}
SchemaDraft draft;
OpenApiVersion oapi;
};
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// Forward declarations // Forward declarations
@ -233,6 +323,8 @@ public:
virtual void NotOneOf(ISchemaValidator** subvalidators, SizeType count) = 0; virtual void NotOneOf(ISchemaValidator** subvalidators, SizeType count) = 0;
virtual void MultipleOneOf(SizeType index1, SizeType index2) = 0; virtual void MultipleOneOf(SizeType index1, SizeType index2) = 0;
virtual void Disallowed() = 0; virtual void Disallowed() = 0;
virtual void DisallowedWhenWriting() = 0;
virtual void DisallowedWhenReading() = 0;
}; };
@ -253,10 +345,10 @@ public:
bool Uint(unsigned u) { Number n; n.u.u = u; n.d = static_cast<double>(u); return WriteNumber(n); } bool Uint(unsigned u) { Number n; n.u.u = u; n.d = static_cast<double>(u); return WriteNumber(n); }
bool Int64(int64_t i) { Number n; n.u.i = i; n.d = static_cast<double>(i); return WriteNumber(n); } bool Int64(int64_t i) { Number n; n.u.i = i; n.d = static_cast<double>(i); return WriteNumber(n); }
bool Uint64(uint64_t u) { Number n; n.u.u = u; n.d = static_cast<double>(u); return WriteNumber(n); } bool Uint64(uint64_t u) { Number n; n.u.u = u; n.d = static_cast<double>(u); return WriteNumber(n); }
bool Double(double d) { bool Double(double d) {
Number n; Number n;
if (d < 0) n.u.i = static_cast<int64_t>(d); if (d < 0) n.u.i = static_cast<int64_t>(d);
else n.u.u = static_cast<uint64_t>(d); else n.u.u = static_cast<uint64_t>(d);
n.d = d; n.d = d;
return WriteNumber(n); return WriteNumber(n);
} }
@ -350,10 +442,11 @@ struct SchemaValidationContext {
kPatternValidatorWithAdditionalProperty kPatternValidatorWithAdditionalProperty
}; };
SchemaValidationContext(SchemaValidatorFactoryType& f, ErrorHandlerType& eh, const SchemaType* s) : SchemaValidationContext(SchemaValidatorFactoryType& f, ErrorHandlerType& eh, const SchemaType* s, unsigned fl = 0) :
factory(f), factory(f),
error_handler(eh), error_handler(eh),
schema(s), schema(s),
flags(fl),
valueSchema(), valueSchema(),
invalidKeyword(), invalidKeyword(),
invalidCode(), invalidCode(),
@ -401,6 +494,7 @@ struct SchemaValidationContext {
SchemaValidatorFactoryType& factory; SchemaValidatorFactoryType& factory;
ErrorHandlerType& error_handler; ErrorHandlerType& error_handler;
const SchemaType* schema; const SchemaType* schema;
unsigned flags;
const SchemaType* valueSchema; const SchemaType* valueSchema;
const Ch* invalidKeyword; const Ch* invalidKeyword;
ValidateErrorCode invalidCode; ValidateErrorCode invalidCode;
@ -443,6 +537,7 @@ public:
allocator_(allocator), allocator_(allocator),
uri_(schemaDocument->GetURI(), *allocator), uri_(schemaDocument->GetURI(), *allocator),
id_(id, allocator), id_(id, allocator),
spec_(schemaDocument->GetSpecification()),
pointer_(p, allocator), pointer_(p, allocator),
typeless_(schemaDocument->GetTypeless()), typeless_(schemaDocument->GetTypeless()),
enum_(), enum_(),
@ -475,8 +570,15 @@ public:
maxLength_(~SizeType(0)), maxLength_(~SizeType(0)),
exclusiveMinimum_(false), exclusiveMinimum_(false),
exclusiveMaximum_(false), exclusiveMaximum_(false),
defaultValueLength_(0) defaultValueLength_(0),
readOnly_(false),
writeOnly_(false),
nullable_(false)
{ {
GenericStringBuffer<EncodingType> sb;
p.StringifyUriFragment(sb);
RAPIDJSON_SCHEMA_PRINT(Method, "Schema::Schema", sb.GetString(), id.GetString());
typedef typename ValueType::ConstValueIterator ConstValueIterator; typedef typename ValueType::ConstValueIterator ConstValueIterator;
typedef typename ValueType::ConstMemberIterator ConstMemberIterator; typedef typename ValueType::ConstMemberIterator ConstMemberIterator;
@ -495,10 +597,13 @@ public:
return; return;
// If we have an id property, resolve it with the in-scope id // If we have an id property, resolve it with the in-scope id
// Not supported for open api 2.0 or 3.0
if (spec_.oapi != kVersion20 && spec_.oapi != kVersion30)
if (const ValueType* v = GetMember(value, GetIdString())) { if (const ValueType* v = GetMember(value, GetIdString())) {
if (v->IsString()) { if (v->IsString()) {
UriType local(*v, allocator); UriType local(*v, allocator);
id_ = local.Resolve(id_, allocator); id_ = local.Resolve(id_, allocator);
RAPIDJSON_SCHEMA_PRINT(SchemaIds, id.GetString(), v->GetString(), id_.GetString());
} }
} }
@ -525,8 +630,11 @@ public:
} }
} }
if (schemaDocument) { if (schemaDocument)
AssignIfExist(allOf_, *schemaDocument, p, value, GetAllOfString(), document); AssignIfExist(allOf_, *schemaDocument, p, value, GetAllOfString(), document);
// AnyOf, OneOf, Not not supported for open api 2.0
if (schemaDocument && spec_.oapi != kVersion20) {
AssignIfExist(anyOf_, *schemaDocument, p, value, GetAnyOfString(), document); AssignIfExist(anyOf_, *schemaDocument, p, value, GetAnyOfString(), document);
AssignIfExist(oneOf_, *schemaDocument, p, value, GetOneOfString(), document); AssignIfExist(oneOf_, *schemaDocument, p, value, GetOneOfString(), document);
@ -555,6 +663,8 @@ public:
if (itr->IsString()) if (itr->IsString())
AddUniqueElement(allProperties, *itr); AddUniqueElement(allProperties, *itr);
// Dependencies not supported for open api 2.0 and 3.0
if (spec_.oapi != kVersion20 && spec_.oapi != kVersion30)
if (dependencies && dependencies->IsObject()) if (dependencies && dependencies->IsObject())
for (ConstMemberIterator itr = dependencies->MemberBegin(); itr != dependencies->MemberEnd(); ++itr) { for (ConstMemberIterator itr = dependencies->MemberBegin(); itr != dependencies->MemberEnd(); ++itr) {
AddUniqueElement(allProperties, itr->name); AddUniqueElement(allProperties, itr->name);
@ -584,6 +694,8 @@ public:
} }
} }
// PatternProperties not supported for open api 2.0 and 3.0
if (spec_.oapi != kVersion20 && spec_.oapi != kVersion30)
if (const ValueType* v = GetMember(value, GetPatternPropertiesString())) { if (const ValueType* v = GetMember(value, GetPatternPropertiesString())) {
PointerType q = p.Append(GetPatternPropertiesString(), allocator_); PointerType q = p.Append(GetPatternPropertiesString(), allocator_);
patternProperties_ = static_cast<PatternProperty*>(allocator_->Malloc(sizeof(PatternProperty) * v->MemberCount())); patternProperties_ = static_cast<PatternProperty*>(allocator_->Malloc(sizeof(PatternProperty) * v->MemberCount()));
@ -608,6 +720,8 @@ public:
} }
} }
// Dependencies not supported for open api 2.0 and 3.0
if (spec_.oapi != kVersion20 && spec_.oapi != kVersion30)
if (dependencies && dependencies->IsObject()) { if (dependencies && dependencies->IsObject()) {
PointerType q = p.Append(GetDependenciesString(), allocator_); PointerType q = p.Append(GetDependenciesString(), allocator_);
hasDependencies_ = true; hasDependencies_ = true;
@ -659,6 +773,8 @@ public:
AssignIfExist(minItems_, value, GetMinItemsString()); AssignIfExist(minItems_, value, GetMinItemsString());
AssignIfExist(maxItems_, value, GetMaxItemsString()); AssignIfExist(maxItems_, value, GetMaxItemsString());
// AdditionalItems not supported for openapi 2.0 and 3.0
if (spec_.oapi != kVersion20 && spec_.oapi != kVersion30)
if (const ValueType* v = GetMember(value, GetAdditionalItemsString())) { if (const ValueType* v = GetMember(value, GetAdditionalItemsString())) {
if (v->IsBool()) if (v->IsBool())
additionalItems_ = v->GetBool(); additionalItems_ = v->GetBool();
@ -696,6 +812,23 @@ public:
if (v->IsString()) if (v->IsString())
defaultValueLength_ = v->GetStringLength(); defaultValueLength_ = v->GetStringLength();
// ReadOnly - open api only (until draft 7 supported)
// WriteOnly - open api 3 only (until draft 7 supported)
// Both can't be true
if (spec_.oapi != kVersionNone)
AssignIfExist(readOnly_, value, GetReadOnlyString());
if (spec_.oapi >= kVersion30)
AssignIfExist(writeOnly_, value, GetWriteOnlyString());
if (readOnly_ && writeOnly_)
schemaDocument->SchemaError(kSchemaErrorReadOnlyAndWriteOnly, p);
// Nullable - open api 3 only
// If true add 'null' as allowable type
if (spec_.oapi >= kVersion30) {
AssignIfExist(nullable_, value, GetNullableString());
if (nullable_)
AddType(GetNullString());
}
} }
~Schema() { ~Schema() {
@ -727,11 +860,16 @@ public:
return id_; return id_;
} }
const Specification& GetSpecification() const {
return spec_;
}
const PointerType& GetPointer() const { const PointerType& GetPointer() const {
return pointer_; return pointer_;
} }
bool BeginValue(Context& context) const { bool BeginValue(Context& context) const {
RAPIDJSON_SCHEMA_PRINT(Method, "Schema::BeginValue");
if (context.inArray) { if (context.inArray) {
if (uniqueItems_) if (uniqueItems_)
context.valueUniqueness = true; context.valueUniqueness = true;
@ -763,6 +901,7 @@ public:
} }
RAPIDJSON_FORCEINLINE bool EndValue(Context& context) const { RAPIDJSON_FORCEINLINE bool EndValue(Context& context) const {
RAPIDJSON_SCHEMA_PRINT(Method, "Schema::EndValue");
// Only check pattern properties if we have validators // Only check pattern properties if we have validators
if (context.patternPropertiesValidatorCount > 0) { if (context.patternPropertiesValidatorCount > 0) {
bool otherValid = false; bool otherValid = false;
@ -853,6 +992,7 @@ public:
} }
bool Null(Context& context) const { bool Null(Context& context) const {
RAPIDJSON_SCHEMA_PRINT(Method, "Schema::Null");
if (!(type_ & (1 << kNullSchemaType))) { if (!(type_ & (1 << kNullSchemaType))) {
DisallowedType(context, GetNullString()); DisallowedType(context, GetNullString());
RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorType); RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorType);
@ -860,39 +1000,43 @@ public:
return CreateParallelValidator(context); return CreateParallelValidator(context);
} }
bool Bool(Context& context, bool) const { bool Bool(Context& context, bool b) const {
if (!(type_ & (1 << kBooleanSchemaType))) { RAPIDJSON_SCHEMA_PRINT(Method, "Schema::Bool", b);
DisallowedType(context, GetBooleanString()); if (!CheckBool(context, b))
RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorType); return false;
}
return CreateParallelValidator(context); return CreateParallelValidator(context);
} }
bool Int(Context& context, int i) const { bool Int(Context& context, int i) const {
RAPIDJSON_SCHEMA_PRINT(Method, "Schema::Int", (int64_t)i);
if (!CheckInt(context, i)) if (!CheckInt(context, i))
return false; return false;
return CreateParallelValidator(context); return CreateParallelValidator(context);
} }
bool Uint(Context& context, unsigned u) const { bool Uint(Context& context, unsigned u) const {
RAPIDJSON_SCHEMA_PRINT(Method, "Schema::Uint", (uint64_t)u);
if (!CheckUint(context, u)) if (!CheckUint(context, u))
return false; return false;
return CreateParallelValidator(context); return CreateParallelValidator(context);
} }
bool Int64(Context& context, int64_t i) const { bool Int64(Context& context, int64_t i) const {
RAPIDJSON_SCHEMA_PRINT(Method, "Schema::Int64", i);
if (!CheckInt(context, i)) if (!CheckInt(context, i))
return false; return false;
return CreateParallelValidator(context); return CreateParallelValidator(context);
} }
bool Uint64(Context& context, uint64_t u) const { bool Uint64(Context& context, uint64_t u) const {
RAPIDJSON_SCHEMA_PRINT(Method, "Schema::Uint64", u);
if (!CheckUint(context, u)) if (!CheckUint(context, u))
return false; return false;
return CreateParallelValidator(context); return CreateParallelValidator(context);
} }
bool Double(Context& context, double d) const { bool Double(Context& context, double d) const {
RAPIDJSON_SCHEMA_PRINT(Method, "Schema::Double", d);
if (!(type_ & (1 << kNumberSchemaType))) { if (!(type_ & (1 << kNumberSchemaType))) {
DisallowedType(context, GetNumberString()); DisallowedType(context, GetNumberString());
RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorType); RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorType);
@ -911,6 +1055,7 @@ public:
} }
bool String(Context& context, const Ch* str, SizeType length, bool) const { bool String(Context& context, const Ch* str, SizeType length, bool) const {
RAPIDJSON_SCHEMA_PRINT(Method, "Schema::String", str);
if (!(type_ & (1 << kStringSchemaType))) { if (!(type_ & (1 << kStringSchemaType))) {
DisallowedType(context, GetStringString()); DisallowedType(context, GetStringString());
RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorType); RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorType);
@ -939,6 +1084,7 @@ public:
} }
bool StartObject(Context& context) const { bool StartObject(Context& context) const {
RAPIDJSON_SCHEMA_PRINT(Method, "Schema::StartObject");
if (!(type_ & (1 << kObjectSchemaType))) { if (!(type_ & (1 << kObjectSchemaType))) {
DisallowedType(context, GetObjectString()); DisallowedType(context, GetObjectString());
RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorType); RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorType);
@ -960,6 +1106,8 @@ public:
} }
bool Key(Context& context, const Ch* str, SizeType len, bool) const { bool Key(Context& context, const Ch* str, SizeType len, bool) const {
RAPIDJSON_SCHEMA_PRINT(Method, "Schema::Key", str);
if (patternProperties_) { if (patternProperties_) {
context.patternPropertiesSchemaCount = 0; context.patternPropertiesSchemaCount = 0;
for (SizeType i = 0; i < patternPropertyCount_; i++) for (SizeType i = 0; i < patternPropertyCount_; i++)
@ -1011,6 +1159,7 @@ public:
} }
bool EndObject(Context& context, SizeType memberCount) const { bool EndObject(Context& context, SizeType memberCount) const {
RAPIDJSON_SCHEMA_PRINT(Method, "Schema::EndObject");
if (hasRequired_) { if (hasRequired_) {
context.error_handler.StartMissingProperties(); context.error_handler.StartMissingProperties();
for (SizeType index = 0; index < propertyCount_; index++) for (SizeType index = 0; index < propertyCount_; index++)
@ -1058,6 +1207,7 @@ public:
} }
bool StartArray(Context& context) const { bool StartArray(Context& context) const {
RAPIDJSON_SCHEMA_PRINT(Method, "Schema::StartArray");
context.arrayElementIndex = 0; context.arrayElementIndex = 0;
context.inArray = true; // Ensure we note that we are in an array context.inArray = true; // Ensure we note that we are in an array
@ -1070,6 +1220,7 @@ public:
} }
bool EndArray(Context& context, SizeType elementCount) const { bool EndArray(Context& context, SizeType elementCount) const {
RAPIDJSON_SCHEMA_PRINT(Method, "Schema::EndArray");
context.inArray = false; context.inArray = false;
if (elementCount < minItems_) { if (elementCount < minItems_) {
@ -1118,6 +1269,9 @@ public:
case kValidateErrorAnyOf: return GetAnyOfString(); case kValidateErrorAnyOf: return GetAnyOfString();
case kValidateErrorNot: return GetNotString(); case kValidateErrorNot: return GetNotString();
case kValidateErrorReadOnly: return GetReadOnlyString();
case kValidateErrorWriteOnly: return GetWriteOnlyString();
default: return GetNullString(); default: return GetNullString();
} }
} }
@ -1165,15 +1319,14 @@ public:
RAPIDJSON_STRING_(ExclusiveMaximum, 'e', 'x', 'c', 'l', 'u', 's', 'i', 'v', 'e', 'M', 'a', 'x', 'i', 'm', 'u', 'm') RAPIDJSON_STRING_(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_(Schema, '$', 's', 'c', 'h', 'e', 'm', 'a')
RAPIDJSON_STRING_(Ref, '$', 'r', 'e', 'f') RAPIDJSON_STRING_(Ref, '$', 'r', 'e', 'f')
RAPIDJSON_STRING_(Id, 'i', 'd') RAPIDJSON_STRING_(Id, 'i', 'd')
RAPIDJSON_STRING_(Swagger, 's', 'w', 'a', 'g', 'g', 'e', 'r')
RAPIDJSON_STRING_(SchemeEnd, ':') RAPIDJSON_STRING_(OpenApi, 'o', 'p', 'e', 'n', 'a', 'p', 'i')
RAPIDJSON_STRING_(AuthStart, '/', '/') RAPIDJSON_STRING_(ReadOnly, 'r', 'e', 'a', 'd', 'O', 'n', 'l', 'y')
RAPIDJSON_STRING_(QueryStart, '?') RAPIDJSON_STRING_(WriteOnly, 'w', 'r', 'i', 't', 'e', 'O', 'n', 'l', 'y')
RAPIDJSON_STRING_(FragStart, '#') RAPIDJSON_STRING_(Nullable, 'n', 'u', 'l', 'l', 'a', 'b', 'l', 'e')
RAPIDJSON_STRING_(Slash, '/')
RAPIDJSON_STRING_(Dot, '.')
#undef RAPIDJSON_STRING_ #undef RAPIDJSON_STRING_
@ -1307,6 +1460,7 @@ private:
// Creates parallel validators for allOf, anyOf, oneOf, not and schema dependencies, if required. // Creates parallel validators for allOf, anyOf, oneOf, not and schema dependencies, if required.
// Also creates a hasher for enums and array uniqueness, if required. // Also creates a hasher for enums and array uniqueness, if required.
// Also a useful place to add type-independent error checks.
bool CreateParallelValidator(Context& context) const { bool CreateParallelValidator(Context& context) const {
if (enum_ || context.arrayUniqueness) if (enum_ || context.arrayUniqueness)
context.hasher = context.factory.CreateHasher(); context.hasher = context.factory.CreateHasher();
@ -1337,6 +1491,16 @@ private:
} }
} }
// Add any other type-independent checks here
if (readOnly_ && (context.flags & kValidateWriteFlag)) {
context.error_handler.DisallowedWhenWriting();
RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorReadOnly);
}
if (writeOnly_ && (context.flags & kValidateReadFlag)) {
context.error_handler.DisallowedWhenReading();
RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorWriteOnly);
}
return true; return true;
} }
@ -1359,6 +1523,14 @@ private:
return false; return false;
} }
bool CheckBool(Context& context, bool) const {
if (!(type_ & (1 << kBooleanSchemaType))) {
DisallowedType(context, GetBooleanString());
RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorType);
}
return true;
}
bool CheckInt(Context& context, int64_t i) const { bool CheckInt(Context& context, int64_t i) const {
if (!(type_ & ((1 << kIntegerSchemaType) | (1 << kNumberSchemaType)))) { if (!(type_ & ((1 << kIntegerSchemaType) | (1 << kNumberSchemaType)))) {
DisallowedType(context, GetIntegerString()); DisallowedType(context, GetIntegerString());
@ -1524,6 +1696,7 @@ private:
AllocatorType* allocator_; AllocatorType* allocator_;
SValue uri_; SValue uri_;
UriType id_; UriType id_;
Specification spec_;
PointerType pointer_; PointerType pointer_;
const SchemaType* typeless_; const SchemaType* typeless_;
uint64_t* enum_; uint64_t* enum_;
@ -1568,6 +1741,10 @@ private:
bool exclusiveMaximum_; bool exclusiveMaximum_;
SizeType defaultValueLength_; SizeType defaultValueLength_;
bool readOnly_;
bool writeOnly_;
bool nullable_;
}; };
template<typename Stack, typename Ch> template<typename Stack, typename Ch>
@ -1614,7 +1791,12 @@ 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(const GenericUri<ValueType, AllocatorType> uri) { return GetRemoteDocument(uri.GetBaseString(), uri.GetBaseStringLength()); } virtual const SchemaDocumentType* GetRemoteDocument(const GenericUri<ValueType, AllocatorType> uri, Specification& spec) {
// Default implementation just calls through for compatibility
// Following line suppresses unused parameter warning
if (false) printf("GetRemoteDocument: %d %d\n", spec.draft, spec.oapi);
return GetRemoteDocument(uri.GetBaseString(), uri.GetBaseStringLength());
}
}; };
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
@ -1656,10 +1838,12 @@ public:
\param remoteProvider An optional remote schema document provider for resolving remote reference. Can be null. \param 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 \param pointer An optional JSON pointer to the start of the schema document
\param spec Optional schema draft or OpenAPI version. Used if no specification in document. Defaults to draft-04.
*/ */
explicit GenericSchemaDocument(const ValueType& document, const Ch* uri = 0, SizeType uriLength = 0, 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 const PointerType& pointer = PointerType(), // PR #1393
const Specification& spec = Specification(kDraft04)) :
remoteProvider_(remoteProvider), remoteProvider_(remoteProvider),
allocator_(allocator), allocator_(allocator),
ownAllocator_(), ownAllocator_(),
@ -1667,9 +1851,11 @@ public:
typeless_(), typeless_(),
schemaMap_(allocator, kInitialSchemaMapSize), schemaMap_(allocator, kInitialSchemaMapSize),
schemaRef_(allocator, kInitialSchemaRefSize), schemaRef_(allocator, kInitialSchemaRefSize),
spec_(spec),
error_(kObjectType), error_(kObjectType),
currentError_() currentError_()
{ {
RAPIDJSON_SCHEMA_PRINT(Method, "GenericSchemaDocument::GenericSchemaDocument");
if (!allocator_) if (!allocator_)
ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator)(); ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator)();
@ -1680,6 +1866,10 @@ public:
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_, docId_); new (typeless_) SchemaType(this, PointerType(), ValueType(kObjectType).Move(), ValueType(kObjectType).Move(), allocator_, docId_);
// Establish the schema draft or open api version.
// We only ever look for '$schema' or 'swagger' or 'openapi' at the root of the document.
SetSchemaSpecification(document);
// Generate root schema, it will call CreateSchema() to create sub-schemas, // Generate root schema, it will call CreateSchema() to create sub-schemas,
// And call HandleRefSchema() if there are $ref. // And call HandleRefSchema() if there are $ref.
// PR #1393 use input pointer if supplied // PR #1393 use input pointer if supplied
@ -1713,6 +1903,7 @@ public:
schemaRef_(std::move(rhs.schemaRef_)), schemaRef_(std::move(rhs.schemaRef_)),
uri_(std::move(rhs.uri_)), uri_(std::move(rhs.uri_)),
docId_(std::move(rhs.docId_)), docId_(std::move(rhs.docId_)),
spec_(rhs.spec_),
error_(std::move(rhs.error_)), error_(std::move(rhs.error_)),
currentError_(std::move(rhs.currentError_)) currentError_(std::move(rhs.currentError_))
{ {
@ -1743,6 +1934,23 @@ public:
const GValue& GetURI() const { return uri_; } const GValue& GetURI() const { return uri_; }
const Specification& GetSpecification() const { return spec_; }
bool IsSupportedSpecification() const { return spec_.IsSupported(); }
//! Static method to get the specification of any schema document
// Returns kDraftNone if document is silent
static const Specification GetSpecification(const ValueType& document) {
SchemaDraft draft = GetSchemaDraft(document);
if (draft != kDraftNone)
return Specification(draft);
else {
OpenApiVersion oapi = GetOpenApiVersion(document);
if (oapi != kVersionNone)
return Specification(oapi);
}
return Specification(kDraftNone);
}
//! Get the root schema. //! Get the root schema.
const SchemaType& GetRoot() const { return *root_; } const SchemaType& GetRoot() const { return *root_; }
@ -1761,6 +1969,10 @@ public:
case kSchemaErrorRefNoRemoteProvider: return GetRefNoRemoteProviderString(); case kSchemaErrorRefNoRemoteProvider: return GetRefNoRemoteProviderString();
case kSchemaErrorRefNoRemoteSchema: return GetRefNoRemoteSchemaString(); case kSchemaErrorRefNoRemoteSchema: return GetRefNoRemoteSchemaString();
case kSchemaErrorRegexInvalid: return GetRegexInvalidString(); case kSchemaErrorRegexInvalid: return GetRegexInvalidString();
case kSchemaErrorSpecUnknown: return GetSpecUnknownString();
case kSchemaErrorSpecUnsupported: return GetSpecUnsupportedString();
case kSchemaErrorSpecIllegal: return GetSpecIllegalString();
case kSchemaErrorReadOnlyAndWriteOnly: return GetReadOnlyAndWriteOnlyString();
default: return GetNullString(); default: return GetNullString();
} }
} }
@ -1829,6 +2041,7 @@ public:
} }
void AddCurrentError(const SchemaErrorCode code, const PointerType& location) { void AddCurrentError(const SchemaErrorCode code, const PointerType& location) {
RAPIDJSON_SCHEMA_PRINT(InvalidKeyword, GetSchemaErrorKeyword(code));
currentError_.AddMember(GetErrorCodeString(), code, *allocator_); currentError_.AddMember(GetErrorCodeString(), code, *allocator_);
AddErrorInstanceLocation(currentError_, location); AddErrorInstanceLocation(currentError_, location);
AddError(GValue(GetSchemaErrorKeyword(code)).Move(), currentError_); AddError(GValue(GetSchemaErrorKeyword(code)).Move(), currentError_);
@ -1847,6 +2060,9 @@ public:
RAPIDJSON_STRING_(Offset, 'o', 'f', 'f', 's', 'e', 't') RAPIDJSON_STRING_(Offset, 'o', 'f', 'f', 's', 'e', 't')
RAPIDJSON_STRING_(Null, 'n', 'u', 'l', 'l') RAPIDJSON_STRING_(Null, 'n', 'u', 'l', 'l')
RAPIDJSON_STRING_(SpecUnknown, 'S', 'p', 'e', 'c', 'U', 'n', 'k', 'n', 'o', 'w', 'n')
RAPIDJSON_STRING_(SpecUnsupported, 'S', 'p', 'e', 'c', 'U', 'n', 's', 'u', 'p', 'p', 'o', 'r', 't', 'e', 'd')
RAPIDJSON_STRING_(SpecIllegal, 'S', 'p', 'e', 'c', 'I', 'l', 'l', 'e', 'g', 'a', 'l')
RAPIDJSON_STRING_(StartUnknown, 'S', 't', 'a', 'r', 't', 'U', 'n', 'k', 'n', 'o', 'w', 'n') RAPIDJSON_STRING_(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_(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_(RefInvalid, 'R', 'e', 'f', 'I', 'n', 'v', 'a', 'l', 'i', 'd')
@ -1855,10 +2071,94 @@ public:
RAPIDJSON_STRING_(RefCyclical, 'R', 'e', 'f', 'C', 'y', 'c', 'l', 'i', 'c', 'a', 'l') RAPIDJSON_STRING_(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_(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_(RefNoRemoteSchema, 'R', 'e', 'f', 'N', 'o', 'R', 'e', 'm', 'o', 't', 'e', 'S', 'c', 'h', 'e', 'm', 'a')
RAPIDJSON_STRING_(ReadOnlyAndWriteOnly, 'R', 'e', 'a', 'd', 'O', 'n', 'l', 'y', 'A', 'n', 'd', 'W', 'r', 'i', 't', 'e', 'O', 'n', 'l', 'y')
RAPIDJSON_STRING_(RegexInvalid, 'R', 'e', 'g', 'e', 'x', 'I', 'n', 'v', 'a', 'l', 'i', 'd') RAPIDJSON_STRING_(RegexInvalid, 'R', 'e', 'g', 'e', 'x', 'I', 'n', 'v', 'a', 'l', 'i', 'd')
#undef RAPIDJSON_STRING_ #undef RAPIDJSON_STRING_
// Static method to get schema draft of any schema document
static SchemaDraft GetSchemaDraft(const ValueType& document) {
static const Ch kDraft03String[] = { 'h', 't', 't', 'p', ':', '/', '/', 'j', 's', 'o', 'n', '-', 's', 'c', 'h', 'e', 'm', 'a', '.', 'o', 'r', 'g', '/', 'd', 'r', 'a', 'f', 't', '-', '0', '3', '/', 's', 'c', 'h', 'e', 'm', 'a', '#', '\0' };
static const Ch kDraft04String[] = { 'h', 't', 't', 'p', ':', '/', '/', 'j', 's', 'o', 'n', '-', 's', 'c', 'h', 'e', 'm', 'a', '.', 'o', 'r', 'g', '/', 'd', 'r', 'a', 'f', 't', '-', '0', '4', '/', 's', 'c', 'h', 'e', 'm', 'a', '#', '\0' };
static const Ch kDraft05String[] = { 'h', 't', 't', 'p', ':', '/', '/', 'j', 's', 'o', 'n', '-', 's', 'c', 'h', 'e', 'm', 'a', '.', 'o', 'r', 'g', '/', 'd', 'r', 'a', 'f', 't', '-', '0', '5', '/', 's', 'c', 'h', 'e', 'm', 'a', '#', '\0' };
static const Ch kDraft06String[] = { 'h', 't', 't', 'p', ':', '/', '/', 'j', 's', 'o', 'n', '-', 's', 'c', 'h', 'e', 'm', 'a', '.', 'o', 'r', 'g', '/', 'd', 'r', 'a', 'f', 't', '-', '0', '6', '/', 's', 'c', 'h', 'e', 'm', 'a', '#', '\0' };
static const Ch kDraft07String[] = { 'h', 't', 't', 'p', ':', '/', '/', 'j', 's', 'o', 'n', '-', 's', 'c', 'h', 'e', 'm', 'a', '.', 'o', 'r', 'g', '/', 'd', 'r', 'a', 'f', 't', '-', '0', '7', '/', 's', 'c', 'h', 'e', 'm', 'a', '#', '\0' };
static const Ch kDraft2019_09String[] = { 'h', 't', 't', 'p', 's', ':', '/', '/', 'j', 's', 'o', 'n', '-', 's', 'c', 'h', 'e', 'm', 'a', '.', 'o', 'r', 'g', '/', 'd', 'r', 'a', 'f', 't', '/', '2', '0', '1', '9', '-', '0', '9', '/', 's', 'c', 'h', 'e', 'm', 'a', '\0' };
static const Ch kDraft2020_12String[] = { 'h', 't', 't', 'p', 's', ':', '/', '/', 'j', 's', 'o', 'n', '-', 's', 'c', 'h', 'e', 'm', 'a', '.', 'o', 'r', 'g', '/', 'd', 'r', 'a', 'f', 't', '/', '2', '0', '2', '0', '-', '1', '2', '/', 's', 'c', 'h', 'e', 'm', 'a', '\0' };
if (!document.IsObject()) {
return kDraftNone;
}
// Get the schema draft from the $schema keyword at the supplied location
typename ValueType::ConstMemberIterator itr = document.FindMember(SchemaType::GetSchemaString());
if (itr != document.MemberEnd()) {
if (!itr->value.IsString()) return kDraftUnknown;
const UriType draftUri(itr->value);
// Check base uri for match
if (draftUri.Match(UriType(kDraft04String), false)) return kDraft04;
if (draftUri.Match(UriType(kDraft05String), false)) return kDraft05;
if (draftUri.Match(UriType(kDraft06String), false)) return kDraft06;
if (draftUri.Match(UriType(kDraft07String), false)) return kDraft07;
if (draftUri.Match(UriType(kDraft03String), false)) return kDraft03;
if (draftUri.Match(UriType(kDraft2019_09String), false)) return kDraft2019_09;
if (draftUri.Match(UriType(kDraft2020_12String), false)) return kDraft2020_12;
return kDraftUnknown;
}
// $schema not found
return kDraftNone;
}
// Get open api version of any schema document
static OpenApiVersion GetOpenApiVersion(const ValueType& document) {
static const Ch kVersion20String[] = { '2', '.', '0', '\0' };
static const Ch kVersion30String[] = { '3', '.', '0', '.', '\0' }; // ignore patch level
static const Ch kVersion31String[] = { '3', '.', '1', '.', '\0' }; // ignore patch level
static SizeType len = internal::StrLen<Ch>(kVersion30String);
if (!document.IsObject()) {
return kVersionNone;
}
// Get the open api version from the swagger / openapi keyword at the supplied location
typename ValueType::ConstMemberIterator itr = document.FindMember(SchemaType::GetSwaggerString());
if (itr == document.MemberEnd()) itr = document.FindMember(SchemaType::GetOpenApiString());
if (itr != document.MemberEnd()) {
if (!itr->value.IsString()) return kVersionUnknown;
const ValueType kVersion20Value(kVersion20String);
if (kVersion20Value == itr->value) return kVersion20; // must match 2.0 exactly
const ValueType kVersion30Value(kVersion30String);
if (itr->value.GetStringLength() > len && kVersion30Value == ValueType(itr->value.GetString(), len)) return kVersion30; // must match 3.0.x
const ValueType kVersion31Value(kVersion31String);
if (itr->value.GetStringLength() > len && kVersion31Value == ValueType(itr->value.GetString(), len)) return kVersion31; // must match 3.1.x
return kVersionUnknown;
}
// swagger or openapi not found
return kVersionNone;
}
// Get the draft of the schema or the open api version (which implies the draft).
// Report an error if schema draft or open api version not supported or not recognized, or both in document, and carry on.
void SetSchemaSpecification(const ValueType& document) {
// Look for '$schema', 'swagger' or 'openapi' keyword at document root
SchemaDraft docDraft = GetSchemaDraft(document);
OpenApiVersion docOapi = GetOpenApiVersion(document);
// Error if both in document
if (docDraft != kDraftNone && docOapi != kVersionNone)
SchemaError(kSchemaErrorSpecIllegal, PointerType());
// Use document draft or open api version if present or use spec from constructor
if (docDraft != kDraftNone)
spec_ = Specification(docDraft);
else if (docOapi != kVersionNone)
spec_ = Specification(docOapi);
// Error if draft or version unknown
if (spec_.draft == kDraftUnknown || spec_.oapi == kVersionUnknown)
SchemaError(kSchemaErrorSpecUnknown, PointerType());
else if (!spec_.IsSupported())
SchemaError(kSchemaErrorSpecUnsupported, PointerType());
}
// Changed by PR #1393 // 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) {
@ -1875,6 +2175,9 @@ public:
// Changed by PR #1393 // Changed by PR #1393
const UriType& CreateSchema(const SchemaType** schema, const PointerType& pointer, const ValueType& v, const ValueType& document, const UriType& id) { const UriType& CreateSchema(const SchemaType** schema, const PointerType& pointer, const ValueType& v, const ValueType& document, const UriType& id) {
RAPIDJSON_ASSERT(pointer.IsValid()); RAPIDJSON_ASSERT(pointer.IsValid());
GenericStringBuffer<EncodingType> sb;
pointer.StringifyUriFragment(sb);
RAPIDJSON_SCHEMA_PRINT(Method, "GenericSchemaDocument::CreateSchema", sb.GetString(), id.GetString());
if (v.IsObject()) { if (v.IsObject()) {
if (const SchemaType* sc = GetSchema(pointer)) { if (const SchemaType* sc = GetSchema(pointer)) {
if (schema) if (schema)
@ -1904,6 +2207,9 @@ public:
if (itr == v.MemberEnd()) if (itr == v.MemberEnd())
return false; return false;
GenericStringBuffer<EncodingType> sb;
source.StringifyUriFragment(sb);
RAPIDJSON_SCHEMA_PRINT(Method, "GenericSchemaDocument::HandleRefSchema", sb.GetString(), id.GetString());
// Resolve the source pointer to the $ref'ed schema (finally) // Resolve the source pointer to the $ref'ed schema (finally)
new (schemaRef_.template Push<SchemaRefPtr>()) SchemaRefPtr(&source); new (schemaRef_.template Push<SchemaRefPtr>()) SchemaRefPtr(&source);
@ -1915,6 +2221,7 @@ public:
// 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_);
RAPIDJSON_SCHEMA_PRINT(SchemaIds, id.GetString(), itr->value.GetString(), ref.GetString());
// See if the resolved $ref minus the fragment matches a resolved id in this document // See if the resolved $ref minus the fragment matches a resolved id in this document
// Search from the root. Returns the subschema in the document and its absolute JSON pointer. // Search from the root. Returns the subschema in the document and its absolute JSON pointer.
PointerType basePointer = PointerType(); PointerType basePointer = PointerType();
@ -1924,7 +2231,7 @@ public:
if (!remoteProvider_) if (!remoteProvider_)
SchemaError(kSchemaErrorRefNoRemoteProvider, source); SchemaError(kSchemaErrorRefNoRemoteProvider, source);
else { else {
if (const GenericSchemaDocument* remoteDocument = remoteProvider_->GetRemoteDocument(ref)) { if (const GenericSchemaDocument* remoteDocument = remoteProvider_->GetRemoteDocument(ref, spec_)) {
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] == '/') {
@ -1979,10 +2286,13 @@ public:
} }
} else { } else {
// Plain name fragment, relative to the resolved URI // Plain name fragment, relative to the resolved URI
// Not supported in open api 2.0 and 3.0
PointerType pointer(allocator_); PointerType pointer(allocator_);
if (spec_.oapi == kVersion20 || spec_.oapi == kVersion30)
SchemaErrorValue(kSchemaErrorRefPlainName, source, s, len);
// See if the fragment matches an id in this document. // 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.
if (const ValueType *pv = FindId(*base, ref, pointer, UriType(ref.GetBaseString(), ref.GetBaseStringLength(), allocator_), true, basePointer)) { else if (const ValueType *pv = FindId(*base, ref, pointer, UriType(ref.GetBaseString(), ref.GetBaseStringLength(), allocator_), true, basePointer)) {
if (IsCyclicRef(pointer)) if (IsCyclicRef(pointer))
SchemaErrorValue(kSchemaErrorRefCyclical, source, ref.GetString(), ref.GetStringLength()); SchemaErrorValue(kSchemaErrorRefCyclical, source, ref.GetString(), ref.GetStringLength());
else { else {
@ -2024,6 +2334,7 @@ public:
} }
// See if it matches // See if it matches
if (localuri.Match(finduri, full)) { if (localuri.Match(finduri, full)) {
RAPIDJSON_SCHEMA_PRINT(Method, "GenericSchemaDocument::FindId (match)", full ? localuri.GetString() : localuri.GetBaseString());
resval = const_cast<ValueType *>(&doc); resval = const_cast<ValueType *>(&doc);
resptr = here; resptr = here;
return resval; return resval;
@ -2050,6 +2361,7 @@ public:
// Added by PR #1393 // Added by PR #1393
void AddSchemaRefs(SchemaType* schema) { void AddSchemaRefs(SchemaType* schema) {
RAPIDJSON_SCHEMA_PRINT(Method, "GenericSchemaDocument::AddSchemaRefs");
while (!schemaRef_.Empty()) { while (!schemaRef_.Empty()) {
SchemaRefPtr *ref = schemaRef_.template Pop<SchemaRefPtr>(1); SchemaRefPtr *ref = schemaRef_.template Pop<SchemaRefPtr>(1);
SchemaEntry *entry = schemaMap_.template Push<SchemaEntry>(); SchemaEntry *entry = schemaMap_.template Push<SchemaEntry>();
@ -2093,6 +2405,7 @@ public:
internal::Stack<Allocator> schemaRef_; // Stores Pointer(s) from $ref(s) until resolved internal::Stack<Allocator> schemaRef_; // Stores Pointer(s) from $ref(s) until resolved
GValue uri_; // Schema document URI GValue uri_; // Schema document URI
UriType docId_; UriType docId_;
Specification spec_;
GValue error_; GValue error_;
GValue currentError_; GValue currentError_;
}; };
@ -2158,11 +2471,10 @@ public:
currentError_(), currentError_(),
missingDependents_(), missingDependents_(),
valid_(true), valid_(true),
flags_(kValidateDefaultFlags) flags_(kValidateDefaultFlags),
#if RAPIDJSON_SCHEMA_VERBOSE depth_(0)
, depth_(0)
#endif
{ {
RAPIDJSON_SCHEMA_PRINT(Method, "GenericSchemaValidator::GenericSchemaValidator");
} }
//! Constructor with output handler. //! Constructor with output handler.
@ -2190,11 +2502,10 @@ public:
currentError_(), currentError_(),
missingDependents_(), missingDependents_(),
valid_(true), valid_(true),
flags_(kValidateDefaultFlags) flags_(kValidateDefaultFlags),
#if RAPIDJSON_SCHEMA_VERBOSE depth_(0)
, depth_(0)
#endif
{ {
RAPIDJSON_SCHEMA_PRINT(Method, "GenericSchemaValidator::GenericSchemaValidator (output handler)");
} }
//! Destructor. //! Destructor.
@ -2455,6 +2766,14 @@ public:
currentError_.SetObject(); currentError_.SetObject();
AddCurrentError(kValidateErrorNot); AddCurrentError(kValidateErrorNot);
} }
void DisallowedWhenWriting() {
currentError_.SetObject();
AddCurrentError(kValidateErrorReadOnly);
}
void DisallowedWhenReading() {
currentError_.SetObject();
AddCurrentError(kValidateErrorWriteOnly);
}
#define RAPIDJSON_STRING_(name, ...) \ #define RAPIDJSON_STRING_(name, ...) \
static const StringRefType& Get##name##String() {\ static const StringRefType& Get##name##String() {\
@ -2477,21 +2796,12 @@ public:
#undef RAPIDJSON_STRING_ #undef RAPIDJSON_STRING_
#if RAPIDJSON_SCHEMA_VERBOSE
#define RAPIDJSON_SCHEMA_HANDLE_BEGIN_VERBOSE_() \
RAPIDJSON_MULTILINEMACRO_BEGIN\
*documentStack_.template Push<Ch>() = '\0';\
documentStack_.template Pop<Ch>(1);\
internal::PrintInvalidDocument(documentStack_.template Bottom<Ch>());\
RAPIDJSON_MULTILINEMACRO_END
#else
#define RAPIDJSON_SCHEMA_HANDLE_BEGIN_VERBOSE_()
#endif
#define RAPIDJSON_SCHEMA_HANDLE_BEGIN_(method, arg1)\ #define RAPIDJSON_SCHEMA_HANDLE_BEGIN_(method, arg1)\
if (!valid_) return false; \ if (!valid_) return false; \
if ((!BeginValue() && !GetContinueOnErrors()) || (!CurrentSchema().method arg1 && !GetContinueOnErrors())) {\ if ((!BeginValue() && !GetContinueOnErrors()) || (!CurrentSchema().method arg1 && !GetContinueOnErrors())) {\
RAPIDJSON_SCHEMA_HANDLE_BEGIN_VERBOSE_();\ *documentStack_.template Push<Ch>() = '\0';\
documentStack_.template Pop<Ch>(1);\
RAPIDJSON_SCHEMA_PRINT(InvalidDocument, documentStack_.template Bottom<Ch>());\
valid_ = false;\ valid_ = false;\
return valid_;\ return valid_;\
} }
@ -2530,6 +2840,7 @@ RAPIDJSON_MULTILINEMACRO_END
{ RAPIDJSON_SCHEMA_HANDLE_VALUE_(String, (CurrentContext(), str, length, copy), (str, length, copy)); } { RAPIDJSON_SCHEMA_HANDLE_VALUE_(String, (CurrentContext(), str, length, copy), (str, length, copy)); }
bool StartObject() { bool StartObject() {
RAPIDJSON_SCHEMA_PRINT(Method, "GenericSchemaValidator::StartObject");
RAPIDJSON_SCHEMA_HANDLE_BEGIN_(StartObject, (CurrentContext())); RAPIDJSON_SCHEMA_HANDLE_BEGIN_(StartObject, (CurrentContext()));
RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(StartObject, ()); RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(StartObject, ());
valid_ = !outputHandler_ || outputHandler_->StartObject(); valid_ = !outputHandler_ || outputHandler_->StartObject();
@ -2537,6 +2848,7 @@ RAPIDJSON_MULTILINEMACRO_END
} }
bool Key(const Ch* str, SizeType len, bool copy) { bool Key(const Ch* str, SizeType len, bool copy) {
RAPIDJSON_SCHEMA_PRINT(Method, "GenericSchemaValidator::Key", str);
if (!valid_) return false; if (!valid_) return false;
AppendToken(str, len); AppendToken(str, len);
if (!CurrentSchema().Key(CurrentContext(), str, len, copy) && !GetContinueOnErrors()) { if (!CurrentSchema().Key(CurrentContext(), str, len, copy) && !GetContinueOnErrors()) {
@ -2549,6 +2861,7 @@ RAPIDJSON_MULTILINEMACRO_END
} }
bool EndObject(SizeType memberCount) { bool EndObject(SizeType memberCount) {
RAPIDJSON_SCHEMA_PRINT(Method, "GenericSchemaValidator::EndObject");
if (!valid_) return false; if (!valid_) return false;
RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(EndObject, (memberCount)); RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(EndObject, (memberCount));
if (!CurrentSchema().EndObject(CurrentContext(), memberCount) && !GetContinueOnErrors()) { if (!CurrentSchema().EndObject(CurrentContext(), memberCount) && !GetContinueOnErrors()) {
@ -2559,6 +2872,7 @@ RAPIDJSON_MULTILINEMACRO_END
} }
bool StartArray() { bool StartArray() {
RAPIDJSON_SCHEMA_PRINT(Method, "GenericSchemaValidator::StartArray");
RAPIDJSON_SCHEMA_HANDLE_BEGIN_(StartArray, (CurrentContext())); RAPIDJSON_SCHEMA_HANDLE_BEGIN_(StartArray, (CurrentContext()));
RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(StartArray, ()); RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(StartArray, ());
valid_ = !outputHandler_ || outputHandler_->StartArray(); valid_ = !outputHandler_ || outputHandler_->StartArray();
@ -2566,6 +2880,7 @@ RAPIDJSON_MULTILINEMACRO_END
} }
bool EndArray(SizeType elementCount) { bool EndArray(SizeType elementCount) {
RAPIDJSON_SCHEMA_PRINT(Method, "GenericSchemaValidator::EndArray");
if (!valid_) return false; if (!valid_) return false;
RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(EndArray, (elementCount)); RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(EndArray, (elementCount));
if (!CurrentSchema().EndArray(CurrentContext(), elementCount) && !GetContinueOnErrors()) { if (!CurrentSchema().EndArray(CurrentContext(), elementCount) && !GetContinueOnErrors()) {
@ -2575,17 +2890,16 @@ RAPIDJSON_MULTILINEMACRO_END
RAPIDJSON_SCHEMA_HANDLE_END_(EndArray, (elementCount)); RAPIDJSON_SCHEMA_HANDLE_END_(EndArray, (elementCount));
} }
#undef RAPIDJSON_SCHEMA_HANDLE_BEGIN_VERBOSE_
#undef RAPIDJSON_SCHEMA_HANDLE_BEGIN_ #undef RAPIDJSON_SCHEMA_HANDLE_BEGIN_
#undef RAPIDJSON_SCHEMA_HANDLE_PARALLEL_ #undef RAPIDJSON_SCHEMA_HANDLE_PARALLEL_
#undef RAPIDJSON_SCHEMA_HANDLE_VALUE_ #undef RAPIDJSON_SCHEMA_HANDLE_VALUE_
// Implementation of ISchemaStateFactory<SchemaType> // Implementation of ISchemaStateFactory<SchemaType>
virtual ISchemaValidator* CreateSchemaValidator(const SchemaType& root, const bool inheritContinueOnErrors) { virtual ISchemaValidator* CreateSchemaValidator(const SchemaType& root, const bool inheritContinueOnErrors) {
*documentStack_.template Push<Ch>() = '\0';
documentStack_.template Pop<Ch>(1);
ISchemaValidator* sv = new (GetStateAllocator().Malloc(sizeof(GenericSchemaValidator))) GenericSchemaValidator(*schemaDocument_, root, documentStack_.template Bottom<char>(), documentStack_.GetSize(), ISchemaValidator* sv = new (GetStateAllocator().Malloc(sizeof(GenericSchemaValidator))) GenericSchemaValidator(*schemaDocument_, root, documentStack_.template Bottom<char>(), documentStack_.GetSize(),
#if RAPIDJSON_SCHEMA_VERBOSE
depth_ + 1, depth_ + 1,
#endif
&GetStateAllocator()); &GetStateAllocator());
sv->SetValidateFlags(inheritContinueOnErrors ? GetValidateFlags() : GetValidateFlags() & ~(unsigned)kValidateContinueOnErrorFlag); sv->SetValidateFlags(inheritContinueOnErrors ? GetValidateFlags() : GetValidateFlags() & ~(unsigned)kValidateContinueOnErrorFlag);
return sv; return sv;
@ -2629,9 +2943,7 @@ private:
const SchemaDocumentType& schemaDocument, const SchemaDocumentType& schemaDocument,
const SchemaType& root, const SchemaType& root,
const char* basePath, size_t basePathSize, const char* basePath, size_t basePathSize,
#if RAPIDJSON_SCHEMA_VERBOSE
unsigned depth, unsigned depth,
#endif
StateAllocator* allocator = 0, StateAllocator* allocator = 0,
size_t schemaStackCapacity = kDefaultSchemaStackCapacity, size_t schemaStackCapacity = kDefaultSchemaStackCapacity,
size_t documentStackCapacity = kDefaultDocumentStackCapacity) size_t documentStackCapacity = kDefaultDocumentStackCapacity)
@ -2647,11 +2959,10 @@ private:
currentError_(), currentError_(),
missingDependents_(), missingDependents_(),
valid_(true), valid_(true),
flags_(kValidateDefaultFlags) flags_(kValidateDefaultFlags),
#if RAPIDJSON_SCHEMA_VERBOSE depth_(depth)
, depth_(depth)
#endif
{ {
RAPIDJSON_SCHEMA_PRINT(Method, "GenericSchemaValidator::GenericSchemaValidator (internal)", basePath && basePathSize ? basePath : "");
if (basePath && basePathSize) if (basePath && basePathSize)
memcpy(documentStack_.template Push<char>(basePathSize), basePath, basePathSize); memcpy(documentStack_.template Push<char>(basePathSize), basePath, basePathSize);
} }
@ -2667,6 +2978,7 @@ private:
} }
bool BeginValue() { bool BeginValue() {
RAPIDJSON_SCHEMA_PRINT(Method, "GenericSchemaValidator::BeginValue");
if (schemaStack_.Empty()) if (schemaStack_.Empty())
PushSchema(root_); PushSchema(root_);
else { else {
@ -2699,17 +3011,15 @@ private:
} }
bool EndValue() { bool EndValue() {
RAPIDJSON_SCHEMA_PRINT(Method, "GenericSchemaValidator::EndValue");
if (!CurrentSchema().EndValue(CurrentContext()) && !GetContinueOnErrors()) if (!CurrentSchema().EndValue(CurrentContext()) && !GetContinueOnErrors())
return false; return false;
#if RAPIDJSON_SCHEMA_VERBOSE
GenericStringBuffer<EncodingType> sb; GenericStringBuffer<EncodingType> sb;
schemaDocument_->GetPointer(&CurrentSchema()).Stringify(sb); schemaDocument_->GetPointer(&CurrentSchema()).StringifyUriFragment(sb);
*documentStack_.template Push<Ch>() = '\0'; *documentStack_.template Push<Ch>() = '\0';
documentStack_.template Pop<Ch>(1); documentStack_.template Pop<Ch>(1);
internal::PrintValidatorPointers(depth_, sb.GetString(), documentStack_.template Bottom<Ch>()); RAPIDJSON_SCHEMA_PRINT(ValidatorPointers, sb.GetString(), documentStack_.template Bottom<Ch>(), depth_);
#endif
void* hasher = CurrentContext().hasher; void* hasher = CurrentContext().hasher;
uint64_t h = hasher && CurrentContext().arrayUniqueness ? static_cast<HasherType*>(hasher)->GetHashCode() : 0; uint64_t h = hasher && CurrentContext().arrayUniqueness ? static_cast<HasherType*>(hasher)->GetHashCode() : 0;
@ -2760,7 +3070,7 @@ private:
} }
} }
RAPIDJSON_FORCEINLINE void PushSchema(const SchemaType& schema) { new (schemaStack_.template Push<Context>()) Context(*this, *this, &schema); } RAPIDJSON_FORCEINLINE void PushSchema(const SchemaType& schema) { new (schemaStack_.template Push<Context>()) Context(*this, *this, &schema, flags_); }
RAPIDJSON_FORCEINLINE void PopSchema() { RAPIDJSON_FORCEINLINE void PopSchema() {
Context* c = schemaStack_.template Pop<Context>(1); Context* c = schemaStack_.template Pop<Context>(1);
@ -2862,9 +3172,7 @@ private:
ValueType missingDependents_; ValueType missingDependents_;
bool valid_; bool valid_;
unsigned flags_; unsigned flags_;
#if RAPIDJSON_SCHEMA_VERBOSE
unsigned depth_; unsigned depth_;
#endif
}; };
typedef GenericSchemaValidator<SchemaDocument> SchemaValidator; typedef GenericSchemaValidator<SchemaDocument> SchemaValidator;

View File

@ -118,12 +118,7 @@ TEST(SchemaValidator, Hasher) {
#define VALIDATE_(schema, json, expected, expected2) \ #define VALIDATE_(schema, json, expected, expected2) \
{\ {\
EXPECT_TRUE(expected2 == schema.GetError().ObjectEmpty());\ EXPECT_TRUE(expected2 == schema.GetError().ObjectEmpty());\
if (expected2 && !schema.GetError().ObjectEmpty()) {\ EXPECT_TRUE(schema.IsSupportedSpecification());\
StringBuffer ssb;\
Writer<StringBuffer> ws(ssb);\
schema.GetError().Accept(ws);\
printf("Schema error: %s\n", ssb.GetString());\
}\
SchemaValidator validator(schema);\ SchemaValidator validator(schema);\
Document d;\ Document d;\
/*printf("\n%s\n", json);*/\ /*printf("\n%s\n", json);*/\
@ -162,12 +157,7 @@ TEST(SchemaValidator, Hasher) {
flags, SchemaValidatorType, PointerType) \ flags, SchemaValidatorType, PointerType) \
{\ {\
EXPECT_TRUE(schema.GetError().ObjectEmpty());\ EXPECT_TRUE(schema.GetError().ObjectEmpty());\
if (!schema.GetError().ObjectEmpty()) {\ EXPECT_TRUE(schema.IsSupportedSpecification());\
StringBuffer ssb;\
Writer<StringBuffer> ws(ssb);\
schema.GetError().Accept(ws);\
printf("Schema error: %s\n", ssb.GetString());\
}\
SchemaValidatorType validator(schema);\ SchemaValidatorType validator(schema);\
validator.SetValidateFlags(flags);\ validator.SetValidateFlags(flags);\
Document d;\ Document d;\
@ -2163,9 +2153,13 @@ public:
} }
virtual const SchemaDocumentType* GetRemoteDocument(const char* uri, SizeType length) { virtual const SchemaDocumentType* GetRemoteDocument(const char* uri, SizeType length) {
//printf("GetRemoteDocument : %s\n", uri);
for (size_t i = 0; i < kCount; i++) for (size_t i = 0; i < kCount; i++)
if (typename SchemaDocumentType::GValue(uri, length) == sd_[i]->GetURI()) if (typename SchemaDocumentType::GValue(uri, length) == sd_[i]->GetURI()) {
//printf("Matched document");
return sd_[i]; return sd_[i];
}
//printf("No matched document");
return 0; return 0;
} }
@ -2999,6 +2993,334 @@ TEST(SchemaValidator, DuplicateKeyword) {
// SchemaDocument tests // SchemaDocument tests
// Specification (schema draft, open api version)
TEST(SchemaValidator, Schema_SupportedNotObject) {
Document sd;
sd.Parse("true");
ASSERT_FALSE(sd.HasParseError());
SchemaDocument s(sd);
ASSERT_TRUE(s.IsSupportedSpecification());
ASSERT_TRUE(s.GetSpecification().draft == kDraft04);
ASSERT_TRUE(s.GetSpecification().oapi == kVersionNone);
EXPECT_TRUE(s.GetError().ObjectEmpty());
}
TEST(SchemaValidator, Schema_SupportedNoSpec) {
Document sd;
sd.Parse("{\"type\": \"integer\"}");
ASSERT_FALSE(sd.HasParseError());
SchemaDocument s(sd);
ASSERT_TRUE(s.IsSupportedSpecification());
ASSERT_TRUE(s.GetSpecification().draft == kDraft04);
ASSERT_TRUE(s.GetSpecification().oapi == kVersionNone);
EXPECT_TRUE(s.GetError().ObjectEmpty());
}
TEST(SchemaValidator, Schema_SupportedNoSpecStatic) {
typedef GenericSchemaDocument<Value, MemoryPoolAllocator<> > SchemaDocumentType;
Document sd;
sd.Parse("{\"type\": \"integer\"}");
ASSERT_FALSE(sd.HasParseError());
Specification spec = SchemaDocumentType::GetSpecification(sd);
ASSERT_FALSE(spec.IsSupported());
ASSERT_TRUE(spec.draft == kDraftNone);
ASSERT_TRUE(spec.oapi == kVersionNone);
}
TEST(SchemaValidator, Schema_SupportedDraft5Static) {
typedef GenericSchemaDocument<Value, MemoryPoolAllocator<> > SchemaDocumentType;
Document sd;
sd.Parse("{\"$schema\":\"http://json-schema.org/draft-05/schema#\", \"type\": \"integer\"}");
ASSERT_FALSE(sd.HasParseError());
Specification spec = SchemaDocumentType::GetSpecification(sd);
ASSERT_TRUE(spec.IsSupported());
ASSERT_TRUE(spec.draft == kDraft05);
ASSERT_TRUE(spec.oapi == kVersionNone);
}
TEST(SchemaValidator, Schema_SupportedDraft4) {
Document sd;
sd.Parse("{\"$schema\":\"http://json-schema.org/draft-04/schema#\", \"type\": \"integer\"}");
ASSERT_FALSE(sd.HasParseError());
SchemaDocument s(sd);
ASSERT_TRUE(s.IsSupportedSpecification());
ASSERT_TRUE(s.GetSpecification().draft == kDraft04);
ASSERT_TRUE(s.GetSpecification().oapi == kVersionNone);
EXPECT_TRUE(s.GetError().ObjectEmpty());
}
TEST(SchemaValidator, Schema_SupportedDraft4NoFrag) {
Document sd;
sd.Parse("{\"$schema\":\"http://json-schema.org/draft-04/schema\", \"type\": \"integer\"}");
ASSERT_FALSE(sd.HasParseError());
SchemaDocument s(sd);
ASSERT_TRUE(s.IsSupportedSpecification());
ASSERT_TRUE(s.GetSpecification().draft == kDraft04);
ASSERT_TRUE(s.GetSpecification().oapi == kVersionNone);
EXPECT_TRUE(s.GetError().ObjectEmpty());
}
TEST(SchemaValidator, Schema_SupportedDraft5) {
Document sd;
sd.Parse("{\"$schema\":\"http://json-schema.org/draft-05/schema#\", \"type\": \"integer\"}");
ASSERT_FALSE(sd.HasParseError());
SchemaDocument s(sd);
ASSERT_TRUE(s.IsSupportedSpecification());
ASSERT_TRUE(s.GetSpecification().draft == kDraft05);
ASSERT_TRUE(s.GetSpecification().oapi == kVersionNone);
EXPECT_TRUE(s.GetError().ObjectEmpty());
}
TEST(SchemaValidator, Schema_SupportedDraft5NoFrag) {
Document sd;
sd.Parse("{\"$schema\":\"http://json-schema.org/draft-05/schema\", \"type\": \"integer\"}");
ASSERT_FALSE(sd.HasParseError());
SchemaDocument s(sd);
ASSERT_TRUE(s.IsSupportedSpecification());
ASSERT_TRUE(s.GetSpecification().draft == kDraft05);
ASSERT_TRUE(s.GetSpecification().oapi == kVersionNone);
EXPECT_TRUE(s.GetError().ObjectEmpty());
}
TEST(SchemaValidator, Schema_IgnoreDraftEmbedded) {
Document sd;
sd.Parse("{\"root\": {\"$schema\":\"http://json-schema.org/draft-05/schema#\", \"type\": \"integer\"}}");
ASSERT_FALSE(sd.HasParseError());
SchemaDocument s(sd, 0, 0, 0, 0, SchemaDocument::PointerType("/root"));
ASSERT_TRUE(s.IsSupportedSpecification());
ASSERT_TRUE(s.GetSpecification().draft == kDraft04);
ASSERT_TRUE(s.GetSpecification().oapi == kVersionNone);
EXPECT_TRUE(s.GetError().ObjectEmpty());
}
TEST(SchemaValidator, Schema_SupportedDraftOverride) {
Document sd;
sd.Parse("{\"type\": \"integer\"}");
ASSERT_FALSE(sd.HasParseError());
SchemaDocument s(sd, 0, 0, 0, 0, 0, Specification(kDraft04));
ASSERT_TRUE(s.IsSupportedSpecification());
ASSERT_TRUE(s.GetSpecification().draft == kDraft04);
ASSERT_TRUE(s.GetSpecification().oapi == kVersionNone);
EXPECT_TRUE(s.GetError().ObjectEmpty());
}
TEST(SchemaValidator, Schema_UnknownDraftOverride) {
Document sd;
sd.Parse("{\"type\": \"integer\"}");
ASSERT_FALSE(sd.HasParseError());
SchemaDocument s(sd, 0, 0, 0, 0, 0, Specification(kDraftUnknown));
ASSERT_FALSE(s.IsSupportedSpecification());
ASSERT_TRUE(s.GetSpecification().draft == kDraftUnknown);
ASSERT_TRUE(s.GetSpecification().oapi == kVersionNone);
SCHEMAERROR(s, "{\"SpecUnknown\":{\"errorCode\":10,\"instanceRef\":\"#\"}}");
}
TEST(SchemaValidator, Schema_UnsupportedDraftOverride) {
Document sd;
sd.Parse("{\"type\": \"integer\"}");
ASSERT_FALSE(sd.HasParseError());
SchemaDocument s(sd, 0, 0, 0, 0, 0, Specification(kDraft03));
ASSERT_FALSE(s.IsSupportedSpecification());
ASSERT_TRUE(s.GetSpecification().draft == kDraft03);
ASSERT_TRUE(s.GetSpecification().oapi == kVersionNone);
SCHEMAERROR(s, "{\"SpecUnsupported\":{\"errorCode\":11,\"instanceRef\":\"#\"}}");
}
TEST(SchemaValidator, Schema_UnknownDraft) {
Document sd;
sd.Parse("{\"$schema\":\"http://json-schema.org/draft-xxx/schema#\", \"type\": \"integer\"}");
ASSERT_FALSE(sd.HasParseError());
SchemaDocument s(sd);
ASSERT_FALSE(s.IsSupportedSpecification());
ASSERT_TRUE(s.GetSpecification().draft == kDraftUnknown);
ASSERT_TRUE(s.GetSpecification().oapi == kVersionNone);
SCHEMAERROR(s, "{\"SpecUnknown\":{\"errorCode\":10,\"instanceRef\":\"#\"}}");
}
TEST(SchemaValidator, Schema_UnknownDraftNotString) {
Document sd;
sd.Parse("{\"$schema\": 4, \"type\": \"integer\"}");
ASSERT_FALSE(sd.HasParseError());
SchemaDocument s(sd);
ASSERT_FALSE(s.IsSupportedSpecification());
ASSERT_TRUE(s.GetSpecification().draft == kDraftUnknown);
ASSERT_TRUE(s.GetSpecification().oapi == kVersionNone);
SCHEMAERROR(s, "{\"SpecUnknown\":{\"errorCode\":10,\"instanceRef\":\"#\"}}");
}
TEST(SchemaValidator, Schema_UnsupportedDraft3) {
Document sd;
sd.Parse("{\"$schema\":\"http://json-schema.org/draft-03/schema#\", \"type\": \"integer\"}");
ASSERT_FALSE(sd.HasParseError());
SchemaDocument s(sd);
ASSERT_FALSE(s.IsSupportedSpecification());
ASSERT_TRUE(s.GetSpecification().draft == kDraft03);
ASSERT_TRUE(s.GetSpecification().oapi == kVersionNone);
SCHEMAERROR(s, "{\"SpecUnsupported\":{\"errorCode\":11,\"instanceRef\":\"#\"}}");
}
TEST(SchemaValidator, Schema_UnsupportedDraft6) {
Document sd;
sd.Parse("{\"$schema\":\"http://json-schema.org/draft-06/schema#\", \"type\": \"integer\"}");
ASSERT_FALSE(sd.HasParseError());
SchemaDocument s(sd);
ASSERT_FALSE(s.IsSupportedSpecification());
ASSERT_TRUE(s.GetSpecification().draft == kDraft06);
SCHEMAERROR(s, "{\"SpecUnsupported\":{\"errorCode\":11,\"instanceRef\":\"#\"}}");
}
TEST(SchemaValidator, Schema_UnsupportedDraft7) {
Document sd;
sd.Parse("{\"$schema\":\"http://json-schema.org/draft-07/schema#\", \"type\": \"integer\"}");
ASSERT_FALSE(sd.HasParseError());
SchemaDocument s(sd);
ASSERT_FALSE(s.IsSupportedSpecification());
ASSERT_TRUE(s.GetSpecification().draft == kDraft07);
ASSERT_TRUE(s.GetSpecification().oapi == kVersionNone);
SCHEMAERROR(s, "{\"SpecUnsupported\":{\"errorCode\":11,\"instanceRef\":\"#\"}}");
}
TEST(SchemaValidator, Schema_UnsupportedDraft2019_09) {
Document sd;
sd.Parse("{\"$schema\":\"https://json-schema.org/draft/2019-09/schema\", \"type\": \"integer\"}");
ASSERT_FALSE(sd.HasParseError());
SchemaDocument s(sd);
ASSERT_FALSE(s.IsSupportedSpecification());
ASSERT_TRUE(s.GetSpecification().draft == kDraft2019_09);
ASSERT_TRUE(s.GetSpecification().oapi == kVersionNone);
SCHEMAERROR(s, "{\"SpecUnsupported\":{\"errorCode\":11,\"instanceRef\":\"#\"}}");
}
TEST(SchemaValidator, Schema_UnsupportedDraft2020_12) {
Document sd;
sd.Parse("{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\", \"type\": \"integer\"}");
ASSERT_FALSE(sd.HasParseError());
SchemaDocument s(sd);
ASSERT_FALSE(s.IsSupportedSpecification());
ASSERT_TRUE(s.GetSpecification().draft == kDraft2020_12);
ASSERT_TRUE(s.GetSpecification().oapi == kVersionNone);
SCHEMAERROR(s, "{\"SpecUnsupported\":{\"errorCode\":11,\"instanceRef\":\"#\"}}");
}
TEST(SchemaValidator, Schema_SupportedVersion20Static) {
typedef GenericSchemaDocument<Value, MemoryPoolAllocator<> > SchemaDocumentType;
Document sd;
sd.Parse("{\"swagger\":\"2.0\"}");
ASSERT_FALSE(sd.HasParseError());
Specification spec = SchemaDocumentType::GetSpecification(sd);
ASSERT_TRUE(spec.IsSupported());
ASSERT_TRUE(spec.draft == kDraft04);
ASSERT_TRUE(spec.oapi == kVersion20);
}
TEST(SchemaValidator, Schema_SupportedVersion20) {
Document sd;
sd.Parse("{\"swagger\":\"2.0\"}");
ASSERT_FALSE(sd.HasParseError());
SchemaDocument s(sd);
ASSERT_TRUE(s.IsSupportedSpecification());
ASSERT_TRUE(s.GetSpecification().oapi == kVersion20);
ASSERT_TRUE(s.GetSpecification().draft == kDraft04);
EXPECT_TRUE(s.GetError().ObjectEmpty());
}
TEST(SchemaValidator, Schema_SupportedVersion30x) {
Document sd;
sd.Parse("{\"openapi\":\"3.0.0\"}");
ASSERT_FALSE(sd.HasParseError());
SchemaDocument s(sd);
ASSERT_TRUE(s.IsSupportedSpecification());
ASSERT_TRUE(s.GetSpecification().oapi == kVersion30);
ASSERT_TRUE(s.GetSpecification().draft == kDraft05);
EXPECT_TRUE(s.GetError().ObjectEmpty());
}
TEST(SchemaValidator, Schema_SupportedVersionOverride) {
Document sd;
sd.Parse("{\"type\": \"integer\"}");
ASSERT_FALSE(sd.HasParseError());
SchemaDocument s(sd, 0, 0, 0, 0, 0, Specification(kVersion20));
ASSERT_TRUE(s.IsSupportedSpecification());
ASSERT_TRUE(s.GetSpecification().oapi == kVersion20);
ASSERT_TRUE(s.GetSpecification().draft == kDraft04);
EXPECT_TRUE(s.GetError().ObjectEmpty());
}
TEST(SchemaValidator, Schema_UnknownVersionOverride) {
Document sd;
sd.Parse("{\"type\": \"integer\"}");
ASSERT_FALSE(sd.HasParseError());
SchemaDocument s(sd, 0, 0, 0, 0, 0, Specification(kVersionUnknown));
ASSERT_FALSE(s.IsSupportedSpecification());
ASSERT_TRUE(s.GetSpecification().oapi == kVersionUnknown);
ASSERT_TRUE(s.GetSpecification().draft == kDraft04);
SCHEMAERROR(s, "{\"SpecUnknown\":{\"errorCode\":10,\"instanceRef\":\"#\"}}");
}
TEST(SchemaValidator, Schema_UnsupportedVersionOverride) {
Document sd;
sd.Parse("{\"type\": \"integer\"}");
ASSERT_FALSE(sd.HasParseError());
SchemaDocument s(sd, 0, 0, 0, 0, 0, Specification(kVersion31));
ASSERT_FALSE(s.IsSupportedSpecification());
ASSERT_TRUE(s.GetSpecification().oapi == kVersion31);
ASSERT_TRUE(s.GetSpecification().draft == kDraft2020_12);
SCHEMAERROR(s, "{\"SpecUnsupported\":{\"errorCode\":11,\"instanceRef\":\"#\"}}");
}
TEST(SchemaValidator, Schema_UnknownVersion) {
Document sd;
sd.Parse("{\"openapi\":\"1.0\"}");
ASSERT_FALSE(sd.HasParseError());
SchemaDocument s(sd);
ASSERT_FALSE(s.IsSupportedSpecification());
ASSERT_TRUE(s.GetSpecification().oapi == kVersionUnknown);
ASSERT_TRUE(s.GetSpecification().draft == kDraft04);
SCHEMAERROR(s, "{\"SpecUnknown\":{\"errorCode\":10,\"instanceRef\":\"#\"}}");
}
TEST(SchemaValidator, Schema_UnknownVersionShort) {
Document sd;
sd.Parse("{\"openapi\":\"3.0.\"}");
ASSERT_FALSE(sd.HasParseError());
SchemaDocument s(sd);
ASSERT_FALSE(s.IsSupportedSpecification());
ASSERT_TRUE(s.GetSpecification().oapi == kVersionUnknown);
ASSERT_TRUE(s.GetSpecification().draft == kDraft04);
SCHEMAERROR(s, "{\"SpecUnknown\":{\"errorCode\":10,\"instanceRef\":\"#\"}}");
}
TEST(SchemaValidator, Schema_UnknownVersionNotString) {
Document sd;
sd.Parse("{\"swagger\": 2}");
ASSERT_FALSE(sd.HasParseError());
SchemaDocument s(sd);
ASSERT_FALSE(s.IsSupportedSpecification());
ASSERT_TRUE(s.GetSpecification().oapi == kVersionUnknown);
ASSERT_TRUE(s.GetSpecification().draft == kDraft04);
SCHEMAERROR(s, "{\"SpecUnknown\":{\"errorCode\":10,\"instanceRef\":\"#\"}}");
}
TEST(SchemaValidator, Schema_UnsupportedVersion31) {
Document sd;
sd.Parse("{\"openapi\":\"3.1.0\"}");
ASSERT_FALSE(sd.HasParseError());
SchemaDocument s(sd);
ASSERT_FALSE(s.IsSupportedSpecification());
ASSERT_TRUE(s.GetSpecification().oapi == kVersion31);
ASSERT_TRUE(s.GetSpecification().draft == kDraft2020_12);
SCHEMAERROR(s, "{\"SpecUnsupported\":{\"errorCode\":11,\"instanceRef\":\"#\"}}");
}
TEST(SchemaValidator, Schema_DraftAndVersion) {
Document sd;
sd.Parse("{\"swagger\": \"2.0\", \"$schema\": \"http://json-schema.org/draft-04/schema#\"}");
ASSERT_FALSE(sd.HasParseError());
SchemaDocument s(sd);
ASSERT_TRUE(s.IsSupportedSpecification());
SCHEMAERROR(s, "{\"SpecIllegal\":{\"errorCode\":12,\"instanceRef\":\"#\"}}");
}
TEST(SchemaValidator, Schema_StartUnknown) { TEST(SchemaValidator, Schema_StartUnknown) {
Document sd; Document sd;
sd.Parse("{\"type\": \"integer\"}"); sd.Parse("{\"type\": \"integer\"}");
@ -3007,6 +3329,25 @@ TEST(SchemaValidator, Schema_StartUnknown) {
SCHEMAERROR(s, "{\"StartUnknown\":{\"errorCode\":1,\"instanceRef\":\"#\", \"value\":\"#/nowhere\"}}"); SCHEMAERROR(s, "{\"StartUnknown\":{\"errorCode\":1,\"instanceRef\":\"#\", \"value\":\"#/nowhere\"}}");
} }
TEST(SchemaValidator, Schema_MultipleErrors) {
Document sd;
sd.Parse("{\"swagger\": \"foo\", \"$schema\": \"bar\"}");
ASSERT_FALSE(sd.HasParseError());
SchemaDocument s(sd);
SCHEMAERROR(s, "{ \"SpecUnknown\": {\"errorCode\":10,\"instanceRef\":\"#\"},"
" \"SpecIllegal\": {\"errorCode\":12,\"instanceRef\":\"#\"}"
"}");
}
// $ref is a non-JSON pointer fragment - not allowed when OpenAPI
TEST(SchemaValidator, Schema_RefPlainNameOpenApi) {
typedef GenericSchemaDocument<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 // $ref is a non-JSON pointer fragment - not allowed when remote document
TEST(SchemaValidator, Schema_RefPlainNameRemote) { TEST(SchemaValidator, Schema_RefPlainNameRemote) {
typedef GenericSchemaDocument<Value, MemoryPoolAllocator<> > SchemaDocumentType; typedef GenericSchemaDocument<Value, MemoryPoolAllocator<> > SchemaDocumentType;
@ -3019,9 +3360,10 @@ TEST(SchemaValidator, Schema_RefPlainNameRemote) {
// $ref is an empty string // $ref is an empty string
TEST(SchemaValidator, Schema_RefEmptyString) { TEST(SchemaValidator, Schema_RefEmptyString) {
typedef GenericSchemaDocument<Value, MemoryPoolAllocator<> > SchemaDocumentType;
Document sd; Document sd;
sd.Parse("{\"type\": \"object\", \"properties\": {\"myInt1\": {\"$ref\": \"\"}}}"); sd.Parse("{\"type\": \"object\", \"properties\": {\"myInt1\": {\"$ref\": \"\"}}}");
SchemaDocument s(sd); SchemaDocumentType s(sd);
SCHEMAERROR(s, "{\"RefInvalid\":{\"errorCode\":3,\"instanceRef\":\"#/properties/myInt1\"}}"); SCHEMAERROR(s, "{\"RefInvalid\":{\"errorCode\":3,\"instanceRef\":\"#/properties/myInt1\"}}");
} }
@ -3046,9 +3388,10 @@ TEST(SchemaValidator, Schema_RefNoRemoteSchema) {
// $ref pointer is invalid // $ref pointer is invalid
TEST(SchemaValidator, Schema_RefPointerInvalid) { TEST(SchemaValidator, Schema_RefPointerInvalid) {
typedef GenericSchemaDocument<Value, MemoryPoolAllocator<> > SchemaDocumentType;
Document sd; Document sd;
sd.Parse("{\"type\": \"object\", \"properties\": {\"myInt\": {\"$ref\": \"#/&&&&&\"}}}"); sd.Parse("{\"type\": \"object\", \"properties\": {\"myInt\": {\"$ref\": \"#/&&&&&\"}}}");
SchemaDocument s(sd); SchemaDocumentType s(sd);
SCHEMAERROR(s, "{\"RefPointerInvalid\":{\"errorCode\":4,\"instanceRef\":\"#/properties/myInt\",\"value\":\"#/&&&&&\",\"offset\":2}}"); SCHEMAERROR(s, "{\"RefPointerInvalid\":{\"errorCode\":4,\"instanceRef\":\"#/properties/myInt\",\"value\":\"#/&&&&&\",\"offset\":2}}");
} }
@ -3064,17 +3407,19 @@ TEST(SchemaValidator, Schema_RefPointerInvalidRemote) {
// $ref is unknown non-pointer // $ref is unknown non-pointer
TEST(SchemaValidator, Schema_RefUnknownPlainName) { TEST(SchemaValidator, Schema_RefUnknownPlainName) {
typedef GenericSchemaDocument<Value, MemoryPoolAllocator<> > SchemaDocumentType;
Document sd; Document sd;
sd.Parse("{\"type\": \"object\", \"properties\": {\"myInt\": {\"$ref\": \"#plainname\"}}}"); sd.Parse("{\"type\": \"object\", \"properties\": {\"myInt\": {\"$ref\": \"#plainname\"}}}");
SchemaDocument s(sd); SchemaDocumentType s(sd);
SCHEMAERROR(s, "{\"RefUnknown\":{\"errorCode\":5,\"instanceRef\":\"#/properties/myInt\",\"value\":\"#plainname\"}}"); SCHEMAERROR(s, "{\"RefUnknown\":{\"errorCode\":5,\"instanceRef\":\"#/properties/myInt\",\"value\":\"#plainname\"}}");
} }
/// $ref is unknown pointer /// $ref is unknown pointer
TEST(SchemaValidator, Schema_RefUnknownPointer) { TEST(SchemaValidator, Schema_RefUnknownPointer) {
typedef GenericSchemaDocument<Value, MemoryPoolAllocator<> > SchemaDocumentType;
Document sd; Document sd;
sd.Parse("{\"type\": \"object\", \"properties\": {\"myInt\": {\"$ref\": \"#/a/b\"}}}"); sd.Parse("{\"type\": \"object\", \"properties\": {\"myInt\": {\"$ref\": \"#/a/b\"}}}");
SchemaDocument s(sd); SchemaDocumentType s(sd);
SCHEMAERROR(s, "{\"RefUnknown\":{\"errorCode\":5,\"instanceRef\":\"#/properties/myInt\",\"value\":\"#/a/b\"}}"); SCHEMAERROR(s, "{\"RefUnknown\":{\"errorCode\":5,\"instanceRef\":\"#/properties/myInt\",\"value\":\"#/a/b\"}}");
} }
@ -3090,6 +3435,7 @@ TEST(SchemaValidator, Schema_RefUnknownPointerRemote) {
// $ref is cyclical // $ref is cyclical
TEST(SchemaValidator, Schema_RefCyclical) { TEST(SchemaValidator, Schema_RefCyclical) {
typedef GenericSchemaDocument<Value, MemoryPoolAllocator<> > SchemaDocumentType;
Document sd; Document sd;
sd.Parse("{\"type\": \"object\", \"properties\": {" sd.Parse("{\"type\": \"object\", \"properties\": {"
" \"cyclic_source\": {" " \"cyclic_source\": {"
@ -3099,10 +3445,130 @@ TEST(SchemaValidator, Schema_RefCyclical) {
" \"$ref\": \"#/properties/cyclic_source\"" " \"$ref\": \"#/properties/cyclic_source\""
" }" " }"
"}}"); "}}");
SchemaDocument s(sd); SchemaDocumentType s(sd);
SCHEMAERROR(s, "{\"RefCyclical\":{\"errorCode\":6,\"instanceRef\":\"#/properties/cyclic_target\",\"value\":\"#/properties/cyclic_source\"}}"); SCHEMAERROR(s, "{\"RefCyclical\":{\"errorCode\":6,\"instanceRef\":\"#/properties/cyclic_target\",\"value\":\"#/properties/cyclic_source\"}}");
} }
TEST(SchemaValidator, Schema_ReadOnlyAndWriteOnly) {
Document sd;
sd.Parse("{\"type\": \"integer\", \"readOnly\": true, \"writeOnly\": true}");
ASSERT_FALSE(sd.HasParseError());
SchemaDocument s1(sd, 0, 0, 0, 0, 0, Specification(kDraft04));
EXPECT_TRUE(s1.GetError().ObjectEmpty());
SchemaDocument s2(sd, 0, 0, 0, 0, 0, Specification(kVersion30));
SCHEMAERROR(s2, "{\"ReadOnlyAndWriteOnly\":{\"errorCode\":13,\"instanceRef\":\"#\"}}");
}
TEST(SchemaValidator, ReadOnlyWhenWriting) {
Document sd;
sd.Parse(
"{"
" \"type\":\"object\","
" \"properties\": {"
" \"rprop\" : {"
" \"type\": \"string\","
" \"readOnly\": true"
" }"
" }"
"}");
SchemaDocument s(sd, 0, 0, 0, 0, 0, Specification(kVersion20));
VALIDATE(s, "{ \"rprop\": \"hello\" }", true);
INVALIDATE_(s, "{ \"rprop\": \"hello\" }", "/properties/rprop", "readOnly", "/rprop",
"{ \"readOnly\": {"
" \"errorCode\": 26, \"instanceRef\": \"#/rprop\", \"schemaRef\": \"#/properties/rprop\""
" }"
"}",
kValidateDefaultFlags | kValidateWriteFlag, SchemaValidator, Pointer);
}
TEST(SchemaValidator, WriteOnlyWhenReading) {
Document sd;
sd.Parse(
"{"
" \"type\":\"object\","
" \"properties\": {"
" \"wprop\" : {"
" \"type\": \"boolean\","
" \"writeOnly\": true"
" }"
" }"
"}");
SchemaDocument s(sd, 0, 0, 0, 0, 0, Specification(kVersion30));
VALIDATE(s, "{ \"wprop\": true }", true);
INVALIDATE_(s, "{ \"wprop\": true }", "/properties/wprop", "writeOnly", "/wprop",
"{ \"writeOnly\": {"
" \"errorCode\": 27, \"instanceRef\": \"#/wprop\", \"schemaRef\": \"#/properties/wprop\""
" }"
"}",
kValidateDefaultFlags | kValidateReadFlag, SchemaValidator, Pointer);
}
TEST(SchemaValidator, NullableTrue) {
Document sd;
sd.Parse("{\"type\": \"string\", \"nullable\": true}");
SchemaDocument s(sd, 0, 0, 0, 0, 0, kVersion20);
VALIDATE(s, "\"hello\"", true);
INVALIDATE(s, "null", "", "type", "",
"{ \"type\": {"
" \"errorCode\": 20,"
" \"instanceRef\": \"#\", \"schemaRef\": \"#\","
" \"expected\": [\"string\"], \"actual\": \"null\""
"}}");
INVALIDATE(s, "false", "", "type", "",
"{ \"type\": {"
" \"errorCode\": 20,"
" \"instanceRef\": \"#\", \"schemaRef\": \"#\","
" \"expected\": [\"string\"], \"actual\": \"boolean\""
"}}");
SchemaDocument s30(sd, 0, 0, 0, 0, 0, kVersion30);
VALIDATE(s30, "\"hello\"", true);
VALIDATE(s30, "null", true);
INVALIDATE(s30, "false", "", "type", "",
"{ \"type\": {"
" \"errorCode\": 20,"
" \"instanceRef\": \"#\", \"schemaRef\": \"#\","
" \"expected\": [\"null\", \"string\"], \"actual\": \"boolean\""
"}}");
}
TEST(SchemaValidator, NullableFalse) {
Document sd;
sd.Parse("{\"type\": \"string\", \"nullable\": false}");
SchemaDocument s(sd, 0, 0, 0, 0, 0, kVersion20);
VALIDATE(s, "\"hello\"", true);
INVALIDATE(s, "null", "", "type", "",
"{ \"type\": {"
" \"errorCode\": 20,"
" \"instanceRef\": \"#\", \"schemaRef\": \"#\","
" \"expected\": [\"string\"], \"actual\": \"null\""
"}}");
INVALIDATE(s, "false", "", "type", "",
"{ \"type\": {"
" \"errorCode\": 20,"
" \"instanceRef\": \"#\", \"schemaRef\": \"#\","
" \"expected\": [\"string\"], \"actual\": \"boolean\""
"}}");
SchemaDocument s30(sd, 0, 0, 0, 0, 0, kVersion30);
VALIDATE(s30, "\"hello\"", true);
INVALIDATE(s, "null", "", "type", "",
"{ \"type\": {"
" \"errorCode\": 20,"
" \"instanceRef\": \"#\", \"schemaRef\": \"#\","
" \"expected\": [\"string\"], \"actual\": \"null\""
"}}");
INVALIDATE(s30, "false", "", "type", "",
"{ \"type\": {"
" \"errorCode\": 20,"
" \"instanceRef\": \"#\", \"schemaRef\": \"#\","
" \"expected\": [\"string\"], \"actual\": \"boolean\""
"}}");
}
#if defined(_MSC_VER) || defined(__clang__) #if defined(_MSC_VER) || defined(__clang__)
RAPIDJSON_DIAG_POP RAPIDJSON_DIAG_POP