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

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

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