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