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

Side by Side Diff: trunk/src/extensions/renderer/resources/json_schema.js

Issue 309413002: Revert 274558 "Move some extensions renderer resources to extens..." (Closed) Base URL: svn://svn.chromium.org/chrome/
Patch Set: Created 6 years, 6 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 2014 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
7 // extension_renderer_resources.grd to 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 var loadTypeSchema = require('utils').loadTypeSchema;
42 var CHECK = requireNative('logging').CHECK;
43
44 function isInstanceOfClass(instance, className) {
45 while ((instance = instance.__proto__)) {
46 if (instance.constructor.name == className)
47 return true;
48 }
49 return false;
50 }
51
52 function isOptionalValue(value) {
53 return typeof(value) === 'undefined' || value === null;
54 }
55
56 function enumToString(enumValue) {
57 if (enumValue.name === undefined)
58 return enumValue;
59
60 return enumValue.name;
61 }
62
63 /**
64 * Validates an instance against a schema and accumulates errors. Usage:
65 *
66 * var validator = new JSONSchemaValidator();
67 * validator.validate(inst, schema);
68 * if (validator.errors.length == 0)
69 * console.log("Valid!");
70 * else
71 * console.log(validator.errors);
72 *
73 * The errors property contains a list of objects. Each object has two
74 * properties: "path" and "message". The "path" property contains the path to
75 * the key that had the problem, and the "message" property contains a sentence
76 * describing the error.
77 */
78 function JSONSchemaValidator() {
79 this.errors = [];
80 this.types = [];
81 }
82
83 JSONSchemaValidator.messages = {
84 invalidEnum: "Value must be one of: [*].",
85 propertyRequired: "Property is required.",
86 unexpectedProperty: "Unexpected property.",
87 arrayMinItems: "Array must have at least * items.",
88 arrayMaxItems: "Array must not have more than * items.",
89 itemRequired: "Item is required.",
90 stringMinLength: "String must be at least * characters long.",
91 stringMaxLength: "String must not be more than * characters long.",
92 stringPattern: "String must match the pattern: *.",
93 numberFiniteNotNan: "Value must not be *.",
94 numberMinValue: "Value must not be less than *.",
95 numberMaxValue: "Value must not be greater than *.",
96 numberIntValue: "Value must fit in a 32-bit signed integer.",
97 numberMaxDecimal: "Value must not have more than * decimal places.",
98 invalidType: "Expected '*' but got '*'.",
99 invalidTypeIntegerNumber:
100 "Expected 'integer' but got 'number', consider using Math.round().",
101 invalidChoice: "Value does not match any valid type choices.",
102 invalidPropertyType: "Missing property type.",
103 schemaRequired: "Schema value required.",
104 unknownSchemaReference: "Unknown schema reference: *.",
105 notInstance: "Object must be an instance of *."
106 };
107
108 /**
109 * Builds an error message. Key is the property in the |errors| object, and
110 * |opt_replacements| is an array of values to replace "*" characters with.
111 */
112 JSONSchemaValidator.formatError = function(key, opt_replacements) {
113 var message = this.messages[key];
114 if (opt_replacements) {
115 for (var i = 0; i < opt_replacements.length; i++) {
116 message = message.replace("*", opt_replacements[i]);
117 }
118 }
119 return message;
120 };
121
122 /**
123 * Classifies a value as one of the JSON schema primitive types. Note that we
124 * don't explicitly disallow 'function', because we want to allow functions in
125 * the input values.
126 */
127 JSONSchemaValidator.getType = function(value) {
128 var s = typeof value;
129
130 if (s == "object") {
131 if (value === null) {
132 return "null";
133 } else if (Object.prototype.toString.call(value) == "[object Array]") {
134 return "array";
135 } else if (typeof(ArrayBuffer) != "undefined" &&
136 value.constructor == ArrayBuffer) {
137 return "binary";
138 }
139 } else if (s == "number") {
140 if (value % 1 == 0) {
141 return "integer";
142 }
143 }
144
145 return s;
146 };
147
148 /**
149 * Add types that may be referenced by validated schemas that reference them
150 * with "$ref": <typeId>. Each type must be a valid schema and define an
151 * "id" property.
152 */
153 JSONSchemaValidator.prototype.addTypes = function(typeOrTypeList) {
154 function addType(validator, type) {
155 if (!type.id)
156 throw new Error("Attempt to addType with missing 'id' property");
157 validator.types[type.id] = type;
158 }
159
160 if (typeOrTypeList instanceof Array) {
161 for (var i = 0; i < typeOrTypeList.length; i++) {
162 addType(this, typeOrTypeList[i]);
163 }
164 } else {
165 addType(this, typeOrTypeList);
166 }
167 }
168
169 /**
170 * Returns a list of strings of the types that this schema accepts.
171 */
172 JSONSchemaValidator.prototype.getAllTypesForSchema = function(schema) {
173 var schemaTypes = [];
174 if (schema.type)
175 $Array.push(schemaTypes, schema.type);
176 if (schema.choices) {
177 for (var i = 0; i < schema.choices.length; i++) {
178 var choiceTypes = this.getAllTypesForSchema(schema.choices[i]);
179 schemaTypes = $Array.concat(schemaTypes, choiceTypes);
180 }
181 }
182 var ref = schema['$ref'];
183 if (ref) {
184 var type = this.getOrAddType(ref);
185 CHECK(type, 'Could not find type ' + ref);
186 schemaTypes = $Array.concat(schemaTypes, this.getAllTypesForSchema(type));
187 }
188 return schemaTypes;
189 };
190
191 JSONSchemaValidator.prototype.getOrAddType = function(typeName) {
192 if (!this.types[typeName])
193 this.types[typeName] = loadTypeSchema(typeName);
194 return this.types[typeName];
195 };
196
197 /**
198 * Returns true if |schema| would accept an argument of type |type|.
199 */
200 JSONSchemaValidator.prototype.isValidSchemaType = function(type, schema) {
201 if (type == 'any')
202 return true;
203
204 // TODO(kalman): I don't understand this code. How can type be "null"?
205 if (schema.optional && (type == "null" || type == "undefined"))
206 return true;
207
208 var schemaTypes = this.getAllTypesForSchema(schema);
209 for (var i = 0; i < schemaTypes.length; i++) {
210 if (schemaTypes[i] == "any" || type == schemaTypes[i] ||
211 (type == "integer" && schemaTypes[i] == "number"))
212 return true;
213 }
214
215 return false;
216 };
217
218 /**
219 * Returns true if there is a non-null argument that both |schema1| and
220 * |schema2| would accept.
221 */
222 JSONSchemaValidator.prototype.checkSchemaOverlap = function(schema1, schema2) {
223 var schema1Types = this.getAllTypesForSchema(schema1);
224 for (var i = 0; i < schema1Types.length; i++) {
225 if (this.isValidSchemaType(schema1Types[i], schema2))
226 return true;
227 }
228 return false;
229 };
230
231 /**
232 * Validates an instance against a schema. The instance can be any JavaScript
233 * value and will be validated recursively. When this method returns, the
234 * |errors| property will contain a list of errors, if any.
235 */
236 JSONSchemaValidator.prototype.validate = function(instance, schema, opt_path) {
237 var path = opt_path || "";
238
239 if (!schema) {
240 this.addError(path, "schemaRequired");
241 return;
242 }
243
244 // If this schema defines itself as reference type, save it in this.types.
245 if (schema.id)
246 this.types[schema.id] = schema;
247
248 // If the schema has an extends property, the instance must validate against
249 // that schema too.
250 if (schema.extends)
251 this.validate(instance, schema.extends, path);
252
253 // If the schema has a $ref property, the instance must validate against
254 // that schema too. It must be present in this.types to be referenced.
255 var ref = schema["$ref"];
256 if (ref) {
257 if (!this.getOrAddType(ref))
258 this.addError(path, "unknownSchemaReference", [ ref ]);
259 else
260 this.validate(instance, this.getOrAddType(ref), path)
261 }
262
263 // If the schema has a choices property, the instance must validate against at
264 // least one of the items in that array.
265 if (schema.choices) {
266 this.validateChoices(instance, schema, path);
267 return;
268 }
269
270 // If the schema has an enum property, the instance must be one of those
271 // values.
272 if (schema.enum) {
273 if (!this.validateEnum(instance, schema, path))
274 return;
275 }
276
277 if (schema.type && schema.type != "any") {
278 if (!this.validateType(instance, schema, path))
279 return;
280
281 // Type-specific validation.
282 switch (schema.type) {
283 case "object":
284 this.validateObject(instance, schema, path);
285 break;
286 case "array":
287 this.validateArray(instance, schema, path);
288 break;
289 case "string":
290 this.validateString(instance, schema, path);
291 break;
292 case "number":
293 case "integer":
294 this.validateNumber(instance, schema, path);
295 break;
296 }
297 }
298 };
299
300 /**
301 * Validates an instance against a choices schema. The instance must match at
302 * least one of the provided choices.
303 */
304 JSONSchemaValidator.prototype.validateChoices =
305 function(instance, schema, path) {
306 var originalErrors = this.errors;
307
308 for (var i = 0; i < schema.choices.length; i++) {
309 this.errors = [];
310 this.validate(instance, schema.choices[i], path);
311 if (this.errors.length == 0) {
312 this.errors = originalErrors;
313 return;
314 }
315 }
316
317 this.errors = originalErrors;
318 this.addError(path, "invalidChoice");
319 };
320
321 /**
322 * Validates an instance against a schema with an enum type. Populates the
323 * |errors| property, and returns a boolean indicating whether the instance
324 * validates.
325 */
326 JSONSchemaValidator.prototype.validateEnum = function(instance, schema, path) {
327 for (var i = 0; i < schema.enum.length; i++) {
328 if (instance === enumToString(schema.enum[i]))
329 return true;
330 }
331
332 this.addError(path, "invalidEnum",
333 [schema.enum.map(enumToString).join(", ")]);
334 return false;
335 };
336
337 /**
338 * Validates an instance against an object schema and populates the errors
339 * property.
340 */
341 JSONSchemaValidator.prototype.validateObject =
342 function(instance, schema, path) {
343 if (schema.properties) {
344 for (var prop in schema.properties) {
345 // It is common in JavaScript to add properties to Object.prototype. This
346 // check prevents such additions from being interpreted as required
347 // schema properties.
348 // TODO(aa): If it ever turns out that we actually want this to work,
349 // there are other checks we could put here, like requiring that schema
350 // properties be objects that have a 'type' property.
351 if (!$Object.hasOwnProperty(schema.properties, prop))
352 continue;
353
354 var propPath = path ? path + "." + prop : prop;
355 if (schema.properties[prop] == undefined) {
356 this.addError(propPath, "invalidPropertyType");
357 } else if (prop in instance && !isOptionalValue(instance[prop])) {
358 this.validate(instance[prop], schema.properties[prop], propPath);
359 } else if (!schema.properties[prop].optional) {
360 this.addError(propPath, "propertyRequired");
361 }
362 }
363 }
364
365 // If "instanceof" property is set, check that this object inherits from
366 // the specified constructor (function).
367 if (schema.isInstanceOf) {
368 if (!isInstanceOfClass(instance, schema.isInstanceOf))
369 this.addError(propPath, "notInstance", [schema.isInstanceOf]);
370 }
371
372 // Exit early from additional property check if "type":"any" is defined.
373 if (schema.additionalProperties &&
374 schema.additionalProperties.type &&
375 schema.additionalProperties.type == "any") {
376 return;
377 }
378
379 // By default, additional properties are not allowed on instance objects. This
380 // can be overridden by setting the additionalProperties property to a schema
381 // which any additional properties must validate against.
382 for (var prop in instance) {
383 if (schema.properties && prop in schema.properties)
384 continue;
385
386 // Any properties inherited through the prototype are ignored.
387 if (!$Object.hasOwnProperty(instance, prop))
388 continue;
389
390 var propPath = path ? path + "." + prop : prop;
391 if (schema.additionalProperties)
392 this.validate(instance[prop], schema.additionalProperties, propPath);
393 else
394 this.addError(propPath, "unexpectedProperty");
395 }
396 };
397
398 /**
399 * Validates an instance against an array schema and populates the errors
400 * property.
401 */
402 JSONSchemaValidator.prototype.validateArray = function(instance, schema, path) {
403 var typeOfItems = JSONSchemaValidator.getType(schema.items);
404
405 if (typeOfItems == 'object') {
406 if (schema.minItems && instance.length < schema.minItems) {
407 this.addError(path, "arrayMinItems", [schema.minItems]);
408 }
409
410 if (typeof schema.maxItems != "undefined" &&
411 instance.length > schema.maxItems) {
412 this.addError(path, "arrayMaxItems", [schema.maxItems]);
413 }
414
415 // If the items property is a single schema, each item in the array must
416 // have that schema.
417 for (var i = 0; i < instance.length; i++) {
418 this.validate(instance[i], schema.items, path + "." + i);
419 }
420 } else if (typeOfItems == 'array') {
421 // If the items property is an array of schemas, each item in the array must
422 // validate against the corresponding schema.
423 for (var i = 0; i < schema.items.length; i++) {
424 var itemPath = path ? path + "." + i : String(i);
425 if (i in instance && !isOptionalValue(instance[i])) {
426 this.validate(instance[i], schema.items[i], itemPath);
427 } else if (!schema.items[i].optional) {
428 this.addError(itemPath, "itemRequired");
429 }
430 }
431
432 if (schema.additionalProperties) {
433 for (var i = schema.items.length; i < instance.length; i++) {
434 var itemPath = path ? path + "." + i : String(i);
435 this.validate(instance[i], schema.additionalProperties, itemPath);
436 }
437 } else {
438 if (instance.length > schema.items.length) {
439 this.addError(path, "arrayMaxItems", [schema.items.length]);
440 }
441 }
442 }
443 };
444
445 /**
446 * Validates a string and populates the errors property.
447 */
448 JSONSchemaValidator.prototype.validateString =
449 function(instance, schema, path) {
450 if (schema.minLength && instance.length < schema.minLength)
451 this.addError(path, "stringMinLength", [schema.minLength]);
452
453 if (schema.maxLength && instance.length > schema.maxLength)
454 this.addError(path, "stringMaxLength", [schema.maxLength]);
455
456 if (schema.pattern && !schema.pattern.test(instance))
457 this.addError(path, "stringPattern", [schema.pattern]);
458 };
459
460 /**
461 * Validates a number and populates the errors property. The instance is
462 * assumed to be a number.
463 */
464 JSONSchemaValidator.prototype.validateNumber =
465 function(instance, schema, path) {
466 // Forbid NaN, +Infinity, and -Infinity. Our APIs don't use them, and
467 // JSON serialization encodes them as 'null'. Re-evaluate supporting
468 // them if we add an API that could reasonably take them as a parameter.
469 if (isNaN(instance) ||
470 instance == Number.POSITIVE_INFINITY ||
471 instance == Number.NEGATIVE_INFINITY )
472 this.addError(path, "numberFiniteNotNan", [instance]);
473
474 if (schema.minimum !== undefined && instance < schema.minimum)
475 this.addError(path, "numberMinValue", [schema.minimum]);
476
477 if (schema.maximum !== undefined && instance > schema.maximum)
478 this.addError(path, "numberMaxValue", [schema.maximum]);
479
480 // Check for integer values outside of -2^31..2^31-1.
481 if (schema.type === "integer" && (instance | 0) !== instance)
482 this.addError(path, "numberIntValue", []);
483
484 if (schema.maxDecimal && instance * Math.pow(10, schema.maxDecimal) % 1)
485 this.addError(path, "numberMaxDecimal", [schema.maxDecimal]);
486 };
487
488 /**
489 * Validates the primitive type of an instance and populates the errors
490 * property. Returns true if the instance validates, false otherwise.
491 */
492 JSONSchemaValidator.prototype.validateType = function(instance, schema, path) {
493 var actualType = JSONSchemaValidator.getType(instance);
494 if (schema.type == actualType ||
495 (schema.type == "number" && actualType == "integer")) {
496 return true;
497 } else if (schema.type == "integer" && actualType == "number") {
498 this.addError(path, "invalidTypeIntegerNumber");
499 return false;
500 } else {
501 this.addError(path, "invalidType", [schema.type, actualType]);
502 return false;
503 }
504 };
505
506 /**
507 * Adds an error message. |key| is an index into the |messages| object.
508 * |replacements| is an array of values to replace '*' characters in the
509 * message.
510 */
511 JSONSchemaValidator.prototype.addError = function(path, key, replacements) {
512 $Array.push(this.errors, {
513 path: path,
514 message: JSONSchemaValidator.formatError(key, replacements)
515 });
516 };
517
518 /**
519 * Resets errors to an empty list so you can call 'validate' again.
520 */
521 JSONSchemaValidator.prototype.resetErrors = function() {
522 this.errors = [];
523 };
524
525 exports.JSONSchemaValidator = JSONSchemaValidator;
OLDNEW
« no previous file with comments | « trunk/src/extensions/renderer/resources/image_util.js ('k') | trunk/src/extensions/renderer/resources/last_error.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698