From 05e7b3397758bd31032aa66620e15fd8ab2869f5 Mon Sep 17 00:00:00 2001 From: Steve Hanson Date: Thu, 28 Jan 2021 12:11:43 +0000 Subject: [PATCH] code and tests --- .gitignore | 1 + bin/unittestschema/address.json | Bin 0 -> 3157 bytes bin/unittestschema/allOf_address.json | Bin 0 -> 84 bytes bin/unittestschema/anyOf_address.json | Bin 0 -> 84 bytes bin/unittestschema/oneOf_address.json | Bin 0 -> 84 bytes example/schemavalidator/schemavalidator.cpp | 116 ++++ include/rapidjson/error/en.h | 48 ++ include/rapidjson/error/error.h | 55 ++ include/rapidjson/schema.h | 434 +++++++++----- test/perftest/schematest.cpp | 13 +- test/unittest/schematest.cpp | 622 +++++++++++++++++--- 11 files changed, 1076 insertions(+), 213 deletions(-) create mode 100644 bin/unittestschema/address.json create mode 100644 bin/unittestschema/allOf_address.json create mode 100644 bin/unittestschema/anyOf_address.json create mode 100644 bin/unittestschema/oneOf_address.json diff --git a/.gitignore b/.gitignore index 1d3073f..5932e82 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ !/bin/encodings !/bin/jsonchecker !/bin/types +!/bin/unittestschema /build /doc/html /doc/doxygen_*.db diff --git a/bin/unittestschema/address.json b/bin/unittestschema/address.json new file mode 100644 index 0000000000000000000000000000000000000000..007d971dafa038c6675eeca46fadfb5329bdafba GIT binary patch literal 3157 zcmb7GQBT`25PsjUh&&!BYgtpqCU`*%X%b>+o2tF64aiN7;wg2CXCT_*nq*xPz^fTPHzK#B8$j7ykeMeyF%(1vpj3yNZNZ)}^BJ47INqs!i(WHnfi`n|Jc6?X^gNZ(rfL9m!j0rx;^sMED_ZBNHoT`>u& zBG_+VtY(P6KO7hiN3G_@VrSAiVnwuS%@bzh)ThLBZs%&*&@^#d`uB~Rnk&j_M(wWl zF-;qr*0}fhPRL147%gYX zhgTQ8;w77M$lP1e%g7S9Ww7(7MtZ9gO{LG&FUZpS*7V?g1!~quz54HUQmKUbVBY~b P9uJT9sSa7L->v@vbyq7K literal 0 HcmV?d00001 diff --git a/bin/unittestschema/allOf_address.json b/bin/unittestschema/allOf_address.json new file mode 100644 index 0000000000000000000000000000000000000000..fd501f66d457484de2a462ff5db2441e80210c5b GIT binary patch literal 84 zcmb>CQczGz%*pXjQ?gQs267Y>s=*{sSfwZxB&w8AQc_^0ub-2joS2i5UtD5kXk=`n apO}(Tlv-S@msOmfr>q3lQwtCQczGz%&YWIQ?gQs267Y>s=*{sSfwZxB&w8AQc_^0ub-2joS2i5UtD5kXk=`n apO}(Tlv-S@msOmfr>q3lQwtCQczIJ&r9`BQ?gQs267Y>s=*{sSfwZxB&w8AQc_^0ub-2joS2i5UtD5kXk=`n apO}(Tlv-S@msOmfr>q3lQwt +#include +#include using namespace rapidjson; +typedef GenericValue, CrtAllocator > ValueType; + +// Forward ref +static void CreateErrorMessages(const ValueType& errors, size_t depth, const char* context); + +// Convert GenericValue to std::string +static std::string GetString(const ValueType& val) { + std::string str(""); + if (val.IsString()) { + str = val.GetString(); + } else if (val.IsDouble()) { + str = std::to_string(val.GetDouble()); + } else if (val.IsUint()) { + str = std::to_string(val.GetUint()); + } else if (val.IsInt()) { + str = std::to_string(val.GetInt()); + } else if (val.IsUint64()) { + str = std::to_string(val.GetUint64()); + } else if (val.IsInt64()) { + str = std::to_string(val.GetInt64()); + } else if (val.IsBool()) { + str = std::to_string(val.GetBool()); + } else if (val.IsFloat()) { + str = std::to_string(val.GetFloat()); + } + return str; +} + +// Create the error message for a named error +// Expects the error object to contain at least member properties: +// { +// "errorCode": , +// "instanceRef": "", +// "schemaRef": "" +// } +// Additional properties may be present for use as inserts. +// An "errors" property may be present if there are child errors. +static void HandleError(const char* errorName, const ValueType& error, size_t depth, const char* context) { + // Get error code and look up error message text (English) + int code = error["errorCode"].GetInt(); + std::string message(GetValidateError_En(static_cast(code))); + // For each member property in the error, see if its name exists as an insert in the error message and if so replace with the stringified property value + // So for example - "Number '%actual' is not a multiple of the 'multipleOf' value '%expected'." - we would expect "actual" and "expected" members. + for (ValueType::ConstMemberIterator insertsItr = error.MemberBegin(); insertsItr != error.MemberEnd(); ++insertsItr) { + std::string insertRegex("\\%"); + insertRegex += insertsItr->name.GetString(); // eg "\%actual" + if (std::regex_search(message, std::regex(insertRegex))) { + std::string insertString(""); + const ValueType &insert = insertsItr->value; + if (insert.IsArray()) { + // Member is an array so create comma-separated list of items for the insert string + for (ValueType::ConstValueIterator itemsItr = insert.Begin(); itemsItr != insert.End(); ++itemsItr) { + if (itemsItr != insert.Begin()) insertString += ","; + insertString += GetString(*itemsItr); + } + } else { + insertString += GetString(insert); + } + message = std::regex_replace(message, std::regex(insertRegex), insertString); + } + } + // Output error message, references, context + std::string indent(depth*2, ' '); + std::cout << indent << "Error Name: " << errorName << std::endl; + std::cout << indent << "Message: " << message.c_str() << std::endl; + std::cout << indent << "Instance: " << error["instanceRef"].GetString() << std::endl; + std::cout << indent << "Schema: " << error["schemaRef"].GetString() << std::endl; + if (depth > 0 ) std::cout << indent << "Context: " << context << std::endl; + std::cout << std::endl; + + // If child errors exist, apply the process recursively to each error structure. + // This occurs for "oneOf", "allOf", "anyOf" and "dependencies" errors, so pass the error name as context. + if (error.HasMember("errors")) { + depth++; + const ValueType& childErrors = error["errors"]; + if (childErrors.IsArray()) { + // Array - each item is an error structure - example + // "anyOf": {"errorCode": ..., "errors":[{"pattern": {"errorCode\": ...\"}}, {"pattern": {"errorCode\": ...}}] + for (ValueType::ConstValueIterator errorsItr = childErrors.Begin(); errorsItr != childErrors.End(); ++errorsItr) { + CreateErrorMessages(*errorsItr, depth, errorName); + } + } else if (childErrors.IsObject()) { + // Object - each member is an error structure - example + // "dependencies": {"errorCode": ..., "errors": {"address": {"required": {"errorCode": ...}}, "name": {"required": {"errorCode": ...}}} + for (ValueType::ConstMemberIterator propsItr = childErrors.MemberBegin(); propsItr != childErrors.MemberEnd(); ++propsItr) { + CreateErrorMessages(propsItr->value, depth, errorName); + } + } + } +} + +// Create error message for all errors in an error structure +// Context is used to indicate whether the error structure has a parent 'dependencies', 'allOf', 'anyOf' or 'oneOf' error +static void CreateErrorMessages(const ValueType& errors, size_t depth = 0, const char* context = 0) { + // Each member property contains one or more errors of a given type + for (ValueType::ConstMemberIterator errorTypeItr = errors.MemberBegin(); errorTypeItr != errors.MemberEnd(); ++errorTypeItr) { + const char* errorName = errorTypeItr->name.GetString(); + const ValueType& errorContent = errorTypeItr->value; + if (errorContent.IsArray()) { + // Member is an array where each item is an error - eg "type": [{"errorCode": ...}, {"errorCode": ...}] + for (ValueType::ConstValueIterator contentItr = errorContent.Begin(); contentItr != errorContent.End(); ++contentItr) { + HandleError(errorName, *contentItr, depth, context); + } + } else if (errorContent.IsObject()) { + // Member is an object which is a single error - eg "type": {"errorCode": ... } + HandleError(errorName, errorContent, depth, context); + } + } +} + int main(int argc, char *argv[]) { if (argc != 2) { fprintf(stderr, "Usage: schemavalidator schema.json < input.json\n"); @@ -65,6 +178,8 @@ int main(int argc, char *argv[]) { validator.GetInvalidSchemaPointer().StringifyUriFragment(sb); fprintf(stderr, "Invalid schema: %s\n", sb.GetString()); fprintf(stderr, "Invalid keyword: %s\n", validator.GetInvalidSchemaKeyword()); + fprintf(stderr, "Invalid code: %d\n", validator.GetInvalidSchemaCode()); + fprintf(stderr, "Invalid message: %s\n", GetValidateError_En(validator.GetInvalidSchemaCode())); sb.Clear(); validator.GetInvalidDocumentPointer().StringifyUriFragment(sb); fprintf(stderr, "Invalid document: %s\n", sb.GetString()); @@ -73,6 +188,7 @@ int main(int argc, char *argv[]) { PrettyWriter w(sb); validator.GetError().Accept(w); fprintf(stderr, "Error report:\n%s\n", sb.GetString()); + CreateErrorMessages(validator.GetError()); return EXIT_FAILURE; } } diff --git a/include/rapidjson/error/en.h b/include/rapidjson/error/en.h index 37a62eb..5d2e57b 100644 --- a/include/rapidjson/error/en.h +++ b/include/rapidjson/error/en.h @@ -65,6 +65,54 @@ inline const RAPIDJSON_ERROR_CHARTYPE* GetParseError_En(ParseErrorCode parseErro } } +//! Maps error code of validation into error message. +/*! + \ingroup RAPIDJSON_ERRORS + \param validateErrorCode Error code obtained from validator. + \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* GetValidateError_En(ValidateErrorCode validateErrorCode) { + switch (validateErrorCode) { + case kValidateErrors: return RAPIDJSON_ERROR_STRING("One or more validation errors have occurred"); + case kValidateErrorNone: return RAPIDJSON_ERROR_STRING("No error."); + + case kValidateErrorMultipleOf: return RAPIDJSON_ERROR_STRING("Number '%actual' is not a multiple of the 'multipleOf' value '%expected'."); + case kValidateErrorMaximum: return RAPIDJSON_ERROR_STRING("Number '%actual' is greater than the 'maximum' value '%expected'."); + case kValidateErrorExclusiveMaximum: return RAPIDJSON_ERROR_STRING("Number '%actual' is greater than or equal to the 'exclusiveMaximum' value '%expected'."); + case kValidateErrorMinimum: return RAPIDJSON_ERROR_STRING("Number '%actual' is less than the 'minimum' value '%expected'."); + case kValidateErrorExclusiveMinimum: return RAPIDJSON_ERROR_STRING("Number '%actual' is less than or equal to the 'exclusiveMinimum' value '%expected'."); + + case kValidateErrorMaxLength: return RAPIDJSON_ERROR_STRING("String '%actual' is longer than the 'maxLength' value '%expected'."); + case kValidateErrorMinLength: return RAPIDJSON_ERROR_STRING("String '%actual' is shorter than the 'minLength' value '%expected'."); + case kValidateErrorPattern: return RAPIDJSON_ERROR_STRING("String '%actual' does not match the 'pattern' regular expression."); + + case kValidateErrorMaxItems: return RAPIDJSON_ERROR_STRING("Array of length '%actual' is longer than the 'maxItems' value '%expected'."); + case kValidateErrorMinItems: return RAPIDJSON_ERROR_STRING("Array of length '%actual' is shorter than the 'minItems' value '%expected'."); + case kValidateErrorUniqueItems: return RAPIDJSON_ERROR_STRING("Array has duplicate items at indices '%duplicates' but 'uniqueItems' is true."); + case kValidateErrorAdditionalItems: return RAPIDJSON_ERROR_STRING("Array has an additional item at index '%disallowed' that is not allowed by the schema."); + + case kValidateErrorMaxProperties: return RAPIDJSON_ERROR_STRING("Object has '%actual' members which is more than 'maxProperties' value '%expected'."); + case kValidateErrorMinProperties: return RAPIDJSON_ERROR_STRING("Object has '%actual' members which is less than 'minProperties' value '%expected'."); + case kValidateErrorRequired: return RAPIDJSON_ERROR_STRING("Object is missing the following members required by the schema: '%missing'."); + case kValidateErrorAdditionalProperties: return RAPIDJSON_ERROR_STRING("Object has an additional member '%disallowed' that is not allowed by the schema."); + case kValidateErrorPatternProperties: return RAPIDJSON_ERROR_STRING("Object has 'patternProperties' that are not allowed by the schema."); + case kValidateErrorDependencies: return RAPIDJSON_ERROR_STRING("Object has missing property or schema dependencies, refer to following errors."); + + case kValidateErrorEnum: return RAPIDJSON_ERROR_STRING("Property has a value that is not one of its allowed enumerated values."); + 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 kValidateErrorOneOfMatch: return RAPIDJSON_ERROR_STRING("Property matched more than one of the sub-schemas specified by 'oneOf'."); + 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 kValidateErrorNot: return RAPIDJSON_ERROR_STRING("Property matched the sub-schema specified by 'not'."); + + default: return RAPIDJSON_ERROR_STRING("Unknown error."); + } +} + RAPIDJSON_NAMESPACE_END #ifdef __clang__ diff --git a/include/rapidjson/error/error.h b/include/rapidjson/error/error.h index 71f6ec4..6270da1 100644 --- a/include/rapidjson/error/error.h +++ b/include/rapidjson/error/error.h @@ -152,6 +152,61 @@ private: */ typedef const RAPIDJSON_ERROR_CHARTYPE* (*GetParseErrorFunc)(ParseErrorCode); +/////////////////////////////////////////////////////////////////////////////// +// ValidateErrorCode + +//! Error codes when validating. +/*! \ingroup RAPIDJSON_ERRORS + \see GenericSchemaValidator +*/ +enum ValidateErrorCode { + kValidateErrors = -1, //!< Top level error code when kValidateContinueOnErrorsFlag set. + kValidateErrorNone = 0, //!< No error. + + kValidateErrorMultipleOf, //!< Number is not a multiple of the 'multipleOf' value. + kValidateErrorMaximum, //!< Number is greater than the 'maximum' value. + kValidateErrorExclusiveMaximum, //!< Number is greater than or equal to the 'maximum' value. + kValidateErrorMinimum, //!< Number is less than the 'minimum' value. + kValidateErrorExclusiveMinimum, //!< Number is less than or equal to the 'minimum' value. + + kValidateErrorMaxLength, //!< String is longer than the 'maxLength' value. + kValidateErrorMinLength, //!< String is longer than the 'maxLength' value. + kValidateErrorPattern, //!< String does not match the 'pattern' regular expression. + + kValidateErrorMaxItems, //!< Array is longer than the 'maxItems' value. + kValidateErrorMinItems, //!< Array is shorter than the 'minItems' value. + kValidateErrorUniqueItems, //!< Array has duplicate items but 'uniqueItems' is true. + kValidateErrorAdditionalItems, //!< Array has additional items that are not allowed by the schema. + + kValidateErrorMaxProperties, //!< Object has more members than 'maxProperties' value. + kValidateErrorMinProperties, //!< Object has less members than 'minProperties' value. + kValidateErrorRequired, //!< Object is missing one or more members required by the schema. + kValidateErrorAdditionalProperties, //!< Object has additional members that are not allowed by the schema. + kValidateErrorPatternProperties, //!< See other errors. + kValidateErrorDependencies, //!< Object has missing property or schema dependencies. + + 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.. + + 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'. + 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'. + kValidateErrorNot //!< Property matched the sub-schema specified by 'not'. +}; + +//! Function pointer type of GetValidateError(). +/*! \ingroup RAPIDJSON_ERRORS + + This is the prototype for \c GetValidateError_X(), where \c X is a locale. + User can dynamically change locale in runtime, e.g.: +\code + GetValidateErrorFunc GetValidateError = GetValidateError_En; // or whatever + const RAPIDJSON_ERROR_CHARTYPE* s = GetValidateError(validator.GetInvalidSchemaCode()); +\endcode +*/ +typedef const RAPIDJSON_ERROR_CHARTYPE* (*GetValidateErrorFunc)(ValidateErrorCode); + RAPIDJSON_NAMESPACE_END #ifdef __clang__ diff --git a/include/rapidjson/schema.h b/include/rapidjson/schema.h index fc39d06..b23a04d 100644 --- a/include/rapidjson/schema.h +++ b/include/rapidjson/schema.h @@ -18,6 +18,7 @@ #include "document.h" #include "pointer.h" #include "stringbuffer.h" +#include "error/en.h" #include // abs, floor #if !defined(RAPIDJSON_SCHEMA_USE_INTERNALREGEX) @@ -113,13 +114,36 @@ inline void PrintValidatorPointers(unsigned depth, const wchar_t* s, const wchar #define RAPIDJSON_INVALID_KEYWORD_VERBOSE(keyword) #endif -#define RAPIDJSON_INVALID_KEYWORD_RETURN(keyword)\ +#define RAPIDJSON_INVALID_KEYWORD_RETURN(code)\ RAPIDJSON_MULTILINEMACRO_BEGIN\ - context.invalidKeyword = keyword.GetString();\ - RAPIDJSON_INVALID_KEYWORD_VERBOSE(keyword.GetString());\ + context.invalidCode = code;\ + context.invalidKeyword = SchemaType::GetValidateErrorKeyword(code).GetString();\ + RAPIDJSON_INVALID_KEYWORD_VERBOSE(context.invalidKeyword);\ return false;\ RAPIDJSON_MULTILINEMACRO_END +/////////////////////////////////////////////////////////////////////////////// +// ValidateFlag + +/*! \def RAPIDJSON_VALIDATE_DEFAULT_FLAGS + \ingroup RAPIDJSON_CONFIG + \brief User-defined kValidateDefaultFlags definition. + + User can define this as any \c ValidateFlag combinations. +*/ +#ifndef RAPIDJSON_VALIDATE_DEFAULT_FLAGS +#define RAPIDJSON_VALIDATE_DEFAULT_FLAGS kValidateNoFlags +#endif + +//! Combination of validate flags +/*! \see + */ +enum ValidateFlag : unsigned { + kValidateNoFlags = 0, //!< No flags are set. + kValidateContinueOnErrorFlag = 1, //!< Don't stop after first validation error. + kValidateDefaultFlags = RAPIDJSON_VALIDATE_DEFAULT_FLAGS //!< Default validate flags. Can be customized by defining RAPIDJSON_VALIDATE_DEFAULT_FLAGS +}; + /////////////////////////////////////////////////////////////////////////////// // Forward declarations @@ -138,6 +162,8 @@ class ISchemaValidator { public: virtual ~ISchemaValidator() {} virtual bool IsValid() const = 0; + virtual void SetValidateFlags(unsigned flags) = 0; + virtual unsigned GetValidateFlags() const = 0; }; /////////////////////////////////////////////////////////////////////////////// @@ -147,7 +173,7 @@ template class ISchemaStateFactory { public: virtual ~ISchemaStateFactory() {} - virtual ISchemaValidator* CreateSchemaValidator(const SchemaType&) = 0; + virtual ISchemaValidator* CreateSchemaValidator(const SchemaType&, const bool inheritContinueOnErrors) = 0; virtual void DestroySchemaValidator(ISchemaValidator* validator) = 0; virtual void* CreateHasher() = 0; virtual uint64_t GetHashCode(void* hasher) = 0; @@ -201,13 +227,13 @@ public: virtual void AddDependencySchemaError(const SValue& souceName, ISchemaValidator* subvalidator) = 0; virtual bool EndDependencyErrors() = 0; - virtual void DisallowedValue() = 0; + virtual void DisallowedValue(const ValidateErrorCode code) = 0; virtual void StartDisallowedType() = 0; virtual void AddExpectedType(const typename SchemaType::ValueType& expectedType) = 0; virtual void EndDisallowedType(const typename SchemaType::ValueType& actualType) = 0; virtual void NotAllOf(ISchemaValidator** subvalidators, SizeType count) = 0; virtual void NoneOf(ISchemaValidator** subvalidators, SizeType count) = 0; - virtual void NotOneOf(ISchemaValidator** subvalidators, SizeType count) = 0; + virtual void NotOneOf(ISchemaValidator** subvalidators, SizeType count, bool matched) = 0; virtual void Disallowed() = 0; }; @@ -332,6 +358,7 @@ struct SchemaValidationContext { schema(s), valueSchema(), invalidKeyword(), + invalidCode(), hasher(), arrayElementHashCodes(), validators(), @@ -372,6 +399,7 @@ struct SchemaValidationContext { const SchemaType* schema; const SchemaType* valueSchema; const Ch* invalidKeyword; + ValidateErrorCode invalidCode; void* hasher; // Only validator access void* arrayElementHashCodes; // Only validator access this ISchemaValidator** validators; @@ -443,6 +471,7 @@ public: exclusiveMaximum_(false), defaultValueLength_(0) { + //std::cout << "Schema constructor " << schemaDocument << std::endl; // SMH typedef typename ValueType::ConstValueIterator ConstValueIterator; typedef typename ValueType::ConstMemberIterator ConstMemberIterator; @@ -458,7 +487,7 @@ public: AddType(*itr); } - if (const ValueType* v = GetMember(value, GetEnumString())) + if (const ValueType* v = GetMember(value, GetEnumString())) { if (v->IsArray() && v->Size() > 0) { enum_ = static_cast(allocator_->Malloc(sizeof(uint64_t) * v->Size())); for (ConstValueIterator itr = v->Begin(); itr != v->End(); ++itr) { @@ -470,6 +499,7 @@ public: enum_[enumCount_++] = h.GetHashCode(); } } + } if (schemaDocument) { AssignIfExist(allOf_, *schemaDocument, p, value, GetAllOfString(), document); @@ -688,7 +718,11 @@ public: context.valueSchema = typeless_; else { context.error_handler.DisallowedItem(context.arrayElementIndex); - RAPIDJSON_INVALID_KEYWORD_RETURN(GetItemsString()); + // Must set valueSchema for when kValidateContinueOnErrorFlag is set, else reports spurious type error + context.valueSchema = typeless_; + // Must bump arrayElementIndex for when kValidateContinueOnErrorFlag is set + context.arrayElementIndex++; + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorAdditionalItems); } } else @@ -716,28 +750,29 @@ public: if (context.objectPatternValidatorType == Context::kPatternValidatorOnly) { if (!patternValid) { context.error_handler.PropertyViolations(context.patternPropertiesValidators, count); - RAPIDJSON_INVALID_KEYWORD_RETURN(GetPatternPropertiesString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorPatternProperties); } } else if (context.objectPatternValidatorType == Context::kPatternValidatorWithProperty) { if (!patternValid || !otherValid) { context.error_handler.PropertyViolations(context.patternPropertiesValidators, count + 1); - RAPIDJSON_INVALID_KEYWORD_RETURN(GetPatternPropertiesString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorPatternProperties); } } else if (!patternValid && !otherValid) { // kPatternValidatorWithAdditionalProperty) context.error_handler.PropertyViolations(context.patternPropertiesValidators, count + 1); - RAPIDJSON_INVALID_KEYWORD_RETURN(GetPatternPropertiesString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorPatternProperties); } } - if (enum_) { + // For enums only check if we have a hasher + if (enum_ && context.hasher) { const uint64_t h = context.factory.GetHashCode(context.hasher); for (SizeType i = 0; i < enumCount_; i++) if (enum_[i] == h) goto foundEnum; - context.error_handler.DisallowedValue(); - RAPIDJSON_INVALID_KEYWORD_RETURN(GetEnumString()); + context.error_handler.DisallowedValue(kValidateErrorEnum); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorEnum); foundEnum:; } @@ -745,7 +780,7 @@ public: for (SizeType i = allOf_.begin; i < allOf_.begin + allOf_.count; i++) if (!context.validators[i]->IsValid()) { context.error_handler.NotAllOf(&context.validators[allOf_.begin], allOf_.count); - RAPIDJSON_INVALID_KEYWORD_RETURN(GetAllOfString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorAllOf); } if (anyOf_.schemas) { @@ -753,7 +788,7 @@ public: if (context.validators[i]->IsValid()) goto foundAny; context.error_handler.NoneOf(&context.validators[anyOf_.begin], anyOf_.count); - RAPIDJSON_INVALID_KEYWORD_RETURN(GetAnyOfString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorAnyOf); foundAny:; } @@ -762,20 +797,20 @@ public: for (SizeType i = oneOf_.begin; i < oneOf_.begin + oneOf_.count; i++) if (context.validators[i]->IsValid()) { if (oneValid) { - context.error_handler.NotOneOf(&context.validators[oneOf_.begin], oneOf_.count); - RAPIDJSON_INVALID_KEYWORD_RETURN(GetOneOfString()); + context.error_handler.NotOneOf(&context.validators[oneOf_.begin], oneOf_.count, true); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorOneOfMatch); } else oneValid = true; } if (!oneValid) { - context.error_handler.NotOneOf(&context.validators[oneOf_.begin], oneOf_.count); - RAPIDJSON_INVALID_KEYWORD_RETURN(GetOneOfString()); + context.error_handler.NotOneOf(&context.validators[oneOf_.begin], oneOf_.count, false); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorOneOf); } } if (not_ && context.validators[notValidatorIndex_]->IsValid()) { context.error_handler.Disallowed(); - RAPIDJSON_INVALID_KEYWORD_RETURN(GetNotString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorNot); } return true; @@ -784,7 +819,7 @@ public: bool Null(Context& context) const { if (!(type_ & (1 << kNullSchemaType))) { DisallowedType(context, GetNullString()); - RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorType); } return CreateParallelValidator(context); } @@ -792,7 +827,7 @@ public: bool Bool(Context& context, bool) const { if (!(type_ & (1 << kBooleanSchemaType))) { DisallowedType(context, GetBooleanString()); - RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorType); } return CreateParallelValidator(context); } @@ -824,7 +859,7 @@ public: bool Double(Context& context, double d) const { if (!(type_ & (1 << kNumberSchemaType))) { DisallowedType(context, GetNumberString()); - RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorType); } if (!minimum_.IsNull() && !CheckDoubleMinimum(context, d)) @@ -842,7 +877,7 @@ public: bool String(Context& context, const Ch* str, SizeType length, bool) const { if (!(type_ & (1 << kStringSchemaType))) { DisallowedType(context, GetStringString()); - RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorType); } if (minLength_ != 0 || maxLength_ != SizeType(~0)) { @@ -850,27 +885,28 @@ public: if (internal::CountStringCodePoint(str, length, &count)) { if (count < minLength_) { context.error_handler.TooShort(str, length, minLength_); - RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinLengthString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorMinLength); } if (count > maxLength_) { context.error_handler.TooLong(str, length, maxLength_); - RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaxLengthString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorMaxLength); } } } if (pattern_ && !IsPatternMatch(pattern_, str, length)) { context.error_handler.DoesNotMatch(str, length); - RAPIDJSON_INVALID_KEYWORD_RETURN(GetPatternString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorPattern); } return CreateParallelValidator(context); } bool StartObject(Context& context) const { + //std::cout << " schema StartObject" << std::endl; // SMH if (!(type_ & (1 << kObjectSchemaType))) { DisallowedType(context, GetObjectString()); - RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorType); } if (hasDependencies_ || hasRequired_) { @@ -889,6 +925,7 @@ public: } bool Key(Context& context, const Ch* str, SizeType len, bool) const { + //std::cout << " schema Key" << std::endl; // SMH if (patternProperties_) { context.patternPropertiesSchemaCount = 0; for (SizeType i = 0; i < patternPropertyCount_; i++) @@ -930,14 +967,17 @@ public: } if (context.patternPropertiesSchemaCount == 0) { // patternProperties are not additional properties + // Must set valueSchema for when kValidateContinueOnErrorFlag is set, else reports spurious type error + context.valueSchema = typeless_; context.error_handler.DisallowedProperty(str, len); - RAPIDJSON_INVALID_KEYWORD_RETURN(GetAdditionalPropertiesString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorAdditionalProperties); } return true; } bool EndObject(Context& context, SizeType memberCount) const { + //std::cout << " schema EndObject with members " << memberCount << std::endl; // SMH if (hasRequired_) { context.error_handler.StartMissingProperties(); for (SizeType index = 0; index < propertyCount_; index++) @@ -945,17 +985,17 @@ public: if (properties_[index].schema->defaultValueLength_ == 0 ) context.error_handler.AddMissingProperty(properties_[index].name); if (context.error_handler.EndMissingProperties()) - RAPIDJSON_INVALID_KEYWORD_RETURN(GetRequiredString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorRequired); } if (memberCount < minProperties_) { context.error_handler.TooFewProperties(memberCount, minProperties_); - RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinPropertiesString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorMinProperties); } if (memberCount > maxProperties_) { context.error_handler.TooManyProperties(memberCount, maxProperties_); - RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaxPropertiesString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorMaxProperties); } if (hasDependencies_) { @@ -978,40 +1018,80 @@ public: } } if (context.error_handler.EndDependencyErrors()) - RAPIDJSON_INVALID_KEYWORD_RETURN(GetDependenciesString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorDependencies); } return true; } bool StartArray(Context& context) const { + //std::cout << " schema StartArray" << std::endl; // SMH + context.arrayElementIndex = 0; + context.inArray = true; // Ensure we note that we are in an array + if (!(type_ & (1 << kArraySchemaType))) { DisallowedType(context, GetArrayString()); - RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorType); } - context.arrayElementIndex = 0; - context.inArray = true; - return CreateParallelValidator(context); } bool EndArray(Context& context, SizeType elementCount) const { + //std::cout << " schema EndArray" << std::endl; // SMH context.inArray = false; if (elementCount < minItems_) { context.error_handler.TooFewItems(elementCount, minItems_); - RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinItemsString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorMinItems); } if (elementCount > maxItems_) { context.error_handler.TooManyItems(elementCount, maxItems_); - RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaxItemsString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorMaxItems); } return true; } + static const ValueType& GetValidateErrorKeyword(ValidateErrorCode validateErrorCode) { + switch (validateErrorCode) { + case kValidateErrorMultipleOf: return GetMultipleOfString(); + case kValidateErrorMaximum: return GetMaximumString(); + case kValidateErrorExclusiveMaximum: return GetMaximumString(); // Same + case kValidateErrorMinimum: return GetMinimumString(); + case kValidateErrorExclusiveMinimum: return GetMinimumString(); // Same + + case kValidateErrorMaxLength: return GetMaxLengthString(); + case kValidateErrorMinLength: return GetMinLengthString(); + case kValidateErrorPattern: return GetPatternString(); + + case kValidateErrorMaxItems: return GetMaxItemsString(); + case kValidateErrorMinItems: return GetMinItemsString(); + case kValidateErrorUniqueItems: return GetUniqueItemsString(); + case kValidateErrorAdditionalItems: return GetAdditionalItemsString(); + + case kValidateErrorMaxProperties: return GetMaxPropertiesString(); + case kValidateErrorMinProperties: return GetMinPropertiesString(); + case kValidateErrorRequired: return GetRequiredString(); + case kValidateErrorAdditionalProperties: return GetAdditionalPropertiesString(); + case kValidateErrorPatternProperties: return GetPatternPropertiesString(); + case kValidateErrorDependencies: return GetDependenciesString(); + + case kValidateErrorEnum: return GetEnumString(); + case kValidateErrorType: return GetTypeString(); + + case kValidateErrorOneOf: return GetOneOfString(); + case kValidateErrorOneOfMatch: return GetOneOfString(); // Same + case kValidateErrorAllOf: return GetAllOfString(); + case kValidateErrorAnyOf: return GetAnyOfString(); + case kValidateErrorNot: return GetNotString(); + + default: return GetNullString(); + } + } + + // Generate functions for string literal according to Ch #define RAPIDJSON_STRING_(name, ...) \ static const ValueType& Get##name##String() {\ @@ -1190,31 +1270,32 @@ private: context.validators = static_cast(context.factory.MallocState(sizeof(ISchemaValidator*) * validatorCount_)); context.validatorCount = validatorCount_; + // Always return after first failure for these sub-validators if (allOf_.schemas) - CreateSchemaValidators(context, allOf_); + CreateSchemaValidators(context, allOf_, false); if (anyOf_.schemas) - CreateSchemaValidators(context, anyOf_); + CreateSchemaValidators(context, anyOf_, false); if (oneOf_.schemas) - CreateSchemaValidators(context, oneOf_); + CreateSchemaValidators(context, oneOf_, false); if (not_) - context.validators[notValidatorIndex_] = context.factory.CreateSchemaValidator(*not_); - + context.validators[notValidatorIndex_] = context.factory.CreateSchemaValidator(*not_, false); + if (hasSchemaDependencies_) { for (SizeType i = 0; i < propertyCount_; i++) if (properties_[i].dependenciesSchema) - context.validators[properties_[i].dependenciesValidatorIndex] = context.factory.CreateSchemaValidator(*properties_[i].dependenciesSchema); + context.validators[properties_[i].dependenciesValidatorIndex] = context.factory.CreateSchemaValidator(*properties_[i].dependenciesSchema, false); } } return true; } - void CreateSchemaValidators(Context& context, const SchemaArray& schemas) const { + void CreateSchemaValidators(Context& context, const SchemaArray& schemas, const bool inheritContinueOnErrors) const { for (SizeType i = 0; i < schemas.count; i++) - context.validators[schemas.begin + i] = context.factory.CreateSchemaValidator(*schemas.schemas[i]); + context.validators[schemas.begin + i] = context.factory.CreateSchemaValidator(*schemas.schemas[i], inheritContinueOnErrors); } // O(n) @@ -1234,19 +1315,19 @@ private: bool CheckInt(Context& context, int64_t i) const { if (!(type_ & ((1 << kIntegerSchemaType) | (1 << kNumberSchemaType)))) { DisallowedType(context, GetIntegerString()); - RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorType); } if (!minimum_.IsNull()) { if (minimum_.IsInt64()) { if (exclusiveMinimum_ ? i <= minimum_.GetInt64() : i < minimum_.GetInt64()) { context.error_handler.BelowMinimum(i, minimum_, exclusiveMinimum_); - RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinimumString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(exclusiveMinimum_ ? kValidateErrorExclusiveMinimum : kValidateErrorMinimum); } } else if (minimum_.IsUint64()) { context.error_handler.BelowMinimum(i, minimum_, exclusiveMinimum_); - RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinimumString()); // i <= max(int64_t) < minimum.GetUint64() + RAPIDJSON_INVALID_KEYWORD_RETURN(exclusiveMinimum_ ? kValidateErrorExclusiveMinimum : kValidateErrorMinimum); // i <= max(int64_t) < minimum.GetUint64() } else if (!CheckDoubleMinimum(context, static_cast(i))) return false; @@ -1256,7 +1337,7 @@ private: if (maximum_.IsInt64()) { if (exclusiveMaximum_ ? i >= maximum_.GetInt64() : i > maximum_.GetInt64()) { context.error_handler.AboveMaximum(i, maximum_, exclusiveMaximum_); - RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaximumString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(exclusiveMaximum_ ? kValidateErrorExclusiveMaximum : kValidateErrorMaximum); } } else if (maximum_.IsUint64()) { } @@ -1269,7 +1350,7 @@ private: if (multipleOf_.IsUint64()) { if (static_cast(i >= 0 ? i : -i) % multipleOf_.GetUint64() != 0) { context.error_handler.NotMultipleOf(i, multipleOf_); - RAPIDJSON_INVALID_KEYWORD_RETURN(GetMultipleOfString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorMultipleOf); } } else if (!CheckDoubleMultipleOf(context, static_cast(i))) @@ -1282,14 +1363,14 @@ private: bool CheckUint(Context& context, uint64_t i) const { if (!(type_ & ((1 << kIntegerSchemaType) | (1 << kNumberSchemaType)))) { DisallowedType(context, GetIntegerString()); - RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorType); } if (!minimum_.IsNull()) { if (minimum_.IsUint64()) { if (exclusiveMinimum_ ? i <= minimum_.GetUint64() : i < minimum_.GetUint64()) { context.error_handler.BelowMinimum(i, minimum_, exclusiveMinimum_); - RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinimumString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(exclusiveMinimum_ ? kValidateErrorExclusiveMinimum : kValidateErrorMinimum); } } else if (minimum_.IsInt64()) @@ -1302,12 +1383,12 @@ private: if (maximum_.IsUint64()) { if (exclusiveMaximum_ ? i >= maximum_.GetUint64() : i > maximum_.GetUint64()) { context.error_handler.AboveMaximum(i, maximum_, exclusiveMaximum_); - RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaximumString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(exclusiveMaximum_ ? kValidateErrorExclusiveMaximum : kValidateErrorMaximum); } } else if (maximum_.IsInt64()) { context.error_handler.AboveMaximum(i, maximum_, exclusiveMaximum_); - RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaximumString()); // i >= 0 > maximum_ + RAPIDJSON_INVALID_KEYWORD_RETURN(exclusiveMaximum_ ? kValidateErrorExclusiveMaximum : kValidateErrorMaximum); // i >= 0 > maximum_ } else if (!CheckDoubleMaximum(context, static_cast(i))) return false; @@ -1317,7 +1398,7 @@ private: if (multipleOf_.IsUint64()) { if (i % multipleOf_.GetUint64() != 0) { context.error_handler.NotMultipleOf(i, multipleOf_); - RAPIDJSON_INVALID_KEYWORD_RETURN(GetMultipleOfString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorMultipleOf); } } else if (!CheckDoubleMultipleOf(context, static_cast(i))) @@ -1330,7 +1411,7 @@ private: bool CheckDoubleMinimum(Context& context, double d) const { if (exclusiveMinimum_ ? d <= minimum_.GetDouble() : d < minimum_.GetDouble()) { context.error_handler.BelowMinimum(d, minimum_, exclusiveMinimum_); - RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinimumString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(exclusiveMinimum_ ? kValidateErrorExclusiveMinimum : kValidateErrorMinimum); } return true; } @@ -1338,7 +1419,7 @@ private: bool CheckDoubleMaximum(Context& context, double d) const { if (exclusiveMaximum_ ? d >= maximum_.GetDouble() : d > maximum_.GetDouble()) { context.error_handler.AboveMaximum(d, maximum_, exclusiveMaximum_); - RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaximumString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(exclusiveMaximum_ ? kValidateErrorExclusiveMaximum : kValidateErrorMaximum); } return true; } @@ -1349,7 +1430,7 @@ private: double r = a - q * b; if (r > 0.0) { context.error_handler.NotMultipleOf(d, multipleOf_); - RAPIDJSON_INVALID_KEYWORD_RETURN(GetMultipleOfString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorMultipleOf); } return true; } @@ -1532,6 +1613,7 @@ public: schemaMap_(allocator, kInitialSchemaMapSize), schemaRef_(allocator, kInitialSchemaRefSize) { + //std::cout << "schema document constructor " << root_ << std::endl; // SMH if (!allocator_) ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator)(); @@ -1763,8 +1845,7 @@ template < class GenericSchemaValidator : public internal::ISchemaStateFactory, public internal::ISchemaValidator, - public internal::IValidationErrorHandler -{ + public internal::IValidationErrorHandler { public: typedef typename SchemaDocumentType::SchemaType SchemaType; typedef typename SchemaDocumentType::PointerType PointerType; @@ -1797,11 +1878,13 @@ public: error_(kObjectType), currentError_(), missingDependents_(), - valid_(true) + valid_(true), + flags_(kValidateDefaultFlags) #if RAPIDJSON_SCHEMA_VERBOSE , depth_(0) #endif { + //std::cout << "validator constructor" << std::endl; // SMH } //! Constructor with output handler. @@ -1828,11 +1911,13 @@ public: error_(kObjectType), currentError_(), missingDependents_(), - valid_(true) + valid_(true), + flags_(kValidateDefaultFlags) #if RAPIDJSON_SCHEMA_VERBOSE , depth_(0) #endif { + //std::cout << "validator constructor with handler" << std::endl; // SMH } //! Destructor. @@ -1846,31 +1931,61 @@ public: while (!schemaStack_.Empty()) PopSchema(); documentStack_.Clear(); + ResetError(); + } + + //! Reset the error state. + void ResetError() { error_.SetObject(); currentError_.SetNull(); missingDependents_.SetNull(); valid_ = true; } + //! Implementation of ISchemaValidator + void SetValidateFlags(unsigned flags) { + flags_ = flags; + } + virtual unsigned GetValidateFlags() const { + return flags_; + } + //! Checks whether the current state is valid. // Implementation of ISchemaValidator - virtual bool IsValid() const { return valid_; } + virtual bool IsValid() const { + if (!valid_) return false; + if (GetContinueOnErrors() && !error_.ObjectEmpty()) return false; + return true; + } //! Gets the error object. ValueType& GetError() { return error_; } const ValueType& GetError() const { return error_; } //! Gets the JSON pointer pointed to the invalid schema. + // If reporting all errors, the stack will be empty. PointerType GetInvalidSchemaPointer() const { return schemaStack_.Empty() ? PointerType() : CurrentSchema().GetPointer(); } //! Gets the keyword of invalid schema. + // If reporting all errors, the stack will be empty, so return "errors". const Ch* GetInvalidSchemaKeyword() const { - return schemaStack_.Empty() ? 0 : CurrentContext().invalidKeyword; + if (!schemaStack_.Empty()) return CurrentContext().invalidKeyword; + if (GetContinueOnErrors() && !error_.ObjectEmpty()) return (const Ch*)GetErrorsString(); + return 0; + } + + //! Gets the error code of invalid schema. + // If reporting all errors, the stack will be empty, so return kValidateErrors. + ValidateErrorCode GetInvalidSchemaCode() const { + if (!schemaStack_.Empty()) return CurrentContext().invalidCode; + if (GetContinueOnErrors() && !error_.ObjectEmpty()) return kValidateErrors; + return kValidateErrorNone; } //! Gets the JSON pointer pointed to the invalid value. + // If reporting all errors, the stack will be empty. PointerType GetInvalidDocumentPointer() const { if (documentStack_.Empty()) { return PointerType(); @@ -1881,64 +1996,64 @@ public: } void NotMultipleOf(int64_t actual, const SValue& expected) { - AddNumberError(SchemaType::GetMultipleOfString(), ValueType(actual).Move(), expected); + AddNumberError(kValidateErrorMultipleOf, ValueType(actual).Move(), expected); } void NotMultipleOf(uint64_t actual, const SValue& expected) { - AddNumberError(SchemaType::GetMultipleOfString(), ValueType(actual).Move(), expected); + AddNumberError(kValidateErrorMultipleOf, ValueType(actual).Move(), expected); } void NotMultipleOf(double actual, const SValue& expected) { - AddNumberError(SchemaType::GetMultipleOfString(), ValueType(actual).Move(), expected); + AddNumberError(kValidateErrorMultipleOf, ValueType(actual).Move(), expected); } void AboveMaximum(int64_t actual, const SValue& expected, bool exclusive) { - AddNumberError(SchemaType::GetMaximumString(), ValueType(actual).Move(), expected, + AddNumberError(exclusive ? kValidateErrorExclusiveMaximum : kValidateErrorMaximum, ValueType(actual).Move(), expected, exclusive ? &SchemaType::GetExclusiveMaximumString : 0); } void AboveMaximum(uint64_t actual, const SValue& expected, bool exclusive) { - AddNumberError(SchemaType::GetMaximumString(), ValueType(actual).Move(), expected, + AddNumberError(exclusive ? kValidateErrorExclusiveMaximum : kValidateErrorMaximum, ValueType(actual).Move(), expected, exclusive ? &SchemaType::GetExclusiveMaximumString : 0); } void AboveMaximum(double actual, const SValue& expected, bool exclusive) { - AddNumberError(SchemaType::GetMaximumString(), ValueType(actual).Move(), expected, + AddNumberError(exclusive ? kValidateErrorExclusiveMaximum : kValidateErrorMaximum, ValueType(actual).Move(), expected, exclusive ? &SchemaType::GetExclusiveMaximumString : 0); } void BelowMinimum(int64_t actual, const SValue& expected, bool exclusive) { - AddNumberError(SchemaType::GetMinimumString(), ValueType(actual).Move(), expected, + AddNumberError(exclusive ? kValidateErrorExclusiveMinimum : kValidateErrorMinimum, ValueType(actual).Move(), expected, exclusive ? &SchemaType::GetExclusiveMinimumString : 0); } void BelowMinimum(uint64_t actual, const SValue& expected, bool exclusive) { - AddNumberError(SchemaType::GetMinimumString(), ValueType(actual).Move(), expected, + AddNumberError(exclusive ? kValidateErrorExclusiveMinimum : kValidateErrorMinimum, ValueType(actual).Move(), expected, exclusive ? &SchemaType::GetExclusiveMinimumString : 0); } void BelowMinimum(double actual, const SValue& expected, bool exclusive) { - AddNumberError(SchemaType::GetMinimumString(), ValueType(actual).Move(), expected, + AddNumberError(exclusive ? kValidateErrorExclusiveMinimum : kValidateErrorMinimum, ValueType(actual).Move(), expected, exclusive ? &SchemaType::GetExclusiveMinimumString : 0); } void TooLong(const Ch* str, SizeType length, SizeType expected) { - AddNumberError(SchemaType::GetMaxLengthString(), + AddNumberError(kValidateErrorMaxLength, ValueType(str, length, GetStateAllocator()).Move(), SValue(expected).Move()); } void TooShort(const Ch* str, SizeType length, SizeType expected) { - AddNumberError(SchemaType::GetMinLengthString(), + AddNumberError(kValidateErrorMinLength, ValueType(str, length, GetStateAllocator()).Move(), SValue(expected).Move()); } void DoesNotMatch(const Ch* str, SizeType length) { currentError_.SetObject(); currentError_.AddMember(GetActualString(), ValueType(str, length, GetStateAllocator()).Move(), GetStateAllocator()); - AddCurrentError(SchemaType::GetPatternString()); + AddCurrentError(kValidateErrorPattern); } void DisallowedItem(SizeType index) { currentError_.SetObject(); currentError_.AddMember(GetDisallowedString(), ValueType(index).Move(), GetStateAllocator()); - AddCurrentError(SchemaType::GetAdditionalItemsString(), true); + AddCurrentError(kValidateErrorAdditionalItems, true); } void TooFewItems(SizeType actualCount, SizeType expectedCount) { - AddNumberError(SchemaType::GetMinItemsString(), + AddNumberError(kValidateErrorMinItems, ValueType(actualCount).Move(), SValue(expectedCount).Move()); } void TooManyItems(SizeType actualCount, SizeType expectedCount) { - AddNumberError(SchemaType::GetMaxItemsString(), + AddNumberError(kValidateErrorMaxItems, ValueType(actualCount).Move(), SValue(expectedCount).Move()); } void DuplicateItems(SizeType index1, SizeType index2) { @@ -1947,15 +2062,15 @@ public: duplicates.PushBack(index2, GetStateAllocator()); currentError_.SetObject(); currentError_.AddMember(GetDuplicatesString(), duplicates, GetStateAllocator()); - AddCurrentError(SchemaType::GetUniqueItemsString(), true); + AddCurrentError(kValidateErrorUniqueItems, true); } void TooManyProperties(SizeType actualCount, SizeType expectedCount) { - AddNumberError(SchemaType::GetMaxPropertiesString(), + AddNumberError(kValidateErrorMaxProperties, ValueType(actualCount).Move(), SValue(expectedCount).Move()); } void TooFewProperties(SizeType actualCount, SizeType expectedCount) { - AddNumberError(SchemaType::GetMinPropertiesString(), + AddNumberError(kValidateErrorMinProperties, ValueType(actualCount).Move(), SValue(expectedCount).Move()); } void StartMissingProperties() { @@ -1970,7 +2085,7 @@ public: ValueType error(kObjectType); error.AddMember(GetMissingString(), currentError_, GetStateAllocator()); currentError_ = error; - AddCurrentError(SchemaType::GetRequiredString()); + AddCurrentError(kValidateErrorRequired); return true; } void PropertyViolations(ISchemaValidator** subvalidators, SizeType count) { @@ -1980,7 +2095,7 @@ public: void DisallowedProperty(const Ch* name, SizeType length) { currentError_.SetObject(); currentError_.AddMember(GetDisallowedString(), ValueType(name, length, GetStateAllocator()).Move(), GetStateAllocator()); - AddCurrentError(SchemaType::GetAdditionalPropertiesString(), true); + AddCurrentError(kValidateErrorAdditionalProperties, true); } void StartDependencyErrors() { @@ -1993,9 +2108,19 @@ public: missingDependents_.PushBack(ValueType(targetName, GetStateAllocator()).Move(), GetStateAllocator()); } void EndMissingDependentProperties(const SValue& sourceName) { - if (!missingDependents_.Empty()) - currentError_.AddMember(ValueType(sourceName, GetStateAllocator()).Move(), - missingDependents_, GetStateAllocator()); + if (!missingDependents_.Empty()) { + // Create equivalent 'required' error + ValueType error(kObjectType); + ValidateErrorCode code = kValidateErrorRequired; + error.AddMember(GetMissingString(), missingDependents_.Move(), GetStateAllocator()); + AddErrorCode(error, code); + AddErrorInstanceLocation(error, false); + PointerType schemaRef = GetInvalidSchemaPointer().Append(SchemaType::GetValidateErrorKeyword(kValidateErrorDependencies), &GetStateAllocator()); + AddErrorSchemaLocation(error, schemaRef.Append(sourceName.GetString(), sourceName.GetStringLength(), &GetStateAllocator())); + ValueType wrapper(kObjectType); + wrapper.AddMember(ValueType(SchemaType::GetValidateErrorKeyword(code), GetStateAllocator()).Move(), error, GetStateAllocator()); + currentError_.AddMember(ValueType(sourceName, GetStateAllocator()).Move(), wrapper, GetStateAllocator()); + } } void AddDependencySchemaError(const SValue& sourceName, ISchemaValidator* subvalidator) { currentError_.AddMember(ValueType(sourceName, GetStateAllocator()).Move(), @@ -2007,13 +2132,13 @@ public: ValueType error(kObjectType); error.AddMember(GetErrorsString(), currentError_, GetStateAllocator()); currentError_ = error; - AddCurrentError(SchemaType::GetDependenciesString()); + AddCurrentError(kValidateErrorDependencies); return true; } - void DisallowedValue() { + void DisallowedValue(const ValidateErrorCode code = kValidateErrorEnum) { currentError_.SetObject(); - AddCurrentError(SchemaType::GetEnumString()); + AddCurrentError(code); } void StartDisallowedType() { currentError_.SetArray(); @@ -2026,22 +2151,24 @@ public: error.AddMember(GetExpectedString(), currentError_, GetStateAllocator()); error.AddMember(GetActualString(), ValueType(actualType, GetStateAllocator()).Move(), GetStateAllocator()); currentError_ = error; - AddCurrentError(SchemaType::GetTypeString()); + AddCurrentError(kValidateErrorType); } void NotAllOf(ISchemaValidator** subvalidators, SizeType count) { - for (SizeType i = 0; i < count; ++i) { - MergeError(static_cast(subvalidators[i])->GetError()); - } + // Treat allOf like oneOf and anyOf for clarity + AddErrorArray(kValidateErrorAllOf, subvalidators, count); + //for (SizeType i = 0; i < count; ++i) { + // MergeError(static_cast(subvalidators[i])->GetError()); + //} } void NoneOf(ISchemaValidator** subvalidators, SizeType count) { - AddErrorArray(SchemaType::GetAnyOfString(), subvalidators, count); + AddErrorArray(kValidateErrorAnyOf, subvalidators, count); } - void NotOneOf(ISchemaValidator** subvalidators, SizeType count) { - AddErrorArray(SchemaType::GetOneOfString(), subvalidators, count); + void NotOneOf(ISchemaValidator** subvalidators, SizeType count, bool matched = false) { + AddErrorArray(matched ? kValidateErrorOneOfMatch : kValidateErrorOneOf, subvalidators, count); } void Disallowed() { currentError_.SetObject(); - AddCurrentError(SchemaType::GetNotString()); + AddCurrentError(kValidateErrorNot); } #define RAPIDJSON_STRING_(name, ...) \ @@ -2058,6 +2185,8 @@ public: RAPIDJSON_STRING_(Disallowed, 'd', 'i', 's', 'a', 'l', 'l', 'o', 'w', 'e', 'd') RAPIDJSON_STRING_(Missing, 'm', 'i', 's', 's', 'i', 'n', 'g') RAPIDJSON_STRING_(Errors, 'e', 'r', 'r', 'o', 'r', 's') + 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_(Duplicates, 'd', 'u', 'p', 'l', 'i', 'c', 'a', 't', 'e', 's') #undef RAPIDJSON_STRING_ @@ -2075,13 +2204,14 @@ RAPIDJSON_MULTILINEMACRO_END #define RAPIDJSON_SCHEMA_HANDLE_BEGIN_(method, arg1)\ if (!valid_) return false; \ - if (!BeginValue() || !CurrentSchema().method arg1) {\ + if ((!BeginValue() && !GetContinueOnErrors()) || (!CurrentSchema().method arg1 && !GetContinueOnErrors())) {\ RAPIDJSON_SCHEMA_HANDLE_BEGIN_VERBOSE_();\ return valid_ = false;\ } #define RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(method, arg2)\ for (Context* context = schemaStack_.template Bottom(); context != schemaStack_.template End(); context++) {\ + /*std::cout << " ++Parallel context: " << context << std::endl;*/\ if (context->hasher)\ static_cast(context->hasher)->method arg2;\ if (context->validators)\ @@ -2093,9 +2223,12 @@ RAPIDJSON_MULTILINEMACRO_END } #define RAPIDJSON_SCHEMA_HANDLE_END_(method, arg2)\ - return valid_ = EndValue() && (!outputHandler_ || outputHandler_->method arg2) + valid_ = (EndValue() || GetContinueOnErrors()) && (!outputHandler_ || outputHandler_->method arg2);\ + /*std::cout << "### EndValue returns " << valid_ << std::endl;*/\ + return valid_; #define RAPIDJSON_SCHEMA_HANDLE_VALUE_(method, arg1, arg2) \ + /*std::cout << "validator Value " << this << std::endl;*/\ RAPIDJSON_SCHEMA_HANDLE_BEGIN_ (method, arg1);\ RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(method, arg2);\ RAPIDJSON_SCHEMA_HANDLE_END_ (method, arg2) @@ -2113,36 +2246,41 @@ RAPIDJSON_MULTILINEMACRO_END { RAPIDJSON_SCHEMA_HANDLE_VALUE_(String, (CurrentContext(), str, length, copy), (str, length, copy)); } bool StartObject() { + //std::cout << "validator StartObject " << this << std::endl; // SMH RAPIDJSON_SCHEMA_HANDLE_BEGIN_(StartObject, (CurrentContext())); RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(StartObject, ()); return valid_ = !outputHandler_ || outputHandler_->StartObject(); } bool Key(const Ch* str, SizeType len, bool copy) { + //std::cout << "validator Key: " << str << " " << this << (valid_ ? " true" : " false") << std::endl; // SMH if (!valid_) return false; AppendToken(str, len); - if (!CurrentSchema().Key(CurrentContext(), str, len, copy)) return valid_ = false; + if (!CurrentSchema().Key(CurrentContext(), str, len, copy) && !GetContinueOnErrors()) return valid_ = false; RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(Key, (str, len, copy)); return valid_ = !outputHandler_ || outputHandler_->Key(str, len, copy); } - bool EndObject(SizeType memberCount) { + bool EndObject(SizeType memberCount) { + //std::cout << "validator EndObject " << this << std::endl; // SMH if (!valid_) return false; RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(EndObject, (memberCount)); - if (!CurrentSchema().EndObject(CurrentContext(), memberCount)) return valid_ = false; + if (!CurrentSchema().EndObject(CurrentContext(), memberCount) && !GetContinueOnErrors()) return valid_ = false; RAPIDJSON_SCHEMA_HANDLE_END_(EndObject, (memberCount)); } bool StartArray() { + //std::cout << "validator StartArray " << this << std::endl; // SMH RAPIDJSON_SCHEMA_HANDLE_BEGIN_(StartArray, (CurrentContext())); RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(StartArray, ()); return valid_ = !outputHandler_ || outputHandler_->StartArray(); } bool EndArray(SizeType elementCount) { + //std::cout << "validator EndArray " << this << std::endl; // SMH if (!valid_) return false; RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(EndArray, (elementCount)); - if (!CurrentSchema().EndArray(CurrentContext(), elementCount)) return valid_ = false; + if (!CurrentSchema().EndArray(CurrentContext(), elementCount) && !GetContinueOnErrors()) return valid_ = false; RAPIDJSON_SCHEMA_HANDLE_END_(EndArray, (elementCount)); } @@ -2152,12 +2290,15 @@ RAPIDJSON_MULTILINEMACRO_END #undef RAPIDJSON_SCHEMA_HANDLE_VALUE_ // Implementation of ISchemaStateFactory - virtual ISchemaValidator* CreateSchemaValidator(const SchemaType& root) { - return new (GetStateAllocator().Malloc(sizeof(GenericSchemaValidator))) GenericSchemaValidator(*schemaDocument_, root, documentStack_.template Bottom(), documentStack_.GetSize(), + virtual ISchemaValidator* CreateSchemaValidator(const SchemaType& root, const bool inheritContinueOnErrors) { + ISchemaValidator* sv = new (GetStateAllocator().Malloc(sizeof(GenericSchemaValidator))) GenericSchemaValidator(*schemaDocument_, root, documentStack_.template Bottom(), documentStack_.GetSize(), #if RAPIDJSON_SCHEMA_VERBOSE depth_ + 1, #endif &GetStateAllocator()); + sv->SetValidateFlags(inheritContinueOnErrors ? GetValidateFlags() : GetValidateFlags() & ~kValidateContinueOnErrorFlag); + //std::cout << "***** New validator ***** " << sv << " " << sv->GetValidateFlags() << std::endl; + return sv; } virtual void DestroySchemaValidator(ISchemaValidator* validator) { @@ -2214,7 +2355,8 @@ private: error_(kObjectType), currentError_(), missingDependents_(), - valid_(true) + valid_(true), + flags_(kValidateDefaultFlags) #if RAPIDJSON_SCHEMA_VERBOSE , depth_(depth) #endif @@ -2229,6 +2371,10 @@ private: return *stateAllocator_; } + bool GetContinueOnErrors() const { + return flags_ & kValidateContinueOnErrorFlag; + } + bool BeginValue() { if (schemaStack_.Empty()) PushSchema(root_); @@ -2236,7 +2382,7 @@ private: if (CurrentContext().inArray) internal::TokenHelper, Ch>::AppendIndexToken(documentStack_, CurrentContext().arrayElementIndex); - if (!CurrentSchema().BeginValue(CurrentContext())) + if (!CurrentSchema().BeginValue(CurrentContext()) && !GetContinueOnErrors()) return false; SizeType count = CurrentContext().patternPropertiesSchemaCount; @@ -2252,7 +2398,7 @@ private: SizeType& validatorCount = CurrentContext().patternPropertiesValidatorCount; va = static_cast(MallocState(sizeof(ISchemaValidator*) * count)); for (SizeType i = 0; i < count; i++) - va[validatorCount++] = CreateSchemaValidator(*sa[i]); + va[validatorCount++] = CreateSchemaValidator(*sa[i], true); // Inherit continueOnError } CurrentContext().arrayUniqueness = valueUniqueness; @@ -2261,7 +2407,7 @@ private: } bool EndValue() { - if (!CurrentSchema().EndValue(CurrentContext())) + if (!CurrentSchema().EndValue(CurrentContext()) && !GetContinueOnErrors()) return false; #if RAPIDJSON_SCHEMA_VERBOSE @@ -2272,21 +2418,27 @@ private: documentStack_.template Pop(1); internal::PrintValidatorPointers(depth_, sb.GetString(), documentStack_.template Bottom()); #endif - - uint64_t h = CurrentContext().arrayUniqueness ? static_cast(CurrentContext().hasher)->GetHashCode() : 0; + void* hasher = CurrentContext().hasher; + uint64_t h = hasher && CurrentContext().arrayUniqueness ? static_cast(hasher)->GetHashCode() : 0; PopSchema(); if (!schemaStack_.Empty()) { Context& context = CurrentContext(); - if (context.valueUniqueness) { + // Only check uniqueness if there is a hasher + if (hasher && context.valueUniqueness) { HashCodeArray* a = static_cast(context.arrayElementHashCodes); if (!a) CurrentContext().arrayElementHashCodes = a = new (GetStateAllocator().Malloc(sizeof(HashCodeArray))) HashCodeArray(kArrayType); for (typename HashCodeArray::ConstValueIterator itr = a->Begin(); itr != a->End(); ++itr) if (itr->GetUint64() == h) { DuplicateItems(static_cast(itr - a->Begin()), a->Size()); - RAPIDJSON_INVALID_KEYWORD_RETURN(SchemaType::GetUniqueItemsString()); + // Cleanup before returning if continuing + if (GetContinueOnErrors()) { + a->PushBack(h, GetStateAllocator()); + while (!documentStack_.Empty() && *documentStack_.template Pop(1) != '/'); + } + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorUniqueItems); } a->PushBack(h, GetStateAllocator()); } @@ -2327,25 +2479,32 @@ private: c->~Context(); } - void AddErrorLocation(ValueType& result, bool parent) { + void AddErrorInstanceLocation(ValueType& result, bool parent) { GenericStringBuffer sb; PointerType instancePointer = GetInvalidDocumentPointer(); ((parent && instancePointer.GetTokenCount() > 0) - ? PointerType(instancePointer.GetTokens(), instancePointer.GetTokenCount() - 1) - : instancePointer).StringifyUriFragment(sb); + ? PointerType(instancePointer.GetTokens(), instancePointer.GetTokenCount() - 1) + : instancePointer).StringifyUriFragment(sb); ValueType instanceRef(sb.GetString(), static_cast(sb.GetSize() / sizeof(Ch)), - GetStateAllocator()); + GetStateAllocator()); result.AddMember(GetInstanceRefString(), instanceRef, GetStateAllocator()); - sb.Clear(); - memcpy(sb.Push(CurrentSchema().GetURI().GetStringLength()), - CurrentSchema().GetURI().GetString(), - CurrentSchema().GetURI().GetStringLength() * sizeof(Ch)); - GetInvalidSchemaPointer().StringifyUriFragment(sb); + } + + void AddErrorSchemaLocation(ValueType& result, PointerType schema = PointerType()) { + GenericStringBuffer sb; + SizeType len = CurrentSchema().GetURI().GetStringLength(); + if (len) memcpy(sb.Push(len), CurrentSchema().GetURI().GetString(), len * sizeof(Ch)); + if (schema.GetTokenCount()) schema.StringifyUriFragment(sb); + else GetInvalidSchemaPointer().StringifyUriFragment(sb); ValueType schemaRef(sb.GetString(), static_cast(sb.GetSize() / sizeof(Ch)), GetStateAllocator()); result.AddMember(GetSchemaRefString(), schemaRef, GetStateAllocator()); } + void AddErrorCode(ValueType& result, const ValidateErrorCode code) { + result.AddMember(GetErrorCodeString(), code, GetStateAllocator()); + } + void AddError(ValueType& keyword, ValueType& error) { typename ValueType::MemberIterator member = error_.FindMember(keyword); if (member == error_.MemberEnd()) @@ -2360,9 +2519,12 @@ private: } } - void AddCurrentError(const typename SchemaType::ValueType& keyword, bool parent = false) { - AddErrorLocation(currentError_, parent); - AddError(ValueType(keyword, GetStateAllocator(), false).Move(), currentError_); + void AddCurrentError(const ValidateErrorCode code, bool parent = false) { + //std::cout << "==== AddCurrentError ======= " << SchemaType::GetValidateErrorKeyword(code).GetString() << std::endl; + AddErrorCode(currentError_, code); + AddErrorInstanceLocation(currentError_, parent); + AddErrorSchemaLocation(currentError_); + AddError(ValueType(SchemaType::GetValidateErrorKeyword(code), GetStateAllocator(), false).Move(), currentError_); } void MergeError(ValueType& other) { @@ -2371,24 +2533,24 @@ private: } } - void AddNumberError(const typename SchemaType::ValueType& keyword, ValueType& actual, const SValue& expected, + void AddNumberError(const ValidateErrorCode code, ValueType& actual, const SValue& expected, const typename SchemaType::ValueType& (*exclusive)() = 0) { currentError_.SetObject(); currentError_.AddMember(GetActualString(), actual, GetStateAllocator()); currentError_.AddMember(GetExpectedString(), ValueType(expected, GetStateAllocator()).Move(), GetStateAllocator()); if (exclusive) currentError_.AddMember(ValueType(exclusive(), GetStateAllocator()).Move(), true, GetStateAllocator()); - AddCurrentError(keyword); + AddCurrentError(code); } - void AddErrorArray(const typename SchemaType::ValueType& keyword, + void AddErrorArray(const ValidateErrorCode code, ISchemaValidator** subvalidators, SizeType count) { ValueType errors(kArrayType); for (SizeType i = 0; i < count; ++i) errors.PushBack(static_cast(subvalidators[i])->GetError(), GetStateAllocator()); currentError_.SetObject(); currentError_.AddMember(GetErrorsString(), errors, GetStateAllocator()); - AddCurrentError(keyword); + AddCurrentError(code); } const SchemaType& CurrentSchema() const { return *schemaStack_.template Top()->schema; } @@ -2408,6 +2570,7 @@ private: ValueType currentError_; ValueType missingDependents_; bool valid_; + unsigned flags_; #if RAPIDJSON_SCHEMA_VERBOSE unsigned depth_; #endif @@ -2445,7 +2608,7 @@ public: \param is Input stream. \param sd Schema document. */ - SchemaValidatingReader(InputStream& is, const SchemaDocumentType& sd) : is_(is), sd_(sd), invalidSchemaKeyword_(), error_(kObjectType), isValid_(true) {} + SchemaValidatingReader(InputStream& is, const SchemaDocumentType& sd) : is_(is), sd_(sd), invalidSchemaKeyword_(), invalidSchemaCode_(kValidateErrorNone), error_(kObjectType), isValid_(true) {} template bool operator()(Handler& handler) { @@ -2463,6 +2626,7 @@ public: else { invalidSchemaPointer_ = validator.GetInvalidSchemaPointer(); invalidSchemaKeyword_ = validator.GetInvalidSchemaKeyword(); + invalidSchemaCode_ = validator.GetInvalidSchemaCode(); invalidDocumentPointer_ = validator.GetInvalidDocumentPointer(); error_.CopyFrom(validator.GetError(), allocator_); } @@ -2476,6 +2640,7 @@ public: const Ch* GetInvalidSchemaKeyword() const { return invalidSchemaKeyword_; } const PointerType& GetInvalidDocumentPointer() const { return invalidDocumentPointer_; } const ValueType& GetError() const { return error_; } + ValidateErrorCode GetInvalidSchemaCode() const { return invalidSchemaCode_; } private: InputStream& is_; @@ -2485,6 +2650,7 @@ private: PointerType invalidSchemaPointer_; const Ch* invalidSchemaKeyword_; PointerType invalidDocumentPointer_; + ValidateErrorCode invalidSchemaCode_; StackAllocator allocator_; ValueType error_; bool isValid_; diff --git a/test/perftest/schematest.cpp b/test/perftest/schematest.cpp index 7d27344..8237744 100644 --- a/test/perftest/schematest.cpp +++ b/test/perftest/schematest.cpp @@ -51,6 +51,8 @@ RAPIDJSON_DIAG_POP class Schema : public PerfTest { public: + typedef GenericSchemaDocument > SchemaDocumentType; + Schema() {} virtual void SetUp() { @@ -89,6 +91,8 @@ public: char jsonBuffer[65536]; MemoryPoolAllocator<> jsonAllocator(jsonBuffer, sizeof(jsonBuffer)); + char schemaBuffer[65536]; + MemoryPoolAllocator<> schemaAllocator(schemaBuffer, sizeof(schemaBuffer)); for (size_t i = 0; i < ARRAY_SIZE(filenames); i++) { char filename[FILENAME_MAX]; @@ -112,7 +116,7 @@ public: continue; TestSuite* ts = new TestSuite; - ts->schema = new SchemaDocument((*schemaItr)["schema"]); + ts->schema = new SchemaDocumentType((*schemaItr)["schema"], 0, 0, 0, &schemaAllocator); const Value& tests = (*schemaItr)["tests"]; for (Value::ConstValueIterator testItr = tests.Begin(); testItr != tests.End(); ++testItr) { @@ -187,7 +191,7 @@ protected: for (DocumentList::iterator itr = tests.begin(); itr != tests.end(); ++itr) delete *itr; } - SchemaDocument* schema; + SchemaDocumentType* schema; DocumentList tests; }; @@ -199,13 +203,14 @@ TEST_F(Schema, TestSuite) { char validatorBuffer[65536]; MemoryPoolAllocator<> validatorAllocator(validatorBuffer, sizeof(validatorBuffer)); - const int trialCount = 100000; + // DCOLES - Reduce number by a factor of 100 to make it more reasonable and inline with other test counts + const int trialCount = 1000; int testCount = 0; clock_t start = clock(); for (int i = 0; i < trialCount; i++) { for (TestSuiteList::const_iterator itr = testSuites.begin(); itr != testSuites.end(); ++itr) { const TestSuite& ts = **itr; - GenericSchemaValidator >, MemoryPoolAllocator<> > validator(*ts.schema, &validatorAllocator); + GenericSchemaValidator >, MemoryPoolAllocator<> > validator(*ts.schema, &validatorAllocator); for (DocumentList::const_iterator testItr = ts.tests.begin(); testItr != ts.tests.end(); ++testItr) { validator.Reset(); (*testItr)->Accept(validator); diff --git a/test/unittest/schematest.cpp b/test/unittest/schematest.cpp index 8a51b10..459de1a 100644 --- a/test/unittest/schematest.cpp +++ b/test/unittest/schematest.cpp @@ -12,10 +12,14 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. +#define RAPIDJSON_SCHEMA_VERBOSE 0 + #include "unittest.h" #include "rapidjson/schema.h" #include "rapidjson/stringbuffer.h" #include "rapidjson/writer.h" +#include "rapidjson/error/error.h" +#include "rapidjson/error/en.h" #ifdef __clang__ RAPIDJSON_DIAG_PUSH @@ -119,6 +123,8 @@ TEST(SchemaValidator, Hasher) { validator.GetInvalidSchemaPointer().StringifyUriFragment(sb);\ printf("Invalid schema: %s\n", sb.GetString());\ printf("Invalid keyword: %s\n", validator.GetInvalidSchemaKeyword());\ + printf("Invalid code: %d\n", validator.GetInvalidSchemaCode());\ + printf("Invalid message: %s\n", GetValidateError_En(validator.GetInvalidSchemaCode()));\ sb.Clear();\ validator.GetInvalidDocumentPointer().StringifyUriFragment(sb);\ printf("Invalid document: %s\n", sb.GetString());\ @@ -131,19 +137,23 @@ TEST(SchemaValidator, Hasher) { #define INVALIDATE(schema, json, invalidSchemaPointer, invalidSchemaKeyword, invalidDocumentPointer, error) \ {\ - INVALIDATE_(schema, json, invalidSchemaPointer, invalidSchemaKeyword, invalidDocumentPointer, error, SchemaValidator, Pointer) \ + INVALIDATE_(schema, json, invalidSchemaPointer, invalidSchemaKeyword, invalidDocumentPointer, error, kValidateDefaultFlags, SchemaValidator, Pointer) \ } #define INVALIDATE_(schema, json, invalidSchemaPointer, invalidSchemaKeyword, invalidDocumentPointer, error, \ - SchemaValidatorType, PointerType) \ + flags, SchemaValidatorType, PointerType) \ {\ SchemaValidatorType validator(schema);\ + validator.SetValidateFlags(flags);\ Document d;\ /*printf("\n%s\n", json);*/\ d.Parse(json);\ EXPECT_FALSE(d.HasParseError());\ - EXPECT_FALSE(d.Accept(validator));\ + d.Accept(validator);\ EXPECT_FALSE(validator.IsValid());\ + ValidateErrorCode code = validator.GetInvalidSchemaCode();\ + ASSERT_TRUE(code != kValidateErrorNone);\ + ASSERT_TRUE(strcmp(GetValidateError_En(code), "Unknown error.") != 0);\ if (validator.GetInvalidSchemaPointer() != PointerType(invalidSchemaPointer)) {\ StringBuffer sb;\ validator.GetInvalidSchemaPointer().Stringify(sb);\ @@ -191,6 +201,7 @@ TEST(SchemaValidator, MultiType) { VALIDATE(s, "\"Life, the universe, and everything\"", true); INVALIDATE(s, "[\"Life\", \"the universe\", \"and everything\"]", "", "type", "", "{ \"type\": {" + " \"errorCode\": 20," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": [\"string\", \"number\"], \"actual\": \"array\"" "}}"); @@ -203,7 +214,7 @@ TEST(SchemaValidator, Enum_Typed) { VALIDATE(s, "\"red\"", true); INVALIDATE(s, "\"blue\"", "", "enum", "", - "{ \"enum\": { \"instanceRef\": \"#\", \"schemaRef\": \"#\" }}"); + "{ \"enum\": { \"errorCode\": 19, \"instanceRef\": \"#\", \"schemaRef\": \"#\" }}"); } TEST(SchemaValidator, Enum_Typless) { @@ -215,7 +226,7 @@ TEST(SchemaValidator, Enum_Typless) { VALIDATE(s, "null", true); VALIDATE(s, "42", true); INVALIDATE(s, "0", "", "enum", "", - "{ \"enum\": { \"instanceRef\": \"#\", \"schemaRef\": \"#\" }}"); + "{ \"enum\": { \"errorCode\": 19, \"instanceRef\": \"#\", \"schemaRef\": \"#\" }}"); } TEST(SchemaValidator, Enum_InvalidType) { @@ -226,6 +237,7 @@ TEST(SchemaValidator, Enum_InvalidType) { VALIDATE(s, "\"red\"", true); INVALIDATE(s, "null", "", "type", "", "{ \"type\": {" + " \"errorCode\": 20," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": [\"string\"], \"actual\": \"null\"" "}}"); @@ -239,9 +251,12 @@ TEST(SchemaValidator, AllOf) { VALIDATE(s, "\"ok\"", true); INVALIDATE(s, "\"too long\"", "", "allOf", "", - "{ \"maxLength\": { " - " \"instanceRef\": \"#\", \"schemaRef\": \"#/allOf/1\", " - " \"expected\": 5, \"actual\": \"too long\"" + "{ \"allOf\": {" + " \"errors\": [" + " {}," + " {\"maxLength\": {\"errorCode\": 6, \"instanceRef\": \"#\", \"schemaRef\": \"#/allOf/1\", \"expected\": 5, \"actual\": \"too long\"}}" + " ]," + " \"errorCode\": 23, \"instanceRef\": \"#\", \"schemaRef\": \"#\"" "}}"); } { @@ -251,8 +266,12 @@ TEST(SchemaValidator, AllOf) { VALIDATE(s, "\"No way\"", false); INVALIDATE(s, "-1", "", "allOf", "", - "{ \"type\": { \"instanceRef\": \"#\", \"schemaRef\": \"#/allOf/0\"," - " \"expected\": [\"string\"], \"actual\": \"integer\"" + "{ \"allOf\": {" + " \"errors\": [" + " {\"type\": { \"instanceRef\": \"#\", \"schemaRef\": \"#/allOf/0\", \"errorCode\": 20, \"expected\": [\"string\"], \"actual\": \"integer\"}}," + " {}" + " ]," + " \"errorCode\": 23, \"instanceRef\": \"#\", \"schemaRef\": \"#\"" "}}"); } } @@ -266,13 +285,16 @@ TEST(SchemaValidator, AnyOf) { VALIDATE(s, "42", true); INVALIDATE(s, "{ \"Not a\": \"string or number\" }", "", "anyOf", "", "{ \"anyOf\": {" + " \"errorCode\": 24," " \"instanceRef\": \"#\", \"schemaRef\": \"#\", " " \"errors\": [" " { \"type\": {" + " \"errorCode\": 20," " \"instanceRef\": \"#\", \"schemaRef\": \"#/anyOf/0\"," " \"expected\": [\"string\"], \"actual\": \"object\"" " }}," " { \"type\": {" + " \"errorCode\": 20," " \"instanceRef\": \"#\", \"schemaRef\": \"#/anyOf/1\"," " \"expected\": [\"number\"], \"actual\": \"object\"" " }}" @@ -289,20 +311,23 @@ TEST(SchemaValidator, OneOf) { VALIDATE(s, "9", true); INVALIDATE(s, "2", "", "oneOf", "", "{ \"oneOf\": {" + " \"errorCode\": 21," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"errors\": [" " { \"multipleOf\": {" + " \"errorCode\": 1," " \"instanceRef\": \"#\", \"schemaRef\": \"#/oneOf/0\"," " \"expected\": 5, \"actual\": 2" " }}," " { \"multipleOf\": {" + " \"errorCode\": 1," " \"instanceRef\": \"#\", \"schemaRef\": \"#/oneOf/1\"," " \"expected\": 3, \"actual\": 2" " }}" " ]" "}}"); INVALIDATE(s, "15", "", "oneOf", "", - "{ \"oneOf\": { \"instanceRef\": \"#\", \"schemaRef\": \"#\", \"errors\": [{}, {}]}}"); + "{ \"oneOf\": { \"errorCode\": 22, \"instanceRef\": \"#\", \"schemaRef\": \"#\", \"errors\": [{}, {}]}}"); } TEST(SchemaValidator, Not) { @@ -313,7 +338,7 @@ TEST(SchemaValidator, Not) { VALIDATE(s, "42", true); VALIDATE(s, "{ \"key\": \"value\" }", true); INVALIDATE(s, "\"I am a string\"", "", "not", "", - "{ \"not\": { \"instanceRef\": \"#\", \"schemaRef\": \"#\" }}"); + "{ \"not\": { \"errorCode\": 25, \"instanceRef\": \"#\", \"schemaRef\": \"#\" }}"); } TEST(SchemaValidator, Ref) { @@ -378,10 +403,12 @@ TEST(SchemaValidator, Ref_AllOf) { SchemaDocument s(sd); INVALIDATE(s, "{\"shipping_address\": {\"street_address\": \"1600 Pennsylvania Avenue NW\", \"city\": \"Washington\", \"state\": \"DC\"} }", "/properties/shipping_address", "allOf", "/shipping_address", - "{ \"required\": {" - " \"instanceRef\": \"#/shipping_address\"," - " \"schemaRef\": \"#/properties/shipping_address/allOf/1\"," - " \"missing\": [\"type\"]" + "{ \"allOf\": {" + " \"errors\": [" + " {}," + " {\"required\": {\"errorCode\": 15, \"instanceRef\": \"#/shipping_address\", \"schemaRef\": \"#/properties/shipping_address/allOf/1\", \"missing\": [\"type\"]}}" + " ]," + " \"errorCode\":23,\"instanceRef\":\"#/shipping_address\",\"schemaRef\":\"#/properties/shipping_address\"" "}}"); VALIDATE(s, "{\"shipping_address\": {\"street_address\": \"1600 Pennsylvania Avenue NW\", \"city\": \"Washington\", \"state\": \"DC\", \"type\": \"business\"} }", true); } @@ -394,26 +421,31 @@ TEST(SchemaValidator, String) { VALIDATE(s, "\"I'm a string\"", true); INVALIDATE(s, "42", "", "type", "", "{ \"type\": {" + " \"errorCode\": 20," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": [\"string\"], \"actual\": \"integer\"" "}}"); INVALIDATE(s, "2147483648", "", "type", "", "{ \"type\": {" + " \"errorCode\": 20," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": [\"string\"], \"actual\": \"integer\"" "}}"); // 2^31 can only be fit in unsigned INVALIDATE(s, "-2147483649", "", "type", "", "{ \"type\": {" + " \"errorCode\": 20," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": [\"string\"], \"actual\": \"integer\"" "}}"); // -2^31 - 1 can only be fit in int64_t INVALIDATE(s, "4294967296", "", "type", "", "{ \"type\": {" + " \"errorCode\": 20," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": [\"string\"], \"actual\": \"integer\"" "}}"); // 2^32 can only be fit in int64_t INVALIDATE(s, "3.1415926", "", "type", "", "{ \"type\": {" + " \"errorCode\": 20," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": [\"string\"], \"actual\": \"number\"" "}}"); @@ -426,6 +458,7 @@ TEST(SchemaValidator, String_LengthRange) { INVALIDATE(s, "\"A\"", "", "minLength", "", "{ \"minLength\": {" + " \"errorCode\": 7," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 2, \"actual\": \"A\"" "}}"); @@ -433,6 +466,7 @@ TEST(SchemaValidator, String_LengthRange) { VALIDATE(s, "\"ABC\"", true); INVALIDATE(s, "\"ABCD\"", "", "maxLength", "", "{ \"maxLength\": {" + " \"errorCode\": 6," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 3, \"actual\": \"ABCD\"" "}}"); @@ -448,11 +482,13 @@ TEST(SchemaValidator, String_Pattern) { VALIDATE(s, "\"(888)555-1212\"", true); INVALIDATE(s, "\"(888)555-1212 ext. 532\"", "", "pattern", "", "{ \"pattern\": {" + " \"errorCode\": 8," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"actual\": \"(888)555-1212 ext. 532\"" "}}"); INVALIDATE(s, "\"(800)FLOWERS\"", "", "pattern", "", "{ \"pattern\": {" + " \"errorCode\": 8," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"actual\": \"(800)FLOWERS\"" "}}"); @@ -482,11 +518,13 @@ TEST(SchemaValidator, Integer) { VALIDATE(s, "4294967296", true); // 2^32 can only be fit in int64_t INVALIDATE(s, "3.1415926", "", "type", "", "{ \"type\": {" + " \"errorCode\": 20," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": [\"integer\"], \"actual\": \"number\"" "}}"); INVALIDATE(s, "\"42\"", "", "type", "", "{ \"type\": {" + " \"errorCode\": 20," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": [\"integer\"], \"actual\": \"string\"" "}}"); @@ -499,6 +537,7 @@ TEST(SchemaValidator, Integer_Range) { INVALIDATE(s, "-1", "", "minimum", "", "{ \"minimum\": {" + " \"errorCode\": 4," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 0, \"actual\": -1" "}}"); @@ -507,11 +546,13 @@ TEST(SchemaValidator, Integer_Range) { VALIDATE(s, "99", true); INVALIDATE(s, "100", "", "maximum", "", "{ \"maximum\": {" + " \"errorCode\": 3," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 100, \"exclusiveMaximum\": true, \"actual\": 100" "}}"); INVALIDATE(s, "101", "", "maximum", "", "{ \"maximum\": {" + " \"errorCode\": 3," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 100, \"exclusiveMaximum\": true, \"actual\": 101" "}}"); @@ -524,6 +565,7 @@ TEST(SchemaValidator, Integer_Range64Boundary) { INVALIDATE(s, "-9223372036854775808", "", "minimum", "", "{ \"minimum\": {" + " \"errorCode\": 4," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": -9223372036854775807, \"actual\": -9223372036854775808" "}}"); @@ -536,11 +578,13 @@ TEST(SchemaValidator, Integer_Range64Boundary) { VALIDATE(s, "9223372036854775806", true); INVALIDATE(s, "9223372036854775807", "", "maximum", "", "{ \"maximum\": {" + " \"errorCode\": 2," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 9223372036854775806, \"actual\": 9223372036854775807" "}}"); INVALIDATE(s, "18446744073709551615", "", "maximum", "", "{ \"maximum\": {" + " \"errorCode\": 2," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 9223372036854775806, \"actual\": 18446744073709551615" "}}"); // uint64_t max @@ -553,36 +597,43 @@ TEST(SchemaValidator, Integer_RangeU64Boundary) { INVALIDATE(s, "-9223372036854775808", "", "minimum", "", "{ \"minimum\": {" + " \"errorCode\": 4," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 9223372036854775808, \"actual\": -9223372036854775808" "}}"); INVALIDATE(s, "9223372036854775807", "", "minimum", "", "{ \"minimum\": {" + " \"errorCode\": 4," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 9223372036854775808, \"actual\": 9223372036854775807" "}}"); INVALIDATE(s, "-2147483648", "", "minimum", "", "{ \"minimum\": {" + " \"errorCode\": 4," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 9223372036854775808, \"actual\": -2147483648" "}}"); // int min INVALIDATE(s, "0", "", "minimum", "", "{ \"minimum\": {" + " \"errorCode\": 4," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 9223372036854775808, \"actual\": 0" "}}"); INVALIDATE(s, "2147483647", "", "minimum", "", "{ \"minimum\": {" + " \"errorCode\": 4," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 9223372036854775808, \"actual\": 2147483647" "}}"); // int max INVALIDATE(s, "2147483648", "", "minimum", "", "{ \"minimum\": {" + " \"errorCode\": 4," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 9223372036854775808, \"actual\": 2147483648" "}}"); // unsigned first INVALIDATE(s, "4294967295", "", "minimum", "", "{ \"minimum\": {" + " \"errorCode\": 4," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 9223372036854775808, \"actual\": 4294967295" "}}"); // unsigned max @@ -590,6 +641,7 @@ TEST(SchemaValidator, Integer_RangeU64Boundary) { VALIDATE(s, "18446744073709551614", true); INVALIDATE(s, "18446744073709551615", "", "maximum", "", "{ \"maximum\": {" + " \"errorCode\": 2," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 18446744073709551614, \"actual\": 18446744073709551615" "}}"); @@ -602,6 +654,7 @@ TEST(SchemaValidator, Integer_Range64BoundaryExclusive) { INVALIDATE(s, "-9223372036854775808", "", "minimum", "", "{ \"minimum\": {" + " \"errorCode\": 5," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": -9223372036854775808, \"exclusiveMinimum\": true, " " \"actual\": -9223372036854775808" @@ -610,6 +663,7 @@ TEST(SchemaValidator, Integer_Range64BoundaryExclusive) { VALIDATE(s, "18446744073709551614", true); INVALIDATE(s, "18446744073709551615", "", "maximum", "", "{ \"maximum\": {" + " \"errorCode\": 3," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 18446744073709551615, \"exclusiveMaximum\": true, " " \"actual\": 18446744073709551615" @@ -627,11 +681,13 @@ TEST(SchemaValidator, Integer_MultipleOf) { VALIDATE(s, "20", true); INVALIDATE(s, "23", "", "multipleOf", "", "{ \"multipleOf\": {" + " \"errorCode\": 1," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 10, \"actual\": 23" "}}"); INVALIDATE(s, "-23", "", "multipleOf", "", "{ \"multipleOf\": {" + " \"errorCode\": 1," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 10, \"actual\": -23" "}}"); @@ -646,6 +702,7 @@ TEST(SchemaValidator, Integer_MultipleOf64Boundary) { VALIDATE(s, "18446744073709551615", true); INVALIDATE(s, "18446744073709551614", "", "multipleOf", "", "{ \"multipleOf\": {" + " \"errorCode\": 1," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 18446744073709551615, \"actual\": 18446744073709551614" "}}"); @@ -658,6 +715,7 @@ TEST(SchemaValidator, Number_Range) { INVALIDATE(s, "-1", "", "minimum", "", "{ \"minimum\": {" + " \"errorCode\": 4," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 0, \"actual\": -1" "}}"); @@ -668,16 +726,19 @@ TEST(SchemaValidator, Number_Range) { VALIDATE(s, "99.9", true); INVALIDATE(s, "100", "", "maximum", "", "{ \"maximum\": {" + " \"errorCode\": 3," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 100, \"exclusiveMaximum\": true, \"actual\": 100" "}}"); INVALIDATE(s, "100.0", "", "maximum", "", "{ \"maximum\": {" + " \"errorCode\": 3," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 100, \"exclusiveMaximum\": true, \"actual\": 100.0" "}}"); INVALIDATE(s, "101.5", "", "maximum", "", "{ \"maximum\": {" + " \"errorCode\": 3," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 100, \"exclusiveMaximum\": true, \"actual\": 101.5" "}}"); @@ -690,11 +751,13 @@ TEST(SchemaValidator, Number_RangeInt) { INVALIDATE(s, "-101", "", "minimum", "", "{ \"minimum\": {" + " \"errorCode\": 4," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": -100, \"actual\": -101" "}}"); INVALIDATE(s, "-100.1", "", "minimum", "", "{ \"minimum\": {" + " \"errorCode\": 4," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": -100, \"actual\": -100.1" "}}"); @@ -702,46 +765,55 @@ TEST(SchemaValidator, Number_RangeInt) { VALIDATE(s, "-2", true); INVALIDATE(s, "-1", "", "maximum", "", "{ \"maximum\": {" + " \"errorCode\": 3," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": -1, \"exclusiveMaximum\": true, \"actual\": -1" "}}"); INVALIDATE(s, "-0.9", "", "maximum", "", "{ \"maximum\": {" + " \"errorCode\": 3," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": -1, \"exclusiveMaximum\": true, \"actual\": -0.9" "}}"); INVALIDATE(s, "0", "", "maximum", "", "{ \"maximum\": {" + " \"errorCode\": 3," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": -1, \"exclusiveMaximum\": true, \"actual\": 0" "}}"); INVALIDATE(s, "2147483647", "", "maximum", "", "{ \"maximum\": {" + " \"errorCode\": 3," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": -1, \"exclusiveMaximum\": true, \"actual\": 2147483647" "}}"); // int max INVALIDATE(s, "2147483648", "", "maximum", "", "{ \"maximum\": {" + " \"errorCode\": 3," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": -1, \"exclusiveMaximum\": true, \"actual\": 2147483648" "}}"); // unsigned first INVALIDATE(s, "4294967295", "", "maximum", "", "{ \"maximum\": {" + " \"errorCode\": 3," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": -1, \"exclusiveMaximum\": true, \"actual\": 4294967295" "}}"); // unsigned max INVALIDATE(s, "9223372036854775808", "", "maximum", "", "{ \"maximum\": {" + " \"errorCode\": 3," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": -1, \"exclusiveMaximum\": true, \"actual\": 9223372036854775808" "}}"); INVALIDATE(s, "18446744073709551614", "", "maximum", "", "{ \"maximum\": {" + " \"errorCode\": 3," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": -1, \"exclusiveMaximum\": true, \"actual\": 18446744073709551614" "}}"); INVALIDATE(s, "18446744073709551615", "", "maximum", "", "{ \"maximum\": {" + " \"errorCode\": 3," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": -1, \"exclusiveMaximum\": true, \"actual\": 18446744073709551615" "}}"); @@ -754,16 +826,19 @@ TEST(SchemaValidator, Number_RangeDouble) { INVALIDATE(s, "-9223372036854775808", "", "minimum", "", "{ \"minimum\": {" + " \"errorCode\": 4," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 0.1, \"actual\": -9223372036854775808" "}}"); INVALIDATE(s, "-2147483648", "", "minimum", "", "{ \"minimum\": {" + " \"errorCode\": 4," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 0.1, \"actual\": -2147483648" "}}"); // int min INVALIDATE(s, "-1", "", "minimum", "", "{ \"minimum\": {" + " \"errorCode\": 4," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 0.1, \"actual\": -1" "}}"); @@ -773,51 +848,61 @@ TEST(SchemaValidator, Number_RangeDouble) { VALIDATE(s, "100", true); INVALIDATE(s, "101", "", "maximum", "", "{ \"maximum\": {" + " \"errorCode\": 3," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 100.1, \"exclusiveMaximum\": true, \"actual\": 101" "}}"); INVALIDATE(s, "101.5", "", "maximum", "", "{ \"maximum\": {" + " \"errorCode\": 3," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 100.1, \"exclusiveMaximum\": true, \"actual\": 101.5" "}}"); INVALIDATE(s, "18446744073709551614", "", "maximum", "", "{ \"maximum\": {" + " \"errorCode\": 3," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 100.1, \"exclusiveMaximum\": true, \"actual\": 18446744073709551614" "}}"); INVALIDATE(s, "18446744073709551615", "", "maximum", "", "{ \"maximum\": {" + " \"errorCode\": 3," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 100.1, \"exclusiveMaximum\": true, \"actual\": 18446744073709551615" "}}"); INVALIDATE(s, "2147483647", "", "maximum", "", "{ \"maximum\": {" + " \"errorCode\": 3," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 100.1, \"exclusiveMaximum\": true, \"actual\": 2147483647" "}}"); // int max INVALIDATE(s, "2147483648", "", "maximum", "", "{ \"maximum\": {" + " \"errorCode\": 3," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 100.1, \"exclusiveMaximum\": true, \"actual\": 2147483648" "}}"); // unsigned first INVALIDATE(s, "4294967295", "", "maximum", "", "{ \"maximum\": {" + " \"errorCode\": 3," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 100.1, \"exclusiveMaximum\": true, \"actual\": 4294967295" "}}"); // unsigned max INVALIDATE(s, "9223372036854775808", "", "maximum", "", "{ \"maximum\": {" + " \"errorCode\": 3," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 100.1, \"exclusiveMaximum\": true, \"actual\": 9223372036854775808" "}}"); INVALIDATE(s, "18446744073709551614", "", "maximum", "", "{ \"maximum\": {" + " \"errorCode\": 3," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 100.1, \"exclusiveMaximum\": true, \"actual\": 18446744073709551614" "}}"); INVALIDATE(s, "18446744073709551615", "", "maximum", "", "{ \"maximum\": {" + " \"errorCode\": 3," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 100.1, \"exclusiveMaximum\": true, \"actual\": 18446744073709551615" "}}"); @@ -830,31 +915,37 @@ TEST(SchemaValidator, Number_RangeDoubleU64Boundary) { INVALIDATE(s, "-9223372036854775808", "", "minimum", "", "{ \"minimum\": {" + " \"errorCode\": 4," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 9223372036854775808.0, \"actual\": -9223372036854775808" "}}"); INVALIDATE(s, "-2147483648", "", "minimum", "", "{ \"minimum\": {" + " \"errorCode\": 4," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 9223372036854775808.0, \"actual\": -2147483648" "}}"); // int min INVALIDATE(s, "0", "", "minimum", "", "{ \"minimum\": {" + " \"errorCode\": 4," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 9223372036854775808.0, \"actual\": 0" "}}"); INVALIDATE(s, "2147483647", "", "minimum", "", "{ \"minimum\": {" + " \"errorCode\": 4," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 9223372036854775808.0, \"actual\": 2147483647" "}}"); // int max INVALIDATE(s, "2147483648", "", "minimum", "", "{ \"minimum\": {" + " \"errorCode\": 4," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 9223372036854775808.0, \"actual\": 2147483648" "}}"); // unsigned first INVALIDATE(s, "4294967295", "", "minimum", "", "{ \"minimum\": {" + " \"errorCode\": 4," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 9223372036854775808.0, \"actual\": 4294967295" "}}"); // unsigned max @@ -862,6 +953,7 @@ TEST(SchemaValidator, Number_RangeDoubleU64Boundary) { VALIDATE(s, "18446744073709540000", true); INVALIDATE(s, "18446744073709551615", "", "maximum", "", "{ \"maximum\": {" + " \"errorCode\": 2," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 18446744073709550000.0, \"actual\": 18446744073709551615" "}}"); @@ -878,28 +970,33 @@ TEST(SchemaValidator, Number_MultipleOf) { VALIDATE(s, "20", true); INVALIDATE(s, "23", "", "multipleOf", "", "{ \"multipleOf\": {" + " \"errorCode\": 1," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 10.0, \"actual\": 23" "}}"); INVALIDATE(s, "-2147483648", "", "multipleOf", "", "{ \"multipleOf\": {" + " \"errorCode\": 1," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 10.0, \"actual\": -2147483648" "}}"); // int min VALIDATE(s, "-2147483640", true); INVALIDATE(s, "2147483647", "", "multipleOf", "", "{ \"multipleOf\": {" + " \"errorCode\": 1," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 10.0, \"actual\": 2147483647" "}}"); // int max INVALIDATE(s, "2147483648", "", "multipleOf", "", "{ \"multipleOf\": {" + " \"errorCode\": 1," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 10.0, \"actual\": 2147483648" "}}"); // unsigned first VALIDATE(s, "2147483650", true); INVALIDATE(s, "4294967295", "", "multipleOf", "", "{ \"multipleOf\": {" + " \"errorCode\": 1," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 10.0, \"actual\": 4294967295" "}}"); // unsigned max @@ -915,6 +1012,7 @@ TEST(SchemaValidator, Number_MultipleOfOne) { VALIDATE(s, "42.0", true); INVALIDATE(s, "3.1415926", "", "multipleOf", "", "{ \"multipleOf\": {" + " \"errorCode\": 1," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 1, \"actual\": 3.1415926" "}}"); @@ -929,11 +1027,13 @@ TEST(SchemaValidator, Object) { VALIDATE(s, "{\"Sun\":1.9891e30,\"Jupiter\":1.8986e27,\"Saturn\":5.6846e26,\"Neptune\":10.243e25,\"Uranus\":8.6810e25,\"Earth\":5.9736e24,\"Venus\":4.8685e24,\"Mars\":6.4185e23,\"Mercury\":3.3022e23,\"Moon\":7.349e22,\"Pluto\":1.25e22}", true); INVALIDATE(s, "[\"An\", \"array\", \"not\", \"an\", \"object\"]", "", "type", "", "{ \"type\": {" + " \"errorCode\": 20," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": [\"object\"], \"actual\": \"array\"" "}}"); INVALIDATE(s, "\"Not an object\"", "", "type", "", "{ \"type\": {" + " \"errorCode\": 20," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": [\"object\"], \"actual\": \"string\"" "}}"); @@ -956,12 +1056,14 @@ TEST(SchemaValidator, Object_Properties) { VALIDATE(s, "{ \"number\": 1600, \"street_name\": \"Pennsylvania\", \"street_type\": \"Avenue\" }", true); INVALIDATE(s, "{ \"number\": \"1600\", \"street_name\": \"Pennsylvania\", \"street_type\": \"Avenue\" }", "/properties/number", "type", "/number", "{ \"type\": {" + " \"errorCode\": 20," " \"instanceRef\": \"#/number\", \"schemaRef\": \"#/properties/number\"," " \"expected\": [\"number\"], \"actual\": \"string\"" "}}"); INVALIDATE(s, "{ \"number\": \"One\", \"street_name\": \"Microsoft\", \"street_type\": \"Way\" }", "/properties/number", "type", "/number", "{ \"type\": {" + " \"errorCode\": 20," " \"instanceRef\": \"#/number\", \"schemaRef\": \"#/properties/number\"," " \"expected\": [\"number\"], \"actual\": \"string\"" "}}"); // fail fast @@ -990,6 +1092,7 @@ TEST(SchemaValidator, Object_AdditionalPropertiesBoolean) { VALIDATE(s, "{ \"number\": 1600, \"street_name\": \"Pennsylvania\", \"street_type\": \"Avenue\" }", true); INVALIDATE(s, "{ \"number\": 1600, \"street_name\": \"Pennsylvania\", \"street_type\": \"Avenue\", \"direction\": \"NW\" }", "", "additionalProperties", "/direction", "{ \"additionalProperties\": {" + " \"errorCode\": 16," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"disallowed\": \"direction\"" "}}"); @@ -1015,6 +1118,7 @@ TEST(SchemaValidator, Object_AdditionalPropertiesObject) { VALIDATE(s, "{ \"number\": 1600, \"street_name\": \"Pennsylvania\", \"street_type\": \"Avenue\", \"direction\": \"NW\" }", true); INVALIDATE(s, "{ \"number\": 1600, \"street_name\": \"Pennsylvania\", \"street_type\": \"Avenue\", \"office_number\": 201 }", "/additionalProperties", "type", "/office_number", "{ \"type\": {" + " \"errorCode\": 20," " \"instanceRef\": \"#/office_number\", \"schemaRef\": \"#/additionalProperties\"," " \"expected\": [\"string\"], \"actual\": \"integer\"" "}}"); @@ -1039,11 +1143,13 @@ TEST(SchemaValidator, Object_Required) { VALIDATE(s, "{ \"name\": \"William Shakespeare\", \"email\" : \"bill@stratford-upon-avon.co.uk\", \"address\" : \"Henley Street, Stratford-upon-Avon, Warwickshire, England\", \"authorship\" : \"in question\"}", true); INVALIDATE(s, "{ \"name\": \"William Shakespeare\", \"address\" : \"Henley Street, Stratford-upon-Avon, Warwickshire, England\" }", "", "required", "", "{ \"required\": {" + " \"errorCode\": 15," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"missing\": [\"email\"]" "}}"); INVALIDATE(s, "{}", "", "required", "", "{ \"required\": {" + " \"errorCode\": 15," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"missing\": [\"name\", \"email\"]" "}}"); @@ -1067,11 +1173,13 @@ TEST(SchemaValidator, Object_Required_PassWithDefault) { VALIDATE(s, "{ \"email\" : \"bill@stratford-upon-avon.co.uk\", \"address\" : \"Henley Street, Stratford-upon-Avon, Warwickshire, England\", \"authorship\" : \"in question\"}", true); INVALIDATE(s, "{ \"name\": \"William Shakespeare\", \"address\" : \"Henley Street, Stratford-upon-Avon, Warwickshire, England\" }", "", "required", "", "{ \"required\": {" + " \"errorCode\": 15," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"missing\": [\"email\"]" "}}"); INVALIDATE(s, "{}", "", "required", "", "{ \"required\": {" + " \"errorCode\": 15," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"missing\": [\"email\"]" "}}"); @@ -1084,11 +1192,13 @@ TEST(SchemaValidator, Object_PropertiesRange) { INVALIDATE(s, "{}", "", "minProperties", "", "{ \"minProperties\": {" + " \"errorCode\": 14," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 2, \"actual\": 0" "}}"); INVALIDATE(s, "{\"a\":0}", "", "minProperties", "", "{ \"minProperties\": {" + " \"errorCode\": 14," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 2, \"actual\": 1" "}}"); @@ -1096,6 +1206,7 @@ TEST(SchemaValidator, Object_PropertiesRange) { VALIDATE(s, "{\"a\":0,\"b\":1,\"c\":2}", true); INVALIDATE(s, "{\"a\":0,\"b\":1,\"c\":2,\"d\":3}", "", "maxProperties", "", "{ \"maxProperties\": {" + " \"errorCode\": 13," " \"instanceRef\": \"#\", \"schemaRef\": \"#\", " " \"expected\": 3, \"actual\": 4" "}}"); @@ -1123,8 +1234,15 @@ TEST(SchemaValidator, Object_PropertyDependencies) { "\"billing_address\": \"555 Debtor's Lane\" }", true); INVALIDATE(s, "{ \"name\": \"John Doe\", \"credit_card\": 5555555555555555 }", "", "dependencies", "", "{ \"dependencies\": {" + " \"errorCode\": 18," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," - " \"errors\": {\"credit_card\": [\"cvv_code\", \"billing_address\"]}" + " \"errors\": {" + " \"credit_card\": {" + " \"required\": {" + " \"errorCode\": 15," + " \"instanceRef\": \"#\", \"schemaRef\": \"#/dependencies/credit_card\"," + " \"missing\": [\"cvv_code\", \"billing_address\"]" + " } } }" "}}"); VALIDATE(s, "{ \"name\": \"John Doe\"}", true); VALIDATE(s, "{ \"name\": \"John Doe\", \"cvv_code\": 777, \"billing_address\": \"555 Debtor's Lane\" }", true); @@ -1154,10 +1272,12 @@ TEST(SchemaValidator, Object_SchemaDependencies) { VALIDATE(s, "{\"name\": \"John Doe\", \"credit_card\" : 5555555555555555,\"billing_address\" : \"555 Debtor's Lane\"}", true); INVALIDATE(s, "{\"name\": \"John Doe\", \"credit_card\" : 5555555555555555 }", "", "dependencies", "", "{ \"dependencies\": {" + " \"errorCode\": 18," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"errors\": {" " \"credit_card\": {" " \"required\": {" + " \"errorCode\": 15," " \"instanceRef\": \"#\", \"schemaRef\": \"#/dependencies/credit_card\"," " \"missing\": [\"billing_address\"]" " } } }" @@ -1182,18 +1302,20 @@ TEST(SchemaValidator, Object_PatternProperties) { VALIDATE(s, "{ \"I_0\": 42 }", true); INVALIDATE(s, "{ \"S_0\": 42 }", "", "patternProperties", "/S_0", "{ \"type\": {" + " \"errorCode\": 20," " \"instanceRef\": \"#/S_0\", \"schemaRef\": \"#/patternProperties/%5ES_\"," " \"expected\": [\"string\"], \"actual\": \"integer\"" "}}"); INVALIDATE(s, "{ \"I_42\": \"This is a string\" }", "", "patternProperties", "/I_42", "{ \"type\": {" + " \"errorCode\": 20," " \"instanceRef\": \"#/I_42\", \"schemaRef\": \"#/patternProperties/%5EI_\"," " \"expected\": [\"integer\"], \"actual\": \"string\"" "}}"); VALIDATE(s, "{ \"keyword\": \"value\" }", true); } -TEST(SchemaValidator, Object_PattternProperties_ErrorConflict) { +TEST(SchemaValidator, Object_PatternProperties_ErrorConflict) { Document sd; sd.Parse( "{" @@ -1209,9 +1331,11 @@ TEST(SchemaValidator, Object_PattternProperties_ErrorConflict) { INVALIDATE(s, "{ \"I_30\": 7 }", "", "patternProperties", "/I_30", "{ \"multipleOf\": [" " {" + " \"errorCode\": 1," " \"instanceRef\": \"#/I_30\", \"schemaRef\": \"#/patternProperties/%5EI_\"," " \"expected\": 5, \"actual\": 7" " }, {" + " \"errorCode\": 1," " \"instanceRef\": \"#/I_30\", \"schemaRef\": \"#/patternProperties/30%24\"," " \"expected\": 6, \"actual\": 7" " }" @@ -1236,22 +1360,25 @@ TEST(SchemaValidator, Object_Properties_PatternProperties) { VALIDATE(s, "{ \"I_42\": 78 }", true); INVALIDATE(s, "{ \"I_42\": 42 }", "", "patternProperties", "/I_42", "{ \"minimum\": {" + " \"errorCode\": 4," " \"instanceRef\": \"#/I_42\", \"schemaRef\": \"#/properties/I_42\"," " \"expected\": 73, \"actual\": 42" "}}"); INVALIDATE(s, "{ \"I_42\": 7 }", "", "patternProperties", "/I_42", "{ \"minimum\": {" + " \"errorCode\": 4," " \"instanceRef\": \"#/I_42\", \"schemaRef\": \"#/properties/I_42\"," " \"expected\": 73, \"actual\": 7" " }," " \"multipleOf\": {" + " \"errorCode\": 1," " \"instanceRef\": \"#/I_42\", \"schemaRef\": \"#/patternProperties/%5EI_\"," " \"expected\": 6, \"actual\": 7" " }" "}"); } -TEST(SchemaValidator, Object_PatternProperties_AdditionalProperties) { +TEST(SchemaValidator, Object_PatternProperties_AdditionalPropertiesObject) { Document sd; sd.Parse( "{" @@ -1271,10 +1398,35 @@ TEST(SchemaValidator, Object_PatternProperties_AdditionalProperties) { VALIDATE(s, "{ \"keyword\": \"value\" }", true); INVALIDATE(s, "{ \"keyword\": 42 }", "/additionalProperties", "type", "/keyword", "{ \"type\": {" + " \"errorCode\": 20," " \"instanceRef\": \"#/keyword\", \"schemaRef\": \"#/additionalProperties\"," " \"expected\": [\"string\"], \"actual\": \"integer\"" "}}"); } + +// Replaces test Issue285 and tests failure as well as success +TEST(SchemaValidator, Object_PatternProperties_AdditionalPropertiesBoolean) { + Document sd; + sd.Parse( + "{" + " \"type\": \"object\"," + " \"patternProperties\": {" + " \"^S_\": { \"type\": \"string\" }," + " \"^I_\": { \"type\": \"integer\" }" + " }," + " \"additionalProperties\": false" + "}"); + SchemaDocument s(sd); + + VALIDATE(s, "{ \"S_25\": \"This is a string\" }", true); + VALIDATE(s, "{ \"I_0\": 42 }", true); + INVALIDATE(s, "{ \"keyword\": \"value\" }", "", "additionalProperties", "/keyword", + "{ \"additionalProperties\": {" + " \"errorCode\": 16," + " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," + " \"disallowed\": \"keyword\"" + "}}"); +} #endif TEST(SchemaValidator, Array) { @@ -1286,6 +1438,7 @@ TEST(SchemaValidator, Array) { VALIDATE(s, "[3, \"different\", { \"types\" : \"of values\" }]", true); INVALIDATE(s, "{\"Not\": \"an array\"}", "", "type", "", "{ \"type\": {" + " \"errorCode\": 20," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": [\"array\"], \"actual\": \"object\"" "}}"); @@ -1305,6 +1458,7 @@ TEST(SchemaValidator, Array_ItemsList) { VALIDATE(s, "[1, 2, 3, 4, 5]", true); INVALIDATE(s, "[1, 2, \"3\", 4, 5]", "/items", "type", "/2", "{ \"type\": {" + " \"errorCode\": 20," " \"instanceRef\": \"#/2\", \"schemaRef\": \"#/items\"," " \"expected\": [\"number\"], \"actual\": \"string\"" "}}"); @@ -1337,14 +1491,16 @@ TEST(SchemaValidator, Array_ItemsTuple) { VALIDATE(s, "[1600, \"Pennsylvania\", \"Avenue\", \"NW\"]", true); INVALIDATE(s, "[24, \"Sussex\", \"Drive\"]", "/items/2", "enum", "/2", - "{ \"enum\": { \"instanceRef\": \"#/2\", \"schemaRef\": \"#/items/2\" }}"); + "{ \"enum\": { \"errorCode\": 19, \"instanceRef\": \"#/2\", \"schemaRef\": \"#/items/2\" }}"); INVALIDATE(s, "[\"Palais de l'Elysee\"]", "/items/0", "type", "/0", "{ \"type\": {" + " \"errorCode\": 20," " \"instanceRef\": \"#/0\", \"schemaRef\": \"#/items/0\"," " \"expected\": [\"number\"], \"actual\": \"string\"" "}}"); INVALIDATE(s, "[\"Twenty-four\", \"Sussex\", \"Drive\"]", "/items/0", "type", "/0", "{ \"type\": {" + " \"errorCode\": 20," " \"instanceRef\": \"#/0\", \"schemaRef\": \"#/items/0\"," " \"expected\": [\"number\"], \"actual\": \"string\"" "}}"); // fail fast @@ -1352,7 +1508,7 @@ TEST(SchemaValidator, Array_ItemsTuple) { VALIDATE(s, "[1600, \"Pennsylvania\", \"Avenue\", \"NW\", \"Washington\"]", true); } -TEST(SchemaValidator, Array_AdditionalItmes) { +TEST(SchemaValidator, Array_AdditionalItems) { Document sd; sd.Parse( "{" @@ -1379,8 +1535,9 @@ TEST(SchemaValidator, Array_AdditionalItmes) { VALIDATE(s, "[1600, \"Pennsylvania\", \"Avenue\", \"NW\"]", true); VALIDATE(s, "[1600, \"Pennsylvania\", \"Avenue\"]", true); - INVALIDATE(s, "[1600, \"Pennsylvania\", \"Avenue\", \"NW\", \"Washington\"]", "", "items", "/4", + INVALIDATE(s, "[1600, \"Pennsylvania\", \"Avenue\", \"NW\", \"Washington\"]", "", "additionalItems", "/4", "{ \"additionalItems\": {" + " \"errorCode\": 12," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"disallowed\": 4" "}}"); @@ -1393,11 +1550,13 @@ TEST(SchemaValidator, Array_ItemsRange) { INVALIDATE(s, "[]", "", "minItems", "", "{ \"minItems\": {" + " \"errorCode\": 10," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 2, \"actual\": 0" "}}"); INVALIDATE(s, "[1]", "", "minItems", "", "{ \"minItems\": {" + " \"errorCode\": 10," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 2, \"actual\": 1" "}}"); @@ -1405,6 +1564,7 @@ TEST(SchemaValidator, Array_ItemsRange) { VALIDATE(s, "[1, 2, 3]", true); INVALIDATE(s, "[1, 2, 3, 4]", "", "maxItems", "", "{ \"maxItems\": {" + " \"errorCode\": 9," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 3, \"actual\": 4" "}}"); @@ -1418,11 +1578,13 @@ TEST(SchemaValidator, Array_UniqueItems) { VALIDATE(s, "[1, 2, 3, 4, 5]", true); INVALIDATE(s, "[1, 2, 3, 3, 4]", "", "uniqueItems", "/3", "{ \"uniqueItems\": {" + " \"errorCode\": 11," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"duplicates\": [2, 3]" "}}"); INVALIDATE(s, "[1, 2, 3, 3, 3]", "", "uniqueItems", "/3", "{ \"uniqueItems\": {" + " \"errorCode\": 11," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"duplicates\": [2, 3]" "}}"); // fail fast @@ -1438,11 +1600,13 @@ TEST(SchemaValidator, Boolean) { VALIDATE(s, "false", true); INVALIDATE(s, "\"true\"", "", "type", "", "{ \"type\": {" + " \"errorCode\": 20," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": [\"boolean\"], \"actual\": \"string\"" "}}"); INVALIDATE(s, "0", "", "type", "", "{ \"type\": {" + " \"errorCode\": 20," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": [\"boolean\"], \"actual\": \"integer\"" "}}"); @@ -1456,16 +1620,19 @@ TEST(SchemaValidator, Null) { VALIDATE(s, "null", true); INVALIDATE(s, "false", "", "type", "", "{ \"type\": {" + " \"errorCode\": 20," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": [\"null\"], \"actual\": \"boolean\"" "}}"); INVALIDATE(s, "0", "", "type", "", "{ \"type\": {" + " \"errorCode\": 20," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": [\"null\"], \"actual\": \"integer\"" "}}"); INVALIDATE(s, "\"\"", "", "type", "", "{ \"type\": {" + " \"errorCode\": 20," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": [\"null\"], \"actual\": \"string\"" "}}"); @@ -1481,11 +1648,13 @@ TEST(SchemaValidator, ObjectInArray) { VALIDATE(s, "[\"a\"]", true); INVALIDATE(s, "[1]", "/items", "type", "/0", "{ \"type\": {" + " \"errorCode\": 20," " \"instanceRef\": \"#/0\", \"schemaRef\": \"#/items\"," " \"expected\": [\"string\"], \"actual\": \"integer\"" "}}"); INVALIDATE(s, "[{}]", "/items", "type", "/0", "{ \"type\": {" + " \"errorCode\": 20," " \"instanceRef\": \"#/0\", \"schemaRef\": \"#/items\"," " \"expected\": [\"string\"], \"actual\": \"object\"" "}}"); @@ -1508,6 +1677,7 @@ TEST(SchemaValidator, MultiTypeInObject) { VALIDATE(s, "{ \"tel\": \"123-456\" }", true); INVALIDATE(s, "{ \"tel\": true }", "/properties/tel", "type", "/tel", "{ \"type\": {" + " \"errorCode\": 20," " \"instanceRef\": \"#/tel\", \"schemaRef\": \"#/properties/tel\"," " \"expected\": [\"string\", \"integer\"], \"actual\": \"boolean\"" "}}"); @@ -1530,6 +1700,7 @@ TEST(SchemaValidator, MultiTypeWithObject) { VALIDATE(s, "{ \"tel\": 999 }", true); INVALIDATE(s, "{ \"tel\": \"fail\" }", "/properties/tel", "type", "/tel", "{ \"type\": {" + " \"errorCode\": 20," " \"instanceRef\": \"#/tel\", \"schemaRef\": \"#/properties/tel\"," " \"expected\": [\"integer\"], \"actual\": \"string\"" "}}"); @@ -1550,48 +1721,71 @@ TEST(SchemaValidator, AllOf_Nested) { VALIDATE(s, "\"ok\"", true); VALIDATE(s, "\"OK\"", true); INVALIDATE(s, "\"okay\"", "", "allOf", "", - "{ \"enum\": {" - " \"instanceRef\": \"#\", \"schemaRef\": \"#/allOf/2/allOf/1\"" + "{ \"allOf\": {" + " \"errors\": [" + " {},{}," + " { \"allOf\": {" + " \"errors\": [" + " {}," + " { \"enum\": {\"errorCode\": 19, \"instanceRef\": \"#\", \"schemaRef\": \"#/allOf/2/allOf/1\" }}" + " ]," + " \"errorCode\": 23, \"instanceRef\": \"#\", \"schemaRef\": \"#/allOf/2\"" + " }}]," + " \"errorCode\": 23, \"instanceRef\": \"#\", \"schemaRef\": \"#\"" "}}"); INVALIDATE(s, "\"o\"", "", "allOf", "", - "{ \"minLength\": {" - " \"instanceRef\": \"#\", \"schemaRef\": \"#/allOf/0\"," - " \"expected\": 2, \"actual\": \"o\"" + "{ \"allOf\": {" + " \"errors\": [" + " { \"minLength\": {\"actual\": \"o\", \"expected\": 2, \"errorCode\": 7, \"instanceRef\": \"#\", \"schemaRef\": \"#/allOf/0\" }}," + " {},{}" + " ]," + " \"errorCode\": 23, \"instanceRef\": \"#\", \"schemaRef\": \"#\"" "}}"); INVALIDATE(s, "\"n\"", "", "allOf", "", - "{ \"minLength\": {" - " \"instanceRef\": \"#\", \"schemaRef\": \"#/allOf/0\"," - " \"expected\": 2, \"actual\": \"n\"" - " }," - " \"enum\": [" - " {\"instanceRef\": \"#\", \"schemaRef\": \"#/allOf/2/allOf/0\"}," - " {\"instanceRef\": \"#\", \"schemaRef\": \"#/allOf/2/allOf/1\"}" - " ]" - "}") + "{ \"allOf\": {" + " \"errors\": [" + " { \"minLength\": {\"actual\": \"n\", \"expected\": 2, \"errorCode\": 7, \"instanceRef\": \"#\", \"schemaRef\": \"#/allOf/0\" }}," + " {}," + " { \"allOf\": {" + " \"errors\": [" + " { \"enum\": {\"errorCode\": 19 ,\"instanceRef\": \"#\", \"schemaRef\": \"#/allOf/2/allOf/0\"}}," + " { \"enum\": {\"errorCode\": 19, \"instanceRef\": \"#\", \"schemaRef\": \"#/allOf/2/allOf/1\"}}" + " ]," + " \"errorCode\": 23, \"instanceRef\": \"#\", \"schemaRef\": \"#/allOf/2\"" + " }}" + " ]," + " \"errorCode\":23,\"instanceRef\":\"#\",\"schemaRef\":\"#\"" + "}}"); INVALIDATE(s, "\"too long\"", "", "allOf", "", - "{ \"maxLength\": {" - " \"instanceRef\": \"#\", \"schemaRef\": \"#/allOf/1\"," - " \"expected\": 5, \"actual\": \"too long\"" - " }," - " \"enum\": [" - " {\"instanceRef\": \"#\", \"schemaRef\": \"#/allOf/2/allOf/0\"}," - " {\"instanceRef\": \"#\", \"schemaRef\": \"#/allOf/2/allOf/1\"}" - " ]" - "}"); + "{ \"allOf\": {" + " \"errors\": [" + " {}," + " { \"maxLength\": {\"actual\": \"too long\", \"expected\": 5, \"errorCode\": 6, \"instanceRef\": \"#\", \"schemaRef\": \"#/allOf/1\" }}," + " { \"allOf\": {" + " \"errors\": [" + " { \"enum\": {\"errorCode\": 19 ,\"instanceRef\": \"#\", \"schemaRef\": \"#/allOf/2/allOf/0\"}}," + " { \"enum\": {\"errorCode\": 19, \"instanceRef\": \"#\", \"schemaRef\": \"#/allOf/2/allOf/1\"}}" + " ]," + " \"errorCode\": 23, \"instanceRef\": \"#\", \"schemaRef\": \"#/allOf/2\"" + " }}" + " ]," + " \"errorCode\":23,\"instanceRef\":\"#\",\"schemaRef\":\"#\"" + "}}"); INVALIDATE(s, "123", "", "allOf", "", - "{ \"type\": [" - " { \"instanceRef\": \"#\", \"schemaRef\": \"#/allOf/0\"," - " \"expected\": [\"string\"], \"actual\": \"integer\"" - " }," - " { \"instanceRef\": \"#\", \"schemaRef\": \"#/allOf/1\"," - " \"expected\": [\"string\"], \"actual\": \"integer\"" - " }" - " ]," - " \"enum\": [" - " {\"instanceRef\": \"#\", \"schemaRef\": \"#/allOf/2/allOf/0\"}," - " {\"instanceRef\": \"#\", \"schemaRef\": \"#/allOf/2/allOf/1\"}" - " ]" - "}"); + "{ \"allOf\": {" + " \"errors\": [" + " {\"type\": {\"expected\": [\"string\"], \"actual\": \"integer\", \"errorCode\": 20, \"instanceRef\": \"#\", \"schemaRef\": \"#/allOf/0\"}}," + " {\"type\": {\"expected\": [\"string\"], \"actual\": \"integer\", \"errorCode\": 20, \"instanceRef\": \"#\", \"schemaRef\": \"#/allOf/1\"}}," + " { \"allOf\": {" + " \"errors\": [" + " { \"enum\": {\"errorCode\": 19 ,\"instanceRef\": \"#\", \"schemaRef\": \"#/allOf/2/allOf/0\"}}," + " { \"enum\": {\"errorCode\": 19, \"instanceRef\": \"#\", \"schemaRef\": \"#/allOf/2/allOf/1\"}}" + " ]," + " \"errorCode\": 23, \"instanceRef\": \"#\", \"schemaRef\": \"#/allOf/2\"" + " }}" + " ]," + " \"errorCode\":23,\"instanceRef\":\"#\",\"schemaRef\":\"#\"" + "}}"); } TEST(SchemaValidator, EscapedPointer) { @@ -1606,6 +1800,7 @@ TEST(SchemaValidator, EscapedPointer) { SchemaDocument s(sd); INVALIDATE(s, "{\"~/\":true}", "/properties/~0~1", "type", "/~0~1", "{ \"type\": {" + " \"errorCode\": 20," " \"instanceRef\": \"#/~0~1\", \"schemaRef\": \"#/properties/~0~1\"," " \"expected\": [\"number\"], \"actual\": \"boolean\"" "}}"); @@ -1650,11 +1845,14 @@ TEST(SchemaValidator, ValidateMetaSchema) { ASSERT_FALSE(d.HasParseError()); SchemaDocument sd(d); SchemaValidator validator(sd); - if (!d.Accept(validator)) { + d.Accept(validator); + if (!validator.IsValid()) { StringBuffer sb; validator.GetInvalidSchemaPointer().StringifyUriFragment(sb); printf("Invalid schema: %s\n", sb.GetString()); printf("Invalid keyword: %s\n", validator.GetInvalidSchemaKeyword()); + printf("Invalid code: %d\n", validator.GetInvalidSchemaCode()); + printf("Invalid message: %s\n", GetValidateError_En(validator.GetInvalidSchemaCode())); sb.Clear(); validator.GetInvalidDocumentPointer().StringifyUriFragment(sb); printf("Invalid document: %s\n", sb.GetString()); @@ -1681,7 +1879,8 @@ TEST(SchemaValidator, ValidateMetaSchema_UTF16) { ASSERT_FALSE(d.HasParseError()); SD sd(d); SV validator(sd); - if (!d.Accept(validator)) { + d.Accept(validator); + if (!validator.IsValid()) { GenericStringBuffer > sb; validator.GetInvalidSchemaPointer().StringifyUriFragment(sb); wprintf(L"Invalid schema: %ls\n", sb.GetString()); @@ -1709,13 +1908,15 @@ public: "jsonschema/remotes/integer.json", "jsonschema/remotes/subSchemas.json", "jsonschema/remotes/folder/folderInteger.json", - "draft-04/schema" + "draft-04/schema", + "unittestschema/address.json" }; const char* uris[kCount] = { "http://localhost:1234/integer.json", "http://localhost:1234/subSchemas.json", "http://localhost:1234/folder/folderInteger.json", - "http://json-schema.org/draft-04/schema" + "http://json-schema.org/draft-04/schema", + "http://localhost:1234/address.json" }; for (size_t i = 0; i < kCount; i++) { @@ -1757,7 +1958,7 @@ private: RemoteSchemaDocumentProvider(const RemoteSchemaDocumentProvider&); RemoteSchemaDocumentProvider& operator=(const RemoteSchemaDocumentProvider&); - static const size_t kCount = 4; + static const size_t kCount = 5; SchemaDocumentType* sd_[kCount]; typename DocumentType::AllocatorType documentAllocator_; typename SchemaDocumentType::AllocatorType schemaAllocator_; @@ -1826,6 +2027,7 @@ TEST(SchemaValidator, TestSuite) { ADD_FAILURE(); } else { + //printf("json test suite file %s parsed ok\n", filename); GenericDocument, MemoryPoolAllocator<>, MemoryPoolAllocator<> > d(&documentAllocator, 1024, &documentStackAllocator); d.Parse(json); if (d.HasParseError()) { @@ -1846,11 +2048,14 @@ TEST(SchemaValidator, TestSuite) { bool expected = (*testItr)["valid"].GetBool(); testCount++; validator.Reset(); - bool actual = data.Accept(validator); + data.Accept(validator); + bool actual = validator.IsValid(); if (expected != actual) printf("Fail: %30s \"%s\" \"%s\"\n", filename, description1, description2); - else + else { + //printf("Passed: %30s \"%s\" \"%s\"\n", filename, description1, description2); passCount++; + } } } //printf("%zu %zu %zu\n", documentAllocator.Size(), schemaAllocator.Size(), validatorAllocator.Size()); @@ -1865,8 +2070,8 @@ TEST(SchemaValidator, TestSuite) { jsonAllocator.Clear(); } printf("%d / %d passed (%2d%%)\n", passCount, testCount, passCount * 100 / testCount); - // if (passCount != testCount) - // ADD_FAILURE(); +// if (passCount != testCount) +// ADD_FAILURE(); } TEST(SchemaValidatingReader, Simple) { @@ -1897,12 +2102,14 @@ TEST(SchemaValidatingReader, Invalid) { EXPECT_FALSE(reader.IsValid()); EXPECT_EQ(kParseErrorTermination, reader.GetParseResult().Code()); EXPECT_STREQ("maxLength", reader.GetInvalidSchemaKeyword()); + EXPECT_TRUE(reader.GetInvalidSchemaCode() == kValidateErrorMaxLength); EXPECT_TRUE(reader.GetInvalidSchemaPointer() == SchemaDocument::PointerType("")); EXPECT_TRUE(reader.GetInvalidDocumentPointer() == SchemaDocument::PointerType("")); EXPECT_TRUE(d.IsNull()); Document e; e.Parse( "{ \"maxLength\": {" +" \"errorCode\": 6," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 3, \"actual\": \"ABCD\"" "}}"); @@ -1933,9 +2140,11 @@ TEST(SchemaValidatingWriter, Simple) { EXPECT_FALSE(validator.IsValid()); EXPECT_TRUE(validator.GetInvalidSchemaPointer() == SchemaDocument::PointerType("")); EXPECT_TRUE(validator.GetInvalidDocumentPointer() == SchemaDocument::PointerType("")); + EXPECT_TRUE(validator.GetInvalidSchemaCode() == kValidateErrorMaxLength); Document e; e.Parse( "{ \"maxLength\": {" +" \"errorCode\": 6," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": 3, \"actual\": \"ABCD\"" "}}"); @@ -1963,6 +2172,7 @@ TEST(Schema, Issue552) { VALIDATE(s, "\"Life, the universe, and everything\"", true); INVALIDATE(s, "[\"Life\", \"the universe\", \"and everything\"]", "", "type", "", "{ \"type\": {" + " \"errorCode\": 20," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"expected\": [\"string\", \"number\"], \"actual\": \"array\"" "}}"); @@ -1978,6 +2188,7 @@ TEST(SchemaValidator, Issue608) { VALIDATE(s, "{\"a\" : null, \"b\": null}", true); INVALIDATE(s, "{\"a\" : null, \"a\" : null}", "", "required", "", "{ \"required\": {" + " \"errorCode\": 15," " \"instanceRef\": \"#\", \"schemaRef\": \"#\"," " \"missing\": [\"b\"]" "}}"); @@ -1991,13 +2202,6 @@ TEST(SchemaValidator, Issue728_AllOfRef) { VALIDATE(s, "{\"key1\": \"abc\", \"key2\": \"def\"}", true); } -TEST(SchemaValidator, Issue825) { - Document sd; - sd.Parse("{\"type\": \"object\", \"additionalProperties\": false, \"patternProperties\": {\"^i\": { \"type\": \"string\" } } }"); - SchemaDocument s(sd); - VALIDATE(s, "{ \"item\": \"hello\" }", true); -} - TEST(SchemaValidator, Issue1017_allOfHandler) { Document sd; sd.Parse("{\"allOf\": [{\"type\": \"object\",\"properties\": {\"cyanArray2\": {\"type\": \"array\",\"items\": { \"type\": \"string\" }}}},{\"type\": \"object\",\"properties\": {\"blackArray\": {\"type\": \"array\",\"items\": { \"type\": \"string\" }}},\"required\": [ \"blackArray\" ]}]}"); @@ -2027,11 +2231,12 @@ TEST(SchemaValidator, Ref_remote) { typedef GenericPointer > PointerType; INVALIDATE_(s, "null", "/integer", "type", "", "{ \"type\": {" + " \"errorCode\": 20," " \"instanceRef\": \"#\"," " \"schemaRef\": \"http://localhost:1234/subSchemas.json#/integer\"," " \"expected\": [\"integer\"], \"actual\": \"null\"" "}}", - SchemaValidatorType, PointerType); + kValidateDefaultFlags, SchemaValidatorType, PointerType); } TEST(SchemaValidator, Ref_remote_issue1210) { @@ -2072,6 +2277,273 @@ TEST(SchemaValidator, Ref_remote_issue1210) { VALIDATE(sx, "{\"country\":\"US\"}", true); } +// Test that when kValidateContinueOnErrorFlag is set, all errors are reported. +TEST(SchemaValidator, ContinueOnErrors) { + CrtAllocator allocator; + char* schema = ReadFile("unittestschema/address.json", allocator); + Document sd; + sd.Parse(schema); + ASSERT_FALSE(sd.HasParseError()); + 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); + 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", "#", + "{ \"multipleOf\": {" + " \"errorCode\": 1, \"instanceRef\": \"#/version\", \"schemaRef\": \"#/definitions/decimal_type\", \"expected\": 0.1, \"actual\": 1.01" + " }," + " \"minimum\": {" + " \"errorCode\": 5, \"instanceRef\": \"#/address/number\", \"schemaRef\": \"#/definitions/positiveInt_type\", \"expected\": 0, \"actual\": 0, \"exclusiveMinimum\": true" + " }," + " \"type\": [" + " {\"expected\": [\"null\", \"string\"], \"actual\": \"boolean\", \"errorCode\": 20, \"instanceRef\": \"#/address/street2\", \"schemaRef\": \"#/definitions/address_type/properties/street2\"}," + " {\"expected\": [\"string\"], \"actual\": \"integer\", \"errorCode\": 20, \"instanceRef\": \"#/extra/S_xxx\", \"schemaRef\": \"#/properties/extra/patternProperties/%5ES_\"}" + " ]," + " \"maxLength\": {" + " \"actual\": \"RomseyTownFC\", \"expected\": 10, \"errorCode\": 6, \"instanceRef\": \"#/address/city\", \"schemaRef\": \"#/definitions/address_type/properties/city\"" + " }," + " \"anyOf\": {" + " \"errors\":[" + " {\"pattern\": {\"actual\": \"999ABC\", \"errorCode\": 8, \"instanceRef\": \"#/address/postcode\", \"schemaRef\": \"#/definitions/address_type/properties/postcode/anyOf/0\"}}," + " {\"pattern\": {\"actual\": \"999ABC\", \"errorCode\": 8, \"instanceRef\": \"#/address/postcode\", \"schemaRef\": \"#/definitions/address_type/properties/postcode/anyOf/1\"}}" + " ]," + " \"errorCode\": 24, \"instanceRef\": \"#/address/postcode\", \"schemaRef\": \"#/definitions/address_type/properties/postcode\"" + " }," + " \"allOf\": {" + " \"errors\":[" + " {\"enum\":{\"errorCode\":19,\"instanceRef\":\"#/address/country\",\"schemaRef\":\"#/definitions/country_type\"}}" + " ]," + " \"errorCode\":23,\"instanceRef\":\"#/address/country\",\"schemaRef\":\"#/definitions/address_type/properties/country\"" + " }," + " \"minItems\": {" + " \"actual\": 0, \"expected\": 1, \"errorCode\": 10, \"instanceRef\": \"#/phones\", \"schemaRef\": \"#/properties/phones\"" + " }," + " \"additionalProperties\": {" + " \"disallowed\": \"planet\", \"errorCode\": 16, \"instanceRef\": \"#\", \"schemaRef\": \"#\"" + " }," + " \"required\": {" + " \"missing\": [\"street1\"], \"errorCode\": 15, \"instanceRef\": \"#/address\", \"schemaRef\": \"#/definitions/address_type\"" + " }" + "}", + kValidateDefaultFlags | kValidateContinueOnErrorFlag, SchemaValidator, Pointer); + INVALIDATE_(s, "{\"address\": {\"number\": 200, \"street1\": {}, \"street3\": null, \"city\": \"Rom\", \"area\": \"Dorset\", \"postcode\": \"SO51 0GP\"}, \"phones\": [\"0111-222333\", \"0777-666888\", \"0777-666888\"], \"names\": [\"Fred\", \"S\", \"M\", \"Bloggs\"]}", "#", "errors", "#", + "{ \"maximum\": {" + " \"errorCode\": 3, \"instanceRef\": \"#/address/number\", \"schemaRef\": \"#/definitions/positiveInt_type\", \"expected\": 100, \"actual\": 200, \"exclusiveMaximum\": true" + " }," + " \"type\": {" + " \"expected\": [\"string\"], \"actual\": \"object\", \"errorCode\": 20, \"instanceRef\": \"#/address/street1\", \"schemaRef\": \"#/definitions/address_type/properties/street1\"" + " }," + " \"not\": {" + " \"errorCode\": 25, \"instanceRef\": \"#/address/street3\", \"schemaRef\": \"#/definitions/address_type/properties/street3\"" + " }," + " \"minLength\": {" + " \"actual\": \"Rom\", \"expected\": 4, \"errorCode\": 7, \"instanceRef\": \"#/address/city\", \"schemaRef\": \"#/definitions/address_type/properties/city\"" + " }," + " \"maxItems\": {" + " \"actual\": 3, \"expected\": 2, \"errorCode\": 9, \"instanceRef\": \"#/phones\", \"schemaRef\": \"#/properties/phones\"" + " }," + " \"uniqueItems\": {" + " \"duplicates\": [1, 2], \"errorCode\": 11, \"instanceRef\": \"#/phones\", \"schemaRef\": \"#/properties/phones\"" + " }," + " \"minProperties\": {\"actual\": 6, \"expected\": 7, \"errorCode\": 14, \"instanceRef\": \"#/address\", \"schemaRef\": \"#/definitions/address_type\"" + " }," + " \"additionalItems\": [" + " {\"disallowed\": 2, \"errorCode\": 12, \"instanceRef\": \"#/names\", \"schemaRef\": \"#/properties/names\"}," + " {\"disallowed\": 3, \"errorCode\": 12, \"instanceRef\": \"#/names\", \"schemaRef\": \"#/properties/names\"}" + " ]," + " \"dependencies\": {" + " \"errors\": {" + " \"address\": {\"required\": {\"missing\": [\"version\"], \"errorCode\": 15, \"instanceRef\": \"#\", \"schemaRef\": \"#/dependencies/address\"}}," + " \"names\": {\"required\": {\"missing\": [\"version\"], \"errorCode\": 15, \"instanceRef\": \"#\", \"schemaRef\": \"#/dependencies/names\"}}" + " }," + " \"errorCode\": 18, \"instanceRef\": \"#\", \"schemaRef\": \"#\"" + " }," + " \"oneOf\": {" + " \"errors\": [" + " {\"enum\": {\"errorCode\": 19, \"instanceRef\": \"#/address/area\", \"schemaRef\": \"#/definitions/county_type\"}}," + " {\"enum\": {\"errorCode\": 19, \"instanceRef\": \"#/address/area\", \"schemaRef\": \"#/definitions/province_type\"}}" + " ]," + " \"errorCode\": 21, \"instanceRef\": \"#/address/area\", \"schemaRef\": \"#/definitions/address_type/properties/area\"" + " }" + "}", + kValidateDefaultFlags | kValidateContinueOnErrorFlag, SchemaValidator, Pointer); + + CrtAllocator::Free(schema); +} + +// Test that when kValidateContinueOnErrorFlag is set, it is not propagated to oneOf sub-validator so we only get the first error. +TEST(SchemaValidator, ContinueOnErrors_OneOf) { + typedef GenericSchemaDocument > SchemaDocumentType; + RemoteSchemaDocumentProvider provider; + CrtAllocator allocator; + char* schema = ReadFile("unittestschema/oneOf_address.json", allocator); + Document sd; + sd.Parse(schema); + ASSERT_FALSE(sd.HasParseError()); + SchemaDocumentType s(sd, 0, 0, &provider); + typedef GenericSchemaValidator >, MemoryPoolAllocator<> > SchemaValidatorType; + typedef GenericPointer > PointerType; + 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", "#", + "{ \"oneOf\": {" + " \"errors\": [{" + " \"multipleOf\": {" + " \"errorCode\": 1, \"instanceRef\": \"#/version\", \"schemaRef\": \"http://localhost:1234/address.json#/definitions/decimal_type\", \"expected\": 0.1, \"actual\": 1.01" + " }" + " }]," + " \"errorCode\": 21, \"instanceRef\": \"#\", \"schemaRef\": \"#\"" + " }" + "}", + kValidateDefaultFlags | kValidateContinueOnErrorFlag, SchemaValidatorType, PointerType); + CrtAllocator::Free(schema); +} + +// Test that when kValidateContinueOnErrorFlag is set, it is not propagated to allOf sub-validator so we only get the first error. +TEST(SchemaValidator, ContinueOnErrors_AllOf) { + typedef GenericSchemaDocument > SchemaDocumentType; + RemoteSchemaDocumentProvider provider; + CrtAllocator allocator; + char* schema = ReadFile("unittestschema/allOf_address.json", allocator); + Document sd; + sd.Parse(schema); + ASSERT_FALSE(sd.HasParseError()); + SchemaDocumentType s(sd, 0, 0, &provider); + typedef GenericSchemaValidator >, MemoryPoolAllocator<> > SchemaValidatorType; + typedef GenericPointer > PointerType; + 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", "#", + "{ \"allOf\": {" + " \"errors\": [{" + " \"multipleOf\": {" + " \"errorCode\": 1, \"instanceRef\": \"#/version\", \"schemaRef\": \"http://localhost:1234/address.json#/definitions/decimal_type\", \"expected\": 0.1, \"actual\": 1.01" + " }" + " }]," + " \"errorCode\": 23, \"instanceRef\": \"#\", \"schemaRef\": \"#\"" + " }" + "}", + kValidateDefaultFlags | kValidateContinueOnErrorFlag, SchemaValidatorType, PointerType); + CrtAllocator::Free(schema); +} + +// Test that when kValidateContinueOnErrorFlag is set, it is not propagated to anyOf sub-validator so we only get the first error. +TEST(SchemaValidator, ContinueOnErrors_AnyOf) { + typedef GenericSchemaDocument > SchemaDocumentType; + RemoteSchemaDocumentProvider provider; + CrtAllocator allocator; + char* schema = ReadFile("unittestschema/anyOf_address.json", allocator); + Document sd; + sd.Parse(schema); + ASSERT_FALSE(sd.HasParseError()); + SchemaDocumentType s(sd, 0, 0, &provider); + typedef GenericSchemaValidator >, MemoryPoolAllocator<> > SchemaValidatorType; + typedef GenericPointer > PointerType; + 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", "#", + "{ \"anyOf\": {" + " \"errors\": [{" + " \"multipleOf\": {" + " \"errorCode\": 1, \"instanceRef\": \"#/version\", \"schemaRef\": \"http://localhost:1234/address.json#/definitions/decimal_type\", \"expected\": 0.1, \"actual\": 1.01" + " }" + " }]," + " \"errorCode\": 24, \"instanceRef\": \"#\", \"schemaRef\": \"#\"" + " }" + "}", + kValidateDefaultFlags | kValidateContinueOnErrorFlag, SchemaValidatorType, PointerType); + + CrtAllocator::Free(schema); +} + +// Test that when kValidateContinueOnErrorFlag is set, arrays with uniqueItems:true are correctly processed when an item is invalid. +// This tests that we don't blow up if a hasher does not get created. +TEST(SchemaValidator, ContinueOnErrors_UniqueItems) { + CrtAllocator allocator; + char* schema = ReadFile("unittestschema/address.json", allocator); + Document sd; + sd.Parse(schema); + ASSERT_FALSE(sd.HasParseError()); + SchemaDocument s(sd); + VALIDATE(s, "{\"phones\":[\"12-34\",\"56-78\"]}", true); + INVALIDATE_(s, "{\"phones\":[\"12-34\",\"12-34\"]}", "#", "errors", "#", + "{\"uniqueItems\": {\"duplicates\": [0,1], \"errorCode\": 11, \"instanceRef\": \"#/phones\", \"schemaRef\": \"#/properties/phones\"}}", + kValidateDefaultFlags | kValidateContinueOnErrorFlag, SchemaValidator, Pointer); + INVALIDATE_(s, "{\"phones\":[\"ab-34\",\"cd-78\"]}", "#", "errors", "#", + "{\"pattern\": [" + " {\"actual\": \"ab-34\", \"errorCode\": 8, \"instanceRef\": \"#/phones/0\", \"schemaRef\": \"#/definitions/phone_type\"}," + " {\"actual\": \"cd-78\", \"errorCode\": 8, \"instanceRef\": \"#/phones/1\", \"schemaRef\": \"#/definitions/phone_type\"}" + "]}", + kValidateDefaultFlags | kValidateContinueOnErrorFlag, SchemaValidator, Pointer); + CrtAllocator::Free(schema); +} + +// Test that when kValidateContinueOnErrorFlag is set, an enum field is correctly processed when it has an invalid value. +// This tests that we don't blow up if a hasher does not get created. +TEST(SchemaValidator, ContinueOnErrors_Enum) { + CrtAllocator allocator; + char* schema = ReadFile("unittestschema/address.json", allocator); + Document sd; + sd.Parse(schema); + ASSERT_FALSE(sd.HasParseError()); + SchemaDocument s(sd); + VALIDATE(s, "{\"gender\":\"M\"}", true); + INVALIDATE_(s, "{\"gender\":\"X\"}", "#", "errors", "#", + "{\"enum\": {\"errorCode\": 19, \"instanceRef\": \"#/gender\", \"schemaRef\": \"#/properties/gender\"}}", + kValidateDefaultFlags | kValidateContinueOnErrorFlag, SchemaValidator, Pointer); + INVALIDATE_(s, "{\"gender\":1}", "#", "errors", "#", + "{\"type\": {\"expected\":[\"string\"], \"actual\": \"integer\", \"errorCode\": 20, \"instanceRef\": \"#/gender\", \"schemaRef\": \"#/properties/gender\"}}", + kValidateDefaultFlags | kValidateContinueOnErrorFlag, SchemaValidator, Pointer); + CrtAllocator::Free(schema); +} + +// Test that when kValidateContinueOnErrorFlag is set, an array appearing for an object property is handled +// This tests that we don't blow up when there is a type mismatch. +TEST(SchemaValidator, ContinueOnErrors_RogueArray) { + CrtAllocator allocator; + char* schema = ReadFile("unittestschema/address.json", allocator); + Document sd; + sd.Parse(schema); + ASSERT_FALSE(sd.HasParseError()); + SchemaDocument s(sd); + INVALIDATE_(s, "{\"address\":[{\"number\": 0}]}", "#", "errors", "#", + "{\"type\": {\"expected\":[\"object\"], \"actual\": \"array\", \"errorCode\": 20, \"instanceRef\": \"#/address\", \"schemaRef\": \"#/definitions/address_type\"}," + " \"dependencies\": {" + " \"errors\": {" + " \"address\": {\"required\": {\"missing\": [\"version\"], \"errorCode\": 15, \"instanceRef\": \"#\", \"schemaRef\": \"#/dependencies/address\"}}" + " },\"errorCode\": 18, \"instanceRef\": \"#\", \"schemaRef\": \"#\"}}", + kValidateDefaultFlags | kValidateContinueOnErrorFlag, SchemaValidator, Pointer); + CrtAllocator::Free(schema); +} + +// Test that when kValidateContinueOnErrorFlag is set, an object appearing for an array property is handled +// This tests that we don't blow up when there is a type mismatch. +TEST(SchemaValidator, ContinueOnErrors_RogueObject) { + CrtAllocator allocator; + char* schema = ReadFile("unittestschema/address.json", allocator); + Document sd; + sd.Parse(schema); + ASSERT_FALSE(sd.HasParseError()); + SchemaDocument s(sd); + INVALIDATE_(s, "{\"phones\":{\"number\": 0}}", "#", "errors", "#", + "{\"type\": {\"expected\":[\"array\"], \"actual\": \"object\", \"errorCode\": 20, \"instanceRef\": \"#/phones\", \"schemaRef\": \"#/properties/phones\"}}", + kValidateDefaultFlags | kValidateContinueOnErrorFlag, SchemaValidator, Pointer); + CrtAllocator::Free(schema); +} +// Test that when kValidateContinueOnErrorFlag is set, a string appearing for an array or object property is handled +// This tests that we don't blow up when there is a type mismatch. +TEST(SchemaValidator, ContinueOnErrors_RogueString) { + CrtAllocator allocator; + char* schema = ReadFile("unittestschema/address.json", allocator); + Document sd; + sd.Parse(schema); + ASSERT_FALSE(sd.HasParseError()); + SchemaDocument s(sd); + INVALIDATE_(s, "{\"address\":\"number\"}", "#", "errors", "#", + "{\"type\": {\"expected\":[\"object\"], \"actual\": \"string\", \"errorCode\": 20, \"instanceRef\": \"#/address\", \"schemaRef\": \"#/definitions/address_type\"}," + " \"dependencies\": {" + " \"errors\": {" + " \"address\": {\"required\": {\"missing\": [\"version\"], \"errorCode\": 15, \"instanceRef\": \"#\", \"schemaRef\": \"#/dependencies/address\"}}" + " },\"errorCode\": 18, \"instanceRef\": \"#\", \"schemaRef\": \"#\"}}", + kValidateDefaultFlags | kValidateContinueOnErrorFlag, SchemaValidator, Pointer); + INVALIDATE_(s, "{\"phones\":\"number\"}", "#", "errors", "#", + "{\"type\": {\"expected\":[\"array\"], \"actual\": \"string\", \"errorCode\": 20, \"instanceRef\": \"#/phones\", \"schemaRef\": \"#/properties/phones\"}}", + kValidateDefaultFlags | kValidateContinueOnErrorFlag, SchemaValidator, Pointer); + CrtAllocator::Free(schema); +} + #if defined(_MSC_VER) || defined(__clang__) RAPIDJSON_DIAG_POP #endif