146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)// Copyright 2014 The Chromium Authors. All rights reserved.
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// found in the LICENSE file.
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// -----------------------------------------------------------------------------
646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)// NOTE: If you change this file you need to touch
746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)// extension_renderer_resources.grd to have your change take effect.
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// -----------------------------------------------------------------------------
95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)//==============================================================================
115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// This file contains a class that implements a subset of JSON Schema.
125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// See: http://www.json.com/json-schema-proposal/ for more details.
135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)//
145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// The following features of JSON Schema are not implemented:
155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// - requires
165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// - unique
175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// - disallow
185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// - union types (but replaced with 'choices')
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)//
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// The following properties are not applicable to the interface exposed by
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// this class:
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// - options
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// - readonly
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// - title
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// - description
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// - format
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// - default
285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// - transient
295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// - hidden
305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)//
315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// There are also these departures from the JSON Schema proposal:
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// - function and undefined types are supported
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// - null counts as 'unspecified' for optional values
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// - added the 'choices' property, to allow specifying a list of possible types
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)//   for a value
365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// - by default an "object" typed schema does not allow additional properties.
375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)//   if present, "additionalProperties" is to be a schema against which all
385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)//   additional properties will be validated.
395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)//==============================================================================
405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)var loadTypeSchema = require('utils').loadTypeSchema;
422a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)var CHECK = requireNative('logging').CHECK;
435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)function isInstanceOfClass(instance, className) {
45a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  while ((instance = instance.__proto__)) {
46a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)    if (instance.constructor.name == className)
47a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)      return true;
48a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  }
49a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  return false;
505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)function isOptionalValue(value) {
535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return typeof(value) === 'undefined' || value === null;
545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
561e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)function enumToString(enumValue) {
570f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)  if (enumValue.name === undefined)
580f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)    return enumValue;
590f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)
600f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)  return enumValue.name;
611e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)}
621e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Validates an instance against a schema and accumulates errors. Usage:
655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
66868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) * var validator = new JSONSchemaValidator();
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * validator.validate(inst, schema);
685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * if (validator.errors.length == 0)
695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *   console.log("Valid!");
705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * else
715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *   console.log(validator.errors);
725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * The errors property contains a list of objects. Each object has two
745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * properties: "path" and "message". The "path" property contains the path to
755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * the key that had the problem, and the "message" property contains a sentence
765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * describing the error.
775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
78868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)function JSONSchemaValidator() {
795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.errors = [];
805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.types = [];
81868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)}
825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
83868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)JSONSchemaValidator.messages = {
845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  invalidEnum: "Value must be one of: [*].",
855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  propertyRequired: "Property is required.",
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  unexpectedProperty: "Unexpected property.",
875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  arrayMinItems: "Array must have at least * items.",
885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  arrayMaxItems: "Array must not have more than * items.",
895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  itemRequired: "Item is required.",
905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  stringMinLength: "String must be at least * characters long.",
915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  stringMaxLength: "String must not be more than * characters long.",
925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  stringPattern: "String must match the pattern: *.",
935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  numberFiniteNotNan: "Value must not be *.",
945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  numberMinValue: "Value must not be less than *.",
955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  numberMaxValue: "Value must not be greater than *.",
965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  numberIntValue: "Value must fit in a 32-bit signed integer.",
975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  numberMaxDecimal: "Value must not have more than * decimal places.",
985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  invalidType: "Expected '*' but got '*'.",
992a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  invalidTypeIntegerNumber:
1002a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      "Expected 'integer' but got 'number', consider using Math.round().",
1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  invalidChoice: "Value does not match any valid type choices.",
1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  invalidPropertyType: "Missing property type.",
1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  schemaRequired: "Schema value required.",
1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  unknownSchemaReference: "Unknown schema reference: *.",
1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  notInstance: "Object must be an instance of *."
1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Builds an error message. Key is the property in the |errors| object, and
1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * |opt_replacements| is an array of values to replace "*" characters with.
1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
112868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)JSONSchemaValidator.formatError = function(key, opt_replacements) {
1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var message = this.messages[key];
1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (opt_replacements) {
1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for (var i = 0; i < opt_replacements.length; i++) {
1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      message = message.replace("*", opt_replacements[i]);
1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return message;
1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Classifies a value as one of the JSON schema primitive types. Note that we
1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * don't explicitly disallow 'function', because we want to allow functions in
1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * the input values.
1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
127868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)JSONSchemaValidator.getType = function(value) {
1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var s = typeof value;
1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (s == "object") {
1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (value === null) {
1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return "null";
1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    } else if (Object.prototype.toString.call(value) == "[object Array]") {
1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return "array";
1355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    } else if (typeof(ArrayBuffer) != "undefined" &&
1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)               value.constructor == ArrayBuffer) {
1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return "binary";
1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
1395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  } else if (s == "number") {
1405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (value % 1 == 0) {
1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return "integer";
1425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return s;
1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
1475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
1495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Add types that may be referenced by validated schemas that reference them
1505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * with "$ref": <typeId>. Each type must be a valid schema and define an
1515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * "id" property.
1525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
153868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)JSONSchemaValidator.prototype.addTypes = function(typeOrTypeList) {
1545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  function addType(validator, type) {
1555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (!type.id)
1565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      throw new Error("Attempt to addType with missing 'id' property");
1575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    validator.types[type.id] = type;
1585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (typeOrTypeList instanceof Array) {
1615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for (var i = 0; i < typeOrTypeList.length; i++) {
1625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      addType(this, typeOrTypeList[i]);
1635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
1645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  } else {
1655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    addType(this, typeOrTypeList);
1665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
1705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Returns a list of strings of the types that this schema accepts.
1715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
172868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)JSONSchemaValidator.prototype.getAllTypesForSchema = function(schema) {
1735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var schemaTypes = [];
1745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (schema.type)
175eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    $Array.push(schemaTypes, schema.type);
1765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (schema.choices) {
1775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for (var i = 0; i < schema.choices.length; i++) {
1785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      var choiceTypes = this.getAllTypesForSchema(schema.choices[i]);
179eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      schemaTypes = $Array.concat(schemaTypes, choiceTypes);
1805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
1815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1822a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  var ref = schema['$ref'];
1832a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  if (ref) {
1842a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    var type = this.getOrAddType(ref);
1852a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    CHECK(type, 'Could not find type ' + ref);
186eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    schemaTypes = $Array.concat(schemaTypes, this.getAllTypesForSchema(type));
1875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return schemaTypes;
1895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
1905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
191868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)JSONSchemaValidator.prototype.getOrAddType = function(typeName) {
1922a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  if (!this.types[typeName])
1932a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    this.types[typeName] = loadTypeSchema(typeName);
1942a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  return this.types[typeName];
1952a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)};
1962a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
1985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Returns true if |schema| would accept an argument of type |type|.
1995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
200868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)JSONSchemaValidator.prototype.isValidSchemaType = function(type, schema) {
2015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (type == 'any')
2025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return true;
2035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // TODO(kalman): I don't understand this code. How can type be "null"?
2055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (schema.optional && (type == "null" || type == "undefined"))
2065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return true;
2075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2082a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  var schemaTypes = this.getAllTypesForSchema(schema);
2095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  for (var i = 0; i < schemaTypes.length; i++) {
2105d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    if (schemaTypes[i] == "any" || type == schemaTypes[i] ||
2115d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        (type == "integer" && schemaTypes[i] == "number"))
2125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return true;
2135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return false;
2165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
2175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
2195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Returns true if there is a non-null argument that both |schema1| and
2205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * |schema2| would accept.
2215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
222868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)JSONSchemaValidator.prototype.checkSchemaOverlap = function(schema1, schema2) {
2235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var schema1Types = this.getAllTypesForSchema(schema1);
2245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  for (var i = 0; i < schema1Types.length; i++) {
2255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (this.isValidSchemaType(schema1Types[i], schema2))
2265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return true;
2275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return false;
2295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
2305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
2325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Validates an instance against a schema. The instance can be any JavaScript
2335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * value and will be validated recursively. When this method returns, the
2345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * |errors| property will contain a list of errors, if any.
2355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
236868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)JSONSchemaValidator.prototype.validate = function(instance, schema, opt_path) {
2375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var path = opt_path || "";
2385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!schema) {
2405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    this.addError(path, "schemaRequired");
2415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return;
2425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // If this schema defines itself as reference type, save it in this.types.
2455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (schema.id)
2465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    this.types[schema.id] = schema;
2475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // If the schema has an extends property, the instance must validate against
2495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // that schema too.
2505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (schema.extends)
2515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    this.validate(instance, schema.extends, path);
2525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // If the schema has a $ref property, the instance must validate against
2545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // that schema too. It must be present in this.types to be referenced.
2552a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  var ref = schema["$ref"];
2562a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  if (ref) {
2572a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if (!this.getOrAddType(ref))
2582a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      this.addError(path, "unknownSchemaReference", [ ref ]);
2595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    else
2602a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      this.validate(instance, this.getOrAddType(ref), path)
2615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // If the schema has a choices property, the instance must validate against at
2645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // least one of the items in that array.
2655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (schema.choices) {
2665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    this.validateChoices(instance, schema, path);
2675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return;
2685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // If the schema has an enum property, the instance must be one of those
2715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // values.
2725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (schema.enum) {
2735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (!this.validateEnum(instance, schema, path))
2745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return;
2755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (schema.type && schema.type != "any") {
2785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (!this.validateType(instance, schema, path))
2795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return;
2805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // Type-specific validation.
2825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    switch (schema.type) {
2835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      case "object":
2845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        this.validateObject(instance, schema, path);
2855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        break;
2865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      case "array":
2875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        this.validateArray(instance, schema, path);
2885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        break;
2895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      case "string":
2905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        this.validateString(instance, schema, path);
2915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        break;
2925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      case "number":
2935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      case "integer":
2945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        this.validateNumber(instance, schema, path);
2955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        break;
2965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
2975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
2995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
3015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Validates an instance against a choices schema. The instance must match at
3025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * least one of the provided choices.
3035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
304868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)JSONSchemaValidator.prototype.validateChoices =
3055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    function(instance, schema, path) {
3065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var originalErrors = this.errors;
3075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  for (var i = 0; i < schema.choices.length; i++) {
3095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    this.errors = [];
3105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    this.validate(instance, schema.choices[i], path);
3115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (this.errors.length == 0) {
3125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      this.errors = originalErrors;
3135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return;
3145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
3155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
3165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.errors = originalErrors;
3185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.addError(path, "invalidChoice");
3195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
3205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
3225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Validates an instance against a schema with an enum type. Populates the
3235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * |errors| property, and returns a boolean indicating whether the instance
3245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * validates.
3255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
326868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)JSONSchemaValidator.prototype.validateEnum = function(instance, schema, path) {
3275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  for (var i = 0; i < schema.enum.length; i++) {
3281e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    if (instance === enumToString(schema.enum[i]))
3295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return true;
3305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
3315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3321e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)  this.addError(path, "invalidEnum",
3331e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)                [schema.enum.map(enumToString).join(", ")]);
3345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return false;
3355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
3365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
3385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Validates an instance against an object schema and populates the errors
3395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * property.
3405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
341868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)JSONSchemaValidator.prototype.validateObject =
3425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    function(instance, schema, path) {
3435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (schema.properties) {
3445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for (var prop in schema.properties) {
3455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      // It is common in JavaScript to add properties to Object.prototype. This
3465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      // check prevents such additions from being interpreted as required
3475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      // schema properties.
3485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      // TODO(aa): If it ever turns out that we actually want this to work,
3495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      // there are other checks we could put here, like requiring that schema
3505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      // properties be objects that have a 'type' property.
351eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      if (!$Object.hasOwnProperty(schema.properties, prop))
3525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        continue;
3535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      var propPath = path ? path + "." + prop : prop;
3555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if (schema.properties[prop] == undefined) {
3565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        this.addError(propPath, "invalidPropertyType");
3575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      } else if (prop in instance && !isOptionalValue(instance[prop])) {
3585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        this.validate(instance[prop], schema.properties[prop], propPath);
3595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      } else if (!schema.properties[prop].optional) {
3605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        this.addError(propPath, "propertyRequired");
3615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      }
3625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
3635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
3645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // If "instanceof" property is set, check that this object inherits from
3665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // the specified constructor (function).
3675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (schema.isInstanceOf) {
3685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (!isInstanceOfClass(instance, schema.isInstanceOf))
3695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      this.addError(propPath, "notInstance", [schema.isInstanceOf]);
3705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
3715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Exit early from additional property check if "type":"any" is defined.
3735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (schema.additionalProperties &&
3745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      schema.additionalProperties.type &&
3755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      schema.additionalProperties.type == "any") {
3765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return;
3775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
3785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // By default, additional properties are not allowed on instance objects. This
3805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // can be overridden by setting the additionalProperties property to a schema
3815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // which any additional properties must validate against.
3825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  for (var prop in instance) {
3835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (schema.properties && prop in schema.properties)
3845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      continue;
3855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // Any properties inherited through the prototype are ignored.
387eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    if (!$Object.hasOwnProperty(instance, prop))
3885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      continue;
3895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var propPath = path ? path + "." + prop : prop;
3915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (schema.additionalProperties)
3925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      this.validate(instance[prop], schema.additionalProperties, propPath);
3935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    else
3945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      this.addError(propPath, "unexpectedProperty");
3955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
3965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
3975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
3995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Validates an instance against an array schema and populates the errors
4005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * property.
4015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
402868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)JSONSchemaValidator.prototype.validateArray = function(instance, schema, path) {
403868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  var typeOfItems = JSONSchemaValidator.getType(schema.items);
4045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (typeOfItems == 'object') {
4065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (schema.minItems && instance.length < schema.minItems) {
4075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      this.addError(path, "arrayMinItems", [schema.minItems]);
4085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
4095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (typeof schema.maxItems != "undefined" &&
4115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        instance.length > schema.maxItems) {
4125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      this.addError(path, "arrayMaxItems", [schema.maxItems]);
4135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
4145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // If the items property is a single schema, each item in the array must
4165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // have that schema.
4175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for (var i = 0; i < instance.length; i++) {
4185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      this.validate(instance[i], schema.items, path + "." + i);
4195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
4205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  } else if (typeOfItems == 'array') {
4215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // If the items property is an array of schemas, each item in the array must
4225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // validate against the corresponding schema.
4235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for (var i = 0; i < schema.items.length; i++) {
4245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      var itemPath = path ? path + "." + i : String(i);
4255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if (i in instance && !isOptionalValue(instance[i])) {
4265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        this.validate(instance[i], schema.items[i], itemPath);
4275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      } else if (!schema.items[i].optional) {
4285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        this.addError(itemPath, "itemRequired");
4295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      }
4305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
4315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (schema.additionalProperties) {
4335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      for (var i = schema.items.length; i < instance.length; i++) {
4345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        var itemPath = path ? path + "." + i : String(i);
4355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        this.validate(instance[i], schema.additionalProperties, itemPath);
4365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      }
4375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    } else {
4385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if (instance.length > schema.items.length) {
4395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        this.addError(path, "arrayMaxItems", [schema.items.length]);
4405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      }
4415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
4425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
4435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
4445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
4465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Validates a string and populates the errors property.
4475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
448868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)JSONSchemaValidator.prototype.validateString =
4495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    function(instance, schema, path) {
4505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (schema.minLength && instance.length < schema.minLength)
4515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    this.addError(path, "stringMinLength", [schema.minLength]);
4525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (schema.maxLength && instance.length > schema.maxLength)
4545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    this.addError(path, "stringMaxLength", [schema.maxLength]);
4555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (schema.pattern && !schema.pattern.test(instance))
4575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    this.addError(path, "stringPattern", [schema.pattern]);
4585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
4595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
4615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Validates a number and populates the errors property. The instance is
4625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * assumed to be a number.
4635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
464868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)JSONSchemaValidator.prototype.validateNumber =
4655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    function(instance, schema, path) {
4665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Forbid NaN, +Infinity, and -Infinity.  Our APIs don't use them, and
4675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // JSON serialization encodes them as 'null'.  Re-evaluate supporting
4685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // them if we add an API that could reasonably take them as a parameter.
4695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (isNaN(instance) ||
4705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      instance == Number.POSITIVE_INFINITY ||
4715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      instance == Number.NEGATIVE_INFINITY )
4725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    this.addError(path, "numberFiniteNotNan", [instance]);
4735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (schema.minimum !== undefined && instance < schema.minimum)
4755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    this.addError(path, "numberMinValue", [schema.minimum]);
4765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (schema.maximum !== undefined && instance > schema.maximum)
4785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    this.addError(path, "numberMaxValue", [schema.maximum]);
4795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Check for integer values outside of -2^31..2^31-1.
4815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (schema.type === "integer" && (instance | 0) !== instance)
4825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    this.addError(path, "numberIntValue", []);
4835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (schema.maxDecimal && instance * Math.pow(10, schema.maxDecimal) % 1)
4855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    this.addError(path, "numberMaxDecimal", [schema.maxDecimal]);
4865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
4875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
4895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Validates the primitive type of an instance and populates the errors
4905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * property. Returns true if the instance validates, false otherwise.
4915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
492868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)JSONSchemaValidator.prototype.validateType = function(instance, schema, path) {
493868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  var actualType = JSONSchemaValidator.getType(instance);
4942a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  if (schema.type == actualType ||
4952a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      (schema.type == "number" && actualType == "integer")) {
4962a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    return true;
4972a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  } else if (schema.type == "integer" && actualType == "number") {
4982a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    this.addError(path, "invalidTypeIntegerNumber");
4992a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    return false;
5002a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  } else {
5015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    this.addError(path, "invalidType", [schema.type, actualType]);
5025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return false;
5035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
5045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
5055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
5075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Adds an error message. |key| is an index into the |messages| object.
5085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * |replacements| is an array of values to replace '*' characters in the
5095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * message.
5105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
511868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)JSONSchemaValidator.prototype.addError = function(path, key, replacements) {
512eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  $Array.push(this.errors, {
5135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    path: path,
514868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    message: JSONSchemaValidator.formatError(key, replacements)
5155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  });
5165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
5175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
5195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Resets errors to an empty list so you can call 'validate' again.
5205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
521868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)JSONSchemaValidator.prototype.resetErrors = function() {
5225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.errors = [];
5235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
524868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
525868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)exports.JSONSchemaValidator = JSONSchemaValidator;
526