-
Notifications
You must be signed in to change notification settings - Fork 3.5k
/
schemavalidator.cpp
199 lines (183 loc) · 8.5 KB
/
schemavalidator.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
// 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();
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");
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;
}
}