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

Side by Side Diff: chrome/renderer/resources/json_schema.js

Issue 7980055: Move bindings javascript into its own directory. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 9 years, 3 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
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 // -----------------------------------------------------------------------------
6 // NOTE: If you change this file you need to touch renderer_resources.grd to
7 // have your change take effect.
8 // -----------------------------------------------------------------------------
9
10 //==============================================================================
11 // This file contains a class that implements a subset of JSON Schema.
12 // See: http://www.json.com/json-schema-proposal/ for more details.
13 //
14 // The following features of JSON Schema are not implemented:
15 // - requires
16 // - unique
17 // - disallow
18 // - union types (but replaced with 'choices')
19 //
20 // The following properties are not applicable to the interface exposed by
21 // this class:
22 // - options
23 // - readonly
24 // - title
25 // - description
26 // - format
27 // - default
28 // - transient
29 // - hidden
30 //
31 // There are also these departures from the JSON Schema proposal:
32 // - function and undefined types are supported
33 // - null counts as 'unspecified' for optional values
34 // - added the 'choices' property, to allow specifying a list of possible types
35 // for a value
36 // - by default an "object" typed schema does not allow additional properties.
37 // if present, "additionalProperties" is to be a schema against which all
38 // additional properties will be validated.
39 //==============================================================================
40
41 (function() {
42 native function GetChromeHidden();
43 var chromeHidden = GetChromeHidden();
44
45 /**
46 * Validates an instance against a schema and accumulates errors. Usage:
47 *
48 * var validator = new chromeHidden.JSONSchemaValidator();
49 * validator.validate(inst, schema);
50 * if (validator.errors.length == 0)
51 * console.log("Valid!");
52 * else
53 * console.log(validator.errors);
54 *
55 * The errors property contains a list of objects. Each object has two
56 * properties: "path" and "message". The "path" property contains the path to
57 * the key that had the problem, and the "message" property contains a sentence
58 * describing the error.
59 */
60 chromeHidden.JSONSchemaValidator = function() {
61 this.errors = [];
62 this.types = [];
63 };
64
65 chromeHidden.JSONSchemaValidator.messages = {
66 invalidEnum: "Value must be one of: [*].",
67 propertyRequired: "Property is required.",
68 unexpectedProperty: "Unexpected property.",
69 arrayMinItems: "Array must have at least * items.",
70 arrayMaxItems: "Array must not have more than * items.",
71 itemRequired: "Item is required.",
72 stringMinLength: "String must be at least * characters long.",
73 stringMaxLength: "String must not be more than * characters long.",
74 stringPattern: "String must match the pattern: *.",
75 numberFiniteNotNan: "Value must not be *.",
76 numberMinValue: "Value must not be less than *.",
77 numberMaxValue: "Value must not be greater than *.",
78 numberIntValue: "Value must fit in a 32-bit signed integer.",
79 numberMaxDecimal: "Value must not have more than * decimal places.",
80 invalidType: "Expected '*' but got '*'.",
81 invalidChoice: "Value does not match any valid type choices.",
82 invalidPropertyType: "Missing property type.",
83 schemaRequired: "Schema value required.",
84 unknownSchemaReference: "Unknown schema reference: *.",
85 notInstance: "Object must be an instance of *."
86 };
87
88 /**
89 * Builds an error message. Key is the property in the |errors| object, and
90 * |opt_replacements| is an array of values to replace "*" characters with.
91 */
92 chromeHidden.JSONSchemaValidator.formatError = function(key, opt_replacements) {
93 var message = this.messages[key];
94 if (opt_replacements) {
95 for (var i = 0; i < opt_replacements.length; i++) {
96 message = message.replace("*", opt_replacements[i]);
97 }
98 }
99 return message;
100 };
101
102 /**
103 * Classifies a value as one of the JSON schema primitive types. Note that we
104 * don't explicitly disallow 'function', because we want to allow functions in
105 * the input values.
106 */
107 chromeHidden.JSONSchemaValidator.getType = function(value) {
108 var s = typeof value;
109
110 if (s == "object") {
111 if (value === null) {
112 return "null";
113 } else if (Object.prototype.toString.call(value) == "[object Array]") {
114 return "array";
115 }
116 } else if (s == "number") {
117 if (value % 1 == 0) {
118 return "integer";
119 }
120 }
121
122 return s;
123 };
124
125 /**
126 * Add types that may be referenced by validated schemas that reference them
127 * with "$ref": <typeId>. Each type must be a valid schema and define an
128 * "id" property.
129 */
130 chromeHidden.JSONSchemaValidator.prototype.addTypes = function(typeOrTypeList) {
131 function addType(validator, type) {
132 if(!type.id)
133 throw "Attempt to addType with missing 'id' property";
134 validator.types[type.id] = type;
135 }
136
137 if (typeOrTypeList instanceof Array) {
138 for (var i = 0; i < typeOrTypeList.length; i++) {
139 addType(this, typeOrTypeList[i]);
140 }
141 } else {
142 addType(this, typeOrTypeList);
143 }
144 }
145
146 /**
147 * Validates an instance against a schema. The instance can be any JavaScript
148 * value and will be validated recursively. When this method returns, the
149 * |errors| property will contain a list of errors, if any.
150 */
151 chromeHidden.JSONSchemaValidator.prototype.validate = function(
152 instance, schema, opt_path) {
153 var path = opt_path || "";
154
155 if (!schema) {
156 this.addError(path, "schemaRequired");
157 return;
158 }
159
160 // If this schema defines itself as reference type, save it in this.types.
161 if (schema.id)
162 this.types[schema.id] = schema;
163
164 // If the schema has an extends property, the instance must validate against
165 // that schema too.
166 if (schema.extends)
167 this.validate(instance, schema.extends, path);
168
169 // If the schema has a $ref property, the instance must validate against
170 // that schema too. It must be present in this.types to be referenced.
171 if (schema["$ref"]) {
172 if (!this.types[schema["$ref"]])
173 this.addError(path, "unknownSchemaReference", [ schema["$ref"] ]);
174 else
175 this.validate(instance, this.types[schema["$ref"]], path)
176 }
177
178 // If the schema has a choices property, the instance must validate against at
179 // least one of the items in that array.
180 if (schema.choices) {
181 this.validateChoices(instance, schema, path);
182 return;
183 }
184
185 // If the schema has an enum property, the instance must be one of those
186 // values.
187 if (schema.enum) {
188 if (!this.validateEnum(instance, schema, path))
189 return;
190 }
191
192 if (schema.type && schema.type != "any") {
193 if (!this.validateType(instance, schema, path))
194 return;
195
196 // Type-specific validation.
197 switch (schema.type) {
198 case "object":
199 this.validateObject(instance, schema, path);
200 break;
201 case "array":
202 this.validateArray(instance, schema, path);
203 break;
204 case "string":
205 this.validateString(instance, schema, path);
206 break;
207 case "number":
208 case "integer":
209 this.validateNumber(instance, schema, path);
210 break;
211 }
212 }
213 };
214
215 /**
216 * Validates an instance against a choices schema. The instance must match at
217 * least one of the provided choices.
218 */
219 chromeHidden.JSONSchemaValidator.prototype.validateChoices = function(
220 instance, schema, path) {
221 var originalErrors = this.errors;
222
223 for (var i = 0; i < schema.choices.length; i++) {
224 this.errors = [];
225 this.validate(instance, schema.choices[i], path);
226 if (this.errors.length == 0) {
227 this.errors = originalErrors;
228 return;
229 }
230 }
231
232 this.errors = originalErrors;
233 this.addError(path, "invalidChoice");
234 };
235
236 /**
237 * Validates an instance against a schema with an enum type. Populates the
238 * |errors| property, and returns a boolean indicating whether the instance
239 * validates.
240 */
241 chromeHidden.JSONSchemaValidator.prototype.validateEnum = function(
242 instance, schema, path) {
243 for (var i = 0; i < schema.enum.length; i++) {
244 if (instance === schema.enum[i])
245 return true;
246 }
247
248 this.addError(path, "invalidEnum", [schema.enum.join(", ")]);
249 return false;
250 };
251
252 /**
253 * Validates an instance against an object schema and populates the errors
254 * property.
255 */
256 chromeHidden.JSONSchemaValidator.prototype.validateObject = function(
257 instance, schema, path) {
258 for (var prop in schema.properties) {
259 // It is common in JavaScript to add properties to Object.prototype. This
260 // check prevents such additions from being interpreted as required schema
261 // properties.
262 // TODO(aa): If it ever turns out that we actually want this to work, there
263 // are other checks we could put here, like requiring that schema properties
264 // be objects that have a 'type' property.
265 if (!schema.properties.hasOwnProperty(prop))
266 continue;
267
268 var propPath = path ? path + "." + prop : prop;
269 if (schema.properties[prop] == undefined) {
270 this.addError(propPath, "invalidPropertyType");
271 } else if (prop in instance && instance[prop] !== undefined) {
272 this.validate(instance[prop], schema.properties[prop], propPath);
273 } else if (!schema.properties[prop].optional) {
274 this.addError(propPath, "propertyRequired");
275 }
276 }
277
278 // If "instanceof" property is set, check that this object inherits from
279 // the specified constructor (function).
280 if (schema.isInstanceOf) {
281 var isInstance = function() {
282 var constructor = this[schema.isInstanceOf];
283 if (constructor) {
284 return (instance instanceof constructor);
285 }
286
287 // Special-case constructors that can not always be found on the global
288 // object, but for which we to allow validation.
289 var allowedNamedConstructors = {
290 "DOMWindow": true,
291 "ImageData": true
292 }
293 if (!allowedNamedConstructors[schema.isInstanceOf]) {
294 throw "Attempt to validate against an instance ctor that could not be" +
295 "found: " + schema.isInstanceOf;
296 }
297 return (schema.isInstanceOf == instance.constructor.name)
298 }();
299
300 if (!isInstance)
301 this.addError(propPath, "notInstance", [schema.isInstanceOf]);
302 }
303
304 // Exit early from additional property check if "type":"any" is defined.
305 if (schema.additionalProperties &&
306 schema.additionalProperties.type &&
307 schema.additionalProperties.type == "any") {
308 return;
309 }
310
311 // By default, additional properties are not allowed on instance objects. This
312 // can be overridden by setting the additionalProperties property to a schema
313 // which any additional properties must validate against.
314 for (var prop in instance) {
315 if (prop in schema.properties)
316 continue;
317
318 // Any properties inherited through the prototype are ignored.
319 if (!instance.hasOwnProperty(prop))
320 continue;
321
322 var propPath = path ? path + "." + prop : prop;
323 if (schema.additionalProperties)
324 this.validate(instance[prop], schema.additionalProperties, propPath);
325 else
326 this.addError(propPath, "unexpectedProperty");
327 }
328 };
329
330 /**
331 * Validates an instance against an array schema and populates the errors
332 * property.
333 */
334 chromeHidden.JSONSchemaValidator.prototype.validateArray = function(
335 instance, schema, path) {
336 var typeOfItems = chromeHidden.JSONSchemaValidator.getType(schema.items);
337
338 if (typeOfItems == 'object') {
339 if (schema.minItems && instance.length < schema.minItems) {
340 this.addError(path, "arrayMinItems", [schema.minItems]);
341 }
342
343 if (typeof schema.maxItems != "undefined" &&
344 instance.length > schema.maxItems) {
345 this.addError(path, "arrayMaxItems", [schema.maxItems]);
346 }
347
348 // If the items property is a single schema, each item in the array must
349 // have that schema.
350 for (var i = 0; i < instance.length; i++) {
351 this.validate(instance[i], schema.items, path + "." + i);
352 }
353 } else if (typeOfItems == 'array') {
354 // If the items property is an array of schemas, each item in the array must
355 // validate against the corresponding schema.
356 for (var i = 0; i < schema.items.length; i++) {
357 var itemPath = path ? path + "." + i : String(i);
358 if (i in instance && instance[i] !== null && instance[i] !== undefined) {
359 this.validate(instance[i], schema.items[i], itemPath);
360 } else if (!schema.items[i].optional) {
361 this.addError(itemPath, "itemRequired");
362 }
363 }
364
365 if (schema.additionalProperties) {
366 for (var i = schema.items.length; i < instance.length; i++) {
367 var itemPath = path ? path + "." + i : String(i);
368 this.validate(instance[i], schema.additionalProperties, itemPath);
369 }
370 } else {
371 if (instance.length > schema.items.length) {
372 this.addError(path, "arrayMaxItems", [schema.items.length]);
373 }
374 }
375 }
376 };
377
378 /**
379 * Validates a string and populates the errors property.
380 */
381 chromeHidden.JSONSchemaValidator.prototype.validateString = function(
382 instance, schema, path) {
383 if (schema.minLength && instance.length < schema.minLength)
384 this.addError(path, "stringMinLength", [schema.minLength]);
385
386 if (schema.maxLength && instance.length > schema.maxLength)
387 this.addError(path, "stringMaxLength", [schema.maxLength]);
388
389 if (schema.pattern && !schema.pattern.test(instance))
390 this.addError(path, "stringPattern", [schema.pattern]);
391 };
392
393 /**
394 * Validates a number and populates the errors property. The instance is
395 * assumed to be a number.
396 */
397 chromeHidden.JSONSchemaValidator.prototype.validateNumber = function(
398 instance, schema, path) {
399
400 // Forbid NaN, +Infinity, and -Infinity. Our APIs don't use them, and
401 // JSON serialization encodes them as 'null'. Re-evaluate supporting
402 // them if we add an API that could reasonably take them as a parameter.
403 if (isNaN(instance) ||
404 instance == Number.POSITIVE_INFINITY ||
405 instance == Number.NEGATIVE_INFINITY )
406 this.addError(path, "numberFiniteNotNan", [instance]);
407
408 if (schema.minimum !== undefined && instance < schema.minimum)
409 this.addError(path, "numberMinValue", [schema.minimum]);
410
411 if (schema.maximum !== undefined && instance > schema.maximum)
412 this.addError(path, "numberMaxValue", [schema.maximum]);
413
414 // Check for integer values outside of -2^31..2^31-1.
415 if (schema.type === "integer" && (instance | 0) !== instance)
416 this.addError(path, "numberIntValue", []);
417
418 if (schema.maxDecimal && instance * Math.pow(10, schema.maxDecimal) % 1)
419 this.addError(path, "numberMaxDecimal", [schema.maxDecimal]);
420 };
421
422 /**
423 * Validates the primitive type of an instance and populates the errors
424 * property. Returns true if the instance validates, false otherwise.
425 */
426 chromeHidden.JSONSchemaValidator.prototype.validateType = function(
427 instance, schema, path) {
428 var actualType = chromeHidden.JSONSchemaValidator.getType(instance);
429 if (schema.type != actualType && !(schema.type == "number" &&
430 actualType == "integer")) {
431 this.addError(path, "invalidType", [schema.type, actualType]);
432 return false;
433 }
434
435 return true;
436 };
437
438 /**
439 * Adds an error message. |key| is an index into the |messages| object.
440 * |replacements| is an array of values to replace '*' characters in the
441 * message.
442 */
443 chromeHidden.JSONSchemaValidator.prototype.addError = function(
444 path, key, replacements) {
445 this.errors.push({
446 path: path,
447 message: chromeHidden.JSONSchemaValidator.formatError(key, replacements)
448 });
449 };
450
451 })();
OLDNEW
« no previous file with comments | « chrome/renderer/resources/greasemonkey_api.js ('k') | chrome/renderer/resources/renderer_extension_bindings.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698