schemavalidator.cpp 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. // Schema Validator example
  2. // The example validates JSON text from stdin with a JSON schema specified in the argument.
  3. #include <rtthread.h>
  4. #include "rapidjson/error/en.h"
  5. #include "rapidjson/filereadstream.h"
  6. #include "rapidjson/schema.h"
  7. #include "rapidjson/stringbuffer.h"
  8. #include "rapidjson/prettywriter.h"
  9. #include <string>
  10. #include <iostream>
  11. #include <sstream>
  12. using namespace rapidjson;
  13. typedef GenericValue<UTF8<>, CrtAllocator > ValueType;
  14. // Forward ref
  15. static void CreateErrorMessages(const ValueType& errors, size_t depth, const char* context);
  16. // Convert GenericValue to std::string
  17. static std::string GetString(const ValueType& val) {
  18. std::ostringstream s;
  19. if (val.IsString())
  20. s << val.GetString();
  21. else if (val.IsDouble())
  22. s << val.GetDouble();
  23. else if (val.IsUint())
  24. s << val.GetUint();
  25. else if (val.IsInt())
  26. s << val.GetInt();
  27. else if (val.IsUint64())
  28. s << val.GetUint64();
  29. else if (val.IsInt64())
  30. s << val.GetInt64();
  31. else if (val.IsBool() && val.GetBool())
  32. s << "true";
  33. else if (val.IsBool())
  34. s << "false";
  35. else if (val.IsFloat())
  36. s << val.GetFloat();
  37. return s.str();}
  38. // Create the error message for a named error
  39. // The error object can either be empty or contain at least member properties:
  40. // {"errorCode": <code>, "instanceRef": "<pointer>", "schemaRef": "<pointer>" }
  41. // Additional properties may be present for use as inserts.
  42. // An "errors" property may be present if there are child errors.
  43. static void HandleError(const char* errorName, const ValueType& error, size_t depth, const char* context) {
  44. if (!error.ObjectEmpty()) {
  45. // Get error code and look up error message text (English)
  46. int code = error["errorCode"].GetInt();
  47. std::string message(GetValidateError_En(static_cast<ValidateErrorCode>(code)));
  48. // 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
  49. // So for example - "Number '%actual' is not a multiple of the 'multipleOf' value '%expected'." - we would expect "actual" and "expected" members.
  50. for (ValueType::ConstMemberIterator insertsItr = error.MemberBegin();
  51. insertsItr != error.MemberEnd(); ++insertsItr) {
  52. std::string insertName("%");
  53. insertName += insertsItr->name.GetString(); // eg "%actual"
  54. size_t insertPos = message.find(insertName);
  55. if (insertPos != std::string::npos) {
  56. std::string insertString("");
  57. const ValueType &insert = insertsItr->value;
  58. if (insert.IsArray()) {
  59. // Member is an array so create comma-separated list of items for the insert string
  60. for (ValueType::ConstValueIterator itemsItr = insert.Begin(); itemsItr != insert.End(); ++itemsItr) {
  61. if (itemsItr != insert.Begin()) insertString += ",";
  62. insertString += GetString(*itemsItr);
  63. }
  64. } else {
  65. insertString += GetString(insert);
  66. }
  67. message.replace(insertPos, insertName.length(), insertString);
  68. }
  69. }
  70. // Output error message, references, context
  71. std::string indent(depth * 2, ' ');
  72. std::cout << indent << "Error Name: " << errorName << std::endl;
  73. std::cout << indent << "Message: " << message.c_str() << std::endl;
  74. std::cout << indent << "Instance: " << error["instanceRef"].GetString() << std::endl;
  75. std::cout << indent << "Schema: " << error["schemaRef"].GetString() << std::endl;
  76. if (depth > 0) std::cout << indent << "Context: " << context << std::endl;
  77. std::cout << std::endl;
  78. // If child errors exist, apply the process recursively to each error structure.
  79. // This occurs for "oneOf", "allOf", "anyOf" and "dependencies" errors, so pass the error name as context.
  80. if (error.HasMember("errors")) {
  81. depth++;
  82. const ValueType &childErrors = error["errors"];
  83. if (childErrors.IsArray()) {
  84. // Array - each item is an error structure - example
  85. // "anyOf": {"errorCode": ..., "errors":[{"pattern": {"errorCode\": ...\"}}, {"pattern": {"errorCode\": ...}}]
  86. for (ValueType::ConstValueIterator errorsItr = childErrors.Begin();
  87. errorsItr != childErrors.End(); ++errorsItr) {
  88. CreateErrorMessages(*errorsItr, depth, errorName);
  89. }
  90. } else if (childErrors.IsObject()) {
  91. // Object - each member is an error structure - example
  92. // "dependencies": {"errorCode": ..., "errors": {"address": {"required": {"errorCode": ...}}, "name": {"required": {"errorCode": ...}}}
  93. for (ValueType::ConstMemberIterator propsItr = childErrors.MemberBegin();
  94. propsItr != childErrors.MemberEnd(); ++propsItr) {
  95. CreateErrorMessages(propsItr->value, depth, errorName);
  96. }
  97. }
  98. }
  99. }
  100. }
  101. // Create error message for all errors in an error structure
  102. // Context is used to indicate whether the error structure has a parent 'dependencies', 'allOf', 'anyOf' or 'oneOf' error
  103. static void CreateErrorMessages(const ValueType& errors, size_t depth = 0, const char* context = 0) {
  104. // Each member property contains one or more errors of a given type
  105. for (ValueType::ConstMemberIterator errorTypeItr = errors.MemberBegin(); errorTypeItr != errors.MemberEnd(); ++errorTypeItr) {
  106. const char* errorName = errorTypeItr->name.GetString();
  107. const ValueType& errorContent = errorTypeItr->value;
  108. if (errorContent.IsArray()) {
  109. // Member is an array where each item is an error - eg "type": [{"errorCode": ...}, {"errorCode": ...}]
  110. for (ValueType::ConstValueIterator contentItr = errorContent.Begin(); contentItr != errorContent.End(); ++contentItr) {
  111. HandleError(errorName, *contentItr, depth, context);
  112. }
  113. } else if (errorContent.IsObject()) {
  114. // Member is an object which is a single error - eg "type": {"errorCode": ... }
  115. HandleError(errorName, errorContent, depth, context);
  116. }
  117. }
  118. }
  119. int schema_validator(int argc, char *argv[]) {
  120. if (argc != 2) {
  121. fprintf(stderr, "Usage: schemavalidator schema.json < input.json\n");
  122. return EXIT_FAILURE;
  123. }
  124. // Read a JSON schema from file into Document
  125. Document d;
  126. char buffer[4096];
  127. {
  128. FILE *fp = fopen(argv[1], "r");
  129. if (!fp) {
  130. printf("Schema file '%s' not found\n", argv[1]);
  131. return -1;
  132. }
  133. FileReadStream fs(fp, buffer, sizeof(buffer));
  134. d.ParseStream(fs);
  135. if (d.HasParseError()) {
  136. fprintf(stderr, "Schema file '%s' is not a valid JSON\n", argv[1]);
  137. fprintf(stderr, "Error(offset %u): %s\n",
  138. static_cast<unsigned>(d.GetErrorOffset()),
  139. GetParseError_En(d.GetParseError()));
  140. fclose(fp);
  141. return EXIT_FAILURE;
  142. }
  143. fclose(fp);
  144. }
  145. // Then convert the Document into SchemaDocument
  146. SchemaDocument sd(d);
  147. // Use reader to parse the JSON in stdin, and forward SAX events to validator
  148. SchemaValidator validator(sd);
  149. Reader reader;
  150. FileReadStream is(stdin, buffer, sizeof(buffer));
  151. if (!reader.Parse(is, validator) && reader.GetParseErrorCode() != kParseErrorTermination) {
  152. // Schema validator error would cause kParseErrorTermination, which will handle it in next step.
  153. fprintf(stderr, "Input is not a valid JSON\n");
  154. fprintf(stderr, "Error(offset %u): %s\n",
  155. static_cast<unsigned>(reader.GetErrorOffset()),
  156. GetParseError_En(reader.GetParseErrorCode()));
  157. }
  158. // Check the validation result
  159. if (validator.IsValid()) {
  160. printf("Input JSON is valid.\n");
  161. return EXIT_SUCCESS;
  162. }
  163. else {
  164. printf("Input JSON is invalid.\n");
  165. StringBuffer sb;
  166. validator.GetInvalidSchemaPointer().StringifyUriFragment(sb);
  167. fprintf(stderr, "Invalid schema: %s\n", sb.GetString());
  168. fprintf(stderr, "Invalid keyword: %s\n", validator.GetInvalidSchemaKeyword());
  169. fprintf(stderr, "Invalid code: %d\n", validator.GetInvalidSchemaCode());
  170. fprintf(stderr, "Invalid message: %s\n", GetValidateError_En(validator.GetInvalidSchemaCode()));
  171. sb.Clear();
  172. validator.GetInvalidDocumentPointer().StringifyUriFragment(sb);
  173. fprintf(stderr, "Invalid document: %s\n", sb.GetString());
  174. // Detailed violation report is available as a JSON value
  175. sb.Clear();
  176. PrettyWriter<StringBuffer> w(sb);
  177. validator.GetError().Accept(w);
  178. fprintf(stderr, "Error report:\n%s\n", sb.GetString());
  179. CreateErrorMessages(validator.GetError());
  180. return EXIT_FAILURE;
  181. }
  182. }
  183. MSH_CMD_EXPORT(schema_validator, rapid json schema validator example);