OLD | NEW |
---|---|
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/common/json_schema/json_schema_validator.h" | 5 #include "chrome/common/json_schema/json_schema_validator.h" |
6 | 6 |
7 #include <cfloat> | 7 #include <cfloat> |
8 #include <cmath> | 8 #include <cmath> |
9 | 9 |
10 #include "base/json/json_reader.h" | |
10 #include "base/string_util.h" | 11 #include "base/string_util.h" |
12 #include "base/stringprintf.h" | |
11 #include "base/strings/string_number_conversions.h" | 13 #include "base/strings/string_number_conversions.h" |
12 #include "base/values.h" | 14 #include "base/values.h" |
13 #include "chrome/common/json_schema/json_schema_constants.h" | 15 #include "chrome/common/json_schema/json_schema_constants.h" |
14 #include "ui/base/l10n/l10n_util.h" | 16 #include "ui/base/l10n/l10n_util.h" |
15 | 17 |
16 namespace schema = json_schema_constants; | 18 namespace schema = json_schema_constants; |
17 | 19 |
18 namespace { | 20 namespace { |
19 | 21 |
20 double GetNumberValue(const Value* value) { | 22 double GetNumberValue(const Value* value) { |
21 double result = 0; | 23 double result = 0; |
22 CHECK(value->GetAsDouble(&result)) | 24 CHECK(value->GetAsDouble(&result)) |
23 << "Unexpected value type: " << value->GetType(); | 25 << "Unexpected value type: " << value->GetType(); |
24 return result; | 26 return result; |
25 } | 27 } |
26 | 28 |
29 bool 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 bool IsValidSchema(const base::DictionaryValue* dict, std::string* error) { | |
45 static const struct { | |
46 const char* key; | |
47 base::Value::Type type; | |
48 } kExpectedTypes[] = { | |
49 { schema::kAdditionalProperties, base::Value::TYPE_DICTIONARY }, | |
50 { schema::kProperties, base::Value::TYPE_DICTIONARY }, | |
51 | |
52 { schema::kMinimum, base::Value::TYPE_DOUBLE }, | |
53 { schema::kMaximum, base::Value::TYPE_DOUBLE }, | |
54 | |
55 // All of these must be >= 0. | |
56 { schema::kMinItems, base::Value::TYPE_INTEGER }, | |
57 { schema::kMaxItems, base::Value::TYPE_INTEGER }, | |
58 { schema::kMinLength, base::Value::TYPE_INTEGER }, | |
59 { schema::kMaxLength, base::Value::TYPE_INTEGER }, | |
60 | |
61 { schema::kEnum, base::Value::TYPE_LIST }, | |
62 { schema::kChoices, base::Value::TYPE_LIST }, | |
63 | |
64 { schema::kId, base::Value::TYPE_STRING }, | |
65 { schema::kRef, base::Value::TYPE_STRING }, | |
66 { schema::kSchema, base::Value::TYPE_STRING }, | |
67 | |
68 { schema::kOptional, base::Value::TYPE_BOOLEAN }, | |
69 }; | |
70 | |
71 const base::Value* value = NULL; | |
72 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kExpectedTypes); ++i) { | |
73 if (!dict->Get(kExpectedTypes[i].key, &value)) | |
74 continue; | |
not at google - send to devlin
2013/05/16 17:08:20
the problem with this approach is that it won't in
Joao da Silva
2013/05/19 13:16:29
Good idea, done. "type" and "items" is still speci
| |
75 | |
76 if (!value->IsType(kExpectedTypes[i].type)) { | |
77 *error = base::StringPrintf("Invalid value for %s attribute", | |
78 kExpectedTypes[i].key); | |
79 return false; | |
80 } | |
81 | |
82 // This applies to "minItems", "maxItems", "minLength" and "maxLength". | |
83 int integer_value; | |
84 if (value->GetAsInteger(&integer_value) && integer_value < 0) { | |
85 *error = | |
86 base::StringPrintf("Value of %s must be >= 0", kExpectedTypes[i].key); | |
not at google - send to devlin
2013/05/16 17:08:20
might as well include integer_value in here.
Joao da Silva
2013/05/19 13:16:29
Done.
| |
87 return false; | |
88 } | |
89 } | |
90 | |
91 // Validate "type" attribute. | |
92 std::string string_value; | |
93 const base::ListValue* list_value = NULL; | |
94 if (!dict->Get(schema::kType, &value)) { | |
95 *error = "Schema must have a type attribute"; | |
96 return false; | |
97 } else if (value->GetAsString(&string_value)) { | |
not at google - send to devlin
2013/05/16 17:08:20
else unnecessary after a return.
Joao da Silva
2013/05/19 13:16:29
Done.
| |
98 if (!IsValidType(string_value)) { | |
99 *error = "Invalid value for type attribute"; | |
100 return false; | |
101 } | |
102 } else if (value->GetAsList(&list_value)) { | |
not at google - send to devlin
2013/05/16 17:08:20
a switch might be nicer
Joao da Silva
2013/05/19 13:16:29
Done.
| |
103 for (size_t i = 0; i < list_value->GetSize(); ++i) { | |
104 if (!list_value->GetString(i, &string_value) || | |
105 !IsValidType(string_value)) { | |
106 *error = "Invalid value for type attribute"; | |
107 return false; | |
108 } | |
109 } | |
110 } else { | |
111 *error = "Invalid value for type attribute"; | |
112 return false; | |
113 } | |
114 | |
115 // Validate "properties" attribute. Each entry maps a key to a schema. | |
116 const base::DictionaryValue* dictionary_value = NULL; | |
117 if (dict->GetDictionary(schema::kProperties, &dictionary_value)) { | |
118 for (base::DictionaryValue::Iterator it(*dictionary_value); | |
119 !it.IsAtEnd(); it.Advance()) { | |
120 if (!it.value().GetAsDictionary(&dictionary_value) || | |
121 !IsValidSchema(dictionary_value, error)) { | |
122 return false; | |
not at google - send to devlin
2013/05/16 17:08:20
all these times you validate IsValidSchema and the
Joao da Silva
2013/05/19 13:16:29
That's a good idea, and there's a bug here too: if
| |
123 } | |
124 } | |
125 } | |
126 | |
127 // Validate "additionalProperties" attribute, which is a schema. | |
128 if (dict->GetDictionary(schema::kAdditionalProperties, &dictionary_value) && | |
129 !IsValidSchema(dictionary_value, error)) { | |
130 return false; | |
131 } | |
132 | |
133 // Validate "items" attribute, which is a schema or a list of schemas. | |
134 if (dict->Get(schema::kItems, &value)) { | |
135 if (value->GetAsDictionary(&dictionary_value)) { | |
136 if (!IsValidSchema(dictionary_value, error)) | |
137 return false; | |
138 } else if (value->GetAsList(&list_value)) { | |
139 for (size_t i = 0; i < list_value->GetSize(); ++i) { | |
140 if (!list_value->GetDictionary(i, &dictionary_value) || | |
141 !IsValidSchema(dictionary_value, error)) { | |
142 return false; | |
143 } | |
144 } | |
145 } else { | |
146 *error = "Invalid value for items attribute"; | |
147 return false; | |
148 } | |
149 } | |
150 | |
151 // Validate the values contained in an "enum" attribute. | |
152 if (dict->GetList(schema::kEnum, &list_value)) { | |
153 for (size_t i = 0; i < list_value->GetSize(); ++i) { | |
154 list_value->Get(i, &value); | |
155 switch (value->GetType()) { | |
156 case base::Value::TYPE_NULL: case base::Value::TYPE_BOOLEAN: | |
157 case base::Value::TYPE_STRING: case base::Value::TYPE_INTEGER: | |
158 case base::Value::TYPE_DOUBLE: | |
not at google - send to devlin
2013/05/16 17:08:20
nit: 1 case per line, this looks pretty odd.
Joao da Silva
2013/05/19 13:16:29
Done.
| |
159 break; | |
160 default: | |
161 *error = "Invalid value in enum attribute"; | |
162 return false; | |
163 } | |
164 } | |
165 } | |
166 | |
167 // Validate the schemas contained in a "choices" attribute. | |
168 if (dict->GetList(schema::kChoices, &list_value)) { | |
169 for (size_t i = 0; i < list_value->GetSize(); ++i) { | |
170 if (!list_value->GetDictionary(i, &dictionary_value)) { | |
171 *error = "Invalid choices attribute"; | |
172 return false; | |
173 } else if (!IsValidSchema(dictionary_value, error)) { | |
not at google - send to devlin
2013/05/16 17:08:20
else unnecessary after a return
Joao da Silva
2013/05/19 13:16:29
Done.
| |
174 return false; | |
175 } | |
176 } | |
177 } | |
178 | |
179 | |
180 // Unsupported attributes. | |
not at google - send to devlin
2013/05/16 17:08:20
if you do the map thing right at the top then this
Joao da Silva
2013/05/19 13:16:29
Yep, done.
| |
181 if (dict->HasKey(schema::kPattern)) { | |
182 *error = "pattern attribute is not supported"; | |
183 return false; | |
184 } | |
185 | |
186 return true; | |
187 } | |
188 | |
27 } // namespace | 189 } // namespace |
28 | 190 |
29 | 191 |
30 JSONSchemaValidator::Error::Error() { | 192 JSONSchemaValidator::Error::Error() { |
31 } | 193 } |
32 | 194 |
33 JSONSchemaValidator::Error::Error(const std::string& message) | 195 JSONSchemaValidator::Error::Error(const std::string& message) |
34 : path(message) { | 196 : path(message) { |
35 } | 197 } |
36 | 198 |
(...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
114 // static | 276 // static |
115 std::string JSONSchemaValidator::FormatErrorMessage(const std::string& format, | 277 std::string JSONSchemaValidator::FormatErrorMessage(const std::string& format, |
116 const std::string& s1, | 278 const std::string& s1, |
117 const std::string& s2) { | 279 const std::string& s2) { |
118 std::string ret_val = format; | 280 std::string ret_val = format; |
119 ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s1); | 281 ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s1); |
120 ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s2); | 282 ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s2); |
121 return ret_val; | 283 return ret_val; |
122 } | 284 } |
123 | 285 |
286 // static | |
287 scoped_ptr<base::DictionaryValue> JSONSchemaValidator::IsValidSchema( | |
288 const std::string& schema, | |
289 std::string* error) { | |
290 base::JSONParserOptions options = base::JSON_PARSE_RFC; | |
291 scoped_ptr<base::Value> json( | |
292 base::JSONReader::ReadAndReturnError(schema, options, NULL, error)); | |
293 if (!json) | |
294 return scoped_ptr<base::DictionaryValue>(); | |
295 base::DictionaryValue* dict = NULL; | |
296 if (!json->GetAsDictionary(&dict)) { | |
297 *error = "Schema must be a JSON object"; | |
298 return scoped_ptr<base::DictionaryValue>(); | |
299 } | |
300 if (!::IsValidSchema(dict, error)) | |
301 return scoped_ptr<base::DictionaryValue>(); | |
302 ignore_result(json.release()); | |
303 return make_scoped_ptr(dict); | |
not at google - send to devlin
2013/05/16 17:08:20
does just
return json.PassAs<base::DictionaryValu
Joao da Silva
2013/05/19 13:16:29
It doesn't; PassAs<>() can only do upcasts.
| |
304 } | |
305 | |
124 JSONSchemaValidator::JSONSchemaValidator(DictionaryValue* schema) | 306 JSONSchemaValidator::JSONSchemaValidator(DictionaryValue* schema) |
125 : schema_root_(schema), default_allow_additional_properties_(false) { | 307 : schema_root_(schema), default_allow_additional_properties_(false) { |
126 } | 308 } |
127 | 309 |
128 JSONSchemaValidator::JSONSchemaValidator(DictionaryValue* schema, | 310 JSONSchemaValidator::JSONSchemaValidator(DictionaryValue* schema, |
129 ListValue* types) | 311 ListValue* types) |
130 : schema_root_(schema), default_allow_additional_properties_(false) { | 312 : schema_root_(schema), default_allow_additional_properties_(false) { |
131 if (!types) | 313 if (!types) |
132 return; | 314 return; |
133 | 315 |
(...skipping 353 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
487 | 669 |
488 if (*additional_properties_schema) { | 670 if (*additional_properties_schema) { |
489 std::string additional_properties_type(schema::kAny); | 671 std::string additional_properties_type(schema::kAny); |
490 CHECK((*additional_properties_schema)->GetString( | 672 CHECK((*additional_properties_schema)->GetString( |
491 schema::kType, &additional_properties_type)); | 673 schema::kType, &additional_properties_type)); |
492 return additional_properties_type == schema::kAny; | 674 return additional_properties_type == schema::kAny; |
493 } else { | 675 } else { |
494 return default_allow_additional_properties_; | 676 return default_allow_additional_properties_; |
495 } | 677 } |
496 } | 678 } |
OLD | NEW |