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

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

Powered by Google App Engine
This is Rietveld 408576698