Merge pull request #1837 from smhdfdl/multiple-validation-failures-and-validation-messages
Fixes for issues #1835 & #1836 - Multiple validation failures and readable validation messages
This commit is contained in:
commit
13dfc96c9c
1
.gitignore
vendored
1
.gitignore
vendored
@ -3,6 +3,7 @@
|
||||
!/bin/encodings
|
||||
!/bin/jsonchecker
|
||||
!/bin/types
|
||||
!/bin/unittestschema
|
||||
/build
|
||||
/doc/html
|
||||
/doc/doxygen_*.db
|
||||
|
BIN
bin/unittestschema/address.json
Normal file
BIN
bin/unittestschema/address.json
Normal file
Binary file not shown.
BIN
bin/unittestschema/allOf_address.json
Normal file
BIN
bin/unittestschema/allOf_address.json
Normal file
Binary file not shown.
BIN
bin/unittestschema/anyOf_address.json
Normal file
BIN
bin/unittestschema/anyOf_address.json
Normal file
Binary file not shown.
BIN
bin/unittestschema/oneOf_address.json
Normal file
BIN
bin/unittestschema/oneOf_address.json
Normal file
Binary file not shown.
@ -7,9 +7,124 @@
|
||||
#include "rapidjson/schema.h"
|
||||
#include "rapidjson/stringbuffer.h"
|
||||
#include "rapidjson/prettywriter.h"
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
using namespace rapidjson;
|
||||
|
||||
typedef GenericValue<UTF8<>, 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::ostringstream s;
|
||||
if (val.IsString())
|
||||
s << val.GetString();
|
||||
else if (val.IsDouble())
|
||||
s << val.GetDouble();
|
||||
else if (val.IsUint())
|
||||
s << val.GetUint();
|
||||
else if (val.IsInt())
|
||||
s << val.GetInt();
|
||||
else if (val.IsUint64())
|
||||
s << val.GetUint64();
|
||||
else if (val.IsInt64())
|
||||
s << val.GetInt64();
|
||||
else if (val.IsBool() && val.GetBool())
|
||||
s << "true";
|
||||
else if (val.IsBool())
|
||||
s << "false";
|
||||
else if (val.IsFloat())
|
||||
s << val.GetFloat();
|
||||
return s.str();}
|
||||
|
||||
// Create the error message for a named error
|
||||
// The error object can either be empty or contain at least member properties:
|
||||
// {"errorCode": <code>, "instanceRef": "<pointer>", "schemaRef": "<pointer>" }
|
||||
// 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) {
|
||||
if (!error.ObjectEmpty()) {
|
||||
// Get error code and look up error message text (English)
|
||||
int code = error["errorCode"].GetInt();
|
||||
std::string message(GetValidateError_En(static_cast<ValidateErrorCode>(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 insertName("%");
|
||||
insertName += insertsItr->name.GetString(); // eg "%actual"
|
||||
size_t insertPos = message.find(insertName);
|
||||
if (insertPos != std::string::npos) {
|
||||
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.replace(insertPos, insertName.length(), 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 +180,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 +190,7 @@ int main(int argc, char *argv[]) {
|
||||
PrettyWriter<StringBuffer> w(sb);
|
||||
validator.GetError().Accept(w);
|
||||
fprintf(stderr, "Error report:\n%s\n", sb.GetString());
|
||||
CreateErrorMessages(validator.GetError());
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
@ -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__
|
||||
|
@ -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__
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user