Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(38)

Side by Side Diff: chrome/common/json_schema_validator.cc

Issue 4673001: Implements a C++ version of JSONSchemaValidator. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: all done Created 10 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « chrome/common/json_schema_validator.h ('k') | chrome/common/json_schema_validator_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2010 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/common/json_schema_validator.h"
6
7 #include <cmath>
8
9 #include "app/l10n_util.h"
10 #include "base/string_number_conversions.h"
11 #include "base/string_util.h"
12 #include "base/values.h"
13
14 namespace {
15
16 double GetNumberValue(Value* value) {
17 double result = 0;
18 if (value->GetAsReal(&result))
19 return result;
20
21 int int_result = 0;
22 if (value->GetAsInteger(&int_result)) {
23 return int_result;
24 }
25
26 NOTREACHED() << "Unexpected value type: " << value->GetType();
27 return 0;
28 }
29
30 bool GetNumberFromDictionary(DictionaryValue* value, const std::string& key,
31 double* number) {
32 if (value->GetReal(key, number))
33 return true;
34
35 int int_value = 0;
36 if (value->GetInteger(key, &int_value)) {
37 *number = int_value;
38 return true;
39 }
40
41 return false;
42 }
43
44 // Classifies a Value as one of the JSON schema primitive types.
45 std::string GetJSONSchemaType(Value* value) {
46 switch (value->GetType()) {
47 case Value::TYPE_NULL:
48 return "null";
49 case Value::TYPE_BOOLEAN:
50 return "boolean";
51 case Value::TYPE_INTEGER:
52 return "integer";
53 case Value::TYPE_REAL: {
54 double double_value = 0;
55 value->GetAsReal(&double_value);
56 return double_value > floor(double_value) ? "number" : "integer";
57 }
58 case Value::TYPE_STRING:
59 return "string";
60 case Value::TYPE_DICTIONARY:
61 return "object";
62 case Value::TYPE_LIST:
63 return "array";
64 default:
65 NOTREACHED() << "Unexpected value type: " << value->GetType();
66 return "";
67 }
68 }
69
70 } // namespace
71
72
73 JSONSchemaValidator::Error::Error() {
74 }
75
76 JSONSchemaValidator::Error::Error(const std::string& message)
77 : path(message) {
78 }
79
80 JSONSchemaValidator::Error::Error(const std::string& path,
81 const std::string& message)
82 : path(path), message(message) {
83 }
84
85
86 const char JSONSchemaValidator::kUnknownTypeReference[] =
87 "Unknown schema reference: *.";
88 const char JSONSchemaValidator::kInvalidChoice[] =
89 "Value does not match any valid type choices.";
90 const char JSONSchemaValidator::kInvalidEnum[] =
91 "Value does not match any valid enum choices.";
92 const char JSONSchemaValidator::kObjectPropertyIsRequired[] =
93 "Property is required.";
94 const char JSONSchemaValidator::kUnexpectedProperty[] =
95 "Unexpected property.";
96 const char JSONSchemaValidator::kArrayMinItems[] =
97 "Array must have at least * items.";
98 const char JSONSchemaValidator::kArrayMaxItems[] =
99 "Array must not have more than * items.";
100 const char JSONSchemaValidator::kArrayItemRequired[] =
101 "Item is required.";
102 const char JSONSchemaValidator::kStringMinLength[] =
103 "String must be at least * characters long.";
104 const char JSONSchemaValidator::kStringMaxLength[] =
105 "String must not be more than * characters long.";
106 const char JSONSchemaValidator::kStringPattern[] =
107 "String must match the pattern: *.";
108 const char JSONSchemaValidator::kInfinityNaNNotSupported[] =
109 "Value must not be infinity or NaN.";
110 const char JSONSchemaValidator::kNumberMinimum[] =
111 "Value must not be less than *.";
112 const char JSONSchemaValidator::kNumberMaximum[] =
113 "Value must not be greater than *.";
114 const char JSONSchemaValidator::kInvalidType[] =
115 "Expected '*' but got '*'.";
116
117
118 // static
119 std::string JSONSchemaValidator::FormatErrorMessage(const std::string& format,
120 const std::string& s1) {
121 std::string ret_val = format;
122 ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s1);
123 return ret_val;
124 }
125
126 // static
127 std::string JSONSchemaValidator::FormatErrorMessage(const std::string& format,
128 const std::string& s1,
129 const std::string& s2) {
130 std::string ret_val = format;
131 ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s1);
132 ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s2);
133 return ret_val;
134 }
135
136 JSONSchemaValidator::JSONSchemaValidator(DictionaryValue* schema)
137 : schema_root_(schema), default_allow_additional_properties_(false) {
138 }
139
140 JSONSchemaValidator::JSONSchemaValidator(DictionaryValue* schema,
141 ListValue* types)
142 : schema_root_(schema), default_allow_additional_properties_(false) {
143 if (!types)
144 return;
145
146 for (size_t i = 0; i < types->GetSize(); ++i) {
147 DictionaryValue* type = NULL;
148 CHECK(types->GetDictionary(i, &type));
149
150 std::string id;
151 CHECK(type->GetString("id", &id));
152
153 types_[id] = type;
154 }
155 }
156
157
158 bool JSONSchemaValidator::Validate(Value* instance) {
159 errors_.clear();
160 Validate(instance, schema_root_, "");
161 return errors_.empty();
162 }
163
164 void JSONSchemaValidator::Validate(Value* instance,
165 DictionaryValue* schema,
166 const std::string& path) {
167 // If this schema defines itself as reference type, save it in this.types.
168 std::string id;
169 if (schema->GetString("id", &id))
170 types_[id] = schema;
171
172 // If the schema has a $ref property, the instance must validate against
173 // that schema. It must be present in types_ to be referenced.
174 std::string ref;
175 if (schema->GetString("$ref", &ref)) {
Erik does not do reviews 2010/11/09 18:34:21 one thing that's a little weird here is that it lo
Aaron Boodman 2010/11/10 19:03:35 You can define a schema like that, but we won't ho
176 TypeMap::iterator type = types_.find(ref);
177 if (type == types_.end()) {
178 errors_.push_back(
179 Error(path, FormatErrorMessage(kUnknownTypeReference, ref)));
180 } else {
181 Validate(instance, type->second, path);
182 }
183 return;
184 }
185
186 // If the schema has a choices property, the instance must validate against at
187 // least one of the items in that array.
188 ListValue* choices = NULL;
189 if (schema->GetList("choices", &choices)) {
Erik does not do reviews 2010/11/09 18:34:21 feels like these keys should be pulled out as cons
190 ValidateChoices(instance, choices, path);
191 return;
192 }
193
194 // If the schema has an enum property, the instance must be one of those
195 // values.
196 ListValue* enumeration = NULL;
197 if (schema->GetList("enum", &enumeration)) {
198 ValidateEnum(instance, enumeration, path);
199 return;
200 }
201
202 std::string type;
203 schema->GetString("type", &type);
204 CHECK(!type.empty());
205 if (type != "any") {
206 if (!ValidateType(instance, type, path))
207 return;
208
209 // These casts are safe because of checks in ValidateType().
210 if (type == "object")
211 ValidateObject(static_cast<DictionaryValue*>(instance), schema, path);
212 else if (type == "array")
213 ValidateArray(static_cast<ListValue*>(instance), schema, path);
214 else if (type == "string")
215 ValidateString(static_cast<StringValue*>(instance), schema, path);
216 else if (type == "number" || type == "integer")
217 ValidateNumber(instance, schema, path);
218 else if (type != "boolean" && type != "null")
Erik does not do reviews 2010/11/09 18:34:21 why don't we need a ValidateBoolean?
Aaron Boodman 2010/11/10 19:03:35 ValidateType() handles the basic 'do the types mat
219 CHECK(false) << "Unexpected type: " << type;
220 }
221 }
222
223 void JSONSchemaValidator::ValidateChoices(Value* instance,
224 ListValue* choices,
225 const std::string& path) {
226 size_t original_num_errors = errors_.size();
227
228 for (size_t i = 0; i < choices->GetSize(); ++i) {
229 DictionaryValue* choice = NULL;
230 CHECK(choices->GetDictionary(i, &choice));
231
232 errors_.resize(original_num_errors);
233 Validate(instance, choice, path);
234 if (errors_.size() == original_num_errors)
235 return;
236 }
237
238 errors_.resize(original_num_errors);
239 errors_.push_back(Error(path, kInvalidChoice));
240 return;
241 }
242
243 void JSONSchemaValidator::ValidateEnum(Value* instance,
244 ListValue* choices,
245 const std::string& path) {
246 for (size_t i = 0; i < choices->GetSize(); ++i) {
247 Value* choice = NULL;
248 choices->Get(i, &choice);
249 switch (choice->GetType()) {
250 case Value::TYPE_NULL:
251 case Value::TYPE_BOOLEAN:
252 case Value::TYPE_STRING:
253 if (instance->Equals(choice))
254 return;
255
256 case Value::TYPE_INTEGER:
257 case Value::TYPE_REAL:
258 if (instance->IsType(Value::TYPE_INTEGER) ||
259 instance->IsType(Value::TYPE_REAL)) {
260 if (GetNumberValue(choice) == GetNumberValue(instance))
261 return;
262 }
263
264 default:
265 NOTREACHED() << "Unexpected type in enum: " << choice->GetType();
266 }
267 }
268
269 errors_.push_back(Error(path, kInvalidEnum));
270 }
271
272 void JSONSchemaValidator::ValidateObject(DictionaryValue* instance,
273 DictionaryValue* schema,
274 const std::string& path) {
275 DictionaryValue* properties = NULL;
276 schema->GetDictionary("properties", &properties);
277 if (properties) {
278 for (DictionaryValue::key_iterator key = properties->begin_keys();
279 key != properties->end_keys(); ++key) {
280 std::string prop_path = path.empty() ? *key : (path + "." + *key);
281 DictionaryValue* prop_schema = NULL;
282 CHECK(properties->GetDictionary(*key, &prop_schema));
283
284 Value* prop_value = NULL;
285 if (instance->Get(*key, &prop_value)) {
286 Validate(prop_value, prop_schema, prop_path);
287 } else {
288 // Properties are required unless there is an optional field set to
289 // 'true'.
290 bool is_optional = false;
291 prop_schema->GetBoolean("optional", &is_optional);
292 if (!is_optional) {
293 errors_.push_back(Error(prop_path, kObjectPropertyIsRequired));
294 }
295 }
296 }
297 }
298
299 DictionaryValue* additional_properties_schema = NULL;
300 if (SchemaAllowsAnyAdditionalItems(schema, &additional_properties_schema))
301 return;
302
303 // Validate additional properties.
304 for (DictionaryValue::key_iterator key = instance->begin_keys();
305 key != instance->end_keys(); ++key) {
306 if (properties && properties->HasKey(*key))
307 continue;
308
309 std::string prop_path = path.empty() ? *key : path + "." + *key;
310 if (!additional_properties_schema) {
311 errors_.push_back(Error(prop_path, kUnexpectedProperty));
312 } else {
313 Value* prop_value = NULL;
314 CHECK(instance->Get(*key, &prop_value));
315 Validate(prop_value, additional_properties_schema, prop_path);
316 }
317 }
318 }
319
320 void JSONSchemaValidator::ValidateArray(ListValue* instance,
321 DictionaryValue* schema,
322 const std::string& path) {
323 DictionaryValue* single_type = NULL;
324 size_t instance_size = instance->GetSize();
325 if (schema->GetDictionary("items", &single_type)) {
326 int min_items = 0;
327 if (schema->GetInteger("minItems", &min_items)) {
328 CHECK(min_items >= 0);
329 if (instance_size < static_cast<size_t>(min_items)) {
330 errors_.push_back(Error(path, FormatErrorMessage(
331 kArrayMinItems, base::IntToString(min_items))));
332 }
333 }
334
335 int max_items = 0;
336 if (schema->GetInteger("maxItems", &max_items)) {
337 CHECK(max_items >= 0);
338 if (instance_size > static_cast<size_t>(max_items)) {
339 errors_.push_back(Error(path, FormatErrorMessage(
340 kArrayMaxItems, base::IntToString(max_items))));
341 }
342 }
343
344 // If the items property is a single schema, each item in the array must
345 // validate against that schema.
346 for (size_t i = 0; i < instance_size; ++i) {
347 Value* item = NULL;
348 CHECK(instance->Get(i, &item));
349 std::string i_str = base::UintToString(i);
350 std::string item_path = path.empty() ? i_str : (path + "." + i_str);
351 Validate(item, single_type, item_path);
352 }
353
354 return;
355 }
356
357 // Otherwise, the list must be a tuple type, where each item in the list has a
358 // particular schema.
359 ListValue* tuple_type = NULL;
360 schema->GetList("items", &tuple_type);
361 size_t tuple_size = tuple_type ? tuple_type->GetSize() : 0;
362 if (tuple_type) {
363 for (size_t i = 0; i < tuple_size; ++i) {
364 std::string i_str = base::UintToString(i);
365 std::string item_path = path.empty() ? i_str : (path + "." + i_str);
366 DictionaryValue* item_schema = NULL;
367 CHECK(tuple_type->GetDictionary(i, &item_schema));
368 Value* item_value = NULL;
369 instance->Get(i, &item_value);
370 if (item_value && item_value->GetType() != Value::TYPE_NULL) {
371 Validate(item_value, item_schema, item_path);
372 } else {
373 bool is_optional = false;
374 item_schema->GetBoolean("optional", &is_optional);
375 if (!is_optional) {
376 errors_.push_back(Error(item_path, kArrayItemRequired));
377 return;
378 }
379 }
380 }
381 }
382
383 DictionaryValue* additional_properties_schema = NULL;
384 if (SchemaAllowsAnyAdditionalItems(schema, &additional_properties_schema))
385 return;
386
387 if (additional_properties_schema) {
388 // Any additional properties must validate against the additionalProperties
389 // schema.
390 for (size_t i = tuple_size; i < instance_size; ++i) {
391 std::string i_str = base::UintToString(i);
392 std::string item_path = path.empty() ? i_str : (path + "." + i_str);
393 Value* item_value = NULL;
394 CHECK(instance->Get(i, &item_value));
395 Validate(item_value, additional_properties_schema, item_path);
396 }
397 } else if (instance_size > tuple_size) {
398 errors_.push_back(Error(path, FormatErrorMessage(
399 kArrayMaxItems, base::UintToString(tuple_size))));
400 }
401 }
402
403 void JSONSchemaValidator::ValidateString(StringValue* instance,
404 DictionaryValue* schema,
405 const std::string& path) {
406 std::string value;
407 CHECK(instance->GetAsString(&value));
408
409 int min_length = 0;
410 if (schema->GetInteger("minLength", &min_length)) {
411 CHECK(min_length >= 0);
412 if (value.size() < static_cast<size_t>(min_length)) {
413 errors_.push_back(Error(path, FormatErrorMessage(
414 kStringMinLength, base::IntToString(min_length))));
415 }
416 }
417
418 int max_length = 0;
419 if (schema->GetInteger("maxLength", &max_length)) {
420 CHECK(max_length >= 0);
421 if (value.size() > static_cast<size_t>(max_length)) {
422 errors_.push_back(Error(path, FormatErrorMessage(
423 kStringMaxLength, base::IntToString(max_length))));
424 }
425 }
426
427 CHECK(!schema->HasKey("pattern")) << "Pattern is not supported.";
428 }
429
430 void JSONSchemaValidator::ValidateNumber(Value* instance,
431 DictionaryValue* schema,
432 const std::string& path) {
433 double value = GetNumberValue(instance);
434
435 if (isinf(value) || isnan(value)) {
436 errors_.push_back(Error(path, kInfinityNaNNotSupported));
437 return;
438 }
439
440 double minimum = 0;
441 if (GetNumberFromDictionary(schema, "minimum", &minimum)) {
442 if (value < minimum)
443 errors_.push_back(Error(path, FormatErrorMessage(
444 kNumberMinimum, base::DoubleToString(minimum))));
445 }
446
447 double maximum = 0;
448 if (GetNumberFromDictionary(schema, "maximum", &maximum)) {
Erik does not do reviews 2010/11/09 18:34:21 maybe add a CHECK that maximum > minimum
Aaron Boodman 2010/11/10 19:03:35 Hm. That is a good idea, but I want to implement a
449 if (value > maximum)
450 errors_.push_back(Error(path, FormatErrorMessage(
451 kNumberMaximum, base::DoubleToString(maximum))));
452 }
453 }
454
455 bool JSONSchemaValidator::ValidateType(Value* instance,
456 const std::string& expected_type,
457 const std::string& path) {
458 std::string actual_type = GetJSONSchemaType(instance);
459 if (expected_type == actual_type ||
460 (expected_type == "number" && actual_type == "integer")) {
461 return true;
462 } else {
463 errors_.push_back(Error(path, FormatErrorMessage(
464 kInvalidType, expected_type, actual_type)));
465 return false;
466 }
467 }
468
469 bool JSONSchemaValidator::SchemaAllowsAnyAdditionalItems(
470 DictionaryValue* schema, DictionaryValue** additional_properties_schema) {
471 // If the validator allows additional properties globally, and this schema
472 // doesn't override, then we can exit early.
473 schema->GetDictionary("additionalProperties", additional_properties_schema);
474
475 if (*additional_properties_schema) {
476 std::string additional_properties_type("any");
477 CHECK((*additional_properties_schema)->GetString(
478 "type", &additional_properties_type));
479 return additional_properties_type == "any";
480 } else {
481 return default_allow_additional_properties_;
482 }
483 }
OLDNEW
« no previous file with comments | « chrome/common/json_schema_validator.h ('k') | chrome/common/json_schema_validator_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698