1// Copyright 2013 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 "components/json_schema/json_schema_validator.h"
6
7#include <algorithm>
8#include <cfloat>
9#include <cmath>
10
11#include "base/json/json_reader.h"
12#include "base/strings/string_number_conversions.h"
13#include "base/strings/string_util.h"
14#include "base/strings/stringprintf.h"
15#include "base/values.h"
16#include "components/json_schema/json_schema_constants.h"
17
18namespace schema = json_schema_constants;
19
20namespace {
21
22double GetNumberValue(const base::Value* value) {
23  double result = 0;
24  CHECK(value->GetAsDouble(&result))
25      << "Unexpected value type: " << value->GetType();
26  return result;
27}
28
29bool IsValidType(const std::string& type) {
30  static const char* kValidTypes[] = {
31    schema::kAny,
32    schema::kArray,
33    schema::kBoolean,
34    schema::kInteger,
35    schema::kNull,
36    schema::kNumber,
37    schema::kObject,
38    schema::kString,
39  };
40  const char** end = kValidTypes + arraysize(kValidTypes);
41  return std::find(kValidTypes, end, type) != end;
42}
43
44// Maps a schema attribute name to its expected type.
45struct ExpectedType {
46  const char* key;
47  base::Value::Type type;
48};
49
50// Helper for std::lower_bound.
51bool CompareToString(const ExpectedType& entry, const std::string& key) {
52  return entry.key < key;
53}
54
55// If |value| is a dictionary, returns the "name" attribute of |value| or NULL
56// if |value| does not contain a "name" attribute. Otherwise, returns |value|.
57const base::Value* ExtractNameFromDictionary(const base::Value* value) {
58  const base::DictionaryValue* value_dict = NULL;
59  const base::Value* name_value = NULL;
60  if (value->GetAsDictionary(&value_dict)) {
61    value_dict->Get("name", &name_value);
62    return name_value;
63  }
64  return value;
65}
66
67bool IsValidSchema(const base::DictionaryValue* dict,
68                   int options,
69                   std::string* error) {
70  // This array must be sorted, so that std::lower_bound can perform a
71  // binary search.
72  static const ExpectedType kExpectedTypes[] = {
73    // Note: kRef == "$ref", kSchema == "$schema"
74    { schema::kRef,                     base::Value::TYPE_STRING      },
75    { schema::kSchema,                  base::Value::TYPE_STRING      },
76
77    { schema::kAdditionalProperties,    base::Value::TYPE_DICTIONARY  },
78    { schema::kChoices,                 base::Value::TYPE_LIST        },
79    { schema::kDescription,             base::Value::TYPE_STRING      },
80    { schema::kEnum,                    base::Value::TYPE_LIST        },
81    { schema::kId,                      base::Value::TYPE_STRING      },
82    { schema::kMaxItems,                base::Value::TYPE_INTEGER     },
83    { schema::kMaxLength,               base::Value::TYPE_INTEGER     },
84    { schema::kMaximum,                 base::Value::TYPE_DOUBLE      },
85    { schema::kMinItems,                base::Value::TYPE_INTEGER     },
86    { schema::kMinLength,               base::Value::TYPE_INTEGER     },
87    { schema::kMinimum,                 base::Value::TYPE_DOUBLE      },
88    { schema::kOptional,                base::Value::TYPE_BOOLEAN     },
89    { schema::kProperties,              base::Value::TYPE_DICTIONARY  },
90    { schema::kTitle,                   base::Value::TYPE_STRING      },
91  };
92
93  bool has_type_or_ref = false;
94  const base::ListValue* list_value = NULL;
95  const base::DictionaryValue* dictionary_value = NULL;
96  std::string string_value;
97
98  for (base::DictionaryValue::Iterator it(*dict); !it.IsAtEnd(); it.Advance()) {
99    // Validate the "type" attribute, which may be a string or a list.
100    if (it.key() == schema::kType) {
101      switch (it.value().GetType()) {
102        case base::Value::TYPE_STRING:
103          it.value().GetAsString(&string_value);
104          if (!IsValidType(string_value)) {
105            *error = "Invalid value for type attribute";
106            return false;
107          }
108          break;
109        case base::Value::TYPE_LIST:
110          it.value().GetAsList(&list_value);
111          for (size_t i = 0; i < list_value->GetSize(); ++i) {
112            if (!list_value->GetString(i, &string_value) ||
113                !IsValidType(string_value)) {
114              *error = "Invalid value for type attribute";
115              return false;
116            }
117          }
118          break;
119        default:
120          *error = "Invalid value for type attribute";
121          return false;
122      }
123      has_type_or_ref = true;
124      continue;
125    }
126
127    // Validate the "items" attribute, which is a schema or a list of schemas.
128    if (it.key() == schema::kItems) {
129      if (it.value().GetAsDictionary(&dictionary_value)) {
130        if (!IsValidSchema(dictionary_value, options, error)) {
131          DCHECK(!error->empty());
132          return false;
133        }
134      } else if (it.value().GetAsList(&list_value)) {
135        for (size_t i = 0; i < list_value->GetSize(); ++i) {
136          if (!list_value->GetDictionary(i, &dictionary_value)) {
137            *error = base::StringPrintf(
138                "Invalid entry in items attribute at index %d",
139                static_cast<int>(i));
140            return false;
141          }
142          if (!IsValidSchema(dictionary_value, options, error)) {
143            DCHECK(!error->empty());
144            return false;
145          }
146        }
147      } else {
148        *error = "Invalid value for items attribute";
149        return false;
150      }
151      continue;
152    }
153
154    // All the other attributes have a single valid type.
155    const ExpectedType* end = kExpectedTypes + arraysize(kExpectedTypes);
156    const ExpectedType* entry = std::lower_bound(
157        kExpectedTypes, end, it.key(), CompareToString);
158    if (entry == end || entry->key != it.key()) {
159      if (options & JSONSchemaValidator::OPTIONS_IGNORE_UNKNOWN_ATTRIBUTES)
160        continue;
161      *error = base::StringPrintf("Invalid attribute %s", it.key().c_str());
162      return false;
163    }
164
165    if (!it.value().IsType(entry->type)) {
166      *error = base::StringPrintf("Invalid value for %s attribute",
167                                  it.key().c_str());
168      return false;
169    }
170
171    // base::Value::TYPE_INTEGER attributes must be >= 0.
172    // This applies to "minItems", "maxItems", "minLength" and "maxLength".
173    if (it.value().IsType(base::Value::TYPE_INTEGER)) {
174      int integer_value;
175      it.value().GetAsInteger(&integer_value);
176      if (integer_value < 0) {
177        *error = base::StringPrintf("Value of %s must be >= 0, got %d",
178                                    it.key().c_str(), integer_value);
179        return false;
180      }
181    }
182
183    // Validate the "properties" attribute. Each entry maps a key to a schema.
184    if (it.key() == schema::kProperties) {
185      it.value().GetAsDictionary(&dictionary_value);
186      for (base::DictionaryValue::Iterator it(*dictionary_value);
187           !it.IsAtEnd(); it.Advance()) {
188        if (!it.value().GetAsDictionary(&dictionary_value)) {
189          *error = "Invalid value for properties attribute";
190          return false;
191        }
192        if (!IsValidSchema(dictionary_value, options, error)) {
193          DCHECK(!error->empty());
194          return false;
195        }
196      }
197    }
198
199    // Validate "additionalProperties" attribute, which is a schema.
200    if (it.key() == schema::kAdditionalProperties) {
201      it.value().GetAsDictionary(&dictionary_value);
202      if (!IsValidSchema(dictionary_value, options, error)) {
203        DCHECK(!error->empty());
204        return false;
205      }
206    }
207
208    // Validate the values contained in an "enum" attribute.
209    if (it.key() == schema::kEnum) {
210      it.value().GetAsList(&list_value);
211      for (size_t i = 0; i < list_value->GetSize(); ++i) {
212        const base::Value* value = NULL;
213        list_value->Get(i, &value);
214        // Sometimes the enum declaration is a dictionary with the enum value
215        // under "name".
216        value = ExtractNameFromDictionary(value);
217        if (!value) {
218          *error = "Invalid value in enum attribute";
219          return false;
220        }
221        switch (value->GetType()) {
222          case base::Value::TYPE_NULL:
223          case base::Value::TYPE_BOOLEAN:
224          case base::Value::TYPE_INTEGER:
225          case base::Value::TYPE_DOUBLE:
226          case base::Value::TYPE_STRING:
227            break;
228          default:
229            *error = "Invalid value in enum attribute";
230            return false;
231        }
232      }
233    }
234
235    // Validate the schemas contained in a "choices" attribute.
236    if (it.key() == schema::kChoices) {
237      it.value().GetAsList(&list_value);
238      for (size_t i = 0; i < list_value->GetSize(); ++i) {
239        if (!list_value->GetDictionary(i, &dictionary_value)) {
240          *error = "Invalid choices attribute";
241          return false;
242        }
243        if (!IsValidSchema(dictionary_value, options, error)) {
244          DCHECK(!error->empty());
245          return false;
246        }
247      }
248    }
249
250    if (it.key() == schema::kRef)
251      has_type_or_ref = true;
252  }
253
254  if (!has_type_or_ref) {
255    *error = "Schema must have a type or a $ref attribute";
256    return false;
257  }
258
259  return true;
260}
261
262}  // namespace
263
264
265JSONSchemaValidator::Error::Error() {
266}
267
268JSONSchemaValidator::Error::Error(const std::string& message)
269    : path(message) {
270}
271
272JSONSchemaValidator::Error::Error(const std::string& path,
273                                  const std::string& message)
274    : path(path), message(message) {
275}
276
277
278const char JSONSchemaValidator::kUnknownTypeReference[] =
279    "Unknown schema reference: *.";
280const char JSONSchemaValidator::kInvalidChoice[] =
281    "Value does not match any valid type choices.";
282const char JSONSchemaValidator::kInvalidEnum[] =
283    "Value does not match any valid enum choices.";
284const char JSONSchemaValidator::kObjectPropertyIsRequired[] =
285    "Property is required.";
286const char JSONSchemaValidator::kUnexpectedProperty[] =
287    "Unexpected property.";
288const char JSONSchemaValidator::kArrayMinItems[] =
289    "Array must have at least * items.";
290const char JSONSchemaValidator::kArrayMaxItems[] =
291    "Array must not have more than * items.";
292const char JSONSchemaValidator::kArrayItemRequired[] =
293    "Item is required.";
294const char JSONSchemaValidator::kStringMinLength[] =
295    "String must be at least * characters long.";
296const char JSONSchemaValidator::kStringMaxLength[] =
297    "String must not be more than * characters long.";
298const char JSONSchemaValidator::kStringPattern[] =
299    "String must match the pattern: *.";
300const char JSONSchemaValidator::kNumberMinimum[] =
301    "Value must not be less than *.";
302const char JSONSchemaValidator::kNumberMaximum[] =
303    "Value must not be greater than *.";
304const char JSONSchemaValidator::kInvalidType[] =
305    "Expected '*' but got '*'.";
306const char JSONSchemaValidator::kInvalidTypeIntegerNumber[] =
307    "Expected 'integer' but got 'number', consider using Math.round().";
308
309
310// static
311std::string JSONSchemaValidator::GetJSONSchemaType(const base::Value* value) {
312  switch (value->GetType()) {
313    case base::Value::TYPE_NULL:
314      return schema::kNull;
315    case base::Value::TYPE_BOOLEAN:
316      return schema::kBoolean;
317    case base::Value::TYPE_INTEGER:
318      return schema::kInteger;
319    case base::Value::TYPE_DOUBLE: {
320      double double_value = 0;
321      value->GetAsDouble(&double_value);
322      if (std::abs(double_value) <= std::pow(2.0, DBL_MANT_DIG) &&
323          double_value == floor(double_value)) {
324        return schema::kInteger;
325      } else {
326        return schema::kNumber;
327      }
328    }
329    case base::Value::TYPE_STRING:
330      return schema::kString;
331    case base::Value::TYPE_DICTIONARY:
332      return schema::kObject;
333    case base::Value::TYPE_LIST:
334      return schema::kArray;
335    default:
336      NOTREACHED() << "Unexpected value type: " << value->GetType();
337      return std::string();
338  }
339}
340
341// static
342std::string JSONSchemaValidator::FormatErrorMessage(const std::string& format,
343                                                    const std::string& s1) {
344  std::string ret_val = format;
345  ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s1);
346  return ret_val;
347}
348
349// static
350std::string JSONSchemaValidator::FormatErrorMessage(const std::string& format,
351                                                    const std::string& s1,
352                                                    const std::string& s2) {
353  std::string ret_val = format;
354  ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s1);
355  ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s2);
356  return ret_val;
357}
358
359// static
360scoped_ptr<base::DictionaryValue> JSONSchemaValidator::IsValidSchema(
361    const std::string& schema,
362    std::string* error) {
363  return JSONSchemaValidator::IsValidSchema(schema, 0, error);
364}
365
366// static
367scoped_ptr<base::DictionaryValue> JSONSchemaValidator::IsValidSchema(
368    const std::string& schema,
369    int validator_options,
370    std::string* error) {
371  base::JSONParserOptions json_options = base::JSON_PARSE_RFC;
372  scoped_ptr<base::Value> json(
373      base::JSONReader::ReadAndReturnError(schema, json_options, NULL, error));
374  if (!json)
375    return scoped_ptr<base::DictionaryValue>();
376  base::DictionaryValue* dict = NULL;
377  if (!json->GetAsDictionary(&dict)) {
378    *error = "Schema must be a JSON object";
379    return scoped_ptr<base::DictionaryValue>();
380  }
381  if (!::IsValidSchema(dict, validator_options, error))
382    return scoped_ptr<base::DictionaryValue>();
383  ignore_result(json.release());
384  return make_scoped_ptr(dict);
385}
386
387JSONSchemaValidator::JSONSchemaValidator(base::DictionaryValue* schema)
388    : schema_root_(schema), default_allow_additional_properties_(false) {
389}
390
391JSONSchemaValidator::JSONSchemaValidator(base::DictionaryValue* schema,
392                                         base::ListValue* types)
393    : schema_root_(schema), default_allow_additional_properties_(false) {
394  if (!types)
395    return;
396
397  for (size_t i = 0; i < types->GetSize(); ++i) {
398    base::DictionaryValue* type = NULL;
399    CHECK(types->GetDictionary(i, &type));
400
401    std::string id;
402    CHECK(type->GetString(schema::kId, &id));
403
404    CHECK(types_.find(id) == types_.end());
405    types_[id] = type;
406  }
407}
408
409JSONSchemaValidator::~JSONSchemaValidator() {}
410
411bool JSONSchemaValidator::Validate(const base::Value* instance) {
412  errors_.clear();
413  Validate(instance, schema_root_, std::string());
414  return errors_.empty();
415}
416
417void JSONSchemaValidator::Validate(const base::Value* instance,
418                                   const base::DictionaryValue* schema,
419                                   const std::string& path) {
420  // If this schema defines itself as reference type, save it in this.types.
421  std::string id;
422  if (schema->GetString(schema::kId, &id)) {
423    TypeMap::iterator iter = types_.find(id);
424    if (iter == types_.end())
425      types_[id] = schema;
426    else
427      DCHECK(iter->second == schema);
428  }
429
430  // If the schema has a $ref property, the instance must validate against
431  // that schema. It must be present in types_ to be referenced.
432  std::string ref;
433  if (schema->GetString(schema::kRef, &ref)) {
434    TypeMap::iterator type = types_.find(ref);
435    if (type == types_.end()) {
436      errors_.push_back(
437          Error(path, FormatErrorMessage(kUnknownTypeReference, ref)));
438    } else {
439      Validate(instance, type->second, path);
440    }
441    return;
442  }
443
444  // If the schema has a choices property, the instance must validate against at
445  // least one of the items in that array.
446  const base::ListValue* choices = NULL;
447  if (schema->GetList(schema::kChoices, &choices)) {
448    ValidateChoices(instance, choices, path);
449    return;
450  }
451
452  // If the schema has an enum property, the instance must be one of those
453  // values.
454  const base::ListValue* enumeration = NULL;
455  if (schema->GetList(schema::kEnum, &enumeration)) {
456    ValidateEnum(instance, enumeration, path);
457    return;
458  }
459
460  std::string type;
461  schema->GetString(schema::kType, &type);
462  CHECK(!type.empty());
463  if (type != schema::kAny) {
464    if (!ValidateType(instance, type, path))
465      return;
466
467    // These casts are safe because of checks in ValidateType().
468    if (type == schema::kObject) {
469      ValidateObject(static_cast<const base::DictionaryValue*>(instance),
470                     schema,
471                     path);
472    } else if (type == schema::kArray) {
473      ValidateArray(static_cast<const base::ListValue*>(instance),
474                    schema, path);
475    } else if (type == schema::kString) {
476      // Intentionally NOT downcasting to StringValue*. TYPE_STRING only implies
477      // GetAsString() can safely be carried out, not that it's a StringValue.
478      ValidateString(instance, schema, path);
479    } else if (type == schema::kNumber || type == schema::kInteger) {
480      ValidateNumber(instance, schema, path);
481    } else if (type != schema::kBoolean && type != schema::kNull) {
482      NOTREACHED() << "Unexpected type: " << type;
483    }
484  }
485}
486
487void JSONSchemaValidator::ValidateChoices(const base::Value* instance,
488                                          const base::ListValue* choices,
489                                          const std::string& path) {
490  size_t original_num_errors = errors_.size();
491
492  for (size_t i = 0; i < choices->GetSize(); ++i) {
493    const base::DictionaryValue* choice = NULL;
494    CHECK(choices->GetDictionary(i, &choice));
495
496    Validate(instance, choice, path);
497    if (errors_.size() == original_num_errors)
498      return;
499
500    // We discard the error from each choice. We only want to know if any of the
501    // validations succeeded.
502    errors_.resize(original_num_errors);
503  }
504
505  // Now add a generic error that no choices matched.
506  errors_.push_back(Error(path, kInvalidChoice));
507  return;
508}
509
510void JSONSchemaValidator::ValidateEnum(const base::Value* instance,
511                                       const base::ListValue* choices,
512                                       const std::string& path) {
513  for (size_t i = 0; i < choices->GetSize(); ++i) {
514    const base::Value* choice = NULL;
515    CHECK(choices->Get(i, &choice));
516    // Sometimes the enum declaration is a dictionary with the enum value under
517    // "name".
518    choice = ExtractNameFromDictionary(choice);
519    if (!choice) {
520      NOTREACHED();
521    }
522    switch (choice->GetType()) {
523      case base::Value::TYPE_NULL:
524      case base::Value::TYPE_BOOLEAN:
525      case base::Value::TYPE_STRING:
526        if (instance->Equals(choice))
527          return;
528        break;
529
530      case base::Value::TYPE_INTEGER:
531      case base::Value::TYPE_DOUBLE:
532        if (instance->IsType(base::Value::TYPE_INTEGER) ||
533            instance->IsType(base::Value::TYPE_DOUBLE)) {
534          if (GetNumberValue(choice) == GetNumberValue(instance))
535            return;
536        }
537        break;
538
539      default:
540       NOTREACHED() << "Unexpected type in enum: " << choice->GetType();
541    }
542  }
543
544  errors_.push_back(Error(path, kInvalidEnum));
545}
546
547void JSONSchemaValidator::ValidateObject(const base::DictionaryValue* instance,
548                                         const base::DictionaryValue* schema,
549                                         const std::string& path) {
550  const base::DictionaryValue* properties = NULL;
551  schema->GetDictionary(schema::kProperties, &properties);
552  if (properties) {
553    for (base::DictionaryValue::Iterator it(*properties); !it.IsAtEnd();
554         it.Advance()) {
555      std::string prop_path = path.empty() ? it.key() : (path + "." + it.key());
556      const base::DictionaryValue* prop_schema = NULL;
557      CHECK(it.value().GetAsDictionary(&prop_schema));
558
559      const base::Value* prop_value = NULL;
560      if (instance->Get(it.key(), &prop_value)) {
561        Validate(prop_value, prop_schema, prop_path);
562      } else {
563        // Properties are required unless there is an optional field set to
564        // 'true'.
565        bool is_optional = false;
566        prop_schema->GetBoolean(schema::kOptional, &is_optional);
567        if (!is_optional) {
568          errors_.push_back(Error(prop_path, kObjectPropertyIsRequired));
569        }
570      }
571    }
572  }
573
574  const base::DictionaryValue* additional_properties_schema = NULL;
575  if (SchemaAllowsAnyAdditionalItems(schema, &additional_properties_schema))
576    return;
577
578  // Validate additional properties.
579  for (base::DictionaryValue::Iterator it(*instance); !it.IsAtEnd();
580       it.Advance()) {
581    if (properties && properties->HasKey(it.key()))
582      continue;
583
584    std::string prop_path = path.empty() ? it.key() : path + "." + it.key();
585    if (!additional_properties_schema) {
586      errors_.push_back(Error(prop_path, kUnexpectedProperty));
587    } else {
588      Validate(&it.value(), additional_properties_schema, prop_path);
589    }
590  }
591}
592
593void JSONSchemaValidator::ValidateArray(const base::ListValue* instance,
594                                        const base::DictionaryValue* schema,
595                                        const std::string& path) {
596  const base::DictionaryValue* single_type = NULL;
597  size_t instance_size = instance->GetSize();
598  if (schema->GetDictionary(schema::kItems, &single_type)) {
599    int min_items = 0;
600    if (schema->GetInteger(schema::kMinItems, &min_items)) {
601      CHECK(min_items >= 0);
602      if (instance_size < static_cast<size_t>(min_items)) {
603        errors_.push_back(Error(path, FormatErrorMessage(
604            kArrayMinItems, base::IntToString(min_items))));
605      }
606    }
607
608    int max_items = 0;
609    if (schema->GetInteger(schema::kMaxItems, &max_items)) {
610      CHECK(max_items >= 0);
611      if (instance_size > static_cast<size_t>(max_items)) {
612        errors_.push_back(Error(path, FormatErrorMessage(
613            kArrayMaxItems, base::IntToString(max_items))));
614      }
615    }
616
617    // If the items property is a single schema, each item in the array must
618    // validate against that schema.
619    for (size_t i = 0; i < instance_size; ++i) {
620      const base::Value* item = NULL;
621      CHECK(instance->Get(i, &item));
622      std::string i_str = base::Uint64ToString(i);
623      std::string item_path = path.empty() ? i_str : (path + "." + i_str);
624      Validate(item, single_type, item_path);
625    }
626
627    return;
628  }
629
630  // Otherwise, the list must be a tuple type, where each item in the list has a
631  // particular schema.
632  ValidateTuple(instance, schema, path);
633}
634
635void JSONSchemaValidator::ValidateTuple(const base::ListValue* instance,
636                                        const base::DictionaryValue* schema,
637                                        const std::string& path) {
638  const base::ListValue* tuple_type = NULL;
639  schema->GetList(schema::kItems, &tuple_type);
640  size_t tuple_size = tuple_type ? tuple_type->GetSize() : 0;
641  if (tuple_type) {
642    for (size_t i = 0; i < tuple_size; ++i) {
643      std::string i_str = base::Uint64ToString(i);
644      std::string item_path = path.empty() ? i_str : (path + "." + i_str);
645      const base::DictionaryValue* item_schema = NULL;
646      CHECK(tuple_type->GetDictionary(i, &item_schema));
647      const base::Value* item_value = NULL;
648      instance->Get(i, &item_value);
649      if (item_value && item_value->GetType() != base::Value::TYPE_NULL) {
650        Validate(item_value, item_schema, item_path);
651      } else {
652        bool is_optional = false;
653        item_schema->GetBoolean(schema::kOptional, &is_optional);
654        if (!is_optional) {
655          errors_.push_back(Error(item_path, kArrayItemRequired));
656          return;
657        }
658      }
659    }
660  }
661
662  const base::DictionaryValue* additional_properties_schema = NULL;
663  if (SchemaAllowsAnyAdditionalItems(schema, &additional_properties_schema))
664    return;
665
666  size_t instance_size = instance->GetSize();
667  if (additional_properties_schema) {
668    // Any additional properties must validate against the additionalProperties
669    // schema.
670    for (size_t i = tuple_size; i < instance_size; ++i) {
671      std::string i_str = base::Uint64ToString(i);
672      std::string item_path = path.empty() ? i_str : (path + "." + i_str);
673      const base::Value* item_value = NULL;
674      CHECK(instance->Get(i, &item_value));
675      Validate(item_value, additional_properties_schema, item_path);
676    }
677  } else if (instance_size > tuple_size) {
678    errors_.push_back(Error(path, FormatErrorMessage(
679        kArrayMaxItems, base::Uint64ToString(tuple_size))));
680  }
681}
682
683void JSONSchemaValidator::ValidateString(const base::Value* instance,
684                                         const base::DictionaryValue* schema,
685                                         const std::string& path) {
686  std::string value;
687  CHECK(instance->GetAsString(&value));
688
689  int min_length = 0;
690  if (schema->GetInteger(schema::kMinLength, &min_length)) {
691    CHECK(min_length >= 0);
692    if (value.size() < static_cast<size_t>(min_length)) {
693      errors_.push_back(Error(path, FormatErrorMessage(
694          kStringMinLength, base::IntToString(min_length))));
695    }
696  }
697
698  int max_length = 0;
699  if (schema->GetInteger(schema::kMaxLength, &max_length)) {
700    CHECK(max_length >= 0);
701    if (value.size() > static_cast<size_t>(max_length)) {
702      errors_.push_back(Error(path, FormatErrorMessage(
703          kStringMaxLength, base::IntToString(max_length))));
704    }
705  }
706
707  CHECK(!schema->HasKey(schema::kPattern)) << "Pattern is not supported.";
708}
709
710void JSONSchemaValidator::ValidateNumber(const base::Value* instance,
711                                         const base::DictionaryValue* schema,
712                                         const std::string& path) {
713  double value = GetNumberValue(instance);
714
715  // TODO(aa): It would be good to test that the double is not infinity or nan,
716  // but isnan and isinf aren't defined on Windows.
717
718  double minimum = 0;
719  if (schema->GetDouble(schema::kMinimum, &minimum)) {
720    if (value < minimum)
721      errors_.push_back(Error(path, FormatErrorMessage(
722          kNumberMinimum, base::DoubleToString(minimum))));
723  }
724
725  double maximum = 0;
726  if (schema->GetDouble(schema::kMaximum, &maximum)) {
727    if (value > maximum)
728      errors_.push_back(Error(path, FormatErrorMessage(
729          kNumberMaximum, base::DoubleToString(maximum))));
730  }
731}
732
733bool JSONSchemaValidator::ValidateType(const base::Value* instance,
734                                       const std::string& expected_type,
735                                       const std::string& path) {
736  std::string actual_type = GetJSONSchemaType(instance);
737  if (expected_type == actual_type ||
738      (expected_type == schema::kNumber && actual_type == schema::kInteger)) {
739    return true;
740  } else if (expected_type == schema::kInteger &&
741             actual_type == schema::kNumber) {
742    errors_.push_back(Error(path, kInvalidTypeIntegerNumber));
743    return false;
744  } else {
745    errors_.push_back(Error(path, FormatErrorMessage(
746        kInvalidType, expected_type, actual_type)));
747    return false;
748  }
749}
750
751bool JSONSchemaValidator::SchemaAllowsAnyAdditionalItems(
752    const base::DictionaryValue* schema,
753    const base::DictionaryValue** additional_properties_schema) {
754  // If the validator allows additional properties globally, and this schema
755  // doesn't override, then we can exit early.
756  schema->GetDictionary(schema::kAdditionalProperties,
757                        additional_properties_schema);
758
759  if (*additional_properties_schema) {
760    std::string additional_properties_type(schema::kAny);
761    CHECK((*additional_properties_schema)->GetString(
762        schema::kType, &additional_properties_type));
763    return additional_properties_type == schema::kAny;
764  } else {
765    return default_allow_additional_properties_;
766  }
767}
768