// 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 #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::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": , "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) { 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(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(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(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 w(sb); validator.GetError().Accept(w); fprintf(stderr, "Error report:\n%s\n", sb.GetString()); CreateErrorMessages(validator.GetError()); return EXIT_FAILURE; } }