summaryrefslogtreecommitdiffstats
path: root/chrome/renderer/resources/json_schema.js
diff options
context:
space:
mode:
authoraa@chromium.org <aa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-04-14 23:40:41 +0000
committeraa@chromium.org <aa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-04-14 23:40:41 +0000
commit4c29efd402105cd29908d21550829bc089a8fe36 (patch)
treee2e2ac0f5a5d47db3417c1e85205c57ede5e7cb8 /chrome/renderer/resources/json_schema.js
parent5ea8fc38c5cc261077b61469d8ccb158f3e347b8 (diff)
downloadchromium_src-4c29efd402105cd29908d21550829bc089a8fe36.zip
chromium_src-4c29efd402105cd29908d21550829bc089a8fe36.tar.gz
chromium_src-4c29efd402105cd29908d21550829bc089a8fe36.tar.bz2
Add JsonSchema-based validation for the tab APIs.
Arv: can you take json_schema.js and json_schema_test.js. Matt: you take the rest. Review URL: http://codereview.chromium.org/66006 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@13720 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/renderer/resources/json_schema.js')
-rwxr-xr-xchrome/renderer/resources/json_schema.js328
1 files changed, 328 insertions, 0 deletions
diff --git a/chrome/renderer/resources/json_schema.js b/chrome/renderer/resources/json_schema.js
new file mode 100755
index 0000000..f2d14fb
--- /dev/null
+++ b/chrome/renderer/resources/json_schema.js
@@ -0,0 +1,328 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//==============================================================================
+// This file contains a class that implements a subset of JSON Schema.
+// See: http://www.json.com/json-schema-proposal/ for more details.
+//
+// The following features of JSON Schema are not implemented:
+// - requires
+// - unique
+// - disallow
+// - union types
+//
+// The following properties are not applicable to the interface exposed by
+// this class:
+// - options
+// - readonly
+// - title
+// - description
+// - format
+// - default
+// - transient
+// - hidden
+//==============================================================================
+
+var chromium = chromium || {};
+
+/**
+ * Validates an instance against a schema and accumulates errors. Usage:
+ *
+ * var validator = new chromium.JSONSchemaValidator();
+ * validator.validate(inst, schema);
+ * if (validator.errors.length == 0)
+ * console.log("Valid!");
+ * else
+ * console.log(validator.errors);
+ *
+ * The errors property contains a list of objects. Each object has two
+ * properties: "path" and "message". The "path" property contains the path to
+ * the key that had the problem, and the "message" property contains a sentence
+ * describing the error.
+ */
+chromium.JSONSchemaValidator = function() {
+ this.errors = [];
+};
+
+chromium.JSONSchemaValidator.messages = {
+ invalidEnum: "Value must be one of: [*].",
+ propertyRequired: "Property is required.",
+ unexpectedProperty: "Unexpected property.",
+ arrayMinItems: "Array must have at least * items.",
+ arrayMaxItems: "Array must not have more than * items.",
+ itemRequired: "Item is required.",
+ stringMinLength: "String must be at least * characters long.",
+ stringMaxLength: "String must not be more than * characters long.",
+ stringPattern: "String must match the pattern: *.",
+ numberMinValue: "Value must not be less than *.",
+ numberMaxValue: "Value must not be greater than *.",
+ numberMaxDecimal: "Value must not have more than * decimal places.",
+ invalidType: "Expected '*' but got '*'."
+};
+
+/**
+ * Builds an error message. Key is the property in the |errors| object, and
+ * |opt_replacements| is an array of values to replace "*" characters with.
+ */
+chromium.JSONSchemaValidator.formatError = function(key, opt_replacements) {
+ var message = this.messages[key];
+ if (opt_replacements) {
+ for (var i = 0; i < opt_replacements.length; i++) {
+ message = message.replace("*", opt_replacements[i]);
+ }
+ }
+ return message;
+};
+
+/**
+ * Classifies a value as one of the JSON schema primitive types. Note that we
+ * don't explicitly disallow 'function', because we want to allow functions in
+ * the input values.
+ */
+chromium.JSONSchemaValidator.getType = function(value) {
+ var s = typeof value;
+
+ if (s == "object") {
+ if (value === null) {
+ return "null";
+ } else if (value instanceof Array ||
+ Object.prototype.toString.call(value) == "[Object Array]") {
+ return "array";
+ }
+ } else if (s == "number") {
+ if (value % 1 == 0) {
+ return "integer";
+ }
+ }
+
+ return s;
+};
+
+/**
+ * Validates an instance against a schema. The instance can be any JavaScript
+ * value and will be validated recursively. When this method returns, the
+ * |errors| property will contain a list of errors, if any.
+ */
+chromium.JSONSchemaValidator.prototype.validate = function(instance, schema,
+ opt_path) {
+ var path = opt_path || "";
+
+ // If the schema has an extends property, the instance must validate against
+ // that schema too.
+ if (schema.extends)
+ this.validate(instance, schema.extends, path);
+
+ // If the schema has an enum property, the instance must be one of those
+ // values.
+ if (schema.enum) {
+ if (!this.validateEnum(instance, schema, path))
+ return;
+ }
+
+ if (schema.type && schema.type != "any") {
+ if (!this.validateType(instance, schema, path))
+ return;
+
+ // Type-specific validation.
+ switch (schema.type) {
+ case "object":
+ this.validateObject(instance, schema, path);
+ break;
+ case "array":
+ this.validateArray(instance, schema, path);
+ break;
+ case "string":
+ this.validateString(instance, schema, path);
+ break;
+ case "number":
+ case "integer":
+ this.validateNumber(instance, schema, path);
+ break;
+ }
+ }
+};
+
+/**
+ * Validates an instance against a schema with an enum type. Populates the
+ * |errors| property, and returns a boolean indicating whether the instance
+ * validates.
+ */
+chromium.JSONSchemaValidator.prototype.validateEnum = function(instance, schema,
+ path) {
+ for (var i = 0; i < schema.enum.length; i++) {
+ if (instance === schema.enum[i])
+ return true;
+ }
+
+ this.addError(path, "invalidEnum", [schema.enum.join(", ")]);
+ return false;
+};
+
+/**
+ * Validates an instance against an object schema and populates the errors
+ * property.
+ */
+chromium.JSONSchemaValidator.prototype.validateObject = function(instance,
+ schema, path) {
+ for (var prop in schema.properties) {
+ var propPath = path ? path + "." + prop : prop;
+ if (instance.hasOwnProperty(prop)) {
+ this.validate(instance[prop], schema.properties[prop], propPath);
+ } else if (!schema.properties[prop].optional) {
+ this.addError(propPath, "propertyRequired");
+ }
+ }
+
+ // The additionalProperties property can either be |false| or a schema
+ // definition. If |false|, additional properties are not allowed. If a schema
+ // defintion, all additional properties must validate against that schema.
+ if (typeof schema.additionalProperties != "undefined") {
+ for (var prop in instance) {
+ if (instance.hasOwnProperty(prop)) {
+ var propPath = path ? path + "." + prop : prop;
+ if (!schema.properties.hasOwnProperty(prop)) {
+ if (schema.additionalProperties === false)
+ this.addError(propPath, "unexpectedProperty");
+ else
+ this.validate(instance[prop], schema.additionalProperties, propPath);
+ }
+ }
+ }
+ }
+};
+
+/**
+ * Validates an instance against an array schema and populates the errors
+ * property.
+ */
+chromium.JSONSchemaValidator.prototype.validateArray = function(instance,
+ schema, path) {
+ var typeOfItems = chromium.JSONSchemaValidator.getType(schema.items);
+
+ if (typeOfItems == 'object') {
+ if (schema.minItems && instance.length < schema.minItems) {
+ this.addError(path, "arrayMinItems", [schema.minItems]);
+ }
+
+ if (typeof schema.maxItems != "undefined" &&
+ instance.length > schema.maxItems) {
+ this.addError(path, "arrayMaxItems", [schema.maxItems]);
+ }
+
+ // If the items property is a single schema, each item in the array must
+ // have that schema.
+ for (var i = 0; i < instance.length; i++) {
+ this.validate(instance[i], schema.items, path + "[" + i + "]");
+ }
+ } else if (typeOfItems == 'array') {
+ // If the items property is an array of schemas, each item in the array must
+ // validate against the corresponding schema.
+ for (var i = 0; i < schema.items.length; i++) {
+ var itemPath = path ? path + "[" + i + "]" : String(i);
+ if (instance.hasOwnProperty(i)) {
+ this.validate(instance[i], schema.items[i], itemPath);
+ } else if (!schema.items[i].optional) {
+ this.addError(itemPath, "itemRequired");
+ }
+ }
+
+ if (schema.additionalProperties === false) {
+ if (instance.length > schema.items.length) {
+ this.addError(path, "arrayMaxItems", [schema.items.length]);
+ }
+ } else if (schema.additionalProperties) {
+ for (var i = schema.items.length; i < instance.length; i++) {
+ var itemPath = path ? path + "[" + i + "]" : String(i);
+ this.validate(instance[i], schema.additionalProperties, itemPath);
+ }
+ }
+ }
+};
+
+/**
+ * Validates a string and populates the errors property.
+ */
+chromium.JSONSchemaValidator.prototype.validateString = function(instance,
+ schema, path) {
+ if (schema.minLength && instance.length < schema.minLength)
+ this.addError(path, "stringMinLength", [schema.minLength]);
+
+ if (schema.maxLength && instance.length > schema.maxLength)
+ this.addError(path, "stringMaxLength", [schema.maxLength]);
+
+ if (schema.pattern && !schema.pattern.test(instance))
+ this.addError(path, "stringPattern", [schema.pattern]);
+};
+
+/**
+ * Validates a number and populates the errors property. The instance is
+ * assumed to be a number.
+ */
+chromium.JSONSchemaValidator.prototype.validateNumber = function(instance,
+ schema, path) {
+ if (schema.minimum && instance < schema.minimum)
+ this.addError(path, "numberMinValue", [schema.minimum]);
+
+ if (schema.maximum && instance > schema.maximum)
+ this.addError(path, "numberMaxValue", [schema.maximum]);
+
+ if (schema.maxDecimal && instance * Math.pow(10, schema.maxDecimal) % 1)
+ this.addError(path, "numberMaxDecimal", [schema.maxDecimal]);
+};
+
+/**
+ * Validates the primitive type of an instance and populates the errors
+ * property. Returns true if the instance validates, false otherwise.
+ */
+chromium.JSONSchemaValidator.prototype.validateType = function(instance, schema,
+ path) {
+ var actualType = chromium.JSONSchemaValidator.getType(instance);
+ if (schema.type != actualType && !(schema.type == "number" &&
+ actualType == "integer")) {
+ this.addError(path, "invalidType", [schema.type, actualType]);
+ return false;
+ }
+
+ return true;
+};
+
+/**
+ * Adds an error message. |key| is an index into the |messages| object.
+ * |replacements| is an array of values to replace '*' characters in the
+ * message.
+ */
+chromium.JSONSchemaValidator.prototype.addError = function(path, key,
+ replacements) {
+ this.errors.push({
+ path: path,
+ message: chromium.JSONSchemaValidator.formatError(key, replacements)
+ });
+};
+
+// Set up chromium.types with some commonly used types...
+(function() {
+ function extend(base, ext) {
+ var result = {};
+ for (var p in base)
+ result[p] = base[p];
+ for (var p in ext)
+ result[p] = ext[p];
+ return result;
+ }
+
+ var types = {};
+ types.opt = {optional: true};
+ types.bool = {type: "boolean"};
+ types.int = {type: "integer"};
+ types.str = {type: "string"};
+ types.fun = {type: "function"};
+ types.pInt = extend(types.int, {minimum: 0});
+ types.optBool = extend(types.bool, types.opt);
+ types.optInt = extend(types.int, types.opt);
+ types.optStr = extend(types.str, types.opt);
+ types.optFun = extend(types.fun, types.opt);
+ types.optPInt = extend(types.pInt, types.opt);
+
+ chromium.types = types;
+})();