2021-10-06 15:39:12 +00:00
// Schema Validator example
// The example validates JSON text from stdin with a JSON schema specified in the argument.
# define RAPIDJSON_HAS_STDSTRING 1
# include "rapidjson/error/en.h"
# include "rapidjson/filereadstream.h"
# 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 ( ) ;
2023-08-05 09:13:29 +00:00
return s . str ( ) ;
}
2021-10-06 15:39:12 +00:00
// 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 " ) ;
return EXIT_FAILURE ;
}
// Read a JSON schema from file into Document
Document d ;
char buffer [ 4096 ] ;
{
FILE * fp = fopen ( argv [ 1 ] , " r " ) ;
if ( ! fp ) {
printf ( " Schema file '%s' not found \n " , argv [ 1 ] ) ;
return - 1 ;
}
FileReadStream fs ( fp , buffer , sizeof ( buffer ) ) ;
d . ParseStream ( fs ) ;
if ( d . HasParseError ( ) ) {
fprintf ( stderr , " Schema file '%s' is not a valid JSON \n " , argv [ 1 ] ) ;
fprintf ( stderr , " Error(offset %u): %s \n " ,
static_cast < unsigned > ( d . GetErrorOffset ( ) ) ,
GetParseError_En ( d . GetParseError ( ) ) ) ;
fclose ( fp ) ;
return EXIT_FAILURE ;
}
fclose ( fp ) ;
}
// Then convert the Document into SchemaDocument
SchemaDocument sd ( d ) ;
// Use reader to parse the JSON in stdin, and forward SAX events to validator
SchemaValidator validator ( sd ) ;
Reader reader ;
FileReadStream is ( stdin , buffer , sizeof ( buffer ) ) ;
if ( ! reader . Parse ( is , validator ) & & reader . GetParseErrorCode ( ) ! = kParseErrorTermination ) {
// Schema validator error would cause kParseErrorTermination, which will handle it in next step.
fprintf ( stderr , " Input is not a valid JSON \n " ) ;
fprintf ( stderr , " Error(offset %u): %s \n " ,
static_cast < unsigned > ( reader . GetErrorOffset ( ) ) ,
GetParseError_En ( reader . GetParseErrorCode ( ) ) ) ;
}
// Check the validation result
if ( validator . IsValid ( ) ) {
printf ( " Input JSON is valid. \n " ) ;
return EXIT_SUCCESS ;
}
else {
printf ( " Input JSON is invalid. \n " ) ;
StringBuffer sb ;
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 ( ) ) ;
// Detailed violation report is available as a JSON value
sb . Clear ( ) ;
PrettyWriter < StringBuffer > w ( sb ) ;
validator . GetError ( ) . Accept ( w ) ;
fprintf ( stderr , " Error report: \n %s \n " , sb . GetString ( ) ) ;
CreateErrorMessages ( validator . GetError ( ) ) ;
return EXIT_FAILURE ;
}
}