From 14796849ffab50ed69de6bab1badab45f97561c9 Mon Sep 17 00:00:00 2001 From: Simon Goumaz Date: Fri, 26 Apr 2013 15:30:44 +0200 Subject: [PATCH 1/3] Added draft-03 environment option for setting type coercion functions (with matching test) --- lib/json-schema-draft-03.js | 3106 ++++++++++++++++++----------------- lib/jsv.js | 3034 +++++++++++++++++----------------- tests/tests3.js | 143 +- 3 files changed, 3181 insertions(+), 3102 deletions(-) diff --git a/lib/json-schema-draft-03.js b/lib/json-schema-draft-03.js index e0e4843..5e0c2cf 100644 --- a/lib/json-schema-draft-03.js +++ b/lib/json-schema-draft-03.js @@ -1,1552 +1,1554 @@ -/** - * json-schema-draft-03 Environment - * - * @fileOverview Implementation of the third revision of the JSON Schema specification draft. - * @author Gary Court - * @version 1.5.1 - * @see http://github.com/garycourt/JSV - */ - -/* - * Copyright 2010 Gary Court. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are - * permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of - * conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list - * of conditions and the following disclaimer in the documentation and/or other materials - * provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY GARY COURT ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND - * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GARY COURT OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * The views and conclusions contained in the software and documentation are those of the - * authors and should not be interpreted as representing official policies, either expressed - * or implied, of Gary Court or the JSON Schema specification. - */ - -/*jslint white: true, sub: true, onevar: true, undef: true, eqeqeq: true, newcap: true, immed: true, indent: 4 */ -/*global require */ - -(function () { - var O = {}, - JSV = require('./jsv').JSV, - TYPE_VALIDATORS, - ENVIRONMENT, - SCHEMA_00_JSON, - HYPERSCHEMA_00_JSON, - LINKS_00_JSON, - SCHEMA_00, - HYPERSCHEMA_00, - LINKS_00, - SCHEMA_01_JSON, - HYPERSCHEMA_01_JSON, - LINKS_01_JSON, - SCHEMA_01, - HYPERSCHEMA_01, - LINKS_01, - SCHEMA_02_JSON, - HYPERSCHEMA_02_JSON, - LINKS_02_JSON, - SCHEMA_02, - HYPERSCHEMA_02, - LINKS_02, - SCHEMA_03_JSON, - HYPERSCHEMA_03_JSON, - LINKS_03_JSON, - SCHEMA_03, - HYPERSCHEMA_03, - LINKS_03; - - TYPE_VALIDATORS = { - "string" : function (instance, report) { - return instance.getType() === "string"; - }, - - "number" : function (instance, report) { - return instance.getType() === "number"; - }, - - "integer" : function (instance, report) { - return instance.getType() === "number" && instance.getValue() % 1 === 0; - }, - - "boolean" : function (instance, report) { - return instance.getType() === "boolean"; - }, - - "object" : function (instance, report) { - return instance.getType() === "object"; - }, - - "array" : function (instance, report) { - return instance.getType() === "array"; - }, - - "null" : function (instance, report) { - return instance.getType() === "null"; - }, - - "any" : function (instance, report) { - return true; - } - }; - - ENVIRONMENT = new JSV.Environment(); - ENVIRONMENT.setOption("validateReferences", true); - ENVIRONMENT.setOption("enforceReferences", false); - ENVIRONMENT.setOption("strict", false); - - // - // draft-00 - // - - SCHEMA_00_JSON = { - "$schema" : "http://json-schema.org/draft-00/hyper-schema#", - "id" : "http://json-schema.org/draft-00/schema#", - "type" : "object", - - "properties" : { - "type" : { - "type" : ["string", "array"], - "items" : { - "type" : ["string", {"$ref" : "#"}] - }, - "optional" : true, - "uniqueItems" : true, - "default" : "any", - - "parser" : function (instance, self) { - var parser; - - if (instance.getType() === "string") { - return instance.getValue(); - } else if (instance.getType() === "object") { - return instance.getEnvironment().createSchema( - instance, - self.getEnvironment().findSchema(self.resolveURI("#")) - ); - } else if (instance.getType() === "array") { - parser = self.getValueOfProperty("parser"); - return JSV.mapArray(instance.getProperties(), function (prop) { - return parser(prop, self); - }); - } - //else - return "any"; - }, - - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var requiredTypes = JSV.toArray(schema.getAttribute("type")), - x, xl, type, subreport, typeValidators; - - //for instances that are required to be a certain type - if (instance.getType() !== "undefined" && requiredTypes && requiredTypes.length) { - typeValidators = self.getValueOfProperty("typeValidators") || {}; - - //ensure that type matches for at least one of the required types - for (x = 0, xl = requiredTypes.length; x < xl; ++x) { - type = requiredTypes[x]; - if (JSV.isJSONSchema(type)) { - subreport = JSV.createObject(report); - subreport.errors = []; - subreport.validated = JSV.clone(report.validated); - if (type.validate(instance, subreport, parent, parentSchema, name).errors.length === 0) { - return true; //instance matches this schema - } - } else { - if (typeValidators[type] !== O[type] && typeof typeValidators[type] === "function") { - if (typeValidators[type](instance, report)) { - return true; //type is valid - } - } else { - return true; //unknown types are assumed valid - } - } - } - - //if we get to this point, type is invalid - report.addError(instance, schema, "type", "Instance is not a required type", requiredTypes); - return false; - } - //else, anything is allowed if no type is specified - return true; - }, - - "typeValidators" : TYPE_VALIDATORS - }, - - "properties" : { - "type" : "object", - "additionalProperties" : {"$ref" : "#"}, - "optional" : true, - "default" : {}, - - "parser" : function (instance, self, arg) { - var env = instance.getEnvironment(), - selfEnv = self.getEnvironment(); - if (instance.getType() === "object") { - if (arg) { - return env.createSchema(instance.getProperty(arg), selfEnv.findSchema(self.resolveURI("#"))); - } else { - return JSV.mapObject(instance.getProperties(), function (instance) { - return env.createSchema(instance, selfEnv.findSchema(self.resolveURI("#"))); - }); - } - } - //else - return {}; - }, - - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var propertySchemas, key; - //this attribute is for object type instances only - if (instance.getType() === "object") { - //for each property defined in the schema - propertySchemas = schema.getAttribute("properties"); - for (key in propertySchemas) { - if (propertySchemas[key] !== O[key] && propertySchemas[key]) { - //ensure that instance property is valid - propertySchemas[key].validate(instance.getProperty(key), report, instance, schema, key); - } - } - } - } - }, - - "items" : { - "type" : [{"$ref" : "#"}, "array"], - "items" : {"$ref" : "#"}, - "optional" : true, - "default" : {}, - - "parser" : function (instance, self) { - if (instance.getType() === "object") { - return instance.getEnvironment().createSchema(instance, self.getEnvironment().findSchema(self.resolveURI("#"))); - } else if (instance.getType() === "array") { - return JSV.mapArray(instance.getProperties(), function (instance) { - return instance.getEnvironment().createSchema(instance, self.getEnvironment().findSchema(self.resolveURI("#"))); - }); - } - //else - return instance.getEnvironment().createEmptySchema(); - }, - - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var properties, items, x, xl, itemSchema, additionalProperties; - - if (instance.getType() === "array") { - properties = instance.getProperties(); - items = schema.getAttribute("items"); - additionalProperties = schema.getAttribute("additionalProperties"); - - if (JSV.typeOf(items) === "array") { - for (x = 0, xl = properties.length; x < xl; ++x) { - itemSchema = items[x] || additionalProperties; - if (itemSchema !== false) { - itemSchema.validate(properties[x], report, instance, schema, x); - } else { - report.addError(instance, schema, "additionalProperties", "Additional items are not allowed", itemSchema); - } - } - } else { - itemSchema = items || additionalProperties; - for (x = 0, xl = properties.length; x < xl; ++x) { - itemSchema.validate(properties[x], report, instance, schema, x); - } - } - } - } - }, - - "optional" : { - "type" : "boolean", - "optional" : true, - "default" : false, - - "parser" : function (instance, self) { - return !!instance.getValue(); - }, - - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - if (instance.getType() === "undefined" && !schema.getAttribute("optional")) { - report.addError(instance, schema, "optional", "Property is required", false); - } - }, - - "validationRequired" : true - }, - - "additionalProperties" : { - "type" : [{"$ref" : "#"}, "boolean"], - "optional" : true, - "default" : {}, - - "parser" : function (instance, self) { - if (instance.getType() === "object") { - return instance.getEnvironment().createSchema(instance, self.getEnvironment().findSchema(self.resolveURI("#"))); - } else if (instance.getType() === "boolean" && instance.getValue() === false) { - return false; - } - //else - return instance.getEnvironment().createEmptySchema(); - }, - - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var additionalProperties, propertySchemas, properties, key; - //we only need to check against object types as arrays do their own checking on this property - if (instance.getType() === "object") { - additionalProperties = schema.getAttribute("additionalProperties"); - propertySchemas = schema.getAttribute("properties") || {}; - properties = instance.getProperties(); - for (key in properties) { - if (properties[key] !== O[key] && properties[key] && propertySchemas[key] === O[key]) { - if (JSV.isJSONSchema(additionalProperties)) { - additionalProperties.validate(properties[key], report, instance, schema, key); - } else if (additionalProperties === false) { - report.addError(instance, schema, "additionalProperties", "Additional properties are not allowed", additionalProperties); - } - } - } - } - } - }, - - "requires" : { - "type" : ["string", {"$ref" : "#"}], - "optional" : true, - - "parser" : function (instance, self) { - if (instance.getType() === "string") { - return instance.getValue(); - } else if (instance.getType() === "object") { - return instance.getEnvironment().createSchema(instance, self.getEnvironment().findSchema(self.resolveURI("#"))); - } - }, - - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var requires; - if (instance.getType() !== "undefined" && parent && parent.getType() !== "undefined") { - requires = schema.getAttribute("requires"); - if (typeof requires === "string") { - if (parent.getProperty(requires).getType() === "undefined") { - report.addError(instance, schema, "requires", 'Property requires sibling property "' + requires + '"', requires); - } - } else if (JSV.isJSONSchema(requires)) { - requires.validate(parent, report); //WATCH: A "requires" schema does not support the "requires" attribute - } - } - } - }, - - "minimum" : { - "type" : "number", - "optional" : true, - - "parser" : function (instance, self) { - if (instance.getType() === "number") { - return instance.getValue(); - } - }, - - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var minimum, minimumCanEqual; - if (instance.getType() === "number") { - minimum = schema.getAttribute("minimum"); - minimumCanEqual = schema.getAttribute("minimumCanEqual"); - if (typeof minimum === "number" && (instance.getValue() < minimum || (minimumCanEqual === false && instance.getValue() === minimum))) { - report.addError(instance, schema, "minimum", "Number is less than the required minimum value", minimum); - } - } - } - }, - - "maximum" : { - "type" : "number", - "optional" : true, - - "parser" : function (instance, self) { - if (instance.getType() === "number") { - return instance.getValue(); - } - }, - - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var maximum, maximumCanEqual; - if (instance.getType() === "number") { - maximum = schema.getAttribute("maximum"); - maximumCanEqual = schema.getAttribute("maximumCanEqual"); - if (typeof maximum === "number" && (instance.getValue() > maximum || (maximumCanEqual === false && instance.getValue() === maximum))) { - report.addError(instance, schema, "maximum", "Number is greater than the required maximum value", maximum); - } - } - } - }, - - "minimumCanEqual" : { - "type" : "boolean", - "optional" : true, - "requires" : "minimum", - "default" : true, - - "parser" : function (instance, self) { - if (instance.getType() === "boolean") { - return instance.getValue(); - } - //else - return true; - } - }, - - "maximumCanEqual" : { - "type" : "boolean", - "optional" : true, - "requires" : "maximum", - "default" : true, - - "parser" : function (instance, self) { - if (instance.getType() === "boolean") { - return instance.getValue(); - } - //else - return true; - } - }, - - "minItems" : { - "type" : "integer", - "optional" : true, - "minimum" : 0, - "default" : 0, - - "parser" : function (instance, self) { - if (instance.getType() === "number") { - return instance.getValue(); - } - //else - return 0; - }, - - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var minItems; - if (instance.getType() === "array") { - minItems = schema.getAttribute("minItems"); - if (typeof minItems === "number" && instance.getProperties().length < minItems) { - report.addError(instance, schema, "minItems", "The number of items is less than the required minimum", minItems); - } - } - } - }, - - "maxItems" : { - "type" : "integer", - "optional" : true, - "minimum" : 0, - - "parser" : function (instance, self) { - if (instance.getType() === "number") { - return instance.getValue(); - } - }, - - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var maxItems; - if (instance.getType() === "array") { - maxItems = schema.getAttribute("maxItems"); - if (typeof maxItems === "number" && instance.getProperties().length > maxItems) { - report.addError(instance, schema, "maxItems", "The number of items is greater than the required maximum", maxItems); - } - } - } - }, - - "pattern" : { - "type" : "string", - "optional" : true, - "format" : "regex", - - "parser" : function (instance, self) { - if (instance.getType() === "string") { - return instance.getValue(); - } - }, - - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var pattern; - try { - pattern = new RegExp(schema.getAttribute("pattern")); - if (instance.getType() === "string" && pattern && !pattern.test(instance.getValue())) { - report.addError(instance, schema, "pattern", "String does not match pattern", pattern.toString()); - } - } catch (e) { - report.addError(schema, self, "pattern", "Invalid pattern", schema.getValueOfProperty("pattern")); - } - } - }, - - "minLength" : { - "type" : "integer", - "optional" : true, - "minimum" : 0, - "default" : 0, - - "parser" : function (instance, self) { - if (instance.getType() === "number") { - return instance.getValue(); - } - //else - return 0; - }, - - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var minLength; - if (instance.getType() === "string") { - minLength = schema.getAttribute("minLength"); - if (typeof minLength === "number" && instance.getValue().length < minLength) { - report.addError(instance, schema, "minLength", "String is less than the required minimum length", minLength); - } - } - } - }, - - "maxLength" : { - "type" : "integer", - "optional" : true, - - "parser" : function (instance, self) { - if (instance.getType() === "number") { - return instance.getValue(); - } - }, - - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var maxLength; - if (instance.getType() === "string") { - maxLength = schema.getAttribute("maxLength"); - if (typeof maxLength === "number" && instance.getValue().length > maxLength) { - report.addError(instance, schema, "maxLength", "String is greater than the required maximum length", maxLength); - } - } - } - }, - - "enum" : { - "type" : "array", - "optional" : true, - "minItems" : 1, - "uniqueItems" : true, - - "parser" : function (instance, self) { - if (instance.getType() === "array") { - return instance.getValue(); - } - }, - - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var enums, x, xl; - if (instance.getType() !== "undefined") { - enums = schema.getAttribute("enum"); - if (enums) { - for (x = 0, xl = enums.length; x < xl; ++x) { - if (instance.equals(enums[x])) { - return true; - } - } - report.addError(instance, schema, "enum", "Instance is not one of the possible values", enums); - } - } - } - }, - - "title" : { - "type" : "string", - "optional" : true - }, - - "description" : { - "type" : "string", - "optional" : true - }, - - "format" : { - "type" : "string", - "optional" : true, - - "parser" : function (instance, self) { - if (instance.getType() === "string") { - return instance.getValue(); - } - }, - - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var format, formatValidators; - if (instance.getType() === "string") { - format = schema.getAttribute("format"); - formatValidators = self.getValueOfProperty("formatValidators"); - if (typeof format === "string" && formatValidators[format] !== O[format] && typeof formatValidators[format] === "function" && !formatValidators[format].call(this, instance, report)) { - report.addError(instance, schema, "format", "String is not in the required format", format); - } - } - }, - - "formatValidators" : {} - }, - - "contentEncoding" : { - "type" : "string", - "optional" : true - }, - - "default" : { - "type" : "any", - "optional" : true - }, - - "maxDecimal" : { - "type" : "integer", - "optional" : true, - "minimum" : 0, - - "parser" : function (instance, self) { - if (instance.getType() === "number") { - return instance.getValue(); - } - }, - - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var maxDecimal, decimals; - if (instance.getType() === "number") { - maxDecimal = schema.getAttribute("maxDecimal"); - if (typeof maxDecimal === "number") { - decimals = instance.getValue().toString(10).split('.')[1]; - if (decimals && decimals.length > maxDecimal) { - report.addError(instance, schema, "maxDecimal", "The number of decimal places is greater than the allowed maximum", maxDecimal); - } - } - } - } - }, - - "disallow" : { - "type" : ["string", "array"], - "items" : {"type" : "string"}, - "optional" : true, - "uniqueItems" : true, - - "parser" : function (instance, self) { - if (instance.getType() === "string" || instance.getType() === "array") { - return instance.getValue(); - } - }, - - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var disallowedTypes = JSV.toArray(schema.getAttribute("disallow")), - x, xl, key, typeValidators, subreport; - - //for instances that are required to be a certain type - if (instance.getType() !== "undefined" && disallowedTypes && disallowedTypes.length) { - typeValidators = self.getValueOfProperty("typeValidators") || {}; - - //ensure that type matches for at least one of the required types - for (x = 0, xl = disallowedTypes.length; x < xl; ++x) { - key = disallowedTypes[x]; - if (JSV.isJSONSchema(key)) { //this is supported draft-03 and on - subreport = JSV.createObject(report); - subreport.errors = []; - subreport.validated = JSV.clone(report.validated); - if (key.validate(instance, subreport, parent, parentSchema, name).errors.length === 0) { - //instance matches this schema - report.addError(instance, schema, "disallow", "Instance is a disallowed type", disallowedTypes); - return false; - } - } else if (typeValidators[key] !== O[key] && typeof typeValidators[key] === "function") { - if (typeValidators[key](instance, report)) { - report.addError(instance, schema, "disallow", "Instance is a disallowed type", disallowedTypes); - return false; - } - } - /* - else { - report.addError(instance, schema, "disallow", "Instance may be a disallowed type", disallowedTypes); - return false; - } - */ - } - - //if we get to this point, type is valid - return true; - } - //else, everything is allowed if no disallowed types are specified - return true; - }, - - "typeValidators" : TYPE_VALIDATORS - }, - - "extends" : { - "type" : [{"$ref" : "#"}, "array"], - "items" : {"$ref" : "#"}, - "optional" : true, - "default" : {}, - - "parser" : function (instance, self) { - if (instance.getType() === "object") { - return instance.getEnvironment().createSchema(instance, self.getEnvironment().findSchema(self.resolveURI("#"))); - } else if (instance.getType() === "array") { - return JSV.mapArray(instance.getProperties(), function (instance) { - return instance.getEnvironment().createSchema(instance, self.getEnvironment().findSchema(self.resolveURI("#"))); - }); - } - }, - - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var extensions = schema.getAttribute("extends"), x, xl; - if (extensions) { - if (JSV.isJSONSchema(extensions)) { - extensions.validate(instance, report, parent, parentSchema, name); - } else if (JSV.typeOf(extensions) === "array") { - for (x = 0, xl = extensions.length; x < xl; ++x) { - extensions[x].validate(instance, report, parent, parentSchema, name); - } - } - } - } - } - }, - - "optional" : true, - "default" : {}, - "fragmentResolution" : "dot-delimited", - - "parser" : function (instance, self) { - if (instance.getType() === "object") { - return instance.getEnvironment().createSchema(instance, self); - } - }, - - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var propNames = schema.getPropertyNames(), - x, xl, - attributeSchemas = self.getAttribute("properties"), - strict = instance.getEnvironment().getOption("strict"), - validator; - - for (x in attributeSchemas) { - if (attributeSchemas[x] !== O[x]) { - if (attributeSchemas[x].getValueOfProperty("validationRequired")) { - JSV.pushUnique(propNames, x); - } - if (strict && attributeSchemas[x].getValueOfProperty("deprecated")) { - JSV.popFirst(propNames, x); - } - } - } - - for (x = 0, xl = propNames.length; x < xl; ++x) { - if (attributeSchemas[propNames[x]] !== O[propNames[x]]) { - validator = attributeSchemas[propNames[x]].getValueOfProperty("validator"); - if (typeof validator === "function") { - validator(instance, schema, attributeSchemas[propNames[x]], report, parent, parentSchema, name); - } - } - } - } - }; - - HYPERSCHEMA_00_JSON = { - "$schema" : "http://json-schema.org/draft-00/hyper-schema#", - "id" : "http://json-schema.org/draft-00/hyper-schema#", - - "properties" : { - "links" : { - "type" : "array", - "items" : {"$ref" : "links#"}, - "optional" : true, - - "parser" : function (instance, self, arg) { - var links, - linkSchemaURI = self.getValueOfProperty("items")["$ref"], - linkSchema = self.getEnvironment().findSchema(linkSchemaURI), - linkParser = linkSchema && linkSchema.getValueOfProperty("parser"), - selfReferenceVariable; - arg = JSV.toArray(arg); - - if (typeof linkParser === "function") { - links = JSV.mapArray(instance.getProperties(), function (link) { - return linkParser(link, linkSchema); - }); - } else { - links = JSV.toArray(instance.getValue()); - } - - if (arg[0]) { - links = JSV.filterArray(links, function (link) { - return link["rel"] === arg[0]; - }); - } - - if (arg[1]) { - selfReferenceVariable = self.getValueOfProperty("selfReferenceVariable"); - links = JSV.mapArray(links, function (link) { - var instance = arg[1], - href = link["href"]; - href = href.replace(/\{(.+)\}/g, function (str, p1, offset, s) { - var value; - if (p1 === selfReferenceVariable) { - value = instance.getValue(); - } else { - value = instance.getValueOfProperty(p1); - } - return value !== undefined ? String(value) : ""; - }); - return href ? JSV.formatURI(instance.resolveURI(href)) : href; - }); - } - - return links; - }, - - "selfReferenceVariable" : "-this" - }, - - "fragmentResolution" : { - "type" : "string", - "optional" : true, - "default" : "dot-delimited" - }, - - "root" : { - "type" : "boolean", - "optional" : true, - "default" : false - }, - - "readonly" : { - "type" : "boolean", - "optional" : true, - "default" : false - }, - - "pathStart" : { - "type" : "string", - "optional" : true, - "format" : "uri", - - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var pathStart; - if (instance.getType() !== "undefined") { - pathStart = schema.getAttribute("pathStart"); - if (typeof pathStart === "string") { - //TODO: Find out what pathStart is relative to - if (instance.getURI().indexOf(pathStart) !== 0) { - report.addError(instance, schema, "pathStart", "Instance's URI does not start with " + pathStart, pathStart); - } - } - } - } - }, - - "mediaType" : { - "type" : "string", - "optional" : true, - "format" : "media-type" - }, - - "alternate" : { - "type" : "array", - "items" : {"$ref" : "#"}, - "optional" : true - } - }, - - "links" : [ - { - "href" : "{$ref}", - "rel" : "full" - }, - - { - "href" : "{$schema}", - "rel" : "describedby" - }, - - { - "href" : "{id}", - "rel" : "self" - } - ], - - "initializer" : function (instance) { - var link, extension, extended; - - //if there is a link to a different schema, set reference - link = instance._schema.getLink("describedby", instance); - if (link && instance._schema._uri !== link) { - instance.setReference("describedby", link); - } - - //if instance has a URI link to itself, update it's own URI - link = instance._schema.getLink("self", instance); - if (JSV.typeOf(link) === "string") { - instance._uri = JSV.formatURI(link); - } - - //if there is a link to the full representation, set reference - link = instance._schema.getLink("full", instance); - if (link && instance._uri !== link) { - instance.setReference("full", link); - } - - //extend schema - extension = instance.getAttribute("extends"); - if (JSV.isJSONSchema(extension)) { - extended = JSV.inherits(extension, instance, true); - instance = instance._env.createSchema(extended, instance._schema, instance._uri); - } - - return instance; - } - - //not needed as JSV.inherits does the job for us - //"extends" : {"$ref" : "http://json-schema.org/schema#"} - }; - - LINKS_00_JSON = { - "$schema" : "http://json-schema.org/draft-00/hyper-schema#", - "id" : "http://json-schema.org/draft-00/links#", - "type" : "object", - - "properties" : { - "href" : { - "type" : "string" - }, - - "rel" : { - "type" : "string" - }, - - "method" : { - "type" : "string", - "default" : "GET", - "optional" : true - }, - - "enctype" : { - "type" : "string", - "requires" : "method", - "optional" : true - }, - - "properties" : { - "type" : "object", - "additionalProperties" : {"$ref" : "hyper-schema#"}, - "optional" : true, - - "parser" : function (instance, self, arg) { - var env = instance.getEnvironment(), - selfEnv = self.getEnvironment(), - additionalPropertiesSchemaURI = self.getValueOfProperty("additionalProperties")["$ref"]; - if (instance.getType() === "object") { - if (arg) { - return env.createSchema(instance.getProperty(arg), selfEnv.findSchema(self.resolveURI(additionalPropertiesSchemaURI))); - } else { - return JSV.mapObject(instance.getProperties(), function (instance) { - return env.createSchema(instance, selfEnv.findSchema(self.resolveURI(additionalPropertiesSchemaURI))); - }); - } - } - } - } - }, - - "parser" : function (instance, self) { - var selfProperties = self.getProperty("properties"); - if (instance.getType() === "object") { - return JSV.mapObject(instance.getProperties(), function (property, key) { - var propertySchema = selfProperties.getProperty(key), - parser = propertySchema && propertySchema.getValueOfProperty("parser"); - if (typeof parser === "function") { - return parser(property, propertySchema); - } - //else - return property.getValue(); - }); - } - return instance.getValue(); - } - }; - - ENVIRONMENT.setOption("defaultFragmentDelimiter", "."); - ENVIRONMENT.setOption("defaultSchemaURI", "http://json-schema.org/draft-00/schema#"); //updated later - - SCHEMA_00 = ENVIRONMENT.createSchema(SCHEMA_00_JSON, true, "http://json-schema.org/draft-00/schema#"); - HYPERSCHEMA_00 = ENVIRONMENT.createSchema(JSV.inherits(SCHEMA_00, ENVIRONMENT.createSchema(HYPERSCHEMA_00_JSON, true, "http://json-schema.org/draft-00/hyper-schema#"), true), true, "http://json-schema.org/draft-00/hyper-schema#"); - - ENVIRONMENT.setOption("defaultSchemaURI", "http://json-schema.org/draft-00/hyper-schema#"); - - LINKS_00 = ENVIRONMENT.createSchema(LINKS_00_JSON, HYPERSCHEMA_00, "http://json-schema.org/draft-00/links#"); - - // - // draft-01 - // - - SCHEMA_01_JSON = JSV.inherits(SCHEMA_00_JSON, { - "$schema" : "http://json-schema.org/draft-01/hyper-schema#", - "id" : "http://json-schema.org/draft-01/schema#" - }); - - HYPERSCHEMA_01_JSON = JSV.inherits(HYPERSCHEMA_00_JSON, { - "$schema" : "http://json-schema.org/draft-01/hyper-schema#", - "id" : "http://json-schema.org/draft-01/hyper-schema#" - }); - - LINKS_01_JSON = JSV.inherits(LINKS_00_JSON, { - "$schema" : "http://json-schema.org/draft-01/hyper-schema#", - "id" : "http://json-schema.org/draft-01/links#" - }); - - ENVIRONMENT.setOption("defaultSchemaURI", "http://json-schema.org/draft-01/schema#"); //update later - - SCHEMA_01 = ENVIRONMENT.createSchema(SCHEMA_01_JSON, true, "http://json-schema.org/draft-01/schema#"); - HYPERSCHEMA_01 = ENVIRONMENT.createSchema(JSV.inherits(SCHEMA_01, ENVIRONMENT.createSchema(HYPERSCHEMA_01_JSON, true, "http://json-schema.org/draft-01/hyper-schema#"), true), true, "http://json-schema.org/draft-01/hyper-schema#"); - - ENVIRONMENT.setOption("defaultSchemaURI", "http://json-schema.org/draft-01/hyper-schema#"); - - LINKS_01 = ENVIRONMENT.createSchema(LINKS_01_JSON, HYPERSCHEMA_01, "http://json-schema.org/draft-01/links#"); - - // - // draft-02 - // - - SCHEMA_02_JSON = JSV.inherits(SCHEMA_01_JSON, { - "$schema" : "http://json-schema.org/draft-02/hyper-schema#", - "id" : "http://json-schema.org/draft-02/schema#", - - "properties" : { - "uniqueItems" : { - "type" : "boolean", - "optional" : true, - "default" : false, - - "parser" : function (instance, self) { - return !!instance.getValue(); - }, - - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var value, x, xl, y, yl; - if (instance.getType() === "array" && schema.getAttribute("uniqueItems")) { - value = instance.getProperties(); - for (x = 0, xl = value.length - 1; x < xl; ++x) { - for (y = x + 1, yl = value.length; y < yl; ++y) { - if (value[x].equals(value[y])) { - report.addError(instance, schema, "uniqueItems", "Array can only contain unique items", { x : x, y : y }); - } - } - } - } - } - }, - - "maxDecimal" : { - "deprecated" : true - }, - - "divisibleBy" : { - "type" : "number", - "minimum" : 0, - "minimumCanEqual" : false, - "optional" : true, - - "parser" : function (instance, self) { - if (instance.getType() === "number") { - return instance.getValue(); - } - }, - - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var divisor, value, digits; - if (instance.getType() === "number") { - divisor = schema.getAttribute("divisibleBy"); - if (divisor === 0) { - report.addError(instance, schema, "divisibleBy", "Nothing is divisible by 0", divisor); - } else if (divisor !== 1) { - value = instance.getValue(); - digits = Math.max((value.toString().split(".")[1] || " ").length, (divisor.toString().split(".")[1] || " ").length); - digits = parseFloat(((value / divisor) % 1).toFixed(digits)); //cut out floating point errors - if (0 < digits && digits < 1) { - report.addError(instance, schema, "divisibleBy", "Number is not divisible by " + divisor, divisor); - } - } - } - } - } - }, - - "fragmentResolution" : "slash-delimited" - }); - - HYPERSCHEMA_02_JSON = JSV.inherits(HYPERSCHEMA_01_JSON, { - "id" : "http://json-schema.org/draft-02/hyper-schema#", - - "properties" : { - "fragmentResolution" : { - "default" : "slash-delimited" - } - } - }); - - LINKS_02_JSON = JSV.inherits(LINKS_01_JSON, { - "$schema" : "http://json-schema.org/draft-02/hyper-schema#", - "id" : "http://json-schema.org/draft-02/links#", - - "properties" : { - "targetSchema" : { - "$ref" : "hyper-schema#", - - //need this here because parsers are run before links are resolved - "parser" : HYPERSCHEMA_01.getAttribute("parser") - } - } - }); - - ENVIRONMENT.setOption("defaultFragmentDelimiter", "/"); - ENVIRONMENT.setOption("defaultSchemaURI", "http://json-schema.org/draft-02/schema#"); //update later - - SCHEMA_02 = ENVIRONMENT.createSchema(SCHEMA_02_JSON, true, "http://json-schema.org/draft-02/schema#"); - HYPERSCHEMA_02 = ENVIRONMENT.createSchema(JSV.inherits(SCHEMA_02, ENVIRONMENT.createSchema(HYPERSCHEMA_02_JSON, true, "http://json-schema.org/draft-02/hyper-schema#"), true), true, "http://json-schema.org/draft-02/hyper-schema#"); - - ENVIRONMENT.setOption("defaultSchemaURI", "http://json-schema.org/draft-02/hyper-schema#"); - - LINKS_02 = ENVIRONMENT.createSchema(LINKS_02_JSON, HYPERSCHEMA_02, "http://json-schema.org/draft-02/links#"); - - // - // draft-03 - // - - function getMatchedPatternProperties(instance, schema, report, self) { - var matchedProperties = {}, patternProperties, pattern, regexp, properties, key; - - if (instance.getType() === "object") { - patternProperties = schema.getAttribute("patternProperties"); - properties = instance.getProperties(); - for (pattern in patternProperties) { - if (patternProperties[pattern] !== O[pattern]) { - regexp = null; - try { - regexp = new RegExp(pattern); - } catch (e) { - if (report) { - report.addError(schema, self, "patternProperties", "Invalid pattern", pattern); - } - } - - if (regexp) { - for (key in properties) { - if (properties[key] !== O[key] && regexp.test(key)) { - matchedProperties[key] = matchedProperties[key] ? JSV.pushUnique(matchedProperties[key], patternProperties[pattern]) : [ patternProperties[pattern] ]; - } - } - } - } - } - } - - return matchedProperties; - } - - SCHEMA_03_JSON = JSV.inherits(SCHEMA_02_JSON, { - "$schema" : "http://json-schema.org/draft-03/schema#", - "id" : "http://json-schema.org/draft-03/schema#", - - "properties" : { - "patternProperties" : { - "type" : "object", - "additionalProperties" : {"$ref" : "#"}, - "default" : {}, - - "parser" : SCHEMA_02.getValueOfProperty("properties")["properties"]["parser"], - - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var matchedProperties, key, x; - if (instance.getType() === "object") { - matchedProperties = getMatchedPatternProperties(instance, schema, report, self); - for (key in matchedProperties) { - if (matchedProperties[key] !== O[key]) { - x = matchedProperties[key].length; - while (x--) { - matchedProperties[key][x].validate(instance.getProperty(key), report, instance, schema, key); - } - } - } - } - } - }, - - "additionalProperties" : { - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var additionalProperties, propertySchemas, properties, matchedProperties, key; - if (instance.getType() === "object") { - additionalProperties = schema.getAttribute("additionalProperties"); - propertySchemas = schema.getAttribute("properties") || {}; - properties = instance.getProperties(); - matchedProperties = getMatchedPatternProperties(instance, schema); - for (key in properties) { - if (properties[key] !== O[key] && properties[key] && propertySchemas[key] === O[key] && matchedProperties[key] === O[key]) { - if (JSV.isJSONSchema(additionalProperties)) { - additionalProperties.validate(properties[key], report, instance, schema, key); - } else if (additionalProperties === false) { - report.addError(instance, schema, "additionalProperties", "Additional properties are not allowed", additionalProperties); - } - } - } - } - } - }, - - "items" : { - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var properties, items, x, xl, itemSchema, additionalItems; - - if (instance.getType() === "array") { - properties = instance.getProperties(); - items = schema.getAttribute("items"); - additionalItems = schema.getAttribute("additionalItems"); - - if (JSV.typeOf(items) === "array") { - for (x = 0, xl = properties.length; x < xl; ++x) { - itemSchema = items[x] || additionalItems; - if (itemSchema !== false) { - itemSchema.validate(properties[x], report, instance, schema, x); - } else { - report.addError(instance, schema, "additionalItems", "Additional items are not allowed", itemSchema); - } - } - } else { - itemSchema = items || additionalItems; - for (x = 0, xl = properties.length; x < xl; ++x) { - itemSchema.validate(properties[x], report, instance, schema, x); - } - } - } - } - }, - - "additionalItems" : { - "type" : [{"$ref" : "#"}, "boolean"], - "default" : {}, - - "parser" : SCHEMA_02.getValueOfProperty("properties")["additionalProperties"]["parser"], - - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var additionalItems, properties, x, xl; - //only validate if the "items" attribute is undefined - if (instance.getType() === "array" && schema.getProperty("items").getType() === "undefined") { - additionalItems = schema.getAttribute("additionalItems"); - properties = instance.getProperties(); - - if (additionalItems !== false) { - for (x = 0, xl = properties.length; x < xl; ++x) { - additionalItems.validate(properties[x], report, instance, schema, x); - } - } else if (properties.length) { - report.addError(instance, schema, "additionalItems", "Additional items are not allowed", additionalItems); - } - } - } - }, - - "optional" : { - "validationRequired" : false, - "deprecated" : true - }, - - "required" : { - "type" : "boolean", - "default" : false, - - "parser" : function (instance, self) { - return !!instance.getValue(); - }, - - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - if (instance.getType() === "undefined" && schema.getAttribute("required")) { - report.addError(instance, schema, "required", "Property is required", true); - } - } - }, - - "requires" : { - "deprecated" : true - }, - - "dependencies" : { - "type" : "object", - "additionalProperties" : { - "type" : ["string", "array", {"$ref" : "#"}], - "items" : { - "type" : "string" - } - }, - "default" : {}, - - "parser" : function (instance, self, arg) { - function parseProperty(property) { - var type = property.getType(); - if (type === "string" || type === "array") { - return property.getValue(); - } else if (type === "object") { - return property.getEnvironment().createSchema(property, self.getEnvironment().findSchema(self.resolveURI("#"))); - } - } - - if (instance.getType() === "object") { - if (arg) { - return parseProperty(instance.getProperty(arg)); - } else { - return JSV.mapObject(instance.getProperties(), parseProperty); - } - } - //else - return {}; - }, - - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var dependencies, key, dependency, type, x, xl; - if (instance.getType() === "object") { - dependencies = schema.getAttribute("dependencies"); - for (key in dependencies) { - if (dependencies[key] !== O[key] && instance.getProperty(key).getType() !== "undefined") { - dependency = dependencies[key]; - type = JSV.typeOf(dependency); - if (type === "string") { - if (instance.getProperty(dependency).getType() === "undefined") { - report.addError(instance, schema, "dependencies", 'Property "' + key + '" requires sibling property "' + dependency + '"', dependencies); - } - } else if (type === "array") { - for (x = 0, xl = dependency.length; x < xl; ++x) { - if (instance.getProperty(dependency[x]).getType() === "undefined") { - report.addError(instance, schema, "dependencies", 'Property "' + key + '" requires sibling property "' + dependency[x] + '"', dependencies); - } - } - } else if (JSV.isJSONSchema(dependency)) { - dependency.validate(instance, report); - } - } - } - } - } - }, - - "minimumCanEqual" : { - "deprecated" : true - }, - - "maximumCanEqual" : { - "deprecated" : true - }, - - "exclusiveMinimum" : { - "type" : "boolean", - "default" : false, - - "parser" : function (instance, self) { - return !!instance.getValue(); - } - }, - - "exclusiveMaximum" : { - "type" : "boolean", - "default" : false, - - "parser" : function (instance, self) { - return !!instance.getValue(); - } - }, - - "minimum" : { - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var minimum, exclusiveMinimum; - if (instance.getType() === "number") { - minimum = schema.getAttribute("minimum"); - exclusiveMinimum = schema.getAttribute("exclusiveMinimum") || (!instance.getEnvironment().getOption("strict") && !schema.getAttribute("minimumCanEqual")); - if (typeof minimum === "number" && (instance.getValue() < minimum || (exclusiveMinimum === true && instance.getValue() === minimum))) { - report.addError(instance, schema, "minimum", "Number is less than the required minimum value", minimum); - } - } - } - }, - - "maximum" : { - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var maximum, exclusiveMaximum; - if (instance.getType() === "number") { - maximum = schema.getAttribute("maximum"); - exclusiveMaximum = schema.getAttribute("exclusiveMaximum") || (!instance.getEnvironment().getOption("strict") && !schema.getAttribute("maximumCanEqual")); - if (typeof maximum === "number" && (instance.getValue() > maximum || (exclusiveMaximum === true && instance.getValue() === maximum))) { - report.addError(instance, schema, "maximum", "Number is greater than the required maximum value", maximum); - } - } - } - }, - - "contentEncoding" : { - "deprecated" : true - }, - - "divisibleBy" : { - "exclusiveMinimum" : true - }, - - "disallow" : { - "items" : { - "type" : ["string", {"$ref" : "#"}] - }, - - "parser" : SCHEMA_02_JSON["properties"]["type"]["parser"] - }, - - "id" : { - "type" : "string", - "format" : "uri" - }, - - "$ref" : { - "type" : "string", - "format" : "uri" - }, - - "$schema" : { - "type" : "string", - "format" : "uri" - } - }, - - "dependencies" : { - "exclusiveMinimum" : "minimum", - "exclusiveMaximum" : "maximum" - }, - - "initializer" : function (instance) { - var link, extension, extended, - schemaLink = instance.getValueOfProperty("$schema"), - refLink = instance.getValueOfProperty("$ref"), - idLink = instance.getValueOfProperty("id"); - - //if there is a link to a different schema, set reference - if (schemaLink) { - link = instance.resolveURI(schemaLink); - instance.setReference("describedby", link); - } - - //if instance has a URI link to itself, update it's own URI - if (idLink) { - link = instance.resolveURI(idLink); - if (JSV.typeOf(link) === "string") { - instance._uri = JSV.formatURI(link); - } - } - - //if there is a link to the full representation, set reference - if (refLink) { - link = instance.resolveURI(refLink); - instance.setReference("full", link); - } - - //extend schema - extension = instance.getAttribute("extends"); - if (JSV.isJSONSchema(extension)) { - extended = JSV.inherits(extension, instance, true); - instance = instance._env.createSchema(extended, instance._schema, instance._uri); - } - - return instance; - } - }); - - HYPERSCHEMA_03_JSON = JSV.inherits(HYPERSCHEMA_02_JSON, { - "$schema" : "http://json-schema.org/draft-03/hyper-schema#", - "id" : "http://json-schema.org/draft-03/hyper-schema#", - - "properties" : { - "links" : { - "selfReferenceVariable" : "@" - }, - - "root" : { - "deprecated" : true - }, - - "contentEncoding" : { - "deprecated" : false //moved from core to hyper - }, - - "alternate" : { - "deprecated" : true - } - } - }); - - LINKS_03_JSON = JSV.inherits(LINKS_02_JSON, { - "$schema" : "http://json-schema.org/draft-03/hyper-schema#", - "id" : "http://json-schema.org/draft-03/links#", - - "properties" : { - "href" : { - "required" : true, - "format" : "link-description-object-template" - }, - - "rel" : { - "required" : true - }, - - "properties" : { - "deprecated" : true - }, - - "schema" : {"$ref" : "http://json-schema.org/draft-03/hyper-schema#"} - } - }); - - ENVIRONMENT.setOption("defaultSchemaURI", "http://json-schema.org/draft-03/schema#"); //update later - - SCHEMA_03 = ENVIRONMENT.createSchema(SCHEMA_03_JSON, true, "http://json-schema.org/draft-03/schema#"); - HYPERSCHEMA_03 = ENVIRONMENT.createSchema(JSV.inherits(SCHEMA_03, ENVIRONMENT.createSchema(HYPERSCHEMA_03_JSON, true, "http://json-schema.org/draft-03/hyper-schema#"), true), true, "http://json-schema.org/draft-03/hyper-schema#"); - - ENVIRONMENT.setOption("defaultSchemaURI", "http://json-schema.org/draft-03/hyper-schema#"); - - LINKS_03 = ENVIRONMENT.createSchema(LINKS_03_JSON, true, "http://json-schema.org/draft-03/links#"); - - ENVIRONMENT.setOption("latestJSONSchemaSchemaURI", "http://json-schema.org/draft-03/schema#"); - ENVIRONMENT.setOption("latestJSONSchemaHyperSchemaURI", "http://json-schema.org/draft-03/hyper-schema#"); - ENVIRONMENT.setOption("latestJSONSchemaLinksURI", "http://json-schema.org/draft-03/links#"); - - // - //Latest JSON Schema - // - - //Hack, but WAY faster than instantiating a new schema - ENVIRONMENT._schemas["http://json-schema.org/schema#"] = SCHEMA_03; - ENVIRONMENT._schemas["http://json-schema.org/hyper-schema#"] = HYPERSCHEMA_03; - ENVIRONMENT._schemas["http://json-schema.org/links#"] = LINKS_03; - - // - //register environment - // - - JSV.registerEnvironment("json-schema-draft-03", ENVIRONMENT); - if (!JSV.getDefaultEnvironmentID() || JSV.getDefaultEnvironmentID() === "json-schema-draft-01" || JSV.getDefaultEnvironmentID() === "json-schema-draft-02") { - JSV.setDefaultEnvironmentID("json-schema-draft-03"); - } - -}()); \ No newline at end of file +/** + * json-schema-draft-03 Environment + * + * @fileOverview Implementation of the third revision of the JSON Schema specification draft. + * @author Gary Court + * @version 1.5.1 + * @see http://github.com/garycourt/JSV + */ + +/* + * Copyright 2010 Gary Court. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY GARY COURT ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GARY COURT OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of Gary Court or the JSON Schema specification. + */ + +/*jslint white: true, sub: true, onevar: true, undef: true, eqeqeq: true, newcap: true, immed: true, indent: 4 */ +/*global require */ + +(function () { + var O = {}, + JSV = require('./jsv').JSV, + TYPE_VALIDATORS, + ENVIRONMENT, + SCHEMA_00_JSON, + HYPERSCHEMA_00_JSON, + LINKS_00_JSON, + SCHEMA_00, + HYPERSCHEMA_00, + LINKS_00, + SCHEMA_01_JSON, + HYPERSCHEMA_01_JSON, + LINKS_01_JSON, + SCHEMA_01, + HYPERSCHEMA_01, + LINKS_01, + SCHEMA_02_JSON, + HYPERSCHEMA_02_JSON, + LINKS_02_JSON, + SCHEMA_02, + HYPERSCHEMA_02, + LINKS_02, + SCHEMA_03_JSON, + HYPERSCHEMA_03_JSON, + LINKS_03_JSON, + SCHEMA_03, + HYPERSCHEMA_03, + LINKS_03; + + TYPE_VALIDATORS = { + "string" : function (instance, report) { + return instance.getType() === "string"; + }, + + "number" : function (instance, report) { + return instance.getType() === "number"; + }, + + "integer" : function (instance, report) { + return instance.getType() === "number" && instance.getValue() % 1 === 0; + }, + + "boolean" : function (instance, report) { + return instance.getType() === "boolean"; + }, + + "object" : function (instance, report) { + return instance.getType() === "object"; + }, + + "array" : function (instance, report) { + return instance.getType() === "array"; + }, + + "null" : function (instance, report) { + return instance.getType() === "null"; + }, + + "any" : function (instance, report) { + return true; + } + }; + + ENVIRONMENT = new JSV.Environment(); + ENVIRONMENT.setOption("validateReferences", true); + ENVIRONMENT.setOption("enforceReferences", false); + ENVIRONMENT.setOption("strict", false); + + // + // draft-00 + // + + SCHEMA_00_JSON = { + "$schema" : "http://json-schema.org/draft-00/hyper-schema#", + "id" : "http://json-schema.org/draft-00/schema#", + "type" : "object", + + "properties" : { + "type" : { + "type" : ["string", "array"], + "items" : { + "type" : ["string", {"$ref" : "#"}] + }, + "optional" : true, + "uniqueItems" : true, + "default" : "any", + + "parser" : function (instance, self) { + var parser; + + if (instance.getType() === "string") { + return instance.getValue(); + } else if (instance.getType() === "object") { + return instance.getEnvironment().createSchema( + instance, + self.getEnvironment().findSchema(self.resolveURI("#")) + ); + } else if (instance.getType() === "array") { + parser = self.getValueOfProperty("parser"); + return JSV.mapArray(instance.getProperties(), function (prop) { + return parser(prop, self); + }); + } + //else + return "any"; + }, + + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var requiredTypes = JSV.toArray(schema.getAttribute("type")), + x, xl, type, subreport, typeValidators; + + //for instances that are required to be a certain type + if (instance.getType() !== "undefined" && requiredTypes && requiredTypes.length) { + typeValidators = self.getValueOfProperty("typeValidators") || {}; + + //ensure that type matches for at least one of the required types + for (x = 0, xl = requiredTypes.length; x < xl; ++x) { + type = requiredTypes[x]; + if (JSV.isJSONSchema(type)) { + subreport = JSV.createObject(report); + subreport.errors = []; + subreport.validated = JSV.clone(report.validated); + if (type.validate(instance, subreport, parent, parentSchema, name).errors.length === 0) { + return true; //instance matches this schema + } + } else { + instance.applyTypeCoercionIfSet(type); + + if (typeValidators[type] !== O[type] && typeof typeValidators[type] === "function") { + if (typeValidators[type](instance, report)) { + return true; //type is valid + } + } else { + return true; //unknown types are assumed valid + } + } + } + + //if we get to this point, type is invalid + report.addError(instance, schema, "type", "Instance is not a required type", requiredTypes); + return false; + } + //else, anything is allowed if no type is specified + return true; + }, + + "typeValidators" : TYPE_VALIDATORS + }, + + "properties" : { + "type" : "object", + "additionalProperties" : {"$ref" : "#"}, + "optional" : true, + "default" : {}, + + "parser" : function (instance, self, arg) { + var env = instance.getEnvironment(), + selfEnv = self.getEnvironment(); + if (instance.getType() === "object") { + if (arg) { + return env.createSchema(instance.getProperty(arg), selfEnv.findSchema(self.resolveURI("#"))); + } else { + return JSV.mapObject(instance.getProperties(), function (instance) { + return env.createSchema(instance, selfEnv.findSchema(self.resolveURI("#"))); + }); + } + } + //else + return {}; + }, + + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var propertySchemas, key; + //this attribute is for object type instances only + if (instance.getType() === "object") { + //for each property defined in the schema + propertySchemas = schema.getAttribute("properties"); + for (key in propertySchemas) { + if (propertySchemas[key] !== O[key] && propertySchemas[key]) { + //ensure that instance property is valid + propertySchemas[key].validate(instance.getProperty(key), report, instance, schema, key); + } + } + } + } + }, + + "items" : { + "type" : [{"$ref" : "#"}, "array"], + "items" : {"$ref" : "#"}, + "optional" : true, + "default" : {}, + + "parser" : function (instance, self) { + if (instance.getType() === "object") { + return instance.getEnvironment().createSchema(instance, self.getEnvironment().findSchema(self.resolveURI("#"))); + } else if (instance.getType() === "array") { + return JSV.mapArray(instance.getProperties(), function (instance) { + return instance.getEnvironment().createSchema(instance, self.getEnvironment().findSchema(self.resolveURI("#"))); + }); + } + //else + return instance.getEnvironment().createEmptySchema(); + }, + + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var properties, items, x, xl, itemSchema, additionalProperties; + + if (instance.getType() === "array") { + properties = instance.getProperties(); + items = schema.getAttribute("items"); + additionalProperties = schema.getAttribute("additionalProperties"); + + if (JSV.typeOf(items) === "array") { + for (x = 0, xl = properties.length; x < xl; ++x) { + itemSchema = items[x] || additionalProperties; + if (itemSchema !== false) { + itemSchema.validate(properties[x], report, instance, schema, x); + } else { + report.addError(instance, schema, "additionalProperties", "Additional items are not allowed", itemSchema); + } + } + } else { + itemSchema = items || additionalProperties; + for (x = 0, xl = properties.length; x < xl; ++x) { + itemSchema.validate(properties[x], report, instance, schema, x); + } + } + } + } + }, + + "optional" : { + "type" : "boolean", + "optional" : true, + "default" : false, + + "parser" : function (instance, self) { + return !!instance.getValue(); + }, + + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + if (instance.getType() === "undefined" && !schema.getAttribute("optional")) { + report.addError(instance, schema, "optional", "Property is required", false); + } + }, + + "validationRequired" : true + }, + + "additionalProperties" : { + "type" : [{"$ref" : "#"}, "boolean"], + "optional" : true, + "default" : {}, + + "parser" : function (instance, self) { + if (instance.getType() === "object") { + return instance.getEnvironment().createSchema(instance, self.getEnvironment().findSchema(self.resolveURI("#"))); + } else if (instance.getType() === "boolean" && instance.getValue() === false) { + return false; + } + //else + return instance.getEnvironment().createEmptySchema(); + }, + + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var additionalProperties, propertySchemas, properties, key; + //we only need to check against object types as arrays do their own checking on this property + if (instance.getType() === "object") { + additionalProperties = schema.getAttribute("additionalProperties"); + propertySchemas = schema.getAttribute("properties") || {}; + properties = instance.getProperties(); + for (key in properties) { + if (properties[key] !== O[key] && properties[key] && propertySchemas[key] === O[key]) { + if (JSV.isJSONSchema(additionalProperties)) { + additionalProperties.validate(properties[key], report, instance, schema, key); + } else if (additionalProperties === false) { + report.addError(instance, schema, "additionalProperties", "Additional properties are not allowed", additionalProperties); + } + } + } + } + } + }, + + "requires" : { + "type" : ["string", {"$ref" : "#"}], + "optional" : true, + + "parser" : function (instance, self) { + if (instance.getType() === "string") { + return instance.getValue(); + } else if (instance.getType() === "object") { + return instance.getEnvironment().createSchema(instance, self.getEnvironment().findSchema(self.resolveURI("#"))); + } + }, + + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var requires; + if (instance.getType() !== "undefined" && parent && parent.getType() !== "undefined") { + requires = schema.getAttribute("requires"); + if (typeof requires === "string") { + if (parent.getProperty(requires).getType() === "undefined") { + report.addError(instance, schema, "requires", 'Property requires sibling property "' + requires + '"', requires); + } + } else if (JSV.isJSONSchema(requires)) { + requires.validate(parent, report); //WATCH: A "requires" schema does not support the "requires" attribute + } + } + } + }, + + "minimum" : { + "type" : "number", + "optional" : true, + + "parser" : function (instance, self) { + if (instance.getType() === "number") { + return instance.getValue(); + } + }, + + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var minimum, minimumCanEqual; + if (instance.getType() === "number") { + minimum = schema.getAttribute("minimum"); + minimumCanEqual = schema.getAttribute("minimumCanEqual"); + if (typeof minimum === "number" && (instance.getValue() < minimum || (minimumCanEqual === false && instance.getValue() === minimum))) { + report.addError(instance, schema, "minimum", "Number is less than the required minimum value", minimum); + } + } + } + }, + + "maximum" : { + "type" : "number", + "optional" : true, + + "parser" : function (instance, self) { + if (instance.getType() === "number") { + return instance.getValue(); + } + }, + + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var maximum, maximumCanEqual; + if (instance.getType() === "number") { + maximum = schema.getAttribute("maximum"); + maximumCanEqual = schema.getAttribute("maximumCanEqual"); + if (typeof maximum === "number" && (instance.getValue() > maximum || (maximumCanEqual === false && instance.getValue() === maximum))) { + report.addError(instance, schema, "maximum", "Number is greater than the required maximum value", maximum); + } + } + } + }, + + "minimumCanEqual" : { + "type" : "boolean", + "optional" : true, + "requires" : "minimum", + "default" : true, + + "parser" : function (instance, self) { + if (instance.getType() === "boolean") { + return instance.getValue(); + } + //else + return true; + } + }, + + "maximumCanEqual" : { + "type" : "boolean", + "optional" : true, + "requires" : "maximum", + "default" : true, + + "parser" : function (instance, self) { + if (instance.getType() === "boolean") { + return instance.getValue(); + } + //else + return true; + } + }, + + "minItems" : { + "type" : "integer", + "optional" : true, + "minimum" : 0, + "default" : 0, + + "parser" : function (instance, self) { + if (instance.getType() === "number") { + return instance.getValue(); + } + //else + return 0; + }, + + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var minItems; + if (instance.getType() === "array") { + minItems = schema.getAttribute("minItems"); + if (typeof minItems === "number" && instance.getProperties().length < minItems) { + report.addError(instance, schema, "minItems", "The number of items is less than the required minimum", minItems); + } + } + } + }, + + "maxItems" : { + "type" : "integer", + "optional" : true, + "minimum" : 0, + + "parser" : function (instance, self) { + if (instance.getType() === "number") { + return instance.getValue(); + } + }, + + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var maxItems; + if (instance.getType() === "array") { + maxItems = schema.getAttribute("maxItems"); + if (typeof maxItems === "number" && instance.getProperties().length > maxItems) { + report.addError(instance, schema, "maxItems", "The number of items is greater than the required maximum", maxItems); + } + } + } + }, + + "pattern" : { + "type" : "string", + "optional" : true, + "format" : "regex", + + "parser" : function (instance, self) { + if (instance.getType() === "string") { + return instance.getValue(); + } + }, + + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var pattern; + try { + pattern = new RegExp(schema.getAttribute("pattern")); + if (instance.getType() === "string" && pattern && !pattern.test(instance.getValue())) { + report.addError(instance, schema, "pattern", "String does not match pattern", pattern.toString()); + } + } catch (e) { + report.addError(schema, self, "pattern", "Invalid pattern", schema.getValueOfProperty("pattern")); + } + } + }, + + "minLength" : { + "type" : "integer", + "optional" : true, + "minimum" : 0, + "default" : 0, + + "parser" : function (instance, self) { + if (instance.getType() === "number") { + return instance.getValue(); + } + //else + return 0; + }, + + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var minLength; + if (instance.getType() === "string") { + minLength = schema.getAttribute("minLength"); + if (typeof minLength === "number" && instance.getValue().length < minLength) { + report.addError(instance, schema, "minLength", "String is less than the required minimum length", minLength); + } + } + } + }, + + "maxLength" : { + "type" : "integer", + "optional" : true, + + "parser" : function (instance, self) { + if (instance.getType() === "number") { + return instance.getValue(); + } + }, + + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var maxLength; + if (instance.getType() === "string") { + maxLength = schema.getAttribute("maxLength"); + if (typeof maxLength === "number" && instance.getValue().length > maxLength) { + report.addError(instance, schema, "maxLength", "String is greater than the required maximum length", maxLength); + } + } + } + }, + + "enum" : { + "type" : "array", + "optional" : true, + "minItems" : 1, + "uniqueItems" : true, + + "parser" : function (instance, self) { + if (instance.getType() === "array") { + return instance.getValue(); + } + }, + + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var enums, x, xl; + if (instance.getType() !== "undefined") { + enums = schema.getAttribute("enum"); + if (enums) { + for (x = 0, xl = enums.length; x < xl; ++x) { + if (instance.equals(enums[x])) { + return true; + } + } + report.addError(instance, schema, "enum", "Instance is not one of the possible values", enums); + } + } + } + }, + + "title" : { + "type" : "string", + "optional" : true + }, + + "description" : { + "type" : "string", + "optional" : true + }, + + "format" : { + "type" : "string", + "optional" : true, + + "parser" : function (instance, self) { + if (instance.getType() === "string") { + return instance.getValue(); + } + }, + + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var format, formatValidators; + if (instance.getType() === "string") { + format = schema.getAttribute("format"); + formatValidators = self.getValueOfProperty("formatValidators"); + if (typeof format === "string" && formatValidators[format] !== O[format] && typeof formatValidators[format] === "function" && !formatValidators[format].call(this, instance, report)) { + report.addError(instance, schema, "format", "String is not in the required format", format); + } + } + }, + + "formatValidators" : {} + }, + + "contentEncoding" : { + "type" : "string", + "optional" : true + }, + + "default" : { + "type" : "any", + "optional" : true + }, + + "maxDecimal" : { + "type" : "integer", + "optional" : true, + "minimum" : 0, + + "parser" : function (instance, self) { + if (instance.getType() === "number") { + return instance.getValue(); + } + }, + + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var maxDecimal, decimals; + if (instance.getType() === "number") { + maxDecimal = schema.getAttribute("maxDecimal"); + if (typeof maxDecimal === "number") { + decimals = instance.getValue().toString(10).split('.')[1]; + if (decimals && decimals.length > maxDecimal) { + report.addError(instance, schema, "maxDecimal", "The number of decimal places is greater than the allowed maximum", maxDecimal); + } + } + } + } + }, + + "disallow" : { + "type" : ["string", "array"], + "items" : {"type" : "string"}, + "optional" : true, + "uniqueItems" : true, + + "parser" : function (instance, self) { + if (instance.getType() === "string" || instance.getType() === "array") { + return instance.getValue(); + } + }, + + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var disallowedTypes = JSV.toArray(schema.getAttribute("disallow")), + x, xl, key, typeValidators, subreport; + + //for instances that are required to be a certain type + if (instance.getType() !== "undefined" && disallowedTypes && disallowedTypes.length) { + typeValidators = self.getValueOfProperty("typeValidators") || {}; + + //ensure that type matches for at least one of the required types + for (x = 0, xl = disallowedTypes.length; x < xl; ++x) { + key = disallowedTypes[x]; + if (JSV.isJSONSchema(key)) { //this is supported draft-03 and on + subreport = JSV.createObject(report); + subreport.errors = []; + subreport.validated = JSV.clone(report.validated); + if (key.validate(instance, subreport, parent, parentSchema, name).errors.length === 0) { + //instance matches this schema + report.addError(instance, schema, "disallow", "Instance is a disallowed type", disallowedTypes); + return false; + } + } else if (typeValidators[key] !== O[key] && typeof typeValidators[key] === "function") { + if (typeValidators[key](instance, report)) { + report.addError(instance, schema, "disallow", "Instance is a disallowed type", disallowedTypes); + return false; + } + } + /* + else { + report.addError(instance, schema, "disallow", "Instance may be a disallowed type", disallowedTypes); + return false; + } + */ + } + + //if we get to this point, type is valid + return true; + } + //else, everything is allowed if no disallowed types are specified + return true; + }, + + "typeValidators" : TYPE_VALIDATORS + }, + + "extends" : { + "type" : [{"$ref" : "#"}, "array"], + "items" : {"$ref" : "#"}, + "optional" : true, + "default" : {}, + + "parser" : function (instance, self) { + if (instance.getType() === "object") { + return instance.getEnvironment().createSchema(instance, self.getEnvironment().findSchema(self.resolveURI("#"))); + } else if (instance.getType() === "array") { + return JSV.mapArray(instance.getProperties(), function (instance) { + return instance.getEnvironment().createSchema(instance, self.getEnvironment().findSchema(self.resolveURI("#"))); + }); + } + }, + + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var extensions = schema.getAttribute("extends"), x, xl; + if (extensions) { + if (JSV.isJSONSchema(extensions)) { + extensions.validate(instance, report, parent, parentSchema, name); + } else if (JSV.typeOf(extensions) === "array") { + for (x = 0, xl = extensions.length; x < xl; ++x) { + extensions[x].validate(instance, report, parent, parentSchema, name); + } + } + } + } + } + }, + + "optional" : true, + "default" : {}, + "fragmentResolution" : "dot-delimited", + + "parser" : function (instance, self) { + if (instance.getType() === "object") { + return instance.getEnvironment().createSchema(instance, self); + } + }, + + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var propNames = schema.getPropertyNames(), + x, xl, + attributeSchemas = self.getAttribute("properties"), + strict = instance.getEnvironment().getOption("strict"), + validator; + + for (x in attributeSchemas) { + if (attributeSchemas[x] !== O[x]) { + if (attributeSchemas[x].getValueOfProperty("validationRequired")) { + JSV.pushUnique(propNames, x); + } + if (strict && attributeSchemas[x].getValueOfProperty("deprecated")) { + JSV.popFirst(propNames, x); + } + } + } + + for (x = 0, xl = propNames.length; x < xl; ++x) { + if (attributeSchemas[propNames[x]] !== O[propNames[x]]) { + validator = attributeSchemas[propNames[x]].getValueOfProperty("validator"); + if (typeof validator === "function") { + validator(instance, schema, attributeSchemas[propNames[x]], report, parent, parentSchema, name); + } + } + } + } + }; + + HYPERSCHEMA_00_JSON = { + "$schema" : "http://json-schema.org/draft-00/hyper-schema#", + "id" : "http://json-schema.org/draft-00/hyper-schema#", + + "properties" : { + "links" : { + "type" : "array", + "items" : {"$ref" : "links#"}, + "optional" : true, + + "parser" : function (instance, self, arg) { + var links, + linkSchemaURI = self.getValueOfProperty("items")["$ref"], + linkSchema = self.getEnvironment().findSchema(linkSchemaURI), + linkParser = linkSchema && linkSchema.getValueOfProperty("parser"), + selfReferenceVariable; + arg = JSV.toArray(arg); + + if (typeof linkParser === "function") { + links = JSV.mapArray(instance.getProperties(), function (link) { + return linkParser(link, linkSchema); + }); + } else { + links = JSV.toArray(instance.getValue()); + } + + if (arg[0]) { + links = JSV.filterArray(links, function (link) { + return link["rel"] === arg[0]; + }); + } + + if (arg[1]) { + selfReferenceVariable = self.getValueOfProperty("selfReferenceVariable"); + links = JSV.mapArray(links, function (link) { + var instance = arg[1], + href = link["href"]; + href = href.replace(/\{(.+)\}/g, function (str, p1, offset, s) { + var value; + if (p1 === selfReferenceVariable) { + value = instance.getValue(); + } else { + value = instance.getValueOfProperty(p1); + } + return value !== undefined ? String(value) : ""; + }); + return href ? JSV.formatURI(instance.resolveURI(href)) : href; + }); + } + + return links; + }, + + "selfReferenceVariable" : "-this" + }, + + "fragmentResolution" : { + "type" : "string", + "optional" : true, + "default" : "dot-delimited" + }, + + "root" : { + "type" : "boolean", + "optional" : true, + "default" : false + }, + + "readonly" : { + "type" : "boolean", + "optional" : true, + "default" : false + }, + + "pathStart" : { + "type" : "string", + "optional" : true, + "format" : "uri", + + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var pathStart; + if (instance.getType() !== "undefined") { + pathStart = schema.getAttribute("pathStart"); + if (typeof pathStart === "string") { + //TODO: Find out what pathStart is relative to + if (instance.getURI().indexOf(pathStart) !== 0) { + report.addError(instance, schema, "pathStart", "Instance's URI does not start with " + pathStart, pathStart); + } + } + } + } + }, + + "mediaType" : { + "type" : "string", + "optional" : true, + "format" : "media-type" + }, + + "alternate" : { + "type" : "array", + "items" : {"$ref" : "#"}, + "optional" : true + } + }, + + "links" : [ + { + "href" : "{$ref}", + "rel" : "full" + }, + + { + "href" : "{$schema}", + "rel" : "describedby" + }, + + { + "href" : "{id}", + "rel" : "self" + } + ], + + "initializer" : function (instance) { + var link, extension, extended; + + //if there is a link to a different schema, set reference + link = instance._schema.getLink("describedby", instance); + if (link && instance._schema._uri !== link) { + instance.setReference("describedby", link); + } + + //if instance has a URI link to itself, update it's own URI + link = instance._schema.getLink("self", instance); + if (JSV.typeOf(link) === "string") { + instance._uri = JSV.formatURI(link); + } + + //if there is a link to the full representation, set reference + link = instance._schema.getLink("full", instance); + if (link && instance._uri !== link) { + instance.setReference("full", link); + } + + //extend schema + extension = instance.getAttribute("extends"); + if (JSV.isJSONSchema(extension)) { + extended = JSV.inherits(extension, instance, true); + instance = instance._env.createSchema(extended, instance._schema, instance._uri); + } + + return instance; + } + + //not needed as JSV.inherits does the job for us + //"extends" : {"$ref" : "http://json-schema.org/schema#"} + }; + + LINKS_00_JSON = { + "$schema" : "http://json-schema.org/draft-00/hyper-schema#", + "id" : "http://json-schema.org/draft-00/links#", + "type" : "object", + + "properties" : { + "href" : { + "type" : "string" + }, + + "rel" : { + "type" : "string" + }, + + "method" : { + "type" : "string", + "default" : "GET", + "optional" : true + }, + + "enctype" : { + "type" : "string", + "requires" : "method", + "optional" : true + }, + + "properties" : { + "type" : "object", + "additionalProperties" : {"$ref" : "hyper-schema#"}, + "optional" : true, + + "parser" : function (instance, self, arg) { + var env = instance.getEnvironment(), + selfEnv = self.getEnvironment(), + additionalPropertiesSchemaURI = self.getValueOfProperty("additionalProperties")["$ref"]; + if (instance.getType() === "object") { + if (arg) { + return env.createSchema(instance.getProperty(arg), selfEnv.findSchema(self.resolveURI(additionalPropertiesSchemaURI))); + } else { + return JSV.mapObject(instance.getProperties(), function (instance) { + return env.createSchema(instance, selfEnv.findSchema(self.resolveURI(additionalPropertiesSchemaURI))); + }); + } + } + } + } + }, + + "parser" : function (instance, self) { + var selfProperties = self.getProperty("properties"); + if (instance.getType() === "object") { + return JSV.mapObject(instance.getProperties(), function (property, key) { + var propertySchema = selfProperties.getProperty(key), + parser = propertySchema && propertySchema.getValueOfProperty("parser"); + if (typeof parser === "function") { + return parser(property, propertySchema); + } + //else + return property.getValue(); + }); + } + return instance.getValue(); + } + }; + + ENVIRONMENT.setOption("defaultFragmentDelimiter", "."); + ENVIRONMENT.setOption("defaultSchemaURI", "http://json-schema.org/draft-00/schema#"); //updated later + + SCHEMA_00 = ENVIRONMENT.createSchema(SCHEMA_00_JSON, true, "http://json-schema.org/draft-00/schema#"); + HYPERSCHEMA_00 = ENVIRONMENT.createSchema(JSV.inherits(SCHEMA_00, ENVIRONMENT.createSchema(HYPERSCHEMA_00_JSON, true, "http://json-schema.org/draft-00/hyper-schema#"), true), true, "http://json-schema.org/draft-00/hyper-schema#"); + + ENVIRONMENT.setOption("defaultSchemaURI", "http://json-schema.org/draft-00/hyper-schema#"); + + LINKS_00 = ENVIRONMENT.createSchema(LINKS_00_JSON, HYPERSCHEMA_00, "http://json-schema.org/draft-00/links#"); + + // + // draft-01 + // + + SCHEMA_01_JSON = JSV.inherits(SCHEMA_00_JSON, { + "$schema" : "http://json-schema.org/draft-01/hyper-schema#", + "id" : "http://json-schema.org/draft-01/schema#" + }); + + HYPERSCHEMA_01_JSON = JSV.inherits(HYPERSCHEMA_00_JSON, { + "$schema" : "http://json-schema.org/draft-01/hyper-schema#", + "id" : "http://json-schema.org/draft-01/hyper-schema#" + }); + + LINKS_01_JSON = JSV.inherits(LINKS_00_JSON, { + "$schema" : "http://json-schema.org/draft-01/hyper-schema#", + "id" : "http://json-schema.org/draft-01/links#" + }); + + ENVIRONMENT.setOption("defaultSchemaURI", "http://json-schema.org/draft-01/schema#"); //update later + + SCHEMA_01 = ENVIRONMENT.createSchema(SCHEMA_01_JSON, true, "http://json-schema.org/draft-01/schema#"); + HYPERSCHEMA_01 = ENVIRONMENT.createSchema(JSV.inherits(SCHEMA_01, ENVIRONMENT.createSchema(HYPERSCHEMA_01_JSON, true, "http://json-schema.org/draft-01/hyper-schema#"), true), true, "http://json-schema.org/draft-01/hyper-schema#"); + + ENVIRONMENT.setOption("defaultSchemaURI", "http://json-schema.org/draft-01/hyper-schema#"); + + LINKS_01 = ENVIRONMENT.createSchema(LINKS_01_JSON, HYPERSCHEMA_01, "http://json-schema.org/draft-01/links#"); + + // + // draft-02 + // + + SCHEMA_02_JSON = JSV.inherits(SCHEMA_01_JSON, { + "$schema" : "http://json-schema.org/draft-02/hyper-schema#", + "id" : "http://json-schema.org/draft-02/schema#", + + "properties" : { + "uniqueItems" : { + "type" : "boolean", + "optional" : true, + "default" : false, + + "parser" : function (instance, self) { + return !!instance.getValue(); + }, + + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var value, x, xl, y, yl; + if (instance.getType() === "array" && schema.getAttribute("uniqueItems")) { + value = instance.getProperties(); + for (x = 0, xl = value.length - 1; x < xl; ++x) { + for (y = x + 1, yl = value.length; y < yl; ++y) { + if (value[x].equals(value[y])) { + report.addError(instance, schema, "uniqueItems", "Array can only contain unique items", { x : x, y : y }); + } + } + } + } + } + }, + + "maxDecimal" : { + "deprecated" : true + }, + + "divisibleBy" : { + "type" : "number", + "minimum" : 0, + "minimumCanEqual" : false, + "optional" : true, + + "parser" : function (instance, self) { + if (instance.getType() === "number") { + return instance.getValue(); + } + }, + + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var divisor, value, digits; + if (instance.getType() === "number") { + divisor = schema.getAttribute("divisibleBy"); + if (divisor === 0) { + report.addError(instance, schema, "divisibleBy", "Nothing is divisible by 0", divisor); + } else if (divisor !== 1) { + value = instance.getValue(); + digits = Math.max((value.toString().split(".")[1] || " ").length, (divisor.toString().split(".")[1] || " ").length); + digits = parseFloat(((value / divisor) % 1).toFixed(digits)); //cut out floating point errors + if (0 < digits && digits < 1) { + report.addError(instance, schema, "divisibleBy", "Number is not divisible by " + divisor, divisor); + } + } + } + } + } + }, + + "fragmentResolution" : "slash-delimited" + }); + + HYPERSCHEMA_02_JSON = JSV.inherits(HYPERSCHEMA_01_JSON, { + "id" : "http://json-schema.org/draft-02/hyper-schema#", + + "properties" : { + "fragmentResolution" : { + "default" : "slash-delimited" + } + } + }); + + LINKS_02_JSON = JSV.inherits(LINKS_01_JSON, { + "$schema" : "http://json-schema.org/draft-02/hyper-schema#", + "id" : "http://json-schema.org/draft-02/links#", + + "properties" : { + "targetSchema" : { + "$ref" : "hyper-schema#", + + //need this here because parsers are run before links are resolved + "parser" : HYPERSCHEMA_01.getAttribute("parser") + } + } + }); + + ENVIRONMENT.setOption("defaultFragmentDelimiter", "/"); + ENVIRONMENT.setOption("defaultSchemaURI", "http://json-schema.org/draft-02/schema#"); //update later + + SCHEMA_02 = ENVIRONMENT.createSchema(SCHEMA_02_JSON, true, "http://json-schema.org/draft-02/schema#"); + HYPERSCHEMA_02 = ENVIRONMENT.createSchema(JSV.inherits(SCHEMA_02, ENVIRONMENT.createSchema(HYPERSCHEMA_02_JSON, true, "http://json-schema.org/draft-02/hyper-schema#"), true), true, "http://json-schema.org/draft-02/hyper-schema#"); + + ENVIRONMENT.setOption("defaultSchemaURI", "http://json-schema.org/draft-02/hyper-schema#"); + + LINKS_02 = ENVIRONMENT.createSchema(LINKS_02_JSON, HYPERSCHEMA_02, "http://json-schema.org/draft-02/links#"); + + // + // draft-03 + // + + function getMatchedPatternProperties(instance, schema, report, self) { + var matchedProperties = {}, patternProperties, pattern, regexp, properties, key; + + if (instance.getType() === "object") { + patternProperties = schema.getAttribute("patternProperties"); + properties = instance.getProperties(); + for (pattern in patternProperties) { + if (patternProperties[pattern] !== O[pattern]) { + regexp = null; + try { + regexp = new RegExp(pattern); + } catch (e) { + if (report) { + report.addError(schema, self, "patternProperties", "Invalid pattern", pattern); + } + } + + if (regexp) { + for (key in properties) { + if (properties[key] !== O[key] && regexp.test(key)) { + matchedProperties[key] = matchedProperties[key] ? JSV.pushUnique(matchedProperties[key], patternProperties[pattern]) : [ patternProperties[pattern] ]; + } + } + } + } + } + } + + return matchedProperties; + } + + SCHEMA_03_JSON = JSV.inherits(SCHEMA_02_JSON, { + "$schema" : "http://json-schema.org/draft-03/schema#", + "id" : "http://json-schema.org/draft-03/schema#", + + "properties" : { + "patternProperties" : { + "type" : "object", + "additionalProperties" : {"$ref" : "#"}, + "default" : {}, + + "parser" : SCHEMA_02.getValueOfProperty("properties")["properties"]["parser"], + + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var matchedProperties, key, x; + if (instance.getType() === "object") { + matchedProperties = getMatchedPatternProperties(instance, schema, report, self); + for (key in matchedProperties) { + if (matchedProperties[key] !== O[key]) { + x = matchedProperties[key].length; + while (x--) { + matchedProperties[key][x].validate(instance.getProperty(key), report, instance, schema, key); + } + } + } + } + } + }, + + "additionalProperties" : { + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var additionalProperties, propertySchemas, properties, matchedProperties, key; + if (instance.getType() === "object") { + additionalProperties = schema.getAttribute("additionalProperties"); + propertySchemas = schema.getAttribute("properties") || {}; + properties = instance.getProperties(); + matchedProperties = getMatchedPatternProperties(instance, schema); + for (key in properties) { + if (properties[key] !== O[key] && properties[key] && propertySchemas[key] === O[key] && matchedProperties[key] === O[key]) { + if (JSV.isJSONSchema(additionalProperties)) { + additionalProperties.validate(properties[key], report, instance, schema, key); + } else if (additionalProperties === false) { + report.addError(instance, schema, "additionalProperties", "Additional properties are not allowed", additionalProperties); + } + } + } + } + } + }, + + "items" : { + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var properties, items, x, xl, itemSchema, additionalItems; + + if (instance.getType() === "array") { + properties = instance.getProperties(); + items = schema.getAttribute("items"); + additionalItems = schema.getAttribute("additionalItems"); + + if (JSV.typeOf(items) === "array") { + for (x = 0, xl = properties.length; x < xl; ++x) { + itemSchema = items[x] || additionalItems; + if (itemSchema !== false) { + itemSchema.validate(properties[x], report, instance, schema, x); + } else { + report.addError(instance, schema, "additionalItems", "Additional items are not allowed", itemSchema); + } + } + } else { + itemSchema = items || additionalItems; + for (x = 0, xl = properties.length; x < xl; ++x) { + itemSchema.validate(properties[x], report, instance, schema, x); + } + } + } + } + }, + + "additionalItems" : { + "type" : [{"$ref" : "#"}, "boolean"], + "default" : {}, + + "parser" : SCHEMA_02.getValueOfProperty("properties")["additionalProperties"]["parser"], + + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var additionalItems, properties, x, xl; + //only validate if the "items" attribute is undefined + if (instance.getType() === "array" && schema.getProperty("items").getType() === "undefined") { + additionalItems = schema.getAttribute("additionalItems"); + properties = instance.getProperties(); + + if (additionalItems !== false) { + for (x = 0, xl = properties.length; x < xl; ++x) { + additionalItems.validate(properties[x], report, instance, schema, x); + } + } else if (properties.length) { + report.addError(instance, schema, "additionalItems", "Additional items are not allowed", additionalItems); + } + } + } + }, + + "optional" : { + "validationRequired" : false, + "deprecated" : true + }, + + "required" : { + "type" : "boolean", + "default" : false, + + "parser" : function (instance, self) { + return !!instance.getValue(); + }, + + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + if (instance.getType() === "undefined" && schema.getAttribute("required")) { + report.addError(instance, schema, "required", "Property is required", true); + } + } + }, + + "requires" : { + "deprecated" : true + }, + + "dependencies" : { + "type" : "object", + "additionalProperties" : { + "type" : ["string", "array", {"$ref" : "#"}], + "items" : { + "type" : "string" + } + }, + "default" : {}, + + "parser" : function (instance, self, arg) { + function parseProperty(property) { + var type = property.getType(); + if (type === "string" || type === "array") { + return property.getValue(); + } else if (type === "object") { + return property.getEnvironment().createSchema(property, self.getEnvironment().findSchema(self.resolveURI("#"))); + } + } + + if (instance.getType() === "object") { + if (arg) { + return parseProperty(instance.getProperty(arg)); + } else { + return JSV.mapObject(instance.getProperties(), parseProperty); + } + } + //else + return {}; + }, + + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var dependencies, key, dependency, type, x, xl; + if (instance.getType() === "object") { + dependencies = schema.getAttribute("dependencies"); + for (key in dependencies) { + if (dependencies[key] !== O[key] && instance.getProperty(key).getType() !== "undefined") { + dependency = dependencies[key]; + type = JSV.typeOf(dependency); + if (type === "string") { + if (instance.getProperty(dependency).getType() === "undefined") { + report.addError(instance, schema, "dependencies", 'Property "' + key + '" requires sibling property "' + dependency + '"', dependencies); + } + } else if (type === "array") { + for (x = 0, xl = dependency.length; x < xl; ++x) { + if (instance.getProperty(dependency[x]).getType() === "undefined") { + report.addError(instance, schema, "dependencies", 'Property "' + key + '" requires sibling property "' + dependency[x] + '"', dependencies); + } + } + } else if (JSV.isJSONSchema(dependency)) { + dependency.validate(instance, report); + } + } + } + } + } + }, + + "minimumCanEqual" : { + "deprecated" : true + }, + + "maximumCanEqual" : { + "deprecated" : true + }, + + "exclusiveMinimum" : { + "type" : "boolean", + "default" : false, + + "parser" : function (instance, self) { + return !!instance.getValue(); + } + }, + + "exclusiveMaximum" : { + "type" : "boolean", + "default" : false, + + "parser" : function (instance, self) { + return !!instance.getValue(); + } + }, + + "minimum" : { + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var minimum, exclusiveMinimum; + if (instance.getType() === "number") { + minimum = schema.getAttribute("minimum"); + exclusiveMinimum = schema.getAttribute("exclusiveMinimum") || (!instance.getEnvironment().getOption("strict") && !schema.getAttribute("minimumCanEqual")); + if (typeof minimum === "number" && (instance.getValue() < minimum || (exclusiveMinimum === true && instance.getValue() === minimum))) { + report.addError(instance, schema, "minimum", "Number is less than the required minimum value", minimum); + } + } + } + }, + + "maximum" : { + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var maximum, exclusiveMaximum; + if (instance.getType() === "number") { + maximum = schema.getAttribute("maximum"); + exclusiveMaximum = schema.getAttribute("exclusiveMaximum") || (!instance.getEnvironment().getOption("strict") && !schema.getAttribute("maximumCanEqual")); + if (typeof maximum === "number" && (instance.getValue() > maximum || (exclusiveMaximum === true && instance.getValue() === maximum))) { + report.addError(instance, schema, "maximum", "Number is greater than the required maximum value", maximum); + } + } + } + }, + + "contentEncoding" : { + "deprecated" : true + }, + + "divisibleBy" : { + "exclusiveMinimum" : true + }, + + "disallow" : { + "items" : { + "type" : ["string", {"$ref" : "#"}] + }, + + "parser" : SCHEMA_02_JSON["properties"]["type"]["parser"] + }, + + "id" : { + "type" : "string", + "format" : "uri" + }, + + "$ref" : { + "type" : "string", + "format" : "uri" + }, + + "$schema" : { + "type" : "string", + "format" : "uri" + } + }, + + "dependencies" : { + "exclusiveMinimum" : "minimum", + "exclusiveMaximum" : "maximum" + }, + + "initializer" : function (instance) { + var link, extension, extended, + schemaLink = instance.getValueOfProperty("$schema"), + refLink = instance.getValueOfProperty("$ref"), + idLink = instance.getValueOfProperty("id"); + + //if there is a link to a different schema, set reference + if (schemaLink) { + link = instance.resolveURI(schemaLink); + instance.setReference("describedby", link); + } + + //if instance has a URI link to itself, update it's own URI + if (idLink) { + link = instance.resolveURI(idLink); + if (JSV.typeOf(link) === "string") { + instance._uri = JSV.formatURI(link); + } + } + + //if there is a link to the full representation, set reference + if (refLink) { + link = instance.resolveURI(refLink); + instance.setReference("full", link); + } + + //extend schema + extension = instance.getAttribute("extends"); + if (JSV.isJSONSchema(extension)) { + extended = JSV.inherits(extension, instance, true); + instance = instance._env.createSchema(extended, instance._schema, instance._uri); + } + + return instance; + } + }); + + HYPERSCHEMA_03_JSON = JSV.inherits(HYPERSCHEMA_02_JSON, { + "$schema" : "http://json-schema.org/draft-03/hyper-schema#", + "id" : "http://json-schema.org/draft-03/hyper-schema#", + + "properties" : { + "links" : { + "selfReferenceVariable" : "@" + }, + + "root" : { + "deprecated" : true + }, + + "contentEncoding" : { + "deprecated" : false //moved from core to hyper + }, + + "alternate" : { + "deprecated" : true + } + } + }); + + LINKS_03_JSON = JSV.inherits(LINKS_02_JSON, { + "$schema" : "http://json-schema.org/draft-03/hyper-schema#", + "id" : "http://json-schema.org/draft-03/links#", + + "properties" : { + "href" : { + "required" : true, + "format" : "link-description-object-template" + }, + + "rel" : { + "required" : true + }, + + "properties" : { + "deprecated" : true + }, + + "schema" : {"$ref" : "http://json-schema.org/draft-03/hyper-schema#"} + } + }); + + ENVIRONMENT.setOption("defaultSchemaURI", "http://json-schema.org/draft-03/schema#"); //update later + + SCHEMA_03 = ENVIRONMENT.createSchema(SCHEMA_03_JSON, true, "http://json-schema.org/draft-03/schema#"); + HYPERSCHEMA_03 = ENVIRONMENT.createSchema(JSV.inherits(SCHEMA_03, ENVIRONMENT.createSchema(HYPERSCHEMA_03_JSON, true, "http://json-schema.org/draft-03/hyper-schema#"), true), true, "http://json-schema.org/draft-03/hyper-schema#"); + + ENVIRONMENT.setOption("defaultSchemaURI", "http://json-schema.org/draft-03/hyper-schema#"); + + LINKS_03 = ENVIRONMENT.createSchema(LINKS_03_JSON, true, "http://json-schema.org/draft-03/links#"); + + ENVIRONMENT.setOption("latestJSONSchemaSchemaURI", "http://json-schema.org/draft-03/schema#"); + ENVIRONMENT.setOption("latestJSONSchemaHyperSchemaURI", "http://json-schema.org/draft-03/hyper-schema#"); + ENVIRONMENT.setOption("latestJSONSchemaLinksURI", "http://json-schema.org/draft-03/links#"); + + // + //Latest JSON Schema + // + + //Hack, but WAY faster than instantiating a new schema + ENVIRONMENT._schemas["http://json-schema.org/schema#"] = SCHEMA_03; + ENVIRONMENT._schemas["http://json-schema.org/hyper-schema#"] = HYPERSCHEMA_03; + ENVIRONMENT._schemas["http://json-schema.org/links#"] = LINKS_03; + + // + //register environment + // + + JSV.registerEnvironment("json-schema-draft-03", ENVIRONMENT); + if (!JSV.getDefaultEnvironmentID() || JSV.getDefaultEnvironmentID() === "json-schema-draft-01" || JSV.getDefaultEnvironmentID() === "json-schema-draft-02") { + JSV.setDefaultEnvironmentID("json-schema-draft-03"); + } + +}()); diff --git a/lib/jsv.js b/lib/jsv.js index 1ca04ae..2a6f1ed 100644 --- a/lib/jsv.js +++ b/lib/jsv.js @@ -1,1497 +1,1537 @@ -/** - * JSV: JSON Schema Validator - * - * @fileOverview A JavaScript implementation of a extendable, fully compliant JSON Schema validator. - * @author Gary Court - * @version 4.0.2 - * @see http://github.com/garycourt/JSV - */ - -/* - * Copyright 2010 Gary Court. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are - * permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of - * conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list - * of conditions and the following disclaimer in the documentation and/or other materials - * provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY GARY COURT ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND - * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GARY COURT OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * The views and conclusions contained in the software and documentation are those of the - * authors and should not be interpreted as representing official policies, either expressed - * or implied, of Gary Court or the JSON Schema specification. - */ - -/*jslint white: true, sub: true, onevar: true, undef: true, eqeqeq: true, newcap: true, immed: true, indent: 4 */ - -var exports = exports || this, - require = require || function () { - return exports; - }; - -(function () { - - var URI = require("./uri/uri").URI, - O = {}, - I2H = "0123456789abcdef".split(""), - mapArray, filterArray, searchArray, - - JSV; - - // - // Utility functions - // - - function typeOf(o) { - return o === undefined ? "undefined" : (o === null ? "null" : Object.prototype.toString.call(o).split(" ").pop().split("]").shift().toLowerCase()); - } - - /** @inner */ - function F() {} - - function createObject(proto) { - F.prototype = proto || {}; - return new F(); - } - - function mapObject(obj, func, scope) { - var newObj = {}, key; - for (key in obj) { - if (obj[key] !== O[key]) { - newObj[key] = func.call(scope, obj[key], key, obj); - } - } - return newObj; - } - - /** @ignore */ - mapArray = function (arr, func, scope) { - var x = 0, xl = arr.length, newArr = new Array(xl); - for (; x < xl; ++x) { - newArr[x] = func.call(scope, arr[x], x, arr); - } - return newArr; - }; - - if (Array.prototype.map) { - /** @ignore */ - mapArray = function (arr, func, scope) { - return Array.prototype.map.call(arr, func, scope); - }; - } - - /** @ignore */ - filterArray = function (arr, func, scope) { - var x = 0, xl = arr.length, newArr = []; - for (; x < xl; ++x) { - if (func.call(scope, arr[x], x, arr)) { - newArr[newArr.length] = arr[x]; - } - } - return newArr; - }; - - if (Array.prototype.filter) { - /** @ignore */ - filterArray = function (arr, func, scope) { - return Array.prototype.filter.call(arr, func, scope); - }; - } - - /** @ignore */ - searchArray = function (arr, o) { - var x = 0, xl = arr.length; - for (; x < xl; ++x) { - if (arr[x] === o) { - return x; - } - } - return -1; - }; - - if (Array.prototype.indexOf) { - /** @ignore */ - searchArray = function (arr, o) { - return Array.prototype.indexOf.call(arr, o); - }; - } - - function toArray(o) { - return o !== undefined && o !== null ? (o instanceof Array && !o.callee ? o : (typeof o.length !== "number" || o.split || o.setInterval || o.call ? [ o ] : Array.prototype.slice.call(o))) : []; - } - - function keys(o) { - var result = [], key; - - switch (typeOf(o)) { - case "object": - for (key in o) { - if (o[key] !== O[key]) { - result[result.length] = key; - } - } - break; - case "array": - for (key = o.length - 1; key >= 0; --key) { - result[key] = key; - } - break; - } - - return result; - } - - function pushUnique(arr, o) { - if (searchArray(arr, o) === -1) { - arr.push(o); - } - return arr; - } - - function popFirst(arr, o) { - var index = searchArray(arr, o); - if (index > -1) { - arr.splice(index, 1); - } - return arr; - } - - function randomUUID() { - return [ - I2H[Math.floor(Math.random() * 0x10)], - I2H[Math.floor(Math.random() * 0x10)], - I2H[Math.floor(Math.random() * 0x10)], - I2H[Math.floor(Math.random() * 0x10)], - I2H[Math.floor(Math.random() * 0x10)], - I2H[Math.floor(Math.random() * 0x10)], - I2H[Math.floor(Math.random() * 0x10)], - I2H[Math.floor(Math.random() * 0x10)], - "-", - I2H[Math.floor(Math.random() * 0x10)], - I2H[Math.floor(Math.random() * 0x10)], - I2H[Math.floor(Math.random() * 0x10)], - I2H[Math.floor(Math.random() * 0x10)], - "-4", //set 4 high bits of time_high field to version - I2H[Math.floor(Math.random() * 0x10)], - I2H[Math.floor(Math.random() * 0x10)], - I2H[Math.floor(Math.random() * 0x10)], - "-", - I2H[(Math.floor(Math.random() * 0x10) & 0x3) | 0x8], //specify 2 high bits of clock sequence - I2H[Math.floor(Math.random() * 0x10)], - I2H[Math.floor(Math.random() * 0x10)], - I2H[Math.floor(Math.random() * 0x10)], - "-", - I2H[Math.floor(Math.random() * 0x10)], - I2H[Math.floor(Math.random() * 0x10)], - I2H[Math.floor(Math.random() * 0x10)], - I2H[Math.floor(Math.random() * 0x10)], - I2H[Math.floor(Math.random() * 0x10)], - I2H[Math.floor(Math.random() * 0x10)], - I2H[Math.floor(Math.random() * 0x10)], - I2H[Math.floor(Math.random() * 0x10)], - I2H[Math.floor(Math.random() * 0x10)], - I2H[Math.floor(Math.random() * 0x10)], - I2H[Math.floor(Math.random() * 0x10)], - I2H[Math.floor(Math.random() * 0x10)] - ].join(""); - } - - function escapeURIComponent(str) { - return encodeURIComponent(str).replace(/!/g, '%21').replace(/'/g, '%27').replace(/\(/g, '%28').replace(/\)/g, '%29').replace(/\*/g, '%2A'); - } - - function formatURI(uri) { - if (typeof uri === "string" && uri.indexOf("#") === -1) { - uri += "#"; - } - return uri; - } - - function stripInstances(o) { - if (o instanceof JSONInstance) { - return o.getURI(); - } - - switch (typeOf(o)) { - case "undefined": - case "null": - case "boolean": - case "number": - case "string": - return o; //do nothing - - case "object": - return mapObject(o, stripInstances); - - case "array": - return mapArray(o, stripInstances); - - default: - return o.toString(); - } - } - - /** - * The exception that is thrown when a schema fails to be created. - * - * @name InitializationError - * @class - * @param {JSONInstance|String} instance The instance (or instance URI) that is invalid - * @param {JSONSchema|String} schema The schema (or schema URI) that was validating the instance - * @param {String} attr The attribute that failed to validated - * @param {String} message A user-friendly message on why the schema attribute failed to validate the instance - * @param {Any} details The value of the schema attribute - */ - - function InitializationError(instance, schema, attr, message, details) { - Error.call(this, message); - - this.uri = instance instanceof JSONInstance ? instance.getURI() : instance; - this.schemaUri = schema instanceof JSONInstance ? schema.getURI() : schema; - this.attribute = attr; - this.message = message; - this.description = message; //IE - this.details = details; - } - - InitializationError.prototype = new Error(); - InitializationError.prototype.constructor = InitializationError; - InitializationError.prototype.name = "InitializationError"; - - /** - * Defines an error, found by a schema, with an instance. - * This class can only be instantiated by {@link Report#addError}. - * - * @name ValidationError - * @class - * @see Report#addError - */ - - /** - * The URI of the instance that has the error. - * - * @name ValidationError.prototype.uri - * @type String - */ - - /** - * The URI of the schema that generated the error. - * - * @name ValidationError.prototype.schemaUri - * @type String - */ - - /** - * The name of the schema attribute that generated the error. - * - * @name ValidationError.prototype.attribute - * @type String - */ - - /** - * An user-friendly (English) message about what failed to validate. - * - * @name ValidationError.prototype.message - * @type String - */ - - /** - * The value of the schema attribute that generated the error. - * - * @name ValidationError.prototype.details - * @type Any - */ - - /** - * Reports are returned from validation methods to describe the result of a validation. - * - * @name Report - * @class - * @see JSONSchema#validate - * @see Environment#validate - */ - - function Report() { - /** - * An array of {@link ValidationError} objects that define all the errors generated by the schema against the instance. - * - * @name Report.prototype.errors - * @type Array - * @see Report#addError - */ - this.errors = []; - - /** - * A hash table of every instance and what schemas were validated against it. - *

- * The key of each item in the table is the URI of the instance that was validated. - * The value of this key is an array of strings of URIs of the schema that validated it. - *

- * - * @name Report.prototype.validated - * @type Object - * @see Report#registerValidation - * @see Report#isValidatedBy - */ - this.validated = {}; - - /** - * If the report is generated by {@link Environment#validate}, this field is the generated instance. - * - * @name Report.prototype.instance - * @type JSONInstance - * @see Environment#validate - */ - - /** - * If the report is generated by {@link Environment#validate}, this field is the generated schema. - * - * @name Report.prototype.schema - * @type JSONSchema - * @see Environment#validate - */ - - /** - * If the report is generated by {@link Environment#validate}, this field is the schema's schema. - * This value is the same as calling schema.getSchema(). - * - * @name Report.prototype.schemaSchema - * @type JSONSchema - * @see Environment#validate - * @see JSONSchema#getSchema - */ - } - - /** - * Adds a {@link ValidationError} object to the errors field. - * - * @param {JSONInstance|String} instance The instance (or instance URI) that is invalid - * @param {JSONSchema|String} schema The schema (or schema URI) that was validating the instance - * @param {String} attr The attribute that failed to validated - * @param {String} message A user-friendly message on why the schema attribute failed to validate the instance - * @param {Any} details The value of the schema attribute - */ - - Report.prototype.addError = function (instance, schema, attr, message, details) { - this.errors.push({ - uri : instance instanceof JSONInstance ? instance.getURI() : instance, - schemaUri : schema instanceof JSONInstance ? schema.getURI() : schema, - attribute : attr, - message : message, - details : stripInstances(details) - }); - }; - - /** - * Registers that the provided instance URI has been validated by the provided schema URI. - * This is recorded in the validated field. - * - * @param {String} uri The URI of the instance that was validated - * @param {String} schemaUri The URI of the schema that validated the instance - */ - - Report.prototype.registerValidation = function (uri, schemaUri) { - if (!this.validated[uri]) { - this.validated[uri] = [ schemaUri ]; - } else { - this.validated[uri].push(schemaUri); - } - }; - - /** - * Returns if an instance with the provided URI has been validated by the schema with the provided URI. - * - * @param {String} uri The URI of the instance - * @param {String} schemaUri The URI of a schema - * @returns {Boolean} If the instance has been validated by the schema. - */ - - Report.prototype.isValidatedBy = function (uri, schemaUri) { - return !!this.validated[uri] && searchArray(this.validated[uri], schemaUri) !== -1; - }; - - /** - * A wrapper class for binding an Environment, URI and helper methods to an instance. - * This class is most commonly instantiated with {@link Environment#createInstance}. - * - * @name JSONInstance - * @class - * @param {Environment} env The environment this instance belongs to - * @param {JSONInstance|Any} json The value of the instance - * @param {String} [uri] The URI of the instance. If undefined, the URI will be a randomly generated UUID. - * @param {String} [fd] The fragment delimiter for properties. If undefined, uses the environment default. - */ - - function JSONInstance(env, json, uri, fd) { - if (json instanceof JSONInstance) { - if (typeof fd !== "string") { - fd = json._fd; - } - if (typeof uri !== "string") { - uri = json._uri; - } - json = json._value; - } - - if (typeof uri !== "string") { - uri = "urn:uuid:" + randomUUID() + "#"; - } else if (uri.indexOf(":") === -1) { - uri = formatURI(URI.resolve("urn:uuid:" + randomUUID() + "#", uri)); - } - - this._env = env; - this._value = json; - this._uri = uri; - this._fd = fd || this._env._options["defaultFragmentDelimiter"]; - } - - /** - * Returns the environment the instance is bound to. - * - * @returns {Environment} The environment of the instance - */ - - JSONInstance.prototype.getEnvironment = function () { - return this._env; - }; - - /** - * Returns the name of the type of the instance. - * - * @returns {String} The name of the type of the instance - */ - - JSONInstance.prototype.getType = function () { - return typeOf(this._value); - }; - - /** - * Returns the JSON value of the instance. - * - * @returns {Any} The actual JavaScript value of the instance - */ - - JSONInstance.prototype.getValue = function () { - return this._value; - }; - - /** - * Returns the URI of the instance. - * - * @returns {String} The URI of the instance - */ - - JSONInstance.prototype.getURI = function () { - return this._uri; - }; - - /** - * Returns a resolved URI of a provided relative URI against the URI of the instance. - * - * @param {String} uri The relative URI to resolve - * @returns {String} The resolved URI - */ - - JSONInstance.prototype.resolveURI = function (uri) { - return formatURI(URI.resolve(this._uri, uri)); - }; - - /** - * Returns an array of the names of all the properties. - * - * @returns {Array} An array of strings which are the names of all the properties - */ - - JSONInstance.prototype.getPropertyNames = function () { - return keys(this._value); - }; - - /** - * Returns a {@link JSONInstance} of the value of the provided property name. - * - * @param {String} key The name of the property to fetch - * @returns {JSONInstance} The instance of the property value - */ - - JSONInstance.prototype.getProperty = function (key) { - var value = this._value ? this._value[key] : undefined; - if (value instanceof JSONInstance) { - return value; - } - //else - return new JSONInstance(this._env, value, this._uri + this._fd + escapeURIComponent(key), this._fd); - }; - - /** - * Returns all the property instances of the target instance. - *

- * If the target instance is an Object, then the method will return a hash table of {@link JSONInstance}s of all the properties. - * If the target instance is an Array, then the method will return an array of {@link JSONInstance}s of all the items. - *

- * - * @returns {Object|Array|undefined} The list of instances for all the properties - */ - - JSONInstance.prototype.getProperties = function () { - var type = typeOf(this._value), - self = this; - - if (type === "object") { - return mapObject(this._value, function (value, key) { - if (value instanceof JSONInstance) { - return value; - } - return new JSONInstance(self._env, value, self._uri + self._fd + escapeURIComponent(key), self._fd); - }); - } else if (type === "array") { - return mapArray(this._value, function (value, key) { - if (value instanceof JSONInstance) { - return value; - } - return new JSONInstance(self._env, value, self._uri + self._fd + escapeURIComponent(key), self._fd); - }); - } - }; - - /** - * Returns the JSON value of the provided property name. - * This method is a faster version of calling instance.getProperty(key).getValue(). - * - * @param {String} key The name of the property - * @returns {Any} The JavaScript value of the instance - * @see JSONInstance#getProperty - * @see JSONInstance#getValue - */ - - JSONInstance.prototype.getValueOfProperty = function (key) { - if (this._value) { - if (this._value[key] instanceof JSONInstance) { - return this._value[key]._value; - } - return this._value[key]; - } - }; - - /** - * Return if the provided value is the same as the value of the instance. - * - * @param {JSONInstance|Any} instance The value to compare - * @returns {Boolean} If both the instance and the value match - */ - - JSONInstance.prototype.equals = function (instance) { - if (instance instanceof JSONInstance) { - return this._value === instance._value; - } - //else - return this._value === instance; - }; - - /** - * Warning: Not a generic clone function - * Produces a JSV acceptable clone - */ - - function clone(obj, deep) { - var newObj, x; - - if (obj instanceof JSONInstance) { - obj = obj.getValue(); - } - - switch (typeOf(obj)) { - case "object": - if (deep) { - newObj = {}; - for (x in obj) { - if (obj[x] !== O[x]) { - newObj[x] = clone(obj[x], deep); - } - } - return newObj; - } else { - return createObject(obj); - } - break; - case "array": - if (deep) { - newObj = new Array(obj.length); - x = obj.length; - while (--x >= 0) { - newObj[x] = clone(obj[x], deep); - } - return newObj; - } else { - return Array.prototype.slice.call(obj); - } - break; - default: - return obj; - } - } - - /** - * This class binds a {@link JSONInstance} with a {@link JSONSchema} to provided context aware methods. - * - * @name JSONSchema - * @class - * @param {Environment} env The environment this schema belongs to - * @param {JSONInstance|Any} json The value of the schema - * @param {String} [uri] The URI of the schema. If undefined, the URI will be a randomly generated UUID. - * @param {JSONSchema|Boolean} [schema] The schema to bind to the instance. If undefined, the environment's default schema will be used. If true, the instance's schema will be itself. - * @extends JSONInstance - */ - - function JSONSchema(env, json, uri, schema) { - var fr; - JSONInstance.call(this, env, json, uri); - - if (schema === true) { - this._schema = this; - } else if (json instanceof JSONSchema && !(schema instanceof JSONSchema)) { - this._schema = json._schema; //TODO: Make sure cross environments don't mess everything up - } else { - this._schema = schema instanceof JSONSchema ? schema : this._env.getDefaultSchema() || this._env.createEmptySchema(); - } - - //determine fragment delimiter from schema - fr = this._schema.getValueOfProperty("fragmentResolution"); - if (fr === "dot-delimited") { - this._fd = "."; - } else if (fr === "slash-delimited") { - this._fd = "/"; - } - - return this.rebuild(); //this works even when called with "new" - } - - JSONSchema.prototype = createObject(JSONInstance.prototype); - - /** - * Returns the schema of the schema. - * - * @returns {JSONSchema} The schema of the schema - */ - - JSONSchema.prototype.getSchema = function () { - var uri = this._refs && this._refs["describedby"], - newSchema; - - if (uri) { - newSchema = uri && this._env.findSchema(uri); - - if (newSchema) { - if (!newSchema.equals(this._schema)) { - this._schema = newSchema; - this.rebuild(); //if the schema has changed, the context has changed - so everything must be rebuilt - } - } else if (this._env._options["enforceReferences"]) { - throw new InitializationError(this, this._schema, "{describedby}", "Unknown schema reference", uri); - } - } - - return this._schema; - }; - - /** - * Returns the value of the provided attribute name. - *

- * This method is different from {@link JSONInstance#getProperty} as the named property - * is converted using a parser defined by the schema's schema before being returned. This - * makes the return value of this method attribute dependent. - *

- * - * @param {String} key The name of the attribute - * @param {Any} [arg] Some attribute parsers accept special arguments for returning resolved values. This is attribute dependent. - * @returns {JSONSchema|Any} The value of the attribute - */ - - JSONSchema.prototype.getAttribute = function (key, arg) { - var schemaProperty, parser, property, result, - schema = this.getSchema(); //we do this here to make sure the "describedby" reference has not changed, and that the attribute cache is up-to-date - - if (!arg && this._attributes && this._attributes.hasOwnProperty(key)) { - return this._attributes[key]; - } - - schemaProperty = schema.getProperty("properties").getProperty(key); - parser = schemaProperty.getValueOfProperty("parser"); - property = this.getProperty(key); - if (typeof parser === "function") { - result = parser(property, schemaProperty, arg); - if (!arg && this._attributes) { - this._attributes[key] = result; - } - return result; - } - //else - return property.getValue(); - }; - - /** - * Returns all the attributes of the schema. - * - * @returns {Object} A map of all parsed attribute values - */ - - JSONSchema.prototype.getAttributes = function () { - var properties, schemaProperties, key, schemaProperty, parser, - schema = this.getSchema(); //we do this here to make sure the "describedby" reference has not changed, and that the attribute cache is up-to-date - - if (!this._attributes && this.getType() === "object") { - properties = this.getProperties(); - schemaProperties = schema.getProperty("properties"); - this._attributes = {}; - for (key in properties) { - if (properties[key] !== O[key]) { - schemaProperty = schemaProperties && schemaProperties.getProperty(key); - parser = schemaProperty && schemaProperty.getValueOfProperty("parser"); - if (typeof parser === "function") { - this._attributes[key] = parser(properties[key], schemaProperty); - } else { - this._attributes[key] = properties[key].getValue(); - } - } - } - } - - return clone(this._attributes, false); - }; - - /** - * Convenience method for retrieving a link or link object from a schema. - * This method is the same as calling schema.getAttribute("links", [rel, instance])[0];. - * - * @param {String} rel The link relationship - * @param {JSONInstance} [instance] The instance to resolve any URIs from - * @returns {String|Object|undefined} If instance is provided, a string containing the resolve URI of the link is returned. - * If instance is not provided, a link object is returned with details of the link. - * If no link with the provided relationship exists, undefined is returned. - * @see JSONSchema#getAttribute - */ - - JSONSchema.prototype.getLink = function (rel, instance) { - var schemaLinks = this.getAttribute("links", [rel, instance]); - if (schemaLinks && schemaLinks.length && schemaLinks[schemaLinks.length - 1]) { - return schemaLinks[schemaLinks.length - 1]; - } - }; - - /** - * Validates the provided instance against the target schema and returns a {@link Report}. - * - * @param {JSONInstance|Any} instance The instance to validate; may be a {@link JSONInstance} or any JavaScript value - * @param {Report} [report] A {@link Report} to concatenate the result of the validation to. If undefined, a new {@link Report} is created. - * @param {JSONInstance} [parent] The parent/containing instance of the provided instance - * @param {JSONSchema} [parentSchema] The schema of the parent/containing instance - * @param {String} [name] The name of the parent object's property that references the instance - * @returns {Report} The result of the validation - */ - - JSONSchema.prototype.validate = function (instance, report, parent, parentSchema, name) { - var schemaSchema = this.getSchema(), - validator = schemaSchema.getValueOfProperty("validator"); - - if (!(instance instanceof JSONInstance)) { - instance = this.getEnvironment().createInstance(instance); - } - - if (!(report instanceof Report)) { - report = new Report(); - } - - if (this._env._options["validateReferences"] && this._refs) { - if (this._refs["describedby"] && !this._env.findSchema(this._refs["describedby"])) { - report.addError(this, this._schema, "{describedby}", "Unknown schema reference", this._refs["describedby"]); - } - if (this._refs["full"] && !this._env.findSchema(this._refs["full"])) { - report.addError(this, this._schema, "{full}", "Unknown schema reference", this._refs["full"]); - } - } - - if (typeof validator === "function" && !report.isValidatedBy(instance.getURI(), this.getURI())) { - report.registerValidation(instance.getURI(), this.getURI()); - validator(instance, this, schemaSchema, report, parent, parentSchema, name); - } - - return report; - }; - - /** @inner */ - function createFullLookupWrapper(func) { - return /** @inner */ function fullLookupWrapper() { - var scope = this, - stack = [], - uri = scope._refs && scope._refs["full"], - schema; - - while (uri) { - schema = scope._env.findSchema(uri); - if (schema) { - if (schema._value === scope._value) { - break; - } - scope = schema; - stack.push(uri); - uri = scope._refs && scope._refs["full"]; - if (stack.indexOf(uri) > -1) { - break; //stop infinite loop - } - } else if (scope._env._options["enforceReferences"]) { - throw new InitializationError(scope, scope._schema, "{full}", "Unknown schema reference", uri); - } else { - uri = null; - } - } - return func.apply(scope, arguments); - }; - } - - /** - * Wraps all JSONInstance methods with a function that resolves the "full" reference. - * - * @inner - */ - - (function () { - var key; - for (key in JSONSchema.prototype) { - if (JSONSchema.prototype[key] !== O[key] && typeOf(JSONSchema.prototype[key]) === "function") { - JSONSchema.prototype[key] = createFullLookupWrapper(JSONSchema.prototype[key]); - } - } - }()); - - /** - * Reinitializes/re-registers/rebuilds the schema. - *
- * This is used internally, and should only be called when a schema's private variables are modified directly. - * - * @private - * @return {JSONSchema} The newly rebuilt schema - */ - - JSONSchema.prototype.rebuild = function () { - var instance = this, - initializer = instance.getSchema().getValueOfProperty("initializer"); - - //clear previous built values - instance._refs = null; - instance._attributes = null; - - if (typeof initializer === "function") { - instance = initializer(instance); - } - - //register schema - instance._env._schemas[instance._uri] = instance; - - //build & cache the rest of the schema - instance.getAttributes(); - - return instance; - }; - - /** - * Set the provided reference to the given value. - *
- * References are used for establishing soft-links to other {@link JSONSchema}s. - * Currently, the following references are natively supported: - *
- *
full
- *
The value is the URI to the full instance of this instance.
- *
describedby
- *
The value is the URI to the schema of this instance.
- *
- * - * @param {String} name The name of the reference - * @param {String} uri The URI of the schema to refer to - */ - - JSONSchema.prototype.setReference = function (name, uri) { - if (!this._refs) { - this._refs = {}; - } - this._refs[name] = this.resolveURI(uri); - }; - - /** - * Returns the value of the provided reference name. - * - * @param {String} name The name of the reference - * @return {String} The value of the provided reference name - */ - - JSONSchema.prototype.getReference = function (name) { - return this._refs && this._refs[name]; - }; - - /** - * Merges two schemas/instances together. - */ - - function inherits(base, extra, extension) { - var baseType = typeOf(base), - extraType = typeOf(extra), - child, x; - - if (extraType === "undefined") { - return clone(base, true); - } else if (baseType === "undefined" || extraType !== baseType) { - return clone(extra, true); - } else if (extraType === "object") { - if (base instanceof JSONSchema) { - base = base.getAttributes(); - } - if (extra instanceof JSONSchema) { - extra = extra.getAttributes(); - if (extra["extends"] && extension && extra["extends"] instanceof JSONSchema) { - extra["extends"] = [ extra["extends"] ]; - } - } - child = clone(base, true); //this could be optimized as some properties get overwritten - for (x in extra) { - if (extra[x] !== O[x]) { - child[x] = inherits(base[x], extra[x], extension); - } - } - return child; - } else { - return clone(extra, true); - } - } - - /** - * An Environment is a sandbox of schemas thats behavior is different from other environments. - * - * @name Environment - * @class - */ - - function Environment() { - this._id = randomUUID(); - this._schemas = {}; - this._options = {}; - - this.createSchema({}, true, "urn:jsv:empty-schema#"); - } - - /** - * Returns a clone of the target environment. - * - * @returns {Environment} A new {@link Environment} that is a exact copy of the target environment - */ - - Environment.prototype.clone = function () { - var env = new Environment(); - env._schemas = createObject(this._schemas); - env._options = createObject(this._options); - - return env; - }; - - /** - * Returns a new {@link JSONInstance} of the provided data. - * - * @param {JSONInstance|Any} data The value of the instance - * @param {String} [uri] The URI of the instance. If undefined, the URI will be a randomly generated UUID. - * @returns {JSONInstance} A new {@link JSONInstance} from the provided data - */ - - Environment.prototype.createInstance = function (data, uri) { - uri = formatURI(uri); - - if (data instanceof JSONInstance && (!uri || data.getURI() === uri)) { - return data; - } - - return new JSONInstance(this, data, uri); - }; - - /** - * Creates a new {@link JSONSchema} from the provided data, and registers it with the environment. - * - * @param {JSONInstance|Any} data The value of the schema - * @param {JSONSchema|Boolean} [schema] The schema to bind to the instance. If undefined, the environment's default schema will be used. If true, the instance's schema will be itself. - * @param {String} [uri] The URI of the schema. If undefined, the URI will be a randomly generated UUID. - * @returns {JSONSchema} A new {@link JSONSchema} from the provided data - * @throws {InitializationError} If a schema that is not registered with the environment is referenced - */ - - Environment.prototype.createSchema = function (data, schema, uri) { - uri = formatURI(uri); - - if (data instanceof JSONSchema && (!uri || data._uri === uri) && (!schema || data.getSchema().equals(schema))) { - return data; - } - - return new JSONSchema(this, data, uri, schema); - }; - - /** - * Creates an empty schema. - * - * @returns {JSONSchema} The empty schema, who's schema is itself. - */ - - Environment.prototype.createEmptySchema = function () { - return this._schemas["urn:jsv:empty-schema#"]; - }; - - /** - * Returns the schema registered with the provided URI. - * - * @param {String} uri The absolute URI of the required schema - * @returns {JSONSchema|undefined} The request schema, or undefined if not found - */ - - Environment.prototype.findSchema = function (uri) { - return this._schemas[formatURI(uri)]; - }; - - /** - * Sets the specified environment option to the specified value. - * - * @param {String} name The name of the environment option to set - * @param {Any} value The new value of the environment option - */ - - Environment.prototype.setOption = function (name, value) { - this._options[name] = value; - }; - - /** - * Returns the specified environment option. - * - * @param {String} name The name of the environment option to set - * @returns {Any} The value of the environment option - */ - - Environment.prototype.getOption = function (name) { - return this._options[name]; - }; - - /** - * Sets the default fragment delimiter of the environment. - * - * @deprecated Use {@link Environment#setOption} with option "defaultFragmentDelimiter" - * @param {String} fd The fragment delimiter character - */ - - Environment.prototype.setDefaultFragmentDelimiter = function (fd) { - if (typeof fd === "string" && fd.length > 0) { - this._options["defaultFragmentDelimiter"] = fd; - } - }; - - /** - * Returns the default fragment delimiter of the environment. - * - * @deprecated Use {@link Environment#getOption} with option "defaultFragmentDelimiter" - * @returns {String} The fragment delimiter character - */ - - Environment.prototype.getDefaultFragmentDelimiter = function () { - return this._options["defaultFragmentDelimiter"]; - }; - - /** - * Sets the URI of the default schema for the environment. - * - * @deprecated Use {@link Environment#setOption} with option "defaultSchemaURI" - * @param {String} uri The default schema URI - */ - - Environment.prototype.setDefaultSchemaURI = function (uri) { - if (typeof uri === "string") { - this._options["defaultSchemaURI"] = formatURI(uri); - } - }; - - /** - * Returns the default schema of the environment. - * - * @returns {JSONSchema} The default schema - */ - - Environment.prototype.getDefaultSchema = function () { - return this.findSchema(this._options["defaultSchemaURI"]); - }; - - /** - * Validates both the provided schema and the provided instance, and returns a {@link Report}. - * If the schema fails to validate, the instance will not be validated. - * - * @param {JSONInstance|Any} instanceJSON The {@link JSONInstance} or JavaScript value to validate. - * @param {JSONSchema|Any} schemaJSON The {@link JSONSchema} or JavaScript value to use in the validation. This will also be validated againt the schema's schema. - * @returns {Report} The result of the validation - */ - - Environment.prototype.validate = function (instanceJSON, schemaJSON) { - var instance, - schema, - schemaSchema, - report = new Report(); - - try { - instance = this.createInstance(instanceJSON); - report.instance = instance; - } catch (e) { - report.addError(e.uri, e.schemaUri, e.attribute, e.message, e.details); - } - - try { - schema = this.createSchema(schemaJSON); - report.schema = schema; - - schemaSchema = schema.getSchema(); - report.schemaSchema = schemaSchema; - } catch (f) { - report.addError(f.uri, f.schemaUri, f.attribute, f.message, f.details); - } - - if (schemaSchema) { - schemaSchema.validate(schema, report); - } - - if (report.errors.length) { - return report; - } - - return schema.validate(instance, report); - }; - - /** - * @private - */ - - Environment.prototype._checkForInvalidInstances = function (stackSize, schemaURI) { - var result = [], - stack = [ - [schemaURI, this._schemas[schemaURI]] - ], - counter = 0, - item, uri, instance, properties, key; - - while (counter++ < stackSize && stack.length) { - item = stack.shift(); - uri = item[0]; - instance = item[1]; - - if (instance instanceof JSONSchema) { - if (this._schemas[instance._uri] !== instance) { - result.push("Instance " + uri + " does not match " + instance._uri); - } else { - //schema = instance.getSchema(); - //stack.push([uri + "/{schema}", schema]); - - properties = instance.getAttributes(); - for (key in properties) { - if (properties[key] !== O[key]) { - stack.push([uri + "/" + escapeURIComponent(key), properties[key]]); - } - } - } - } else if (typeOf(instance) === "object") { - properties = instance; - for (key in properties) { - if (properties.hasOwnProperty(key)) { - stack.push([uri + "/" + escapeURIComponent(key), properties[key]]); - } - } - } else if (typeOf(instance) === "array") { - properties = instance; - for (key = 0; key < properties.length; ++key) { - stack.push([uri + "/" + escapeURIComponent(key), properties[key]]); - } - } - } - - return result.length ? result : counter; - }; - - /** - * A globaly accessible object that provides the ability to create and manage {@link Environments}, - * as well as providing utility methods. - * - * @namespace - */ - - JSV = { - _environments : {}, - _defaultEnvironmentID : "", - - /** - * Returns if the provide value is an instance of {@link JSONInstance}. - * - * @param o The value to test - * @returns {Boolean} If the provide value is an instance of {@link JSONInstance} - */ - - isJSONInstance : function (o) { - return o instanceof JSONInstance; - }, - - /** - * Returns if the provide value is an instance of {@link JSONSchema}. - * - * @param o The value to test - * @returns {Boolean} If the provide value is an instance of {@link JSONSchema} - */ - - isJSONSchema : function (o) { - return o instanceof JSONSchema; - }, - - /** - * Creates and returns a new {@link Environment} that is a clone of the environment registered with the provided ID. - * If no environment ID is provided, the default environment is cloned. - * - * @param {String} [id] The ID of the environment to clone. If undefined, the default environment ID is used. - * @returns {Environment} A newly cloned {@link Environment} - * @throws {Error} If there is no environment registered with the provided ID - */ - - createEnvironment : function (id) { - id = id || this._defaultEnvironmentID; - - if (!this._environments[id]) { - throw new Error("Unknown Environment ID"); - } - //else - return this._environments[id].clone(); - }, - - Environment : Environment, - - /** - * Registers the provided {@link Environment} with the provided ID. - * - * @param {String} id The ID of the environment - * @param {Environment} env The environment to register - */ - - registerEnvironment : function (id, env) { - id = id || (env || 0)._id; - if (id && !this._environments[id] && env instanceof Environment) { - env._id = id; - this._environments[id] = env; - } - }, - - /** - * Sets which registered ID is the default environment. - * - * @param {String} id The ID of the registered environment that is default - * @throws {Error} If there is no registered environment with the provided ID - */ - - setDefaultEnvironmentID : function (id) { - if (typeof id === "string") { - if (!this._environments[id]) { - throw new Error("Unknown Environment ID"); - } - - this._defaultEnvironmentID = id; - } - }, - - /** - * Returns the ID of the default environment. - * - * @returns {String} The ID of the default environment - */ - - getDefaultEnvironmentID : function () { - return this._defaultEnvironmentID; - }, - - // - // Utility Functions - // - - /** - * Returns the name of the type of the provided value. - * - * @event //utility - * @param {Any} o The value to determine the type of - * @returns {String} The name of the type of the value - */ - typeOf : typeOf, - - /** - * Return a new object that inherits all of the properties of the provided object. - * - * @event //utility - * @param {Object} proto The prototype of the new object - * @returns {Object} A new object that inherits all of the properties of the provided object - */ - createObject : createObject, - - /** - * Returns a new object with each property transformed by the iterator. - * - * @event //utility - * @param {Object} obj The object to transform - * @param {Function} iterator A function that returns the new value of the provided property - * @param {Object} [scope] The value of this in the iterator - * @returns {Object} A new object with each property transformed - */ - mapObject : mapObject, - - /** - * Returns a new array with each item transformed by the iterator. - * - * @event //utility - * @param {Array} arr The array to transform - * @param {Function} iterator A function that returns the new value of the provided item - * @param {Object} scope The value of this in the iterator - * @returns {Array} A new array with each item transformed - */ - mapArray : mapArray, - - /** - * Returns a new array that only contains the items allowed by the iterator. - * - * @event //utility - * @param {Array} arr The array to filter - * @param {Function} iterator The function that returns true if the provided property should be added to the array - * @param {Object} scope The value of this within the iterator - * @returns {Array} A new array that contains the items allowed by the iterator - */ - filterArray : filterArray, - - /** - * Returns the first index in the array that the provided item is located at. - * - * @event //utility - * @param {Array} arr The array to search - * @param {Any} o The item being searched for - * @returns {Number} The index of the item in the array, or -1 if not found - */ - searchArray : searchArray, - - /** - * Returns an array representation of a value. - * - * - * @event //utility - * @param {Any} o The value to convert into an array - * @returns {Array} The value as an array - */ - toArray : toArray, - - /** - * Returns an array of the names of all properties of an object. - * - * @event //utility - * @param {Object|Array} o The object in question - * @returns {Array} The names of all properties - */ - keys : keys, - - /** - * Mutates the array by pushing the provided value onto the array only if it is not already there. - * - * @event //utility - * @param {Array} arr The array to modify - * @param {Any} o The object to add to the array if it is not already there - * @returns {Array} The provided array for chaining - */ - pushUnique : pushUnique, - - /** - * Mutates the array by removing the first item that matches the provided value in the array. - * - * @event //utility - * @param {Array} arr The array to modify - * @param {Any} o The object to remove from the array - * @returns {Array} The provided array for chaining - */ - popFirst : popFirst, - - /** - * Creates a copy of the target object. - *

- * This method will create a new instance of the target, and then mixin the properties of the target. - * If deep is true, then each property will be cloned before mixin. - *

- *

Warning: This is not a generic clone function, as it will only properly clone objects and arrays.

- * - * @event //utility - * @param {Any} o The value to clone - * @param {Boolean} [deep=false] If each property should be recursively cloned - * @returns A cloned copy of the provided value - */ - clone : clone, - - /** - * Generates a pseudo-random UUID. - * - * @event //utility - * @returns {String} A new universally unique ID - */ - randomUUID : randomUUID, - - /** - * Properly escapes a URI component for embedding into a URI string. - * - * @event //utility - * @param {String} str The URI component to escape - * @returns {String} The escaped URI component - */ - escapeURIComponent : escapeURIComponent, - - /** - * Returns a URI that is formated for JSV. Currently, this only ensures that the URI ends with a hash tag (#). - * - * @event //utility - * @param {String} uri The URI to format - * @returns {String} The URI formatted for JSV - */ - formatURI : formatURI, - - /** - * Merges two schemas/instance together. - * - * @event //utility - * @param {JSONSchema|Any} base The old value to merge - * @param {JSONSchema|Any} extra The new value to merge - * @param {Boolean} extension If the merge is a JSON Schema extension - * @return {Any} The modified base value - */ - - inherits : inherits, - - /** - * @private - * @event //utility - */ - - InitializationError : InitializationError - }; - - this.JSV = JSV; //set global object - exports.JSV = JSV; //export to CommonJS - - require("./environments"); //load default environments - -}()); \ No newline at end of file +/** + * JSV: JSON Schema Validator + * + * @fileOverview A JavaScript implementation of a extendable, fully compliant JSON Schema validator. + * @author Gary Court + * @version 4.0.2 + * @see http://github.com/garycourt/JSV + */ + +/* + * Copyright 2010 Gary Court. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY GARY COURT ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GARY COURT OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of Gary Court or the JSON Schema specification. + */ + +/*jslint white: true, sub: true, onevar: true, undef: true, eqeqeq: true, newcap: true, immed: true, indent: 4 */ + +var exports = exports || this, + require = require || function () { + return exports; + }; + +(function () { + + var URI = require("./uri/uri").URI, + O = {}, + I2H = "0123456789abcdef".split(""), + mapArray, filterArray, searchArray, + + JSV; + + // + // Utility functions + // + + function typeOf(o) { + return o === undefined ? "undefined" : (o === null ? "null" : Object.prototype.toString.call(o).split(" ").pop().split("]").shift().toLowerCase()); + } + + /** @inner */ + function F() {} + + function createObject(proto) { + F.prototype = proto || {}; + return new F(); + } + + function mapObject(obj, func, scope) { + var newObj = {}, key; + for (key in obj) { + if (obj[key] !== O[key]) { + newObj[key] = func.call(scope, obj[key], key, obj); + } + } + return newObj; + } + + /** @ignore */ + mapArray = function (arr, func, scope) { + var x = 0, xl = arr.length, newArr = new Array(xl); + for (; x < xl; ++x) { + newArr[x] = func.call(scope, arr[x], x, arr); + } + return newArr; + }; + + if (Array.prototype.map) { + /** @ignore */ + mapArray = function (arr, func, scope) { + return Array.prototype.map.call(arr, func, scope); + }; + } + + /** @ignore */ + filterArray = function (arr, func, scope) { + var x = 0, xl = arr.length, newArr = []; + for (; x < xl; ++x) { + if (func.call(scope, arr[x], x, arr)) { + newArr[newArr.length] = arr[x]; + } + } + return newArr; + }; + + if (Array.prototype.filter) { + /** @ignore */ + filterArray = function (arr, func, scope) { + return Array.prototype.filter.call(arr, func, scope); + }; + } + + /** @ignore */ + searchArray = function (arr, o) { + var x = 0, xl = arr.length; + for (; x < xl; ++x) { + if (arr[x] === o) { + return x; + } + } + return -1; + }; + + if (Array.prototype.indexOf) { + /** @ignore */ + searchArray = function (arr, o) { + return Array.prototype.indexOf.call(arr, o); + }; + } + + function toArray(o) { + return o !== undefined && o !== null ? (o instanceof Array && !o.callee ? o : (typeof o.length !== "number" || o.split || o.setInterval || o.call ? [ o ] : Array.prototype.slice.call(o))) : []; + } + + function keys(o) { + var result = [], key; + + switch (typeOf(o)) { + case "object": + for (key in o) { + if (o[key] !== O[key]) { + result[result.length] = key; + } + } + break; + case "array": + for (key = o.length - 1; key >= 0; --key) { + result[key] = key; + } + break; + } + + return result; + } + + function pushUnique(arr, o) { + if (searchArray(arr, o) === -1) { + arr.push(o); + } + return arr; + } + + function popFirst(arr, o) { + var index = searchArray(arr, o); + if (index > -1) { + arr.splice(index, 1); + } + return arr; + } + + function randomUUID() { + return [ + I2H[Math.floor(Math.random() * 0x10)], + I2H[Math.floor(Math.random() * 0x10)], + I2H[Math.floor(Math.random() * 0x10)], + I2H[Math.floor(Math.random() * 0x10)], + I2H[Math.floor(Math.random() * 0x10)], + I2H[Math.floor(Math.random() * 0x10)], + I2H[Math.floor(Math.random() * 0x10)], + I2H[Math.floor(Math.random() * 0x10)], + "-", + I2H[Math.floor(Math.random() * 0x10)], + I2H[Math.floor(Math.random() * 0x10)], + I2H[Math.floor(Math.random() * 0x10)], + I2H[Math.floor(Math.random() * 0x10)], + "-4", //set 4 high bits of time_high field to version + I2H[Math.floor(Math.random() * 0x10)], + I2H[Math.floor(Math.random() * 0x10)], + I2H[Math.floor(Math.random() * 0x10)], + "-", + I2H[(Math.floor(Math.random() * 0x10) & 0x3) | 0x8], //specify 2 high bits of clock sequence + I2H[Math.floor(Math.random() * 0x10)], + I2H[Math.floor(Math.random() * 0x10)], + I2H[Math.floor(Math.random() * 0x10)], + "-", + I2H[Math.floor(Math.random() * 0x10)], + I2H[Math.floor(Math.random() * 0x10)], + I2H[Math.floor(Math.random() * 0x10)], + I2H[Math.floor(Math.random() * 0x10)], + I2H[Math.floor(Math.random() * 0x10)], + I2H[Math.floor(Math.random() * 0x10)], + I2H[Math.floor(Math.random() * 0x10)], + I2H[Math.floor(Math.random() * 0x10)], + I2H[Math.floor(Math.random() * 0x10)], + I2H[Math.floor(Math.random() * 0x10)], + I2H[Math.floor(Math.random() * 0x10)], + I2H[Math.floor(Math.random() * 0x10)] + ].join(""); + } + + function escapeURIComponent(str) { + return encodeURIComponent(str).replace(/!/g, '%21').replace(/'/g, '%27').replace(/\(/g, '%28').replace(/\)/g, '%29').replace(/\*/g, '%2A'); + } + + function formatURI(uri) { + if (typeof uri === "string" && uri.indexOf("#") === -1) { + uri += "#"; + } + return uri; + } + + function stripInstances(o) { + if (o instanceof JSONInstance) { + return o.getURI(); + } + + switch (typeOf(o)) { + case "undefined": + case "null": + case "boolean": + case "number": + case "string": + return o; //do nothing + + case "object": + return mapObject(o, stripInstances); + + case "array": + return mapArray(o, stripInstances); + + default: + return o.toString(); + } + } + + /** + * The exception that is thrown when a schema fails to be created. + * + * @name InitializationError + * @class + * @param {JSONInstance|String} instance The instance (or instance URI) that is invalid + * @param {JSONSchema|String} schema The schema (or schema URI) that was validating the instance + * @param {String} attr The attribute that failed to validated + * @param {String} message A user-friendly message on why the schema attribute failed to validate the instance + * @param {Any} details The value of the schema attribute + */ + + function InitializationError(instance, schema, attr, message, details) { + Error.call(this, message); + + this.uri = instance instanceof JSONInstance ? instance.getURI() : instance; + this.schemaUri = schema instanceof JSONInstance ? schema.getURI() : schema; + this.attribute = attr; + this.message = message; + this.description = message; //IE + this.details = details; + } + + InitializationError.prototype = new Error(); + InitializationError.prototype.constructor = InitializationError; + InitializationError.prototype.name = "InitializationError"; + + /** + * Defines an error, found by a schema, with an instance. + * This class can only be instantiated by {@link Report#addError}. + * + * @name ValidationError + * @class + * @see Report#addError + */ + + /** + * The URI of the instance that has the error. + * + * @name ValidationError.prototype.uri + * @type String + */ + + /** + * The URI of the schema that generated the error. + * + * @name ValidationError.prototype.schemaUri + * @type String + */ + + /** + * The name of the schema attribute that generated the error. + * + * @name ValidationError.prototype.attribute + * @type String + */ + + /** + * An user-friendly (English) message about what failed to validate. + * + * @name ValidationError.prototype.message + * @type String + */ + + /** + * The value of the schema attribute that generated the error. + * + * @name ValidationError.prototype.details + * @type Any + */ + + /** + * Reports are returned from validation methods to describe the result of a validation. + * + * @name Report + * @class + * @see JSONSchema#validate + * @see Environment#validate + */ + + function Report() { + /** + * An array of {@link ValidationError} objects that define all the errors generated by the schema against the instance. + * + * @name Report.prototype.errors + * @type Array + * @see Report#addError + */ + this.errors = []; + + /** + * A hash table of every instance and what schemas were validated against it. + *

+ * The key of each item in the table is the URI of the instance that was validated. + * The value of this key is an array of strings of URIs of the schema that validated it. + *

+ * + * @name Report.prototype.validated + * @type Object + * @see Report#registerValidation + * @see Report#isValidatedBy + */ + this.validated = {}; + + /** + * If the report is generated by {@link Environment#validate}, this field is the generated instance. + * + * @name Report.prototype.instance + * @type JSONInstance + * @see Environment#validate + */ + + /** + * If the report is generated by {@link Environment#validate}, this field is the generated schema. + * + * @name Report.prototype.schema + * @type JSONSchema + * @see Environment#validate + */ + + /** + * If the report is generated by {@link Environment#validate}, this field is the schema's schema. + * This value is the same as calling schema.getSchema(). + * + * @name Report.prototype.schemaSchema + * @type JSONSchema + * @see Environment#validate + * @see JSONSchema#getSchema + */ + } + + /** + * Adds a {@link ValidationError} object to the errors field. + * + * @param {JSONInstance|String} instance The instance (or instance URI) that is invalid + * @param {JSONSchema|String} schema The schema (or schema URI) that was validating the instance + * @param {String} attr The attribute that failed to validated + * @param {String} message A user-friendly message on why the schema attribute failed to validate the instance + * @param {Any} details The value of the schema attribute + */ + + Report.prototype.addError = function (instance, schema, attr, message, details) { + this.errors.push({ + uri : instance instanceof JSONInstance ? instance.getURI() : instance, + schemaUri : schema instanceof JSONInstance ? schema.getURI() : schema, + attribute : attr, + message : message, + details : stripInstances(details) + }); + }; + + /** + * Registers that the provided instance URI has been validated by the provided schema URI. + * This is recorded in the validated field. + * + * @param {String} uri The URI of the instance that was validated + * @param {String} schemaUri The URI of the schema that validated the instance + */ + + Report.prototype.registerValidation = function (uri, schemaUri) { + if (!this.validated[uri]) { + this.validated[uri] = [ schemaUri ]; + } else { + this.validated[uri].push(schemaUri); + } + }; + + /** + * Returns if an instance with the provided URI has been validated by the schema with the provided URI. + * + * @param {String} uri The URI of the instance + * @param {String} schemaUri The URI of a schema + * @returns {Boolean} If the instance has been validated by the schema. + */ + + Report.prototype.isValidatedBy = function (uri, schemaUri) { + return !!this.validated[uri] && searchArray(this.validated[uri], schemaUri) !== -1; + }; + + /** + * A wrapper class for binding an Environment, URI and helper methods to an instance. + * This class is most commonly instantiated with {@link Environment#createInstance}. + * + * @name JSONInstance + * @class + * @param {Environment} env The environment this instance belongs to + * @param {JSONInstance|Any} json The value of the instance + * @param {String} [uri] The URI of the instance. If undefined, the URI will be a randomly generated UUID. + * @param {String} [fd] The fragment delimiter for properties. If undefined, uses the environment default. + * @param {JSONInstance} parent The parent instance, if any + * @param {String|Number} parentKey The corresponding key on the parent instance, if any + */ + + function JSONInstance(env, json, uri, fd, parent, parentKey) { + if (json instanceof JSONInstance) { + if (typeof fd !== "string") { + fd = json._fd; + } + if (typeof uri !== "string") { + uri = json._uri; + } + json = json._value; + } + + if (typeof uri !== "string") { + uri = "urn:uuid:" + randomUUID() + "#"; + } else if (uri.indexOf(":") === -1) { + uri = formatURI(URI.resolve("urn:uuid:" + randomUUID() + "#", uri)); + } + + this._env = env; + this._value = json; + this._uri = uri; + this._fd = fd || this._env._options["defaultFragmentDelimiter"]; + this._parent = parent; + this._parentKey = parentKey; + } + + /** + * Returns the environment the instance is bound to. + * + * @returns {Environment} The environment of the instance + */ + + JSONInstance.prototype.getEnvironment = function () { + return this._env; + }; + + /** + * Returns the name of the type of the instance. + * + * @returns {String} The name of the type of the instance + */ + + JSONInstance.prototype.getType = function () { + return typeOf(this._value); + }; + + /** + * Returns the JSON value of the instance. + * + * @returns {Any} The actual JavaScript value of the instance + */ + + JSONInstance.prototype.getValue = function () { + return this._value; + }; + + /** + * Coerces the JSON value of the instance according to the matching type coercion function set in + * environment options, if any (option "typeCoercionFns"). + * + * @param {String} type + */ + JSONInstance.prototype.applyTypeCoercionIfSet = function (type) { + var coercionFns = this._env._options["typeCoercionFns"]; + if (coercionFns && coercionFns[type] && typeof coercionFns[type] === "function") { + this._value = coercionFns[type](this._value); + if (this._parent) { + this._parent.setValueOfProperty(this._parentKey, this._value); + } + } + }; + + /** + * Returns the URI of the instance. + * + * @returns {String} The URI of the instance + */ + + JSONInstance.prototype.getURI = function () { + return this._uri; + }; + + /** + * Returns a resolved URI of a provided relative URI against the URI of the instance. + * + * @param {String} uri The relative URI to resolve + * @returns {String} The resolved URI + */ + + JSONInstance.prototype.resolveURI = function (uri) { + return formatURI(URI.resolve(this._uri, uri)); + }; + + /** + * Returns an array of the names of all the properties. + * + * @returns {Array} An array of strings which are the names of all the properties + */ + + JSONInstance.prototype.getPropertyNames = function () { + return keys(this._value); + }; + + /** + * Returns a {@link JSONInstance} of the value of the provided property name. + * + * @param {String} key The name of the property to fetch + * @returns {JSONInstance} The instance of the property value + */ + + JSONInstance.prototype.getProperty = function (key) { + var value = this._value ? this._value[key] : undefined; + if (value instanceof JSONInstance) { + return value; + } + //else + return new JSONInstance(this._env, value, this._uri + this._fd + escapeURIComponent(key), + this._fd, this, key); + }; + + /** + * Returns all the property instances of the target instance. + *

+ * If the target instance is an Object, then the method will return a hash table of {@link JSONInstance}s of all the properties. + * If the target instance is an Array, then the method will return an array of {@link JSONInstance}s of all the items. + *

+ * + * @returns {Object|Array|undefined} The list of instances for all the properties + */ + + JSONInstance.prototype.getProperties = function () { + var type = typeOf(this._value), + self = this; + + if (type === "object") { + return mapObject(this._value, function (value, key) { + if (value instanceof JSONInstance) { + return value; + } + return new JSONInstance(self._env, value, self._uri + self._fd + escapeURIComponent(key), + self._fd, this, key); + }); + } else if (type === "array") { + return mapArray(this._value, function (value, key) { + if (value instanceof JSONInstance) { + return value; + } + return new JSONInstance(self._env, value, self._uri + self._fd + escapeURIComponent(key), + self._fd, this, key); + }); + } + }; + + /** + * Returns the JSON value of the provided property name. + * This method is a faster version of calling instance.getProperty(key).getValue(). + * + * @param {String} key The name of the property + * @returns {Any} The JavaScript value of the instance + * @see JSONInstance#getProperty + * @see JSONInstance#getValue + */ + + JSONInstance.prototype.getValueOfProperty = function (key) { + if (this._value) { + if (this._value[key] instanceof JSONInstance) { + return this._value[key]._value; + } + return this._value[key]; + } + }; + + /** + * Sets the JSON value of the provided property name. + * Used to persist value coercion results. + * + * @param {String} key The name of the property + * @see JSONInstance#applyTypeCoercionIfSet + */ + + JSONInstance.prototype.setValueOfProperty = function (key, value) { + if (this._value) { + if (this._value[key] instanceof JSONInstance) { + this._value[key]._value = value; + } + this._value[key] = value; + } + }; + + /** + * Return if the provided value is the same as the value of the instance. + * + * @param {JSONInstance|Any} instance The value to compare + * @returns {Boolean} If both the instance and the value match + */ + + JSONInstance.prototype.equals = function (instance) { + if (instance instanceof JSONInstance) { + return this._value === instance._value; + } + //else + return this._value === instance; + }; + + /** + * Warning: Not a generic clone function + * Produces a JSV acceptable clone + */ + + function clone(obj, deep) { + var newObj, x; + + if (obj instanceof JSONInstance) { + obj = obj.getValue(); + } + + switch (typeOf(obj)) { + case "object": + if (deep) { + newObj = {}; + for (x in obj) { + if (obj[x] !== O[x]) { + newObj[x] = clone(obj[x], deep); + } + } + return newObj; + } else { + return createObject(obj); + } + break; + case "array": + if (deep) { + newObj = new Array(obj.length); + x = obj.length; + while (--x >= 0) { + newObj[x] = clone(obj[x], deep); + } + return newObj; + } else { + return Array.prototype.slice.call(obj); + } + break; + default: + return obj; + } + } + + /** + * This class binds a {@link JSONInstance} with a {@link JSONSchema} to provided context aware methods. + * + * @name JSONSchema + * @class + * @param {Environment} env The environment this schema belongs to + * @param {JSONInstance|Any} json The value of the schema + * @param {String} [uri] The URI of the schema. If undefined, the URI will be a randomly generated UUID. + * @param {JSONSchema|Boolean} [schema] The schema to bind to the instance. If undefined, the environment's default schema will be used. If true, the instance's schema will be itself. + * @extends JSONInstance + */ + + function JSONSchema(env, json, uri, schema) { + var fr; + JSONInstance.call(this, env, json, uri); + + if (schema === true) { + this._schema = this; + } else if (json instanceof JSONSchema && !(schema instanceof JSONSchema)) { + this._schema = json._schema; //TODO: Make sure cross environments don't mess everything up + } else { + this._schema = schema instanceof JSONSchema ? schema : this._env.getDefaultSchema() || this._env.createEmptySchema(); + } + + //determine fragment delimiter from schema + fr = this._schema.getValueOfProperty("fragmentResolution"); + if (fr === "dot-delimited") { + this._fd = "."; + } else if (fr === "slash-delimited") { + this._fd = "/"; + } + + return this.rebuild(); //this works even when called with "new" + } + + JSONSchema.prototype = createObject(JSONInstance.prototype); + + /** + * Returns the schema of the schema. + * + * @returns {JSONSchema} The schema of the schema + */ + + JSONSchema.prototype.getSchema = function () { + var uri = this._refs && this._refs["describedby"], + newSchema; + + if (uri) { + newSchema = uri && this._env.findSchema(uri); + + if (newSchema) { + if (!newSchema.equals(this._schema)) { + this._schema = newSchema; + this.rebuild(); //if the schema has changed, the context has changed - so everything must be rebuilt + } + } else if (this._env._options["enforceReferences"]) { + throw new InitializationError(this, this._schema, "{describedby}", "Unknown schema reference", uri); + } + } + + return this._schema; + }; + + /** + * Returns the value of the provided attribute name. + *

+ * This method is different from {@link JSONInstance#getProperty} as the named property + * is converted using a parser defined by the schema's schema before being returned. This + * makes the return value of this method attribute dependent. + *

+ * + * @param {String} key The name of the attribute + * @param {Any} [arg] Some attribute parsers accept special arguments for returning resolved values. This is attribute dependent. + * @returns {JSONSchema|Any} The value of the attribute + */ + + JSONSchema.prototype.getAttribute = function (key, arg) { + var schemaProperty, parser, property, result, + schema = this.getSchema(); //we do this here to make sure the "describedby" reference has not changed, and that the attribute cache is up-to-date + + if (!arg && this._attributes && this._attributes.hasOwnProperty(key)) { + return this._attributes[key]; + } + + schemaProperty = schema.getProperty("properties").getProperty(key); + parser = schemaProperty.getValueOfProperty("parser"); + property = this.getProperty(key); + if (typeof parser === "function") { + result = parser(property, schemaProperty, arg); + if (!arg && this._attributes) { + this._attributes[key] = result; + } + return result; + } + //else + return property.getValue(); + }; + + /** + * Returns all the attributes of the schema. + * + * @returns {Object} A map of all parsed attribute values + */ + + JSONSchema.prototype.getAttributes = function () { + var properties, schemaProperties, key, schemaProperty, parser, + schema = this.getSchema(); //we do this here to make sure the "describedby" reference has not changed, and that the attribute cache is up-to-date + + if (!this._attributes && this.getType() === "object") { + properties = this.getProperties(); + schemaProperties = schema.getProperty("properties"); + this._attributes = {}; + for (key in properties) { + if (properties[key] !== O[key]) { + schemaProperty = schemaProperties && schemaProperties.getProperty(key); + parser = schemaProperty && schemaProperty.getValueOfProperty("parser"); + if (typeof parser === "function") { + this._attributes[key] = parser(properties[key], schemaProperty); + } else { + this._attributes[key] = properties[key].getValue(); + } + } + } + } + + return clone(this._attributes, false); + }; + + /** + * Convenience method for retrieving a link or link object from a schema. + * This method is the same as calling schema.getAttribute("links", [rel, instance])[0];. + * + * @param {String} rel The link relationship + * @param {JSONInstance} [instance] The instance to resolve any URIs from + * @returns {String|Object|undefined} If instance is provided, a string containing the resolve URI of the link is returned. + * If instance is not provided, a link object is returned with details of the link. + * If no link with the provided relationship exists, undefined is returned. + * @see JSONSchema#getAttribute + */ + + JSONSchema.prototype.getLink = function (rel, instance) { + var schemaLinks = this.getAttribute("links", [rel, instance]); + if (schemaLinks && schemaLinks.length && schemaLinks[schemaLinks.length - 1]) { + return schemaLinks[schemaLinks.length - 1]; + } + }; + + /** + * Validates the provided instance against the target schema and returns a {@link Report}. + * + * @param {JSONInstance|Any} instance The instance to validate; may be a {@link JSONInstance} or any JavaScript value + * @param {Report} [report] A {@link Report} to concatenate the result of the validation to. If undefined, a new {@link Report} is created. + * @param {JSONInstance} [parent] The parent/containing instance of the provided instance + * @param {JSONSchema} [parentSchema] The schema of the parent/containing instance + * @param {String} [name] The name of the parent object's property that references the instance + * @returns {Report} The result of the validation + */ + + JSONSchema.prototype.validate = function (instance, report, parent, parentSchema, name) { + var schemaSchema = this.getSchema(), + validator = schemaSchema.getValueOfProperty("validator"); + + if (!(instance instanceof JSONInstance)) { + instance = this.getEnvironment().createInstance(instance); + } + + if (!(report instanceof Report)) { + report = new Report(); + } + + if (this._env._options["validateReferences"] && this._refs) { + if (this._refs["describedby"] && !this._env.findSchema(this._refs["describedby"])) { + report.addError(this, this._schema, "{describedby}", "Unknown schema reference", this._refs["describedby"]); + } + if (this._refs["full"] && !this._env.findSchema(this._refs["full"])) { + report.addError(this, this._schema, "{full}", "Unknown schema reference", this._refs["full"]); + } + } + + if (typeof validator === "function" && !report.isValidatedBy(instance.getURI(), this.getURI())) { + report.registerValidation(instance.getURI(), this.getURI()); + validator(instance, this, schemaSchema, report, parent, parentSchema, name); + } + + return report; + }; + + /** @inner */ + function createFullLookupWrapper(func) { + return /** @inner */ function fullLookupWrapper() { + var scope = this, + stack = [], + uri = scope._refs && scope._refs["full"], + schema; + + while (uri) { + schema = scope._env.findSchema(uri); + if (schema) { + if (schema._value === scope._value) { + break; + } + scope = schema; + stack.push(uri); + uri = scope._refs && scope._refs["full"]; + if (stack.indexOf(uri) > -1) { + break; //stop infinite loop + } + } else if (scope._env._options["enforceReferences"]) { + throw new InitializationError(scope, scope._schema, "{full}", "Unknown schema reference", uri); + } else { + uri = null; + } + } + return func.apply(scope, arguments); + }; + } + + /** + * Wraps all JSONInstance methods with a function that resolves the "full" reference. + * + * @inner + */ + + (function () { + var key; + for (key in JSONSchema.prototype) { + if (JSONSchema.prototype[key] !== O[key] && typeOf(JSONSchema.prototype[key]) === "function") { + JSONSchema.prototype[key] = createFullLookupWrapper(JSONSchema.prototype[key]); + } + } + }()); + + /** + * Reinitializes/re-registers/rebuilds the schema. + *
+ * This is used internally, and should only be called when a schema's private variables are modified directly. + * + * @private + * @return {JSONSchema} The newly rebuilt schema + */ + + JSONSchema.prototype.rebuild = function () { + var instance = this, + initializer = instance.getSchema().getValueOfProperty("initializer"); + + //clear previous built values + instance._refs = null; + instance._attributes = null; + + if (typeof initializer === "function") { + instance = initializer(instance); + } + + //register schema + instance._env._schemas[instance._uri] = instance; + + //build & cache the rest of the schema + instance.getAttributes(); + + return instance; + }; + + /** + * Set the provided reference to the given value. + *
+ * References are used for establishing soft-links to other {@link JSONSchema}s. + * Currently, the following references are natively supported: + *
+ *
full
+ *
The value is the URI to the full instance of this instance.
+ *
describedby
+ *
The value is the URI to the schema of this instance.
+ *
+ * + * @param {String} name The name of the reference + * @param {String} uri The URI of the schema to refer to + */ + + JSONSchema.prototype.setReference = function (name, uri) { + if (!this._refs) { + this._refs = {}; + } + this._refs[name] = this.resolveURI(uri); + }; + + /** + * Returns the value of the provided reference name. + * + * @param {String} name The name of the reference + * @return {String} The value of the provided reference name + */ + + JSONSchema.prototype.getReference = function (name) { + return this._refs && this._refs[name]; + }; + + /** + * Merges two schemas/instances together. + */ + + function inherits(base, extra, extension) { + var baseType = typeOf(base), + extraType = typeOf(extra), + child, x; + + if (extraType === "undefined") { + return clone(base, true); + } else if (baseType === "undefined" || extraType !== baseType) { + return clone(extra, true); + } else if (extraType === "object") { + if (base instanceof JSONSchema) { + base = base.getAttributes(); + } + if (extra instanceof JSONSchema) { + extra = extra.getAttributes(); + if (extra["extends"] && extension && extra["extends"] instanceof JSONSchema) { + extra["extends"] = [ extra["extends"] ]; + } + } + child = clone(base, true); //this could be optimized as some properties get overwritten + for (x in extra) { + if (extra[x] !== O[x]) { + child[x] = inherits(base[x], extra[x], extension); + } + } + return child; + } else { + return clone(extra, true); + } + } + + /** + * An Environment is a sandbox of schemas thats behavior is different from other environments. + * + * @name Environment + * @class + */ + + function Environment() { + this._id = randomUUID(); + this._schemas = {}; + this._options = {}; + + this.createSchema({}, true, "urn:jsv:empty-schema#"); + } + + /** + * Returns a clone of the target environment. + * + * @returns {Environment} A new {@link Environment} that is a exact copy of the target environment + */ + + Environment.prototype.clone = function () { + var env = new Environment(); + env._schemas = createObject(this._schemas); + env._options = createObject(this._options); + + return env; + }; + + /** + * Returns a new {@link JSONInstance} of the provided data. + * + * @param {JSONInstance|Any} data The value of the instance + * @param {String} [uri] The URI of the instance. If undefined, the URI will be a randomly generated UUID. + * @returns {JSONInstance} A new {@link JSONInstance} from the provided data + */ + + Environment.prototype.createInstance = function (data, uri) { + uri = formatURI(uri); + + if (data instanceof JSONInstance && (!uri || data.getURI() === uri)) { + return data; + } + + return new JSONInstance(this, data, uri); + }; + + /** + * Creates a new {@link JSONSchema} from the provided data, and registers it with the environment. + * + * @param {JSONInstance|Any} data The value of the schema + * @param {JSONSchema|Boolean} [schema] The schema to bind to the instance. If undefined, the environment's default schema will be used. If true, the instance's schema will be itself. + * @param {String} [uri] The URI of the schema. If undefined, the URI will be a randomly generated UUID. + * @returns {JSONSchema} A new {@link JSONSchema} from the provided data + * @throws {InitializationError} If a schema that is not registered with the environment is referenced + */ + + Environment.prototype.createSchema = function (data, schema, uri) { + uri = formatURI(uri); + + if (data instanceof JSONSchema && (!uri || data._uri === uri) && (!schema || data.getSchema().equals(schema))) { + return data; + } + + return new JSONSchema(this, data, uri, schema); + }; + + /** + * Creates an empty schema. + * + * @returns {JSONSchema} The empty schema, who's schema is itself. + */ + + Environment.prototype.createEmptySchema = function () { + return this._schemas["urn:jsv:empty-schema#"]; + }; + + /** + * Returns the schema registered with the provided URI. + * + * @param {String} uri The absolute URI of the required schema + * @returns {JSONSchema|undefined} The request schema, or undefined if not found + */ + + Environment.prototype.findSchema = function (uri) { + return this._schemas[formatURI(uri)]; + }; + + /** + * Sets the specified environment option to the specified value. + * + * @param {String} name The name of the environment option to set + * @param {Any} value The new value of the environment option + */ + + Environment.prototype.setOption = function (name, value) { + this._options[name] = value; + }; + + /** + * Returns the specified environment option. + * + * @param {String} name The name of the environment option to set + * @returns {Any} The value of the environment option + */ + + Environment.prototype.getOption = function (name) { + return this._options[name]; + }; + + /** + * Sets the default fragment delimiter of the environment. + * + * @deprecated Use {@link Environment#setOption} with option "defaultFragmentDelimiter" + * @param {String} fd The fragment delimiter character + */ + + Environment.prototype.setDefaultFragmentDelimiter = function (fd) { + if (typeof fd === "string" && fd.length > 0) { + this._options["defaultFragmentDelimiter"] = fd; + } + }; + + /** + * Returns the default fragment delimiter of the environment. + * + * @deprecated Use {@link Environment#getOption} with option "defaultFragmentDelimiter" + * @returns {String} The fragment delimiter character + */ + + Environment.prototype.getDefaultFragmentDelimiter = function () { + return this._options["defaultFragmentDelimiter"]; + }; + + /** + * Sets the URI of the default schema for the environment. + * + * @deprecated Use {@link Environment#setOption} with option "defaultSchemaURI" + * @param {String} uri The default schema URI + */ + + Environment.prototype.setDefaultSchemaURI = function (uri) { + if (typeof uri === "string") { + this._options["defaultSchemaURI"] = formatURI(uri); + } + }; + + /** + * Returns the default schema of the environment. + * + * @returns {JSONSchema} The default schema + */ + + Environment.prototype.getDefaultSchema = function () { + return this.findSchema(this._options["defaultSchemaURI"]); + }; + + /** + * Validates both the provided schema and the provided instance, and returns a {@link Report}. + * If the schema fails to validate, the instance will not be validated. + * + * @param {JSONInstance|Any} instanceJSON The {@link JSONInstance} or JavaScript value to validate. + * @param {JSONSchema|Any} schemaJSON The {@link JSONSchema} or JavaScript value to use in the validation. This will also be validated againt the schema's schema. + * @returns {Report} The result of the validation + */ + + Environment.prototype.validate = function (instanceJSON, schemaJSON) { + var instance, + schema, + schemaSchema, + report = new Report(); + + try { + instance = this.createInstance(instanceJSON); + report.instance = instance; + } catch (e) { + report.addError(e.uri, e.schemaUri, e.attribute, e.message, e.details); + } + + try { + schema = this.createSchema(schemaJSON); + report.schema = schema; + + schemaSchema = schema.getSchema(); + report.schemaSchema = schemaSchema; + } catch (f) { + report.addError(f.uri, f.schemaUri, f.attribute, f.message, f.details); + } + + if (schemaSchema) { + schemaSchema.validate(schema, report); + } + + if (report.errors.length) { + return report; + } + + return schema.validate(instance, report); + }; + + /** + * @private + */ + + Environment.prototype._checkForInvalidInstances = function (stackSize, schemaURI) { + var result = [], + stack = [ + [schemaURI, this._schemas[schemaURI]] + ], + counter = 0, + item, uri, instance, properties, key; + + while (counter++ < stackSize && stack.length) { + item = stack.shift(); + uri = item[0]; + instance = item[1]; + + if (instance instanceof JSONSchema) { + if (this._schemas[instance._uri] !== instance) { + result.push("Instance " + uri + " does not match " + instance._uri); + } else { + //schema = instance.getSchema(); + //stack.push([uri + "/{schema}", schema]); + + properties = instance.getAttributes(); + for (key in properties) { + if (properties[key] !== O[key]) { + stack.push([uri + "/" + escapeURIComponent(key), properties[key]]); + } + } + } + } else if (typeOf(instance) === "object") { + properties = instance; + for (key in properties) { + if (properties.hasOwnProperty(key)) { + stack.push([uri + "/" + escapeURIComponent(key), properties[key]]); + } + } + } else if (typeOf(instance) === "array") { + properties = instance; + for (key = 0; key < properties.length; ++key) { + stack.push([uri + "/" + escapeURIComponent(key), properties[key]]); + } + } + } + + return result.length ? result : counter; + }; + + /** + * A globaly accessible object that provides the ability to create and manage {@link Environments}, + * as well as providing utility methods. + * + * @namespace + */ + + JSV = { + _environments : {}, + _defaultEnvironmentID : "", + + /** + * Returns if the provide value is an instance of {@link JSONInstance}. + * + * @param o The value to test + * @returns {Boolean} If the provide value is an instance of {@link JSONInstance} + */ + + isJSONInstance : function (o) { + return o instanceof JSONInstance; + }, + + /** + * Returns if the provide value is an instance of {@link JSONSchema}. + * + * @param o The value to test + * @returns {Boolean} If the provide value is an instance of {@link JSONSchema} + */ + + isJSONSchema : function (o) { + return o instanceof JSONSchema; + }, + + /** + * Creates and returns a new {@link Environment} that is a clone of the environment registered with the provided ID. + * If no environment ID is provided, the default environment is cloned. + * + * @param {String} [id] The ID of the environment to clone. If undefined, the default environment ID is used. + * @returns {Environment} A newly cloned {@link Environment} + * @throws {Error} If there is no environment registered with the provided ID + */ + + createEnvironment : function (id) { + id = id || this._defaultEnvironmentID; + + if (!this._environments[id]) { + throw new Error("Unknown Environment ID"); + } + //else + return this._environments[id].clone(); + }, + + Environment : Environment, + + /** + * Registers the provided {@link Environment} with the provided ID. + * + * @param {String} id The ID of the environment + * @param {Environment} env The environment to register + */ + + registerEnvironment : function (id, env) { + id = id || (env || 0)._id; + if (id && !this._environments[id] && env instanceof Environment) { + env._id = id; + this._environments[id] = env; + } + }, + + /** + * Sets which registered ID is the default environment. + * + * @param {String} id The ID of the registered environment that is default + * @throws {Error} If there is no registered environment with the provided ID + */ + + setDefaultEnvironmentID : function (id) { + if (typeof id === "string") { + if (!this._environments[id]) { + throw new Error("Unknown Environment ID"); + } + + this._defaultEnvironmentID = id; + } + }, + + /** + * Returns the ID of the default environment. + * + * @returns {String} The ID of the default environment + */ + + getDefaultEnvironmentID : function () { + return this._defaultEnvironmentID; + }, + + // + // Utility Functions + // + + /** + * Returns the name of the type of the provided value. + * + * @event //utility + * @param {Any} o The value to determine the type of + * @returns {String} The name of the type of the value + */ + typeOf : typeOf, + + /** + * Return a new object that inherits all of the properties of the provided object. + * + * @event //utility + * @param {Object} proto The prototype of the new object + * @returns {Object} A new object that inherits all of the properties of the provided object + */ + createObject : createObject, + + /** + * Returns a new object with each property transformed by the iterator. + * + * @event //utility + * @param {Object} obj The object to transform + * @param {Function} iterator A function that returns the new value of the provided property + * @param {Object} [scope] The value of this in the iterator + * @returns {Object} A new object with each property transformed + */ + mapObject : mapObject, + + /** + * Returns a new array with each item transformed by the iterator. + * + * @event //utility + * @param {Array} arr The array to transform + * @param {Function} iterator A function that returns the new value of the provided item + * @param {Object} scope The value of this in the iterator + * @returns {Array} A new array with each item transformed + */ + mapArray : mapArray, + + /** + * Returns a new array that only contains the items allowed by the iterator. + * + * @event //utility + * @param {Array} arr The array to filter + * @param {Function} iterator The function that returns true if the provided property should be added to the array + * @param {Object} scope The value of this within the iterator + * @returns {Array} A new array that contains the items allowed by the iterator + */ + filterArray : filterArray, + + /** + * Returns the first index in the array that the provided item is located at. + * + * @event //utility + * @param {Array} arr The array to search + * @param {Any} o The item being searched for + * @returns {Number} The index of the item in the array, or -1 if not found + */ + searchArray : searchArray, + + /** + * Returns an array representation of a value. + * + * + * @event //utility + * @param {Any} o The value to convert into an array + * @returns {Array} The value as an array + */ + toArray : toArray, + + /** + * Returns an array of the names of all properties of an object. + * + * @event //utility + * @param {Object|Array} o The object in question + * @returns {Array} The names of all properties + */ + keys : keys, + + /** + * Mutates the array by pushing the provided value onto the array only if it is not already there. + * + * @event //utility + * @param {Array} arr The array to modify + * @param {Any} o The object to add to the array if it is not already there + * @returns {Array} The provided array for chaining + */ + pushUnique : pushUnique, + + /** + * Mutates the array by removing the first item that matches the provided value in the array. + * + * @event //utility + * @param {Array} arr The array to modify + * @param {Any} o The object to remove from the array + * @returns {Array} The provided array for chaining + */ + popFirst : popFirst, + + /** + * Creates a copy of the target object. + *

+ * This method will create a new instance of the target, and then mixin the properties of the target. + * If deep is true, then each property will be cloned before mixin. + *

+ *

Warning: This is not a generic clone function, as it will only properly clone objects and arrays.

+ * + * @event //utility + * @param {Any} o The value to clone + * @param {Boolean} [deep=false] If each property should be recursively cloned + * @returns A cloned copy of the provided value + */ + clone : clone, + + /** + * Generates a pseudo-random UUID. + * + * @event //utility + * @returns {String} A new universally unique ID + */ + randomUUID : randomUUID, + + /** + * Properly escapes a URI component for embedding into a URI string. + * + * @event //utility + * @param {String} str The URI component to escape + * @returns {String} The escaped URI component + */ + escapeURIComponent : escapeURIComponent, + + /** + * Returns a URI that is formated for JSV. Currently, this only ensures that the URI ends with a hash tag (#). + * + * @event //utility + * @param {String} uri The URI to format + * @returns {String} The URI formatted for JSV + */ + formatURI : formatURI, + + /** + * Merges two schemas/instance together. + * + * @event //utility + * @param {JSONSchema|Any} base The old value to merge + * @param {JSONSchema|Any} extra The new value to merge + * @param {Boolean} extension If the merge is a JSON Schema extension + * @return {Any} The modified base value + */ + + inherits : inherits, + + /** + * @private + * @event //utility + */ + + InitializationError : InitializationError + }; + + this.JSV = JSV; //set global object + exports.JSV = JSV; //export to CommonJS + + require("./environments"); //load default environments + +}()); diff --git a/tests/tests3.js b/tests/tests3.js index bf036e4..81ccbea 100644 --- a/tests/tests3.js +++ b/tests/tests3.js @@ -41,18 +41,18 @@ module(DRAFTS[curDraftId]); test("Acquire Validator", function () { JSV = require('../lib/jsv').JSV; env = null; - + ok(JSV, "JSV is loaded"); - + env = JSV.createEnvironment("json-schema-draft-03"); - + env.setOption("defaultSchemaURI", "http://json-schema.org/" + id + "/hyper-schema#"); env.setOption("latestJSONSchemaSchemaURI", "http://json-schema.org/" + id + "/schema#"); env.setOption("latestJSONSchemaHyperSchemaURI", "http://json-schema.org/" + id + "/hyper-schema#"); env.setOption("latestJSONSchemaLinksURI", "http://json-schema.org/" + id + "/links#"); - + ok(env, "Environment created"); - + }); }(DRAFTS[curDraftId])); @@ -64,7 +64,7 @@ test("Primitive Validation", function () { equal(env.validate(false).errors.length, 0, "Boolean"); equal(env.validate(null).errors.length, 0, "Null"); }); - + test("Type Validation", function () { //simple type equal(env.validate({}, { type : 'object' }).errors.length, 0, "Object"); @@ -75,7 +75,7 @@ test("Type Validation", function () { equal(env.validate(false, { type : 'boolean' }).errors.length, 0, "Boolean"); equal(env.validate(null, { type : 'null' }).errors.length, 0, "Null"); equal(env.validate(true, { type : 'any' }).errors.length, 0, "Any"); - + notEqual(env.validate(null, { type : 'object' }).errors.length, 0, "Object"); notEqual(env.validate(null, { type : 'array' }).errors.length, 0, "Array"); notEqual(env.validate(null, { type : 'string' }).errors.length, 0, "String"); @@ -83,11 +83,11 @@ test("Type Validation", function () { notEqual(env.validate(0.1, { type : 'integer' }).errors.length, 0, "Integer"); notEqual(env.validate(null, { type : 'boolean' }).errors.length, 0, "Boolean"); notEqual(env.validate(false, { type : 'null' }).errors.length, 0, "Null"); - + //union type equal(env.validate({}, { type : ['null', 'boolean', 'number', 'integer', 'string', 'array', 'object'] }).errors.length, 0, "Object"); notEqual(env.validate({}, { type : ['null', 'boolean', 'number', 'integer', 'string', 'array'] }).errors.length, 0, "Object"); - + //schema union type equal(env.validate({}, { type : [{ type : 'string' }, { type : 'object' }] }).errors.length, 0, "Object"); equal(env.validate(55, { type : [{ type : 'string' }, { type : 'object' }, 'number'] }).errors.length, 0, "Object"); @@ -99,7 +99,7 @@ test("Properties Validation", function () { equal(env.validate({ a : 1 }, { type : 'object', properties : { a : {}} }).errors.length, 0); equal(env.validate({ a : 1 }, { type : 'object', properties : { a : { type : 'number' }} }).errors.length, 0); equal(env.validate({ a : { b : 'two' } }, { type : 'object', properties : { a : { type : 'object', properties : { b : { type : 'string' } } }} }).errors.length, 0); - + notEqual(env.validate({ a : 1 }, { type : 'object', properties : { a : { type : 'string' }} }).errors.length, 0); notEqual(env.validate({ a : { b : 'two' } }, { type : 'object', properties : { a : { type : 'object', properties : { b : { type : 'number' } } }} }).errors.length, 0); }); @@ -111,7 +111,7 @@ test("PatternProperties Validation", function () { equal(env.validate({ a : 1 }, { patternProperties : { '[a-z]' : {}} }).errors.length, 0); equal(env.validate({ a : 1, b : 2, cc : '3' }, { patternProperties : { '^[a-z]$' : { type : 'number' }} }).errors.length, 0); equal(env.validate({ a : { b : 'two' } }, { patternProperties : { '[a-z]' : { patternProperties : { '[a-z]' : { type : 'string' } } }} }).errors.length, 0); - + notEqual(env.validate({ a : 1, b : 2, c : '3' }, { patternProperties : { '^[a-z]$' : { type : 'number' }} }).errors.length, 0); notEqual(env.validate({ a : { b : 'two' } }, { patternProperties : { '[a-z]' : { patternProperties : { '[a-z]' : { type : 'number' } } }} }).errors.length, 0); }); @@ -126,10 +126,10 @@ test("AdditionalProperties Validation", function () { equal(env.validate({ a : 1, b : 2, c : 3 }, { additionalProperties : { type : 'number' } }).errors.length, 0); equal(env.validate({ a : 1, b : 2, c : 3 }, { properties : { a : {}, b : {} }, additionalProperties : { type : 'number' } }).errors.length, 0); equal(env.validate({ a : 1, b : 2, c : 3 }, { properties : { a : {}, b : {}, c : {} }, additionalProperties : { type : 'string' } }).errors.length, 0); - + notEqual(env.validate({ a : 1, b : 2, c : 3 }, { properties : { a : {}, b : {} }, additionalProperties : false }).errors.length, 0); notEqual(env.validate({ a : 1, b : 2, c : 3 }, { properties : { a : {}, b : {} }, additionalProperties : { type : 'string' } }).errors.length, 0); - + //array tests equal(env.validate([1, 2, 3], {}).errors.length, 0); equal(env.validate([1, 2, 3], { additionalProperties : true }).errors.length, 0); @@ -140,7 +140,7 @@ test("AdditionalProperties Validation", function () { equal(env.validate(['1', '2'], { items : [ { type : 'string' }, { type : 'string' } ], additionalProperties : false }).errors.length, 0); equal(env.validate(['1', '2', 3], { items : [ { type : 'string' }, { type : 'string' } ], additionalProperties : { type : 'number' } }).errors.length, 0); equal(env.validate(['1', '2', '3'], { items : [ { type : 'string' }, { type : 'string' }, { type : 'string' } ], additionalProperties : { type : 'number' } }).errors.length, 0); - + if (curDraftId < 3) { notEqual(env.validate(['1', '2', '3'], { items : [ { type : 'string' }, { type : 'string' } ], additionalProperties : false }).errors.length, 0); notEqual(env.validate(['1', '2', '3'], { items : [ { type : 'string' }, { type : 'string' } ], additionalProperties : { type : 'number' } }).errors.length, 0); @@ -151,7 +151,7 @@ test("Items Validation", function () { equal(env.validate([], { type : 'array', items : { type : 'string' } }).errors.length, 0); equal(env.validate(['foo'], { type : 'array', items : { type : 'string' } }).errors.length, 0); equal(env.validate(['foo', 2], { type : 'array', items : [{ type : 'string' }, { type : 'number' }] }).errors.length, 0); - + notEqual(env.validate([1], { type : 'array', items : { type : 'string' } }).errors.length, 0); notEqual(env.validate(['foo', 'two'], { type : 'array', items : [{ type : 'string' }, { type : 'number' }] }).errors.length, 0); }); @@ -166,7 +166,7 @@ test("AdditionalItems Validation", function () { equal(env.validate(['1', '2'], { items : [ { type : 'string' }, { type : 'string' } ], additionalItems : false }).errors.length, 0); equal(env.validate(['1', '2', 3], { items : [ { type : 'string' }, { type : 'string' } ], additionalItems : { type : 'number' } }).errors.length, 0); equal(env.validate(['1', '2', '3'], { items : [ { type : 'string' }, { type : 'string' }, { type : 'string' } ], additionalItems : { type : 'number' } }).errors.length, 0); - + notEqual(env.validate([1, 2, 3], { additionalItems : false }).errors.length, 0); notEqual(env.validate([1, 2, 3], { additionalItems : { type : 'string' } }).errors.length, 0); notEqual(env.validate(['1', '2', '3'], { items : [ { type : 'string' }, { type : 'string' } ], additionalItems : false }).errors.length, 0); @@ -179,7 +179,7 @@ test("Optional Validation", function () { equal(env.validate({}, { properties : { a : { optional : true } } }).errors.length, 0); equal(env.validate({ a : false }, { properties : { a : { optional : true } } }).errors.length, 0); equal(env.validate({ a : false }, { properties : { a : { optional : false } } }).errors.length, 0); - + notEqual(env.validate({}, { properties : { a : { optional : false } } }).errors.length, 0); notEqual(env.validate({ b : true }, { properties : { a : { optional : false } } }).errors.length, 0); if (curDraftId < 3) { @@ -196,7 +196,7 @@ test("Required Validation", function () { equal(env.validate({}, { properties : { a : { required : false } } }).errors.length, 0); equal(env.validate({ a : false }, { properties : { a : { required : false } } }).errors.length, 0); equal(env.validate({ a : false }, { properties : { a : { required : true } } }).errors.length, 0); - + notEqual(env.validate({}, { properties : { a : { required : true } } }).errors.length, 0); notEqual(env.validate({ b : true }, { properties : { a : { required : true } } }).errors.length, 0); }); @@ -207,7 +207,7 @@ test("Requires Validation", function () { equal(env.validate({ a : 1, b : 2 }, { properties : { a : {}, b : { requires : 'a' } } }).errors.length, 0); equal(env.validate({ a : 1, b : 2 }, { properties : { a : { requires : 'b' }, b : { requires : 'a' } } }).errors.length, 0); equal(env.validate({ a : 1, b : 2 }, { properties : { b : { requires : { properties : { a : { type : 'number' } } } } } }).errors.length, 0); - + notEqual(env.validate({ b : 2 }, { properties : { b : { requires : 'a' } } }).errors.length, 0); notEqual(env.validate({ a : 1, b : 2 }, { properties : { a : { requires : 'b' }, b : { requires : 'c' } } }).errors.length, 0); notEqual(env.validate({ b : 2 }, { properties : { b : { requires : { properties : { b : { type : 'string' } } } } } }).errors.length, 0); @@ -221,7 +221,7 @@ test("Dependencies Validation", function () { equal(env.validate({ a : 1, b : 2 }, { dependencies : { c : 'd' } }).errors.length, 0); equal(env.validate({ a : 1, b : 2, c : 3 }, { dependencies : { a : ['b'], b : ['a', 'c'] } }).errors.length, 0); equal(env.validate({ a : 1, b : 2 }, { dependencies : { a : { properties : { b : { type : 'number', required : true } } } } }).errors.length, 0); - + notEqual(env.validate({ a : 1, b : 2 }, { dependencies : { a : 'c' } }).errors.length, 0); notEqual(env.validate({ a : 1, b : 2, c : 3 }, { dependencies : { a : ['b'], b : ['a', 'd'] } }).errors.length, 0); notEqual(env.validate({ a : 1, b : 2 }, { dependencies : { a : { properties : { b : { type : 'string', required : true } } } } }).errors.length, 0); @@ -234,7 +234,7 @@ test("Minimum/Maximum Validation", function () { equal(env.validate(5, { minimum : 1, maximum : 10 }).errors.length, 0); equal(env.validate(10, { minimum : 1, maximum : 10 }).errors.length, 0); equal(env.validate(1, { minimum : 1, maximum : 1 }).errors.length, 0); - + notEqual(env.validate(0, { minimum : 1, maximum : 10 }).errors.length, 0); notEqual(env.validate(11, { minimum : 1, maximum : 10 }).errors.length, 0); }); @@ -246,16 +246,16 @@ test("MinimumCanEqual/MaximumCanEqual Validation", function () { equal(env.validate(5, { minimum : 1, maximum : 10, minimumCanEqual : true, maximumCanEqual : true }).errors.length, 0); equal(env.validate(10, { minimum : 1, maximum : 10, minimumCanEqual : true, maximumCanEqual : true }).errors.length, 0); equal(env.validate(1, { minimum : 1, maximum : 1, minimumCanEqual : true, maximumCanEqual : true }).errors.length, 0); - + notEqual(env.validate(0, { minimum : 1, maximum : 10, minimumCanEqual : true, maximumCanEqual : true }).errors.length, 0); notEqual(env.validate(11, { minimum : 1, maximum : 10, minimumCanEqual : true, maximumCanEqual : true }).errors.length, 0); - + //false notEqual(env.validate(0, { minimumCanEqual : false, maximumCanEqual : false }).errors.length, 0); //illegal equal(env.validate(1.0001, { minimum : 1, maximum : 10, minimumCanEqual : false, maximumCanEqual : false }).errors.length, 0); equal(env.validate(5, { minimum : 1, maximum : 10, minimumCanEqual : false, maximumCanEqual : false }).errors.length, 0); equal(env.validate(9.9999, { minimum : 1, maximum : 10, minimumCanEqual : false, maximumCanEqual : false }).errors.length, 0); - + notEqual(env.validate(1, { minimum : 1, maximum : 10, minimumCanEqual : false, maximumCanEqual : false }).errors.length, 0); notEqual(env.validate(10, { minimum : 1, maximum : 10, minimumCanEqual : false, maximumCanEqual : false }).errors.length, 0); notEqual(env.validate(1, { minimum : 1, maximum : 1, minimumCanEqual : false, maximumCanEqual : false }).errors.length, 0); @@ -271,16 +271,16 @@ test("ExclusiveMinimum/ExclusiveMaximum Validation", function () { equal(env.validate(5, { minimum : 1, maximum : 10, exclusiveMinimum : false, exclusiveMaximum : false }).errors.length, 0); equal(env.validate(10, { minimum : 1, maximum : 10, exclusiveMinimum : false, exclusiveMaximum : false }).errors.length, 0); equal(env.validate(1, { minimum : 1, maximum : 1, exclusiveMinimum : false, exclusiveMaximum : false }).errors.length, 0); - + notEqual(env.validate(0, { minimum : 1, maximum : 10, exclusiveMinimum : false, exclusiveMaximum : false }).errors.length, 0); notEqual(env.validate(11, { minimum : 1, maximum : 10, exclusiveMinimum : false, exclusiveMaximum : false }).errors.length, 0); - + //false notEqual(env.validate(0, { exclusiveMinimum : true, exclusiveMaximum : true }).errors.length, 0); //illegal equal(env.validate(1.0001, { minimum : 1, maximum : 10, exclusiveMinimum : true, exclusiveMaximum : true }).errors.length, 0); equal(env.validate(5, { minimum : 1, maximum : 10, exclusiveMinimum : true, exclusiveMaximum : true }).errors.length, 0); equal(env.validate(9.9999, { minimum : 1, maximum : 10, exclusiveMinimum : true, exclusiveMaximum : true }).errors.length, 0); - + notEqual(env.validate(1, { minimum : 1, maximum : 10, exclusiveMinimum : true, exclusiveMaximum : true }).errors.length, 0); notEqual(env.validate(10, { minimum : 1, maximum : 10, exclusiveMinimum : true, exclusiveMaximum : true }).errors.length, 0); notEqual(env.validate(1, { minimum : 1, maximum : 1, exclusiveMinimum : true, exclusiveMaximum : true }).errors.length, 0); @@ -295,7 +295,7 @@ test("MinItems/MaxItems Validation", function () { equal(env.validate([1], { minItems : 1, maxItems : 3 }).errors.length, 0); equal(env.validate([1, 2], { minItems : 1, maxItems : 3 }).errors.length, 0); equal(env.validate([1, 2, 3], { minItems : 1, maxItems : 3 }).errors.length, 0); - + notEqual(env.validate([], { minItems : 1, maxItems : 0 }).errors.length, 0); notEqual(env.validate([], { minItems : 1, maxItems : 3 }).errors.length, 0); notEqual(env.validate([1, 2, 3, 4], { minItems : 1, maxItems : 3 }).errors.length, 0); @@ -311,7 +311,7 @@ test("UniqueItems Validation", function () { equal(env.validate(['a', 'b'], { uniqueItems : true }).errors.length, 0); equal(env.validate([[], []], { uniqueItems : true }).errors.length, 0); equal(env.validate([{}, {}], { uniqueItems : true }).errors.length, 0); - + notEqual(env.validate([null, null], { uniqueItems : true }).errors.length, 0); notEqual(env.validate([false, false], { uniqueItems : true }).errors.length, 0); notEqual(env.validate([1, 2, 1], { uniqueItems : true }).errors.length, 0); @@ -323,7 +323,7 @@ test("Pattern Validation", function () { equal(env.validate('', {}).errors.length, 0); equal(env.validate('', { pattern : '^$' }).errors.length, 0); equal(env.validate('today', { pattern : 'day' }).errors.length, 0); - + notEqual(env.validate('', { pattern : '^ $' }).errors.length, 0); notEqual(env.validate('today', { pattern : 'dam' }).errors.length, 0); notEqual(env.validate('aaaaa', { pattern : 'aa(a' }).errors.length, 0); @@ -335,7 +335,7 @@ test("MinLength/MaxLength Validation", function () { equal(env.validate('1', { minLength : 1, maxLength : 3 }).errors.length, 0); equal(env.validate('12', { minLength : 1, maxLength : 3 }).errors.length, 0); equal(env.validate('123', { minLength : 1, maxLength : 3 }).errors.length, 0); - + notEqual(env.validate('', { minLength : 1, maxLength : 0 }).errors.length, 0); notEqual(env.validate('', { minLength : 1, maxLength : 3 }).errors.length, 0); notEqual(env.validate('1234', { minLength : 1, maxLength : 3 }).errors.length, 0); @@ -347,7 +347,7 @@ test("Enum Validation", function () { equal(env.validate(2, { 'enum' : [1, 2, 3] }).errors.length, 0); equal(env.validate('a', { 'enum' : ['a'] }).errors.length, 0); equal(env.validate({}, { 'properties' : { 'a' : { 'enum' : ['a'], 'optional' : true, 'required' : false } } }).errors.length, 0); - + notEqual(env.validate(true, { 'enum' : ['false', 'true'] }).errors.length, 0); notEqual(env.validate(4, { 'enum' : [1, 2, 3, '4'] }).errors.length, 0); notEqual(env.validate('', { 'enum' : [] }).errors.length, 0); @@ -364,7 +364,7 @@ test("MaxDecimal Validation", function () { equal(env.validate(0, { maxDecimal : 1 }).errors.length, 0); equal(env.validate(0.22, { maxDecimal : 2 }).errors.length, 0); equal(env.validate(0.33, { maxDecimal : 3 }).errors.length, 0); - + notEqual(env.validate(0.1, { maxDecimal : 0 }).errors.length, 0); notEqual(env.validate(0.111, { maxDecimal : 1 }).errors.length, 0); }); @@ -379,7 +379,7 @@ test("DivisibleBy Validation", function () { equal(env.validate(5, { divisibleBy : 2.5 }).errors.length, 0); equal(env.validate(7.5, { divisibleBy : 2.5 }).errors.length, 0); equal(env.validate(9.1, { divisibleBy : 1.3 }).errors.length, 0); - + notEqual(env.validate(0, { divisibleBy : 0 }).errors.length, 0); notEqual(env.validate(7, { divisibleBy : 5 }).errors.length, 0); notEqual(env.validate(4.5, { divisibleBy : 2 }).errors.length, 0); @@ -395,7 +395,7 @@ test("Disallow Validation", function () { equal(env.validate(00, { disallow : ['null', 'boolean', 'string', 'array', 'object'] }).errors.length, 0, "Integer"); equal(env.validate(false, { disallow : ['null', 'number', 'integer', 'string', 'array', 'object'] }).errors.length, 0, "Boolean"); equal(env.validate(null, { disallow : ['boolean', 'number', 'integer', 'string', 'array', 'object'] }).errors.length, 0, "Null"); - + notEqual(env.validate({}, { disallow : 'object' }).errors.length, 0, "Object"); notEqual(env.validate([], { disallow : 'array' }).errors.length, 0, "Array"); notEqual(env.validate('', { disallow : 'string' }).errors.length, 0, "String"); @@ -411,9 +411,9 @@ test("Extends Validation", function () { equal(env.validate({}, { 'extends' : { type : 'object' } }).errors.length, 0); equal(env.validate(1, { type : 'integer', 'extends' : { type : 'number' } }).errors.length, 0); equal(env.validate({ a : 1, b : 2 }, { properties : { a : { type : 'number' } }, additionalProperties : false, 'extends' : { properties : { b : { type : 'number' } } } }).errors.length, 0); - + notEqual(env.validate(1, { type : 'number', 'extends' : { type : 'string' } }).errors.length, 0); - + //TODO: More tests }); @@ -421,7 +421,7 @@ test("JSON Schema Validation", function () { var schema = env.findSchema(env.getOption("latestJSONSchemaSchemaURI")); var hyperSchema = env.findSchema(env.getOption("latestJSONSchemaHyperSchemaURI")); var links = env.findSchema(env.getOption("latestJSONSchemaLinksURI")); - + equal(schema.validate(schema).errors.length, 0, "schema.validate(schema)"); equal(hyperSchema.validate(schema).errors.length, 0, "hyperSchema.validate(schema)"); equal(hyperSchema.validate(hyperSchema).errors.length, 0, "hyperSchema.validate(hyperSchema)"); @@ -434,28 +434,28 @@ test("Links Validation", function () { //full equal(env.validate({ 'a' : {} }, { 'type' : 'object', 'additionalProperties' : { '$ref' : '#' } }).errors.length, 0); notEqual(env.validate({ 'a' : 1 }, { 'type' : 'object', 'additionalProperties' : { '$ref' : '#' } }).errors.length, 0); - + //describedby okNoError(function () { schema = env.createSchema({ "id" : "http://test.example.com/3", "properties" : { "test" : { "type" : "object" } }, "extends" : { "$ref" : "http://json-schema.org/draft-03/schema#" } }, null, "http://test.example.com/3"); equal(env.validate({}, { "$schema" : "http://test.example.com/3", "test" : {} }).errors.length, 0); notEqual(env.validate({}, { "$schema" : "http://test.example.com/3", "test" : 0 }).errors.length, 0); }, "describedby schema"); - + //self okNoError(function () { schema = env.createSchema({ "properties" : { "two" : { "id" : "http://test.example.com/2", "type" : "object" } } }, null, "http://not.example.com/2"); equal(env.validate({}, { "$ref" : "http://test.example.com/2" }).errors.length, 0); notEqual(env.validate(null, { "$ref" : "http://test.example.com/2" }).errors.length, 0); }, "self schema"); - + //links api okNoError(function () { schema = env.createSchema({ "links" : [ { "rel" : "bar", "href" : "http:" + (curDraftId < 3 ? "{-this}" : "{@}") + "#" } ] }); instance = env.createInstance("foo"); equal(schema.getLink("bar", instance), "http:foo#", "'bar' link and self reference"); }, "links api schema"); - + //invalid reference (env.getOption("enforceReferences") ? okError : okNoError)(function () { schema = env.createSchema({ "$ref" : "asdf:qwerty" }); //should throw error @@ -466,11 +466,11 @@ test("Links Validation", function () { test("PathStart Validation", function () { var instance = env.createInstance({}, "http://test.example.com/4"), schema = env.createSchema({"pathStart" : "http://test.example.com"}); - + equal(env.validate(instance, schema).errors.length, 0); - + instance = env.createInstance({}); //random URI - + notEqual(env.validate(instance, schema).errors.length, 0); }); @@ -491,7 +491,7 @@ test("Complex Examples", function () { }, "additionalProperties":false }, undefined, "Common#"); - + var report = env.validate({ "!" : "List", "list" : [ @@ -502,7 +502,7 @@ test("Complex Examples", function () { ], "common" : {"!":"Common"} }, - + { "properties":{ "!":{"type":"string","enum":["List"]}, @@ -527,14 +527,14 @@ test("Complex Examples", function () { ] } }, - + "common":{"$ref":"Common#"} } } ); - + notEqual(report.errors.length, 0, "example 1"); - + //example 2 schema = env.createSchema({ "extends": { @@ -558,10 +558,47 @@ test("Complex Examples", function () { } } }); - + report = env.validate({ "id" : "some id", "role" : "yunowork?"}, schema); - + equal(report.errors.length, 0, "example 2"); }); -} \ No newline at end of file +test("Type Coercion Option", function () { + // use dedicated environment + var envWithTypeCoercion = JSV.createEnvironment("json-schema-draft-03"); + envWithTypeCoercion.setOption('typeCoercionFns', { + 'boolean': function(value) { + if (typeof value !== 'string') { return value; } + return value.toLowerCase() === 'true'; + }, + 'number': function(value) { + if (typeof value !== 'string') { return value; } + return +value; + }, + 'integer': function(value) { + if (typeof value !== 'string') { return value; } + return parseInt(value, 10); + } + }); + + var obj = { + booleanProp: "true", + numberProp: "-123.456", + integerProp: "789" + }; + var schema = { + type: "object", + properties: { + "booleanProp": {type: "boolean"}, + "numberProp": {type: "number"}, + "integerProp": {type: "integer"} + } + }; + equal(envWithTypeCoercion.validate(obj, schema).errors.length, 0); + equal(obj.booleanProp, true); + equal(obj.numberProp, -123.456); + equal(obj.integerProp, 789); +}); + +} From b91e8ac5bf4318c848565b24c250a4109ce403b5 Mon Sep 17 00:00:00 2001 From: Simon Goumaz Date: Mon, 29 Apr 2013 11:46:29 +0200 Subject: [PATCH 2/3] Improved test for type coercion functions --- tests/tests3.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/tests/tests3.js b/tests/tests3.js index 81ccbea..d8b556c 100644 --- a/tests/tests3.js +++ b/tests/tests3.js @@ -585,20 +585,30 @@ test("Type Coercion Option", function () { var obj = { booleanProp: "true", numberProp: "-123.456", - integerProp: "789" + subObject: { + integerProp: "789" + } }; var schema = { type: "object", properties: { "booleanProp": {type: "boolean"}, "numberProp": {type: "number"}, - "integerProp": {type: "integer"} + "subObject": { + type: "object", + properties: { + "integerProp": {type: "integer"} + } + } } }; equal(envWithTypeCoercion.validate(obj, schema).errors.length, 0); equal(obj.booleanProp, true); + equal(typeof obj.booleanProp, "boolean"); equal(obj.numberProp, -123.456); - equal(obj.integerProp, 789); + equal(typeof obj.numberProp, "number"); + equal(obj.subObject.integerProp, 789); + equal(typeof obj.subObject.integerProp, "number"); }); } From aeb0669f8fac2859042500133a6e35b5f31b0ab9 Mon Sep 17 00:00:00 2001 From: Simon Goumaz Date: Mon, 29 Apr 2013 12:02:59 +0200 Subject: [PATCH 3/3] Fixed line endings --- lib/json-schema-draft-03.js | 3108 +++++++++++++++++------------------ lib/jsv.js | 3074 +++++++++++++++++----------------- 2 files changed, 3091 insertions(+), 3091 deletions(-) diff --git a/lib/json-schema-draft-03.js b/lib/json-schema-draft-03.js index 5e0c2cf..354f6ea 100644 --- a/lib/json-schema-draft-03.js +++ b/lib/json-schema-draft-03.js @@ -1,1554 +1,1554 @@ -/** - * json-schema-draft-03 Environment - * - * @fileOverview Implementation of the third revision of the JSON Schema specification draft. - * @author Gary Court - * @version 1.5.1 - * @see http://github.com/garycourt/JSV - */ - -/* - * Copyright 2010 Gary Court. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are - * permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of - * conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list - * of conditions and the following disclaimer in the documentation and/or other materials - * provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY GARY COURT ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND - * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GARY COURT OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * The views and conclusions contained in the software and documentation are those of the - * authors and should not be interpreted as representing official policies, either expressed - * or implied, of Gary Court or the JSON Schema specification. - */ - -/*jslint white: true, sub: true, onevar: true, undef: true, eqeqeq: true, newcap: true, immed: true, indent: 4 */ -/*global require */ - -(function () { - var O = {}, - JSV = require('./jsv').JSV, - TYPE_VALIDATORS, - ENVIRONMENT, - SCHEMA_00_JSON, - HYPERSCHEMA_00_JSON, - LINKS_00_JSON, - SCHEMA_00, - HYPERSCHEMA_00, - LINKS_00, - SCHEMA_01_JSON, - HYPERSCHEMA_01_JSON, - LINKS_01_JSON, - SCHEMA_01, - HYPERSCHEMA_01, - LINKS_01, - SCHEMA_02_JSON, - HYPERSCHEMA_02_JSON, - LINKS_02_JSON, - SCHEMA_02, - HYPERSCHEMA_02, - LINKS_02, - SCHEMA_03_JSON, - HYPERSCHEMA_03_JSON, - LINKS_03_JSON, - SCHEMA_03, - HYPERSCHEMA_03, - LINKS_03; - - TYPE_VALIDATORS = { - "string" : function (instance, report) { - return instance.getType() === "string"; - }, - - "number" : function (instance, report) { - return instance.getType() === "number"; - }, - - "integer" : function (instance, report) { - return instance.getType() === "number" && instance.getValue() % 1 === 0; - }, - - "boolean" : function (instance, report) { - return instance.getType() === "boolean"; - }, - - "object" : function (instance, report) { - return instance.getType() === "object"; - }, - - "array" : function (instance, report) { - return instance.getType() === "array"; - }, - - "null" : function (instance, report) { - return instance.getType() === "null"; - }, - - "any" : function (instance, report) { - return true; - } - }; - - ENVIRONMENT = new JSV.Environment(); - ENVIRONMENT.setOption("validateReferences", true); - ENVIRONMENT.setOption("enforceReferences", false); - ENVIRONMENT.setOption("strict", false); - - // - // draft-00 - // - - SCHEMA_00_JSON = { - "$schema" : "http://json-schema.org/draft-00/hyper-schema#", - "id" : "http://json-schema.org/draft-00/schema#", - "type" : "object", - - "properties" : { - "type" : { - "type" : ["string", "array"], - "items" : { - "type" : ["string", {"$ref" : "#"}] - }, - "optional" : true, - "uniqueItems" : true, - "default" : "any", - - "parser" : function (instance, self) { - var parser; - - if (instance.getType() === "string") { - return instance.getValue(); - } else if (instance.getType() === "object") { - return instance.getEnvironment().createSchema( - instance, - self.getEnvironment().findSchema(self.resolveURI("#")) - ); - } else if (instance.getType() === "array") { - parser = self.getValueOfProperty("parser"); - return JSV.mapArray(instance.getProperties(), function (prop) { - return parser(prop, self); - }); - } - //else - return "any"; - }, - - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var requiredTypes = JSV.toArray(schema.getAttribute("type")), - x, xl, type, subreport, typeValidators; - - //for instances that are required to be a certain type - if (instance.getType() !== "undefined" && requiredTypes && requiredTypes.length) { - typeValidators = self.getValueOfProperty("typeValidators") || {}; - - //ensure that type matches for at least one of the required types - for (x = 0, xl = requiredTypes.length; x < xl; ++x) { - type = requiredTypes[x]; - if (JSV.isJSONSchema(type)) { - subreport = JSV.createObject(report); - subreport.errors = []; - subreport.validated = JSV.clone(report.validated); - if (type.validate(instance, subreport, parent, parentSchema, name).errors.length === 0) { - return true; //instance matches this schema - } - } else { - instance.applyTypeCoercionIfSet(type); - - if (typeValidators[type] !== O[type] && typeof typeValidators[type] === "function") { - if (typeValidators[type](instance, report)) { - return true; //type is valid - } - } else { - return true; //unknown types are assumed valid - } - } - } - - //if we get to this point, type is invalid - report.addError(instance, schema, "type", "Instance is not a required type", requiredTypes); - return false; - } - //else, anything is allowed if no type is specified - return true; - }, - - "typeValidators" : TYPE_VALIDATORS - }, - - "properties" : { - "type" : "object", - "additionalProperties" : {"$ref" : "#"}, - "optional" : true, - "default" : {}, - - "parser" : function (instance, self, arg) { - var env = instance.getEnvironment(), - selfEnv = self.getEnvironment(); - if (instance.getType() === "object") { - if (arg) { - return env.createSchema(instance.getProperty(arg), selfEnv.findSchema(self.resolveURI("#"))); - } else { - return JSV.mapObject(instance.getProperties(), function (instance) { - return env.createSchema(instance, selfEnv.findSchema(self.resolveURI("#"))); - }); - } - } - //else - return {}; - }, - - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var propertySchemas, key; - //this attribute is for object type instances only - if (instance.getType() === "object") { - //for each property defined in the schema - propertySchemas = schema.getAttribute("properties"); - for (key in propertySchemas) { - if (propertySchemas[key] !== O[key] && propertySchemas[key]) { - //ensure that instance property is valid - propertySchemas[key].validate(instance.getProperty(key), report, instance, schema, key); - } - } - } - } - }, - - "items" : { - "type" : [{"$ref" : "#"}, "array"], - "items" : {"$ref" : "#"}, - "optional" : true, - "default" : {}, - - "parser" : function (instance, self) { - if (instance.getType() === "object") { - return instance.getEnvironment().createSchema(instance, self.getEnvironment().findSchema(self.resolveURI("#"))); - } else if (instance.getType() === "array") { - return JSV.mapArray(instance.getProperties(), function (instance) { - return instance.getEnvironment().createSchema(instance, self.getEnvironment().findSchema(self.resolveURI("#"))); - }); - } - //else - return instance.getEnvironment().createEmptySchema(); - }, - - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var properties, items, x, xl, itemSchema, additionalProperties; - - if (instance.getType() === "array") { - properties = instance.getProperties(); - items = schema.getAttribute("items"); - additionalProperties = schema.getAttribute("additionalProperties"); - - if (JSV.typeOf(items) === "array") { - for (x = 0, xl = properties.length; x < xl; ++x) { - itemSchema = items[x] || additionalProperties; - if (itemSchema !== false) { - itemSchema.validate(properties[x], report, instance, schema, x); - } else { - report.addError(instance, schema, "additionalProperties", "Additional items are not allowed", itemSchema); - } - } - } else { - itemSchema = items || additionalProperties; - for (x = 0, xl = properties.length; x < xl; ++x) { - itemSchema.validate(properties[x], report, instance, schema, x); - } - } - } - } - }, - - "optional" : { - "type" : "boolean", - "optional" : true, - "default" : false, - - "parser" : function (instance, self) { - return !!instance.getValue(); - }, - - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - if (instance.getType() === "undefined" && !schema.getAttribute("optional")) { - report.addError(instance, schema, "optional", "Property is required", false); - } - }, - - "validationRequired" : true - }, - - "additionalProperties" : { - "type" : [{"$ref" : "#"}, "boolean"], - "optional" : true, - "default" : {}, - - "parser" : function (instance, self) { - if (instance.getType() === "object") { - return instance.getEnvironment().createSchema(instance, self.getEnvironment().findSchema(self.resolveURI("#"))); - } else if (instance.getType() === "boolean" && instance.getValue() === false) { - return false; - } - //else - return instance.getEnvironment().createEmptySchema(); - }, - - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var additionalProperties, propertySchemas, properties, key; - //we only need to check against object types as arrays do their own checking on this property - if (instance.getType() === "object") { - additionalProperties = schema.getAttribute("additionalProperties"); - propertySchemas = schema.getAttribute("properties") || {}; - properties = instance.getProperties(); - for (key in properties) { - if (properties[key] !== O[key] && properties[key] && propertySchemas[key] === O[key]) { - if (JSV.isJSONSchema(additionalProperties)) { - additionalProperties.validate(properties[key], report, instance, schema, key); - } else if (additionalProperties === false) { - report.addError(instance, schema, "additionalProperties", "Additional properties are not allowed", additionalProperties); - } - } - } - } - } - }, - - "requires" : { - "type" : ["string", {"$ref" : "#"}], - "optional" : true, - - "parser" : function (instance, self) { - if (instance.getType() === "string") { - return instance.getValue(); - } else if (instance.getType() === "object") { - return instance.getEnvironment().createSchema(instance, self.getEnvironment().findSchema(self.resolveURI("#"))); - } - }, - - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var requires; - if (instance.getType() !== "undefined" && parent && parent.getType() !== "undefined") { - requires = schema.getAttribute("requires"); - if (typeof requires === "string") { - if (parent.getProperty(requires).getType() === "undefined") { - report.addError(instance, schema, "requires", 'Property requires sibling property "' + requires + '"', requires); - } - } else if (JSV.isJSONSchema(requires)) { - requires.validate(parent, report); //WATCH: A "requires" schema does not support the "requires" attribute - } - } - } - }, - - "minimum" : { - "type" : "number", - "optional" : true, - - "parser" : function (instance, self) { - if (instance.getType() === "number") { - return instance.getValue(); - } - }, - - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var minimum, minimumCanEqual; - if (instance.getType() === "number") { - minimum = schema.getAttribute("minimum"); - minimumCanEqual = schema.getAttribute("minimumCanEqual"); - if (typeof minimum === "number" && (instance.getValue() < minimum || (minimumCanEqual === false && instance.getValue() === minimum))) { - report.addError(instance, schema, "minimum", "Number is less than the required minimum value", minimum); - } - } - } - }, - - "maximum" : { - "type" : "number", - "optional" : true, - - "parser" : function (instance, self) { - if (instance.getType() === "number") { - return instance.getValue(); - } - }, - - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var maximum, maximumCanEqual; - if (instance.getType() === "number") { - maximum = schema.getAttribute("maximum"); - maximumCanEqual = schema.getAttribute("maximumCanEqual"); - if (typeof maximum === "number" && (instance.getValue() > maximum || (maximumCanEqual === false && instance.getValue() === maximum))) { - report.addError(instance, schema, "maximum", "Number is greater than the required maximum value", maximum); - } - } - } - }, - - "minimumCanEqual" : { - "type" : "boolean", - "optional" : true, - "requires" : "minimum", - "default" : true, - - "parser" : function (instance, self) { - if (instance.getType() === "boolean") { - return instance.getValue(); - } - //else - return true; - } - }, - - "maximumCanEqual" : { - "type" : "boolean", - "optional" : true, - "requires" : "maximum", - "default" : true, - - "parser" : function (instance, self) { - if (instance.getType() === "boolean") { - return instance.getValue(); - } - //else - return true; - } - }, - - "minItems" : { - "type" : "integer", - "optional" : true, - "minimum" : 0, - "default" : 0, - - "parser" : function (instance, self) { - if (instance.getType() === "number") { - return instance.getValue(); - } - //else - return 0; - }, - - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var minItems; - if (instance.getType() === "array") { - minItems = schema.getAttribute("minItems"); - if (typeof minItems === "number" && instance.getProperties().length < minItems) { - report.addError(instance, schema, "minItems", "The number of items is less than the required minimum", minItems); - } - } - } - }, - - "maxItems" : { - "type" : "integer", - "optional" : true, - "minimum" : 0, - - "parser" : function (instance, self) { - if (instance.getType() === "number") { - return instance.getValue(); - } - }, - - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var maxItems; - if (instance.getType() === "array") { - maxItems = schema.getAttribute("maxItems"); - if (typeof maxItems === "number" && instance.getProperties().length > maxItems) { - report.addError(instance, schema, "maxItems", "The number of items is greater than the required maximum", maxItems); - } - } - } - }, - - "pattern" : { - "type" : "string", - "optional" : true, - "format" : "regex", - - "parser" : function (instance, self) { - if (instance.getType() === "string") { - return instance.getValue(); - } - }, - - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var pattern; - try { - pattern = new RegExp(schema.getAttribute("pattern")); - if (instance.getType() === "string" && pattern && !pattern.test(instance.getValue())) { - report.addError(instance, schema, "pattern", "String does not match pattern", pattern.toString()); - } - } catch (e) { - report.addError(schema, self, "pattern", "Invalid pattern", schema.getValueOfProperty("pattern")); - } - } - }, - - "minLength" : { - "type" : "integer", - "optional" : true, - "minimum" : 0, - "default" : 0, - - "parser" : function (instance, self) { - if (instance.getType() === "number") { - return instance.getValue(); - } - //else - return 0; - }, - - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var minLength; - if (instance.getType() === "string") { - minLength = schema.getAttribute("minLength"); - if (typeof minLength === "number" && instance.getValue().length < minLength) { - report.addError(instance, schema, "minLength", "String is less than the required minimum length", minLength); - } - } - } - }, - - "maxLength" : { - "type" : "integer", - "optional" : true, - - "parser" : function (instance, self) { - if (instance.getType() === "number") { - return instance.getValue(); - } - }, - - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var maxLength; - if (instance.getType() === "string") { - maxLength = schema.getAttribute("maxLength"); - if (typeof maxLength === "number" && instance.getValue().length > maxLength) { - report.addError(instance, schema, "maxLength", "String is greater than the required maximum length", maxLength); - } - } - } - }, - - "enum" : { - "type" : "array", - "optional" : true, - "minItems" : 1, - "uniqueItems" : true, - - "parser" : function (instance, self) { - if (instance.getType() === "array") { - return instance.getValue(); - } - }, - - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var enums, x, xl; - if (instance.getType() !== "undefined") { - enums = schema.getAttribute("enum"); - if (enums) { - for (x = 0, xl = enums.length; x < xl; ++x) { - if (instance.equals(enums[x])) { - return true; - } - } - report.addError(instance, schema, "enum", "Instance is not one of the possible values", enums); - } - } - } - }, - - "title" : { - "type" : "string", - "optional" : true - }, - - "description" : { - "type" : "string", - "optional" : true - }, - - "format" : { - "type" : "string", - "optional" : true, - - "parser" : function (instance, self) { - if (instance.getType() === "string") { - return instance.getValue(); - } - }, - - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var format, formatValidators; - if (instance.getType() === "string") { - format = schema.getAttribute("format"); - formatValidators = self.getValueOfProperty("formatValidators"); - if (typeof format === "string" && formatValidators[format] !== O[format] && typeof formatValidators[format] === "function" && !formatValidators[format].call(this, instance, report)) { - report.addError(instance, schema, "format", "String is not in the required format", format); - } - } - }, - - "formatValidators" : {} - }, - - "contentEncoding" : { - "type" : "string", - "optional" : true - }, - - "default" : { - "type" : "any", - "optional" : true - }, - - "maxDecimal" : { - "type" : "integer", - "optional" : true, - "minimum" : 0, - - "parser" : function (instance, self) { - if (instance.getType() === "number") { - return instance.getValue(); - } - }, - - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var maxDecimal, decimals; - if (instance.getType() === "number") { - maxDecimal = schema.getAttribute("maxDecimal"); - if (typeof maxDecimal === "number") { - decimals = instance.getValue().toString(10).split('.')[1]; - if (decimals && decimals.length > maxDecimal) { - report.addError(instance, schema, "maxDecimal", "The number of decimal places is greater than the allowed maximum", maxDecimal); - } - } - } - } - }, - - "disallow" : { - "type" : ["string", "array"], - "items" : {"type" : "string"}, - "optional" : true, - "uniqueItems" : true, - - "parser" : function (instance, self) { - if (instance.getType() === "string" || instance.getType() === "array") { - return instance.getValue(); - } - }, - - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var disallowedTypes = JSV.toArray(schema.getAttribute("disallow")), - x, xl, key, typeValidators, subreport; - - //for instances that are required to be a certain type - if (instance.getType() !== "undefined" && disallowedTypes && disallowedTypes.length) { - typeValidators = self.getValueOfProperty("typeValidators") || {}; - - //ensure that type matches for at least one of the required types - for (x = 0, xl = disallowedTypes.length; x < xl; ++x) { - key = disallowedTypes[x]; - if (JSV.isJSONSchema(key)) { //this is supported draft-03 and on - subreport = JSV.createObject(report); - subreport.errors = []; - subreport.validated = JSV.clone(report.validated); - if (key.validate(instance, subreport, parent, parentSchema, name).errors.length === 0) { - //instance matches this schema - report.addError(instance, schema, "disallow", "Instance is a disallowed type", disallowedTypes); - return false; - } - } else if (typeValidators[key] !== O[key] && typeof typeValidators[key] === "function") { - if (typeValidators[key](instance, report)) { - report.addError(instance, schema, "disallow", "Instance is a disallowed type", disallowedTypes); - return false; - } - } - /* - else { - report.addError(instance, schema, "disallow", "Instance may be a disallowed type", disallowedTypes); - return false; - } - */ - } - - //if we get to this point, type is valid - return true; - } - //else, everything is allowed if no disallowed types are specified - return true; - }, - - "typeValidators" : TYPE_VALIDATORS - }, - - "extends" : { - "type" : [{"$ref" : "#"}, "array"], - "items" : {"$ref" : "#"}, - "optional" : true, - "default" : {}, - - "parser" : function (instance, self) { - if (instance.getType() === "object") { - return instance.getEnvironment().createSchema(instance, self.getEnvironment().findSchema(self.resolveURI("#"))); - } else if (instance.getType() === "array") { - return JSV.mapArray(instance.getProperties(), function (instance) { - return instance.getEnvironment().createSchema(instance, self.getEnvironment().findSchema(self.resolveURI("#"))); - }); - } - }, - - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var extensions = schema.getAttribute("extends"), x, xl; - if (extensions) { - if (JSV.isJSONSchema(extensions)) { - extensions.validate(instance, report, parent, parentSchema, name); - } else if (JSV.typeOf(extensions) === "array") { - for (x = 0, xl = extensions.length; x < xl; ++x) { - extensions[x].validate(instance, report, parent, parentSchema, name); - } - } - } - } - } - }, - - "optional" : true, - "default" : {}, - "fragmentResolution" : "dot-delimited", - - "parser" : function (instance, self) { - if (instance.getType() === "object") { - return instance.getEnvironment().createSchema(instance, self); - } - }, - - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var propNames = schema.getPropertyNames(), - x, xl, - attributeSchemas = self.getAttribute("properties"), - strict = instance.getEnvironment().getOption("strict"), - validator; - - for (x in attributeSchemas) { - if (attributeSchemas[x] !== O[x]) { - if (attributeSchemas[x].getValueOfProperty("validationRequired")) { - JSV.pushUnique(propNames, x); - } - if (strict && attributeSchemas[x].getValueOfProperty("deprecated")) { - JSV.popFirst(propNames, x); - } - } - } - - for (x = 0, xl = propNames.length; x < xl; ++x) { - if (attributeSchemas[propNames[x]] !== O[propNames[x]]) { - validator = attributeSchemas[propNames[x]].getValueOfProperty("validator"); - if (typeof validator === "function") { - validator(instance, schema, attributeSchemas[propNames[x]], report, parent, parentSchema, name); - } - } - } - } - }; - - HYPERSCHEMA_00_JSON = { - "$schema" : "http://json-schema.org/draft-00/hyper-schema#", - "id" : "http://json-schema.org/draft-00/hyper-schema#", - - "properties" : { - "links" : { - "type" : "array", - "items" : {"$ref" : "links#"}, - "optional" : true, - - "parser" : function (instance, self, arg) { - var links, - linkSchemaURI = self.getValueOfProperty("items")["$ref"], - linkSchema = self.getEnvironment().findSchema(linkSchemaURI), - linkParser = linkSchema && linkSchema.getValueOfProperty("parser"), - selfReferenceVariable; - arg = JSV.toArray(arg); - - if (typeof linkParser === "function") { - links = JSV.mapArray(instance.getProperties(), function (link) { - return linkParser(link, linkSchema); - }); - } else { - links = JSV.toArray(instance.getValue()); - } - - if (arg[0]) { - links = JSV.filterArray(links, function (link) { - return link["rel"] === arg[0]; - }); - } - - if (arg[1]) { - selfReferenceVariable = self.getValueOfProperty("selfReferenceVariable"); - links = JSV.mapArray(links, function (link) { - var instance = arg[1], - href = link["href"]; - href = href.replace(/\{(.+)\}/g, function (str, p1, offset, s) { - var value; - if (p1 === selfReferenceVariable) { - value = instance.getValue(); - } else { - value = instance.getValueOfProperty(p1); - } - return value !== undefined ? String(value) : ""; - }); - return href ? JSV.formatURI(instance.resolveURI(href)) : href; - }); - } - - return links; - }, - - "selfReferenceVariable" : "-this" - }, - - "fragmentResolution" : { - "type" : "string", - "optional" : true, - "default" : "dot-delimited" - }, - - "root" : { - "type" : "boolean", - "optional" : true, - "default" : false - }, - - "readonly" : { - "type" : "boolean", - "optional" : true, - "default" : false - }, - - "pathStart" : { - "type" : "string", - "optional" : true, - "format" : "uri", - - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var pathStart; - if (instance.getType() !== "undefined") { - pathStart = schema.getAttribute("pathStart"); - if (typeof pathStart === "string") { - //TODO: Find out what pathStart is relative to - if (instance.getURI().indexOf(pathStart) !== 0) { - report.addError(instance, schema, "pathStart", "Instance's URI does not start with " + pathStart, pathStart); - } - } - } - } - }, - - "mediaType" : { - "type" : "string", - "optional" : true, - "format" : "media-type" - }, - - "alternate" : { - "type" : "array", - "items" : {"$ref" : "#"}, - "optional" : true - } - }, - - "links" : [ - { - "href" : "{$ref}", - "rel" : "full" - }, - - { - "href" : "{$schema}", - "rel" : "describedby" - }, - - { - "href" : "{id}", - "rel" : "self" - } - ], - - "initializer" : function (instance) { - var link, extension, extended; - - //if there is a link to a different schema, set reference - link = instance._schema.getLink("describedby", instance); - if (link && instance._schema._uri !== link) { - instance.setReference("describedby", link); - } - - //if instance has a URI link to itself, update it's own URI - link = instance._schema.getLink("self", instance); - if (JSV.typeOf(link) === "string") { - instance._uri = JSV.formatURI(link); - } - - //if there is a link to the full representation, set reference - link = instance._schema.getLink("full", instance); - if (link && instance._uri !== link) { - instance.setReference("full", link); - } - - //extend schema - extension = instance.getAttribute("extends"); - if (JSV.isJSONSchema(extension)) { - extended = JSV.inherits(extension, instance, true); - instance = instance._env.createSchema(extended, instance._schema, instance._uri); - } - - return instance; - } - - //not needed as JSV.inherits does the job for us - //"extends" : {"$ref" : "http://json-schema.org/schema#"} - }; - - LINKS_00_JSON = { - "$schema" : "http://json-schema.org/draft-00/hyper-schema#", - "id" : "http://json-schema.org/draft-00/links#", - "type" : "object", - - "properties" : { - "href" : { - "type" : "string" - }, - - "rel" : { - "type" : "string" - }, - - "method" : { - "type" : "string", - "default" : "GET", - "optional" : true - }, - - "enctype" : { - "type" : "string", - "requires" : "method", - "optional" : true - }, - - "properties" : { - "type" : "object", - "additionalProperties" : {"$ref" : "hyper-schema#"}, - "optional" : true, - - "parser" : function (instance, self, arg) { - var env = instance.getEnvironment(), - selfEnv = self.getEnvironment(), - additionalPropertiesSchemaURI = self.getValueOfProperty("additionalProperties")["$ref"]; - if (instance.getType() === "object") { - if (arg) { - return env.createSchema(instance.getProperty(arg), selfEnv.findSchema(self.resolveURI(additionalPropertiesSchemaURI))); - } else { - return JSV.mapObject(instance.getProperties(), function (instance) { - return env.createSchema(instance, selfEnv.findSchema(self.resolveURI(additionalPropertiesSchemaURI))); - }); - } - } - } - } - }, - - "parser" : function (instance, self) { - var selfProperties = self.getProperty("properties"); - if (instance.getType() === "object") { - return JSV.mapObject(instance.getProperties(), function (property, key) { - var propertySchema = selfProperties.getProperty(key), - parser = propertySchema && propertySchema.getValueOfProperty("parser"); - if (typeof parser === "function") { - return parser(property, propertySchema); - } - //else - return property.getValue(); - }); - } - return instance.getValue(); - } - }; - - ENVIRONMENT.setOption("defaultFragmentDelimiter", "."); - ENVIRONMENT.setOption("defaultSchemaURI", "http://json-schema.org/draft-00/schema#"); //updated later - - SCHEMA_00 = ENVIRONMENT.createSchema(SCHEMA_00_JSON, true, "http://json-schema.org/draft-00/schema#"); - HYPERSCHEMA_00 = ENVIRONMENT.createSchema(JSV.inherits(SCHEMA_00, ENVIRONMENT.createSchema(HYPERSCHEMA_00_JSON, true, "http://json-schema.org/draft-00/hyper-schema#"), true), true, "http://json-schema.org/draft-00/hyper-schema#"); - - ENVIRONMENT.setOption("defaultSchemaURI", "http://json-schema.org/draft-00/hyper-schema#"); - - LINKS_00 = ENVIRONMENT.createSchema(LINKS_00_JSON, HYPERSCHEMA_00, "http://json-schema.org/draft-00/links#"); - - // - // draft-01 - // - - SCHEMA_01_JSON = JSV.inherits(SCHEMA_00_JSON, { - "$schema" : "http://json-schema.org/draft-01/hyper-schema#", - "id" : "http://json-schema.org/draft-01/schema#" - }); - - HYPERSCHEMA_01_JSON = JSV.inherits(HYPERSCHEMA_00_JSON, { - "$schema" : "http://json-schema.org/draft-01/hyper-schema#", - "id" : "http://json-schema.org/draft-01/hyper-schema#" - }); - - LINKS_01_JSON = JSV.inherits(LINKS_00_JSON, { - "$schema" : "http://json-schema.org/draft-01/hyper-schema#", - "id" : "http://json-schema.org/draft-01/links#" - }); - - ENVIRONMENT.setOption("defaultSchemaURI", "http://json-schema.org/draft-01/schema#"); //update later - - SCHEMA_01 = ENVIRONMENT.createSchema(SCHEMA_01_JSON, true, "http://json-schema.org/draft-01/schema#"); - HYPERSCHEMA_01 = ENVIRONMENT.createSchema(JSV.inherits(SCHEMA_01, ENVIRONMENT.createSchema(HYPERSCHEMA_01_JSON, true, "http://json-schema.org/draft-01/hyper-schema#"), true), true, "http://json-schema.org/draft-01/hyper-schema#"); - - ENVIRONMENT.setOption("defaultSchemaURI", "http://json-schema.org/draft-01/hyper-schema#"); - - LINKS_01 = ENVIRONMENT.createSchema(LINKS_01_JSON, HYPERSCHEMA_01, "http://json-schema.org/draft-01/links#"); - - // - // draft-02 - // - - SCHEMA_02_JSON = JSV.inherits(SCHEMA_01_JSON, { - "$schema" : "http://json-schema.org/draft-02/hyper-schema#", - "id" : "http://json-schema.org/draft-02/schema#", - - "properties" : { - "uniqueItems" : { - "type" : "boolean", - "optional" : true, - "default" : false, - - "parser" : function (instance, self) { - return !!instance.getValue(); - }, - - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var value, x, xl, y, yl; - if (instance.getType() === "array" && schema.getAttribute("uniqueItems")) { - value = instance.getProperties(); - for (x = 0, xl = value.length - 1; x < xl; ++x) { - for (y = x + 1, yl = value.length; y < yl; ++y) { - if (value[x].equals(value[y])) { - report.addError(instance, schema, "uniqueItems", "Array can only contain unique items", { x : x, y : y }); - } - } - } - } - } - }, - - "maxDecimal" : { - "deprecated" : true - }, - - "divisibleBy" : { - "type" : "number", - "minimum" : 0, - "minimumCanEqual" : false, - "optional" : true, - - "parser" : function (instance, self) { - if (instance.getType() === "number") { - return instance.getValue(); - } - }, - - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var divisor, value, digits; - if (instance.getType() === "number") { - divisor = schema.getAttribute("divisibleBy"); - if (divisor === 0) { - report.addError(instance, schema, "divisibleBy", "Nothing is divisible by 0", divisor); - } else if (divisor !== 1) { - value = instance.getValue(); - digits = Math.max((value.toString().split(".")[1] || " ").length, (divisor.toString().split(".")[1] || " ").length); - digits = parseFloat(((value / divisor) % 1).toFixed(digits)); //cut out floating point errors - if (0 < digits && digits < 1) { - report.addError(instance, schema, "divisibleBy", "Number is not divisible by " + divisor, divisor); - } - } - } - } - } - }, - - "fragmentResolution" : "slash-delimited" - }); - - HYPERSCHEMA_02_JSON = JSV.inherits(HYPERSCHEMA_01_JSON, { - "id" : "http://json-schema.org/draft-02/hyper-schema#", - - "properties" : { - "fragmentResolution" : { - "default" : "slash-delimited" - } - } - }); - - LINKS_02_JSON = JSV.inherits(LINKS_01_JSON, { - "$schema" : "http://json-schema.org/draft-02/hyper-schema#", - "id" : "http://json-schema.org/draft-02/links#", - - "properties" : { - "targetSchema" : { - "$ref" : "hyper-schema#", - - //need this here because parsers are run before links are resolved - "parser" : HYPERSCHEMA_01.getAttribute("parser") - } - } - }); - - ENVIRONMENT.setOption("defaultFragmentDelimiter", "/"); - ENVIRONMENT.setOption("defaultSchemaURI", "http://json-schema.org/draft-02/schema#"); //update later - - SCHEMA_02 = ENVIRONMENT.createSchema(SCHEMA_02_JSON, true, "http://json-schema.org/draft-02/schema#"); - HYPERSCHEMA_02 = ENVIRONMENT.createSchema(JSV.inherits(SCHEMA_02, ENVIRONMENT.createSchema(HYPERSCHEMA_02_JSON, true, "http://json-schema.org/draft-02/hyper-schema#"), true), true, "http://json-schema.org/draft-02/hyper-schema#"); - - ENVIRONMENT.setOption("defaultSchemaURI", "http://json-schema.org/draft-02/hyper-schema#"); - - LINKS_02 = ENVIRONMENT.createSchema(LINKS_02_JSON, HYPERSCHEMA_02, "http://json-schema.org/draft-02/links#"); - - // - // draft-03 - // - - function getMatchedPatternProperties(instance, schema, report, self) { - var matchedProperties = {}, patternProperties, pattern, regexp, properties, key; - - if (instance.getType() === "object") { - patternProperties = schema.getAttribute("patternProperties"); - properties = instance.getProperties(); - for (pattern in patternProperties) { - if (patternProperties[pattern] !== O[pattern]) { - regexp = null; - try { - regexp = new RegExp(pattern); - } catch (e) { - if (report) { - report.addError(schema, self, "patternProperties", "Invalid pattern", pattern); - } - } - - if (regexp) { - for (key in properties) { - if (properties[key] !== O[key] && regexp.test(key)) { - matchedProperties[key] = matchedProperties[key] ? JSV.pushUnique(matchedProperties[key], patternProperties[pattern]) : [ patternProperties[pattern] ]; - } - } - } - } - } - } - - return matchedProperties; - } - - SCHEMA_03_JSON = JSV.inherits(SCHEMA_02_JSON, { - "$schema" : "http://json-schema.org/draft-03/schema#", - "id" : "http://json-schema.org/draft-03/schema#", - - "properties" : { - "patternProperties" : { - "type" : "object", - "additionalProperties" : {"$ref" : "#"}, - "default" : {}, - - "parser" : SCHEMA_02.getValueOfProperty("properties")["properties"]["parser"], - - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var matchedProperties, key, x; - if (instance.getType() === "object") { - matchedProperties = getMatchedPatternProperties(instance, schema, report, self); - for (key in matchedProperties) { - if (matchedProperties[key] !== O[key]) { - x = matchedProperties[key].length; - while (x--) { - matchedProperties[key][x].validate(instance.getProperty(key), report, instance, schema, key); - } - } - } - } - } - }, - - "additionalProperties" : { - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var additionalProperties, propertySchemas, properties, matchedProperties, key; - if (instance.getType() === "object") { - additionalProperties = schema.getAttribute("additionalProperties"); - propertySchemas = schema.getAttribute("properties") || {}; - properties = instance.getProperties(); - matchedProperties = getMatchedPatternProperties(instance, schema); - for (key in properties) { - if (properties[key] !== O[key] && properties[key] && propertySchemas[key] === O[key] && matchedProperties[key] === O[key]) { - if (JSV.isJSONSchema(additionalProperties)) { - additionalProperties.validate(properties[key], report, instance, schema, key); - } else if (additionalProperties === false) { - report.addError(instance, schema, "additionalProperties", "Additional properties are not allowed", additionalProperties); - } - } - } - } - } - }, - - "items" : { - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var properties, items, x, xl, itemSchema, additionalItems; - - if (instance.getType() === "array") { - properties = instance.getProperties(); - items = schema.getAttribute("items"); - additionalItems = schema.getAttribute("additionalItems"); - - if (JSV.typeOf(items) === "array") { - for (x = 0, xl = properties.length; x < xl; ++x) { - itemSchema = items[x] || additionalItems; - if (itemSchema !== false) { - itemSchema.validate(properties[x], report, instance, schema, x); - } else { - report.addError(instance, schema, "additionalItems", "Additional items are not allowed", itemSchema); - } - } - } else { - itemSchema = items || additionalItems; - for (x = 0, xl = properties.length; x < xl; ++x) { - itemSchema.validate(properties[x], report, instance, schema, x); - } - } - } - } - }, - - "additionalItems" : { - "type" : [{"$ref" : "#"}, "boolean"], - "default" : {}, - - "parser" : SCHEMA_02.getValueOfProperty("properties")["additionalProperties"]["parser"], - - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var additionalItems, properties, x, xl; - //only validate if the "items" attribute is undefined - if (instance.getType() === "array" && schema.getProperty("items").getType() === "undefined") { - additionalItems = schema.getAttribute("additionalItems"); - properties = instance.getProperties(); - - if (additionalItems !== false) { - for (x = 0, xl = properties.length; x < xl; ++x) { - additionalItems.validate(properties[x], report, instance, schema, x); - } - } else if (properties.length) { - report.addError(instance, schema, "additionalItems", "Additional items are not allowed", additionalItems); - } - } - } - }, - - "optional" : { - "validationRequired" : false, - "deprecated" : true - }, - - "required" : { - "type" : "boolean", - "default" : false, - - "parser" : function (instance, self) { - return !!instance.getValue(); - }, - - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - if (instance.getType() === "undefined" && schema.getAttribute("required")) { - report.addError(instance, schema, "required", "Property is required", true); - } - } - }, - - "requires" : { - "deprecated" : true - }, - - "dependencies" : { - "type" : "object", - "additionalProperties" : { - "type" : ["string", "array", {"$ref" : "#"}], - "items" : { - "type" : "string" - } - }, - "default" : {}, - - "parser" : function (instance, self, arg) { - function parseProperty(property) { - var type = property.getType(); - if (type === "string" || type === "array") { - return property.getValue(); - } else if (type === "object") { - return property.getEnvironment().createSchema(property, self.getEnvironment().findSchema(self.resolveURI("#"))); - } - } - - if (instance.getType() === "object") { - if (arg) { - return parseProperty(instance.getProperty(arg)); - } else { - return JSV.mapObject(instance.getProperties(), parseProperty); - } - } - //else - return {}; - }, - - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var dependencies, key, dependency, type, x, xl; - if (instance.getType() === "object") { - dependencies = schema.getAttribute("dependencies"); - for (key in dependencies) { - if (dependencies[key] !== O[key] && instance.getProperty(key).getType() !== "undefined") { - dependency = dependencies[key]; - type = JSV.typeOf(dependency); - if (type === "string") { - if (instance.getProperty(dependency).getType() === "undefined") { - report.addError(instance, schema, "dependencies", 'Property "' + key + '" requires sibling property "' + dependency + '"', dependencies); - } - } else if (type === "array") { - for (x = 0, xl = dependency.length; x < xl; ++x) { - if (instance.getProperty(dependency[x]).getType() === "undefined") { - report.addError(instance, schema, "dependencies", 'Property "' + key + '" requires sibling property "' + dependency[x] + '"', dependencies); - } - } - } else if (JSV.isJSONSchema(dependency)) { - dependency.validate(instance, report); - } - } - } - } - } - }, - - "minimumCanEqual" : { - "deprecated" : true - }, - - "maximumCanEqual" : { - "deprecated" : true - }, - - "exclusiveMinimum" : { - "type" : "boolean", - "default" : false, - - "parser" : function (instance, self) { - return !!instance.getValue(); - } - }, - - "exclusiveMaximum" : { - "type" : "boolean", - "default" : false, - - "parser" : function (instance, self) { - return !!instance.getValue(); - } - }, - - "minimum" : { - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var minimum, exclusiveMinimum; - if (instance.getType() === "number") { - minimum = schema.getAttribute("minimum"); - exclusiveMinimum = schema.getAttribute("exclusiveMinimum") || (!instance.getEnvironment().getOption("strict") && !schema.getAttribute("minimumCanEqual")); - if (typeof minimum === "number" && (instance.getValue() < minimum || (exclusiveMinimum === true && instance.getValue() === minimum))) { - report.addError(instance, schema, "minimum", "Number is less than the required minimum value", minimum); - } - } - } - }, - - "maximum" : { - "validator" : function (instance, schema, self, report, parent, parentSchema, name) { - var maximum, exclusiveMaximum; - if (instance.getType() === "number") { - maximum = schema.getAttribute("maximum"); - exclusiveMaximum = schema.getAttribute("exclusiveMaximum") || (!instance.getEnvironment().getOption("strict") && !schema.getAttribute("maximumCanEqual")); - if (typeof maximum === "number" && (instance.getValue() > maximum || (exclusiveMaximum === true && instance.getValue() === maximum))) { - report.addError(instance, schema, "maximum", "Number is greater than the required maximum value", maximum); - } - } - } - }, - - "contentEncoding" : { - "deprecated" : true - }, - - "divisibleBy" : { - "exclusiveMinimum" : true - }, - - "disallow" : { - "items" : { - "type" : ["string", {"$ref" : "#"}] - }, - - "parser" : SCHEMA_02_JSON["properties"]["type"]["parser"] - }, - - "id" : { - "type" : "string", - "format" : "uri" - }, - - "$ref" : { - "type" : "string", - "format" : "uri" - }, - - "$schema" : { - "type" : "string", - "format" : "uri" - } - }, - - "dependencies" : { - "exclusiveMinimum" : "minimum", - "exclusiveMaximum" : "maximum" - }, - - "initializer" : function (instance) { - var link, extension, extended, - schemaLink = instance.getValueOfProperty("$schema"), - refLink = instance.getValueOfProperty("$ref"), - idLink = instance.getValueOfProperty("id"); - - //if there is a link to a different schema, set reference - if (schemaLink) { - link = instance.resolveURI(schemaLink); - instance.setReference("describedby", link); - } - - //if instance has a URI link to itself, update it's own URI - if (idLink) { - link = instance.resolveURI(idLink); - if (JSV.typeOf(link) === "string") { - instance._uri = JSV.formatURI(link); - } - } - - //if there is a link to the full representation, set reference - if (refLink) { - link = instance.resolveURI(refLink); - instance.setReference("full", link); - } - - //extend schema - extension = instance.getAttribute("extends"); - if (JSV.isJSONSchema(extension)) { - extended = JSV.inherits(extension, instance, true); - instance = instance._env.createSchema(extended, instance._schema, instance._uri); - } - - return instance; - } - }); - - HYPERSCHEMA_03_JSON = JSV.inherits(HYPERSCHEMA_02_JSON, { - "$schema" : "http://json-schema.org/draft-03/hyper-schema#", - "id" : "http://json-schema.org/draft-03/hyper-schema#", - - "properties" : { - "links" : { - "selfReferenceVariable" : "@" - }, - - "root" : { - "deprecated" : true - }, - - "contentEncoding" : { - "deprecated" : false //moved from core to hyper - }, - - "alternate" : { - "deprecated" : true - } - } - }); - - LINKS_03_JSON = JSV.inherits(LINKS_02_JSON, { - "$schema" : "http://json-schema.org/draft-03/hyper-schema#", - "id" : "http://json-schema.org/draft-03/links#", - - "properties" : { - "href" : { - "required" : true, - "format" : "link-description-object-template" - }, - - "rel" : { - "required" : true - }, - - "properties" : { - "deprecated" : true - }, - - "schema" : {"$ref" : "http://json-schema.org/draft-03/hyper-schema#"} - } - }); - - ENVIRONMENT.setOption("defaultSchemaURI", "http://json-schema.org/draft-03/schema#"); //update later - - SCHEMA_03 = ENVIRONMENT.createSchema(SCHEMA_03_JSON, true, "http://json-schema.org/draft-03/schema#"); - HYPERSCHEMA_03 = ENVIRONMENT.createSchema(JSV.inherits(SCHEMA_03, ENVIRONMENT.createSchema(HYPERSCHEMA_03_JSON, true, "http://json-schema.org/draft-03/hyper-schema#"), true), true, "http://json-schema.org/draft-03/hyper-schema#"); - - ENVIRONMENT.setOption("defaultSchemaURI", "http://json-schema.org/draft-03/hyper-schema#"); - - LINKS_03 = ENVIRONMENT.createSchema(LINKS_03_JSON, true, "http://json-schema.org/draft-03/links#"); - - ENVIRONMENT.setOption("latestJSONSchemaSchemaURI", "http://json-schema.org/draft-03/schema#"); - ENVIRONMENT.setOption("latestJSONSchemaHyperSchemaURI", "http://json-schema.org/draft-03/hyper-schema#"); - ENVIRONMENT.setOption("latestJSONSchemaLinksURI", "http://json-schema.org/draft-03/links#"); - - // - //Latest JSON Schema - // - - //Hack, but WAY faster than instantiating a new schema - ENVIRONMENT._schemas["http://json-schema.org/schema#"] = SCHEMA_03; - ENVIRONMENT._schemas["http://json-schema.org/hyper-schema#"] = HYPERSCHEMA_03; - ENVIRONMENT._schemas["http://json-schema.org/links#"] = LINKS_03; - - // - //register environment - // - - JSV.registerEnvironment("json-schema-draft-03", ENVIRONMENT); - if (!JSV.getDefaultEnvironmentID() || JSV.getDefaultEnvironmentID() === "json-schema-draft-01" || JSV.getDefaultEnvironmentID() === "json-schema-draft-02") { - JSV.setDefaultEnvironmentID("json-schema-draft-03"); - } - -}()); +/** + * json-schema-draft-03 Environment + * + * @fileOverview Implementation of the third revision of the JSON Schema specification draft. + * @author Gary Court + * @version 1.5.1 + * @see http://github.com/garycourt/JSV + */ + +/* + * Copyright 2010 Gary Court. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY GARY COURT ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GARY COURT OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of Gary Court or the JSON Schema specification. + */ + +/*jslint white: true, sub: true, onevar: true, undef: true, eqeqeq: true, newcap: true, immed: true, indent: 4 */ +/*global require */ + +(function () { + var O = {}, + JSV = require('./jsv').JSV, + TYPE_VALIDATORS, + ENVIRONMENT, + SCHEMA_00_JSON, + HYPERSCHEMA_00_JSON, + LINKS_00_JSON, + SCHEMA_00, + HYPERSCHEMA_00, + LINKS_00, + SCHEMA_01_JSON, + HYPERSCHEMA_01_JSON, + LINKS_01_JSON, + SCHEMA_01, + HYPERSCHEMA_01, + LINKS_01, + SCHEMA_02_JSON, + HYPERSCHEMA_02_JSON, + LINKS_02_JSON, + SCHEMA_02, + HYPERSCHEMA_02, + LINKS_02, + SCHEMA_03_JSON, + HYPERSCHEMA_03_JSON, + LINKS_03_JSON, + SCHEMA_03, + HYPERSCHEMA_03, + LINKS_03; + + TYPE_VALIDATORS = { + "string" : function (instance, report) { + return instance.getType() === "string"; + }, + + "number" : function (instance, report) { + return instance.getType() === "number"; + }, + + "integer" : function (instance, report) { + return instance.getType() === "number" && instance.getValue() % 1 === 0; + }, + + "boolean" : function (instance, report) { + return instance.getType() === "boolean"; + }, + + "object" : function (instance, report) { + return instance.getType() === "object"; + }, + + "array" : function (instance, report) { + return instance.getType() === "array"; + }, + + "null" : function (instance, report) { + return instance.getType() === "null"; + }, + + "any" : function (instance, report) { + return true; + } + }; + + ENVIRONMENT = new JSV.Environment(); + ENVIRONMENT.setOption("validateReferences", true); + ENVIRONMENT.setOption("enforceReferences", false); + ENVIRONMENT.setOption("strict", false); + + // + // draft-00 + // + + SCHEMA_00_JSON = { + "$schema" : "http://json-schema.org/draft-00/hyper-schema#", + "id" : "http://json-schema.org/draft-00/schema#", + "type" : "object", + + "properties" : { + "type" : { + "type" : ["string", "array"], + "items" : { + "type" : ["string", {"$ref" : "#"}] + }, + "optional" : true, + "uniqueItems" : true, + "default" : "any", + + "parser" : function (instance, self) { + var parser; + + if (instance.getType() === "string") { + return instance.getValue(); + } else if (instance.getType() === "object") { + return instance.getEnvironment().createSchema( + instance, + self.getEnvironment().findSchema(self.resolveURI("#")) + ); + } else if (instance.getType() === "array") { + parser = self.getValueOfProperty("parser"); + return JSV.mapArray(instance.getProperties(), function (prop) { + return parser(prop, self); + }); + } + //else + return "any"; + }, + + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var requiredTypes = JSV.toArray(schema.getAttribute("type")), + x, xl, type, subreport, typeValidators; + + //for instances that are required to be a certain type + if (instance.getType() !== "undefined" && requiredTypes && requiredTypes.length) { + typeValidators = self.getValueOfProperty("typeValidators") || {}; + + //ensure that type matches for at least one of the required types + for (x = 0, xl = requiredTypes.length; x < xl; ++x) { + type = requiredTypes[x]; + if (JSV.isJSONSchema(type)) { + subreport = JSV.createObject(report); + subreport.errors = []; + subreport.validated = JSV.clone(report.validated); + if (type.validate(instance, subreport, parent, parentSchema, name).errors.length === 0) { + return true; //instance matches this schema + } + } else { + instance.applyTypeCoercionIfSet(type); + + if (typeValidators[type] !== O[type] && typeof typeValidators[type] === "function") { + if (typeValidators[type](instance, report)) { + return true; //type is valid + } + } else { + return true; //unknown types are assumed valid + } + } + } + + //if we get to this point, type is invalid + report.addError(instance, schema, "type", "Instance is not a required type", requiredTypes); + return false; + } + //else, anything is allowed if no type is specified + return true; + }, + + "typeValidators" : TYPE_VALIDATORS + }, + + "properties" : { + "type" : "object", + "additionalProperties" : {"$ref" : "#"}, + "optional" : true, + "default" : {}, + + "parser" : function (instance, self, arg) { + var env = instance.getEnvironment(), + selfEnv = self.getEnvironment(); + if (instance.getType() === "object") { + if (arg) { + return env.createSchema(instance.getProperty(arg), selfEnv.findSchema(self.resolveURI("#"))); + } else { + return JSV.mapObject(instance.getProperties(), function (instance) { + return env.createSchema(instance, selfEnv.findSchema(self.resolveURI("#"))); + }); + } + } + //else + return {}; + }, + + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var propertySchemas, key; + //this attribute is for object type instances only + if (instance.getType() === "object") { + //for each property defined in the schema + propertySchemas = schema.getAttribute("properties"); + for (key in propertySchemas) { + if (propertySchemas[key] !== O[key] && propertySchemas[key]) { + //ensure that instance property is valid + propertySchemas[key].validate(instance.getProperty(key), report, instance, schema, key); + } + } + } + } + }, + + "items" : { + "type" : [{"$ref" : "#"}, "array"], + "items" : {"$ref" : "#"}, + "optional" : true, + "default" : {}, + + "parser" : function (instance, self) { + if (instance.getType() === "object") { + return instance.getEnvironment().createSchema(instance, self.getEnvironment().findSchema(self.resolveURI("#"))); + } else if (instance.getType() === "array") { + return JSV.mapArray(instance.getProperties(), function (instance) { + return instance.getEnvironment().createSchema(instance, self.getEnvironment().findSchema(self.resolveURI("#"))); + }); + } + //else + return instance.getEnvironment().createEmptySchema(); + }, + + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var properties, items, x, xl, itemSchema, additionalProperties; + + if (instance.getType() === "array") { + properties = instance.getProperties(); + items = schema.getAttribute("items"); + additionalProperties = schema.getAttribute("additionalProperties"); + + if (JSV.typeOf(items) === "array") { + for (x = 0, xl = properties.length; x < xl; ++x) { + itemSchema = items[x] || additionalProperties; + if (itemSchema !== false) { + itemSchema.validate(properties[x], report, instance, schema, x); + } else { + report.addError(instance, schema, "additionalProperties", "Additional items are not allowed", itemSchema); + } + } + } else { + itemSchema = items || additionalProperties; + for (x = 0, xl = properties.length; x < xl; ++x) { + itemSchema.validate(properties[x], report, instance, schema, x); + } + } + } + } + }, + + "optional" : { + "type" : "boolean", + "optional" : true, + "default" : false, + + "parser" : function (instance, self) { + return !!instance.getValue(); + }, + + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + if (instance.getType() === "undefined" && !schema.getAttribute("optional")) { + report.addError(instance, schema, "optional", "Property is required", false); + } + }, + + "validationRequired" : true + }, + + "additionalProperties" : { + "type" : [{"$ref" : "#"}, "boolean"], + "optional" : true, + "default" : {}, + + "parser" : function (instance, self) { + if (instance.getType() === "object") { + return instance.getEnvironment().createSchema(instance, self.getEnvironment().findSchema(self.resolveURI("#"))); + } else if (instance.getType() === "boolean" && instance.getValue() === false) { + return false; + } + //else + return instance.getEnvironment().createEmptySchema(); + }, + + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var additionalProperties, propertySchemas, properties, key; + //we only need to check against object types as arrays do their own checking on this property + if (instance.getType() === "object") { + additionalProperties = schema.getAttribute("additionalProperties"); + propertySchemas = schema.getAttribute("properties") || {}; + properties = instance.getProperties(); + for (key in properties) { + if (properties[key] !== O[key] && properties[key] && propertySchemas[key] === O[key]) { + if (JSV.isJSONSchema(additionalProperties)) { + additionalProperties.validate(properties[key], report, instance, schema, key); + } else if (additionalProperties === false) { + report.addError(instance, schema, "additionalProperties", "Additional properties are not allowed", additionalProperties); + } + } + } + } + } + }, + + "requires" : { + "type" : ["string", {"$ref" : "#"}], + "optional" : true, + + "parser" : function (instance, self) { + if (instance.getType() === "string") { + return instance.getValue(); + } else if (instance.getType() === "object") { + return instance.getEnvironment().createSchema(instance, self.getEnvironment().findSchema(self.resolveURI("#"))); + } + }, + + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var requires; + if (instance.getType() !== "undefined" && parent && parent.getType() !== "undefined") { + requires = schema.getAttribute("requires"); + if (typeof requires === "string") { + if (parent.getProperty(requires).getType() === "undefined") { + report.addError(instance, schema, "requires", 'Property requires sibling property "' + requires + '"', requires); + } + } else if (JSV.isJSONSchema(requires)) { + requires.validate(parent, report); //WATCH: A "requires" schema does not support the "requires" attribute + } + } + } + }, + + "minimum" : { + "type" : "number", + "optional" : true, + + "parser" : function (instance, self) { + if (instance.getType() === "number") { + return instance.getValue(); + } + }, + + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var minimum, minimumCanEqual; + if (instance.getType() === "number") { + minimum = schema.getAttribute("minimum"); + minimumCanEqual = schema.getAttribute("minimumCanEqual"); + if (typeof minimum === "number" && (instance.getValue() < minimum || (minimumCanEqual === false && instance.getValue() === minimum))) { + report.addError(instance, schema, "minimum", "Number is less than the required minimum value", minimum); + } + } + } + }, + + "maximum" : { + "type" : "number", + "optional" : true, + + "parser" : function (instance, self) { + if (instance.getType() === "number") { + return instance.getValue(); + } + }, + + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var maximum, maximumCanEqual; + if (instance.getType() === "number") { + maximum = schema.getAttribute("maximum"); + maximumCanEqual = schema.getAttribute("maximumCanEqual"); + if (typeof maximum === "number" && (instance.getValue() > maximum || (maximumCanEqual === false && instance.getValue() === maximum))) { + report.addError(instance, schema, "maximum", "Number is greater than the required maximum value", maximum); + } + } + } + }, + + "minimumCanEqual" : { + "type" : "boolean", + "optional" : true, + "requires" : "minimum", + "default" : true, + + "parser" : function (instance, self) { + if (instance.getType() === "boolean") { + return instance.getValue(); + } + //else + return true; + } + }, + + "maximumCanEqual" : { + "type" : "boolean", + "optional" : true, + "requires" : "maximum", + "default" : true, + + "parser" : function (instance, self) { + if (instance.getType() === "boolean") { + return instance.getValue(); + } + //else + return true; + } + }, + + "minItems" : { + "type" : "integer", + "optional" : true, + "minimum" : 0, + "default" : 0, + + "parser" : function (instance, self) { + if (instance.getType() === "number") { + return instance.getValue(); + } + //else + return 0; + }, + + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var minItems; + if (instance.getType() === "array") { + minItems = schema.getAttribute("minItems"); + if (typeof minItems === "number" && instance.getProperties().length < minItems) { + report.addError(instance, schema, "minItems", "The number of items is less than the required minimum", minItems); + } + } + } + }, + + "maxItems" : { + "type" : "integer", + "optional" : true, + "minimum" : 0, + + "parser" : function (instance, self) { + if (instance.getType() === "number") { + return instance.getValue(); + } + }, + + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var maxItems; + if (instance.getType() === "array") { + maxItems = schema.getAttribute("maxItems"); + if (typeof maxItems === "number" && instance.getProperties().length > maxItems) { + report.addError(instance, schema, "maxItems", "The number of items is greater than the required maximum", maxItems); + } + } + } + }, + + "pattern" : { + "type" : "string", + "optional" : true, + "format" : "regex", + + "parser" : function (instance, self) { + if (instance.getType() === "string") { + return instance.getValue(); + } + }, + + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var pattern; + try { + pattern = new RegExp(schema.getAttribute("pattern")); + if (instance.getType() === "string" && pattern && !pattern.test(instance.getValue())) { + report.addError(instance, schema, "pattern", "String does not match pattern", pattern.toString()); + } + } catch (e) { + report.addError(schema, self, "pattern", "Invalid pattern", schema.getValueOfProperty("pattern")); + } + } + }, + + "minLength" : { + "type" : "integer", + "optional" : true, + "minimum" : 0, + "default" : 0, + + "parser" : function (instance, self) { + if (instance.getType() === "number") { + return instance.getValue(); + } + //else + return 0; + }, + + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var minLength; + if (instance.getType() === "string") { + minLength = schema.getAttribute("minLength"); + if (typeof minLength === "number" && instance.getValue().length < minLength) { + report.addError(instance, schema, "minLength", "String is less than the required minimum length", minLength); + } + } + } + }, + + "maxLength" : { + "type" : "integer", + "optional" : true, + + "parser" : function (instance, self) { + if (instance.getType() === "number") { + return instance.getValue(); + } + }, + + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var maxLength; + if (instance.getType() === "string") { + maxLength = schema.getAttribute("maxLength"); + if (typeof maxLength === "number" && instance.getValue().length > maxLength) { + report.addError(instance, schema, "maxLength", "String is greater than the required maximum length", maxLength); + } + } + } + }, + + "enum" : { + "type" : "array", + "optional" : true, + "minItems" : 1, + "uniqueItems" : true, + + "parser" : function (instance, self) { + if (instance.getType() === "array") { + return instance.getValue(); + } + }, + + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var enums, x, xl; + if (instance.getType() !== "undefined") { + enums = schema.getAttribute("enum"); + if (enums) { + for (x = 0, xl = enums.length; x < xl; ++x) { + if (instance.equals(enums[x])) { + return true; + } + } + report.addError(instance, schema, "enum", "Instance is not one of the possible values", enums); + } + } + } + }, + + "title" : { + "type" : "string", + "optional" : true + }, + + "description" : { + "type" : "string", + "optional" : true + }, + + "format" : { + "type" : "string", + "optional" : true, + + "parser" : function (instance, self) { + if (instance.getType() === "string") { + return instance.getValue(); + } + }, + + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var format, formatValidators; + if (instance.getType() === "string") { + format = schema.getAttribute("format"); + formatValidators = self.getValueOfProperty("formatValidators"); + if (typeof format === "string" && formatValidators[format] !== O[format] && typeof formatValidators[format] === "function" && !formatValidators[format].call(this, instance, report)) { + report.addError(instance, schema, "format", "String is not in the required format", format); + } + } + }, + + "formatValidators" : {} + }, + + "contentEncoding" : { + "type" : "string", + "optional" : true + }, + + "default" : { + "type" : "any", + "optional" : true + }, + + "maxDecimal" : { + "type" : "integer", + "optional" : true, + "minimum" : 0, + + "parser" : function (instance, self) { + if (instance.getType() === "number") { + return instance.getValue(); + } + }, + + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var maxDecimal, decimals; + if (instance.getType() === "number") { + maxDecimal = schema.getAttribute("maxDecimal"); + if (typeof maxDecimal === "number") { + decimals = instance.getValue().toString(10).split('.')[1]; + if (decimals && decimals.length > maxDecimal) { + report.addError(instance, schema, "maxDecimal", "The number of decimal places is greater than the allowed maximum", maxDecimal); + } + } + } + } + }, + + "disallow" : { + "type" : ["string", "array"], + "items" : {"type" : "string"}, + "optional" : true, + "uniqueItems" : true, + + "parser" : function (instance, self) { + if (instance.getType() === "string" || instance.getType() === "array") { + return instance.getValue(); + } + }, + + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var disallowedTypes = JSV.toArray(schema.getAttribute("disallow")), + x, xl, key, typeValidators, subreport; + + //for instances that are required to be a certain type + if (instance.getType() !== "undefined" && disallowedTypes && disallowedTypes.length) { + typeValidators = self.getValueOfProperty("typeValidators") || {}; + + //ensure that type matches for at least one of the required types + for (x = 0, xl = disallowedTypes.length; x < xl; ++x) { + key = disallowedTypes[x]; + if (JSV.isJSONSchema(key)) { //this is supported draft-03 and on + subreport = JSV.createObject(report); + subreport.errors = []; + subreport.validated = JSV.clone(report.validated); + if (key.validate(instance, subreport, parent, parentSchema, name).errors.length === 0) { + //instance matches this schema + report.addError(instance, schema, "disallow", "Instance is a disallowed type", disallowedTypes); + return false; + } + } else if (typeValidators[key] !== O[key] && typeof typeValidators[key] === "function") { + if (typeValidators[key](instance, report)) { + report.addError(instance, schema, "disallow", "Instance is a disallowed type", disallowedTypes); + return false; + } + } + /* + else { + report.addError(instance, schema, "disallow", "Instance may be a disallowed type", disallowedTypes); + return false; + } + */ + } + + //if we get to this point, type is valid + return true; + } + //else, everything is allowed if no disallowed types are specified + return true; + }, + + "typeValidators" : TYPE_VALIDATORS + }, + + "extends" : { + "type" : [{"$ref" : "#"}, "array"], + "items" : {"$ref" : "#"}, + "optional" : true, + "default" : {}, + + "parser" : function (instance, self) { + if (instance.getType() === "object") { + return instance.getEnvironment().createSchema(instance, self.getEnvironment().findSchema(self.resolveURI("#"))); + } else if (instance.getType() === "array") { + return JSV.mapArray(instance.getProperties(), function (instance) { + return instance.getEnvironment().createSchema(instance, self.getEnvironment().findSchema(self.resolveURI("#"))); + }); + } + }, + + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var extensions = schema.getAttribute("extends"), x, xl; + if (extensions) { + if (JSV.isJSONSchema(extensions)) { + extensions.validate(instance, report, parent, parentSchema, name); + } else if (JSV.typeOf(extensions) === "array") { + for (x = 0, xl = extensions.length; x < xl; ++x) { + extensions[x].validate(instance, report, parent, parentSchema, name); + } + } + } + } + } + }, + + "optional" : true, + "default" : {}, + "fragmentResolution" : "dot-delimited", + + "parser" : function (instance, self) { + if (instance.getType() === "object") { + return instance.getEnvironment().createSchema(instance, self); + } + }, + + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var propNames = schema.getPropertyNames(), + x, xl, + attributeSchemas = self.getAttribute("properties"), + strict = instance.getEnvironment().getOption("strict"), + validator; + + for (x in attributeSchemas) { + if (attributeSchemas[x] !== O[x]) { + if (attributeSchemas[x].getValueOfProperty("validationRequired")) { + JSV.pushUnique(propNames, x); + } + if (strict && attributeSchemas[x].getValueOfProperty("deprecated")) { + JSV.popFirst(propNames, x); + } + } + } + + for (x = 0, xl = propNames.length; x < xl; ++x) { + if (attributeSchemas[propNames[x]] !== O[propNames[x]]) { + validator = attributeSchemas[propNames[x]].getValueOfProperty("validator"); + if (typeof validator === "function") { + validator(instance, schema, attributeSchemas[propNames[x]], report, parent, parentSchema, name); + } + } + } + } + }; + + HYPERSCHEMA_00_JSON = { + "$schema" : "http://json-schema.org/draft-00/hyper-schema#", + "id" : "http://json-schema.org/draft-00/hyper-schema#", + + "properties" : { + "links" : { + "type" : "array", + "items" : {"$ref" : "links#"}, + "optional" : true, + + "parser" : function (instance, self, arg) { + var links, + linkSchemaURI = self.getValueOfProperty("items")["$ref"], + linkSchema = self.getEnvironment().findSchema(linkSchemaURI), + linkParser = linkSchema && linkSchema.getValueOfProperty("parser"), + selfReferenceVariable; + arg = JSV.toArray(arg); + + if (typeof linkParser === "function") { + links = JSV.mapArray(instance.getProperties(), function (link) { + return linkParser(link, linkSchema); + }); + } else { + links = JSV.toArray(instance.getValue()); + } + + if (arg[0]) { + links = JSV.filterArray(links, function (link) { + return link["rel"] === arg[0]; + }); + } + + if (arg[1]) { + selfReferenceVariable = self.getValueOfProperty("selfReferenceVariable"); + links = JSV.mapArray(links, function (link) { + var instance = arg[1], + href = link["href"]; + href = href.replace(/\{(.+)\}/g, function (str, p1, offset, s) { + var value; + if (p1 === selfReferenceVariable) { + value = instance.getValue(); + } else { + value = instance.getValueOfProperty(p1); + } + return value !== undefined ? String(value) : ""; + }); + return href ? JSV.formatURI(instance.resolveURI(href)) : href; + }); + } + + return links; + }, + + "selfReferenceVariable" : "-this" + }, + + "fragmentResolution" : { + "type" : "string", + "optional" : true, + "default" : "dot-delimited" + }, + + "root" : { + "type" : "boolean", + "optional" : true, + "default" : false + }, + + "readonly" : { + "type" : "boolean", + "optional" : true, + "default" : false + }, + + "pathStart" : { + "type" : "string", + "optional" : true, + "format" : "uri", + + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var pathStart; + if (instance.getType() !== "undefined") { + pathStart = schema.getAttribute("pathStart"); + if (typeof pathStart === "string") { + //TODO: Find out what pathStart is relative to + if (instance.getURI().indexOf(pathStart) !== 0) { + report.addError(instance, schema, "pathStart", "Instance's URI does not start with " + pathStart, pathStart); + } + } + } + } + }, + + "mediaType" : { + "type" : "string", + "optional" : true, + "format" : "media-type" + }, + + "alternate" : { + "type" : "array", + "items" : {"$ref" : "#"}, + "optional" : true + } + }, + + "links" : [ + { + "href" : "{$ref}", + "rel" : "full" + }, + + { + "href" : "{$schema}", + "rel" : "describedby" + }, + + { + "href" : "{id}", + "rel" : "self" + } + ], + + "initializer" : function (instance) { + var link, extension, extended; + + //if there is a link to a different schema, set reference + link = instance._schema.getLink("describedby", instance); + if (link && instance._schema._uri !== link) { + instance.setReference("describedby", link); + } + + //if instance has a URI link to itself, update it's own URI + link = instance._schema.getLink("self", instance); + if (JSV.typeOf(link) === "string") { + instance._uri = JSV.formatURI(link); + } + + //if there is a link to the full representation, set reference + link = instance._schema.getLink("full", instance); + if (link && instance._uri !== link) { + instance.setReference("full", link); + } + + //extend schema + extension = instance.getAttribute("extends"); + if (JSV.isJSONSchema(extension)) { + extended = JSV.inherits(extension, instance, true); + instance = instance._env.createSchema(extended, instance._schema, instance._uri); + } + + return instance; + } + + //not needed as JSV.inherits does the job for us + //"extends" : {"$ref" : "http://json-schema.org/schema#"} + }; + + LINKS_00_JSON = { + "$schema" : "http://json-schema.org/draft-00/hyper-schema#", + "id" : "http://json-schema.org/draft-00/links#", + "type" : "object", + + "properties" : { + "href" : { + "type" : "string" + }, + + "rel" : { + "type" : "string" + }, + + "method" : { + "type" : "string", + "default" : "GET", + "optional" : true + }, + + "enctype" : { + "type" : "string", + "requires" : "method", + "optional" : true + }, + + "properties" : { + "type" : "object", + "additionalProperties" : {"$ref" : "hyper-schema#"}, + "optional" : true, + + "parser" : function (instance, self, arg) { + var env = instance.getEnvironment(), + selfEnv = self.getEnvironment(), + additionalPropertiesSchemaURI = self.getValueOfProperty("additionalProperties")["$ref"]; + if (instance.getType() === "object") { + if (arg) { + return env.createSchema(instance.getProperty(arg), selfEnv.findSchema(self.resolveURI(additionalPropertiesSchemaURI))); + } else { + return JSV.mapObject(instance.getProperties(), function (instance) { + return env.createSchema(instance, selfEnv.findSchema(self.resolveURI(additionalPropertiesSchemaURI))); + }); + } + } + } + } + }, + + "parser" : function (instance, self) { + var selfProperties = self.getProperty("properties"); + if (instance.getType() === "object") { + return JSV.mapObject(instance.getProperties(), function (property, key) { + var propertySchema = selfProperties.getProperty(key), + parser = propertySchema && propertySchema.getValueOfProperty("parser"); + if (typeof parser === "function") { + return parser(property, propertySchema); + } + //else + return property.getValue(); + }); + } + return instance.getValue(); + } + }; + + ENVIRONMENT.setOption("defaultFragmentDelimiter", "."); + ENVIRONMENT.setOption("defaultSchemaURI", "http://json-schema.org/draft-00/schema#"); //updated later + + SCHEMA_00 = ENVIRONMENT.createSchema(SCHEMA_00_JSON, true, "http://json-schema.org/draft-00/schema#"); + HYPERSCHEMA_00 = ENVIRONMENT.createSchema(JSV.inherits(SCHEMA_00, ENVIRONMENT.createSchema(HYPERSCHEMA_00_JSON, true, "http://json-schema.org/draft-00/hyper-schema#"), true), true, "http://json-schema.org/draft-00/hyper-schema#"); + + ENVIRONMENT.setOption("defaultSchemaURI", "http://json-schema.org/draft-00/hyper-schema#"); + + LINKS_00 = ENVIRONMENT.createSchema(LINKS_00_JSON, HYPERSCHEMA_00, "http://json-schema.org/draft-00/links#"); + + // + // draft-01 + // + + SCHEMA_01_JSON = JSV.inherits(SCHEMA_00_JSON, { + "$schema" : "http://json-schema.org/draft-01/hyper-schema#", + "id" : "http://json-schema.org/draft-01/schema#" + }); + + HYPERSCHEMA_01_JSON = JSV.inherits(HYPERSCHEMA_00_JSON, { + "$schema" : "http://json-schema.org/draft-01/hyper-schema#", + "id" : "http://json-schema.org/draft-01/hyper-schema#" + }); + + LINKS_01_JSON = JSV.inherits(LINKS_00_JSON, { + "$schema" : "http://json-schema.org/draft-01/hyper-schema#", + "id" : "http://json-schema.org/draft-01/links#" + }); + + ENVIRONMENT.setOption("defaultSchemaURI", "http://json-schema.org/draft-01/schema#"); //update later + + SCHEMA_01 = ENVIRONMENT.createSchema(SCHEMA_01_JSON, true, "http://json-schema.org/draft-01/schema#"); + HYPERSCHEMA_01 = ENVIRONMENT.createSchema(JSV.inherits(SCHEMA_01, ENVIRONMENT.createSchema(HYPERSCHEMA_01_JSON, true, "http://json-schema.org/draft-01/hyper-schema#"), true), true, "http://json-schema.org/draft-01/hyper-schema#"); + + ENVIRONMENT.setOption("defaultSchemaURI", "http://json-schema.org/draft-01/hyper-schema#"); + + LINKS_01 = ENVIRONMENT.createSchema(LINKS_01_JSON, HYPERSCHEMA_01, "http://json-schema.org/draft-01/links#"); + + // + // draft-02 + // + + SCHEMA_02_JSON = JSV.inherits(SCHEMA_01_JSON, { + "$schema" : "http://json-schema.org/draft-02/hyper-schema#", + "id" : "http://json-schema.org/draft-02/schema#", + + "properties" : { + "uniqueItems" : { + "type" : "boolean", + "optional" : true, + "default" : false, + + "parser" : function (instance, self) { + return !!instance.getValue(); + }, + + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var value, x, xl, y, yl; + if (instance.getType() === "array" && schema.getAttribute("uniqueItems")) { + value = instance.getProperties(); + for (x = 0, xl = value.length - 1; x < xl; ++x) { + for (y = x + 1, yl = value.length; y < yl; ++y) { + if (value[x].equals(value[y])) { + report.addError(instance, schema, "uniqueItems", "Array can only contain unique items", { x : x, y : y }); + } + } + } + } + } + }, + + "maxDecimal" : { + "deprecated" : true + }, + + "divisibleBy" : { + "type" : "number", + "minimum" : 0, + "minimumCanEqual" : false, + "optional" : true, + + "parser" : function (instance, self) { + if (instance.getType() === "number") { + return instance.getValue(); + } + }, + + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var divisor, value, digits; + if (instance.getType() === "number") { + divisor = schema.getAttribute("divisibleBy"); + if (divisor === 0) { + report.addError(instance, schema, "divisibleBy", "Nothing is divisible by 0", divisor); + } else if (divisor !== 1) { + value = instance.getValue(); + digits = Math.max((value.toString().split(".")[1] || " ").length, (divisor.toString().split(".")[1] || " ").length); + digits = parseFloat(((value / divisor) % 1).toFixed(digits)); //cut out floating point errors + if (0 < digits && digits < 1) { + report.addError(instance, schema, "divisibleBy", "Number is not divisible by " + divisor, divisor); + } + } + } + } + } + }, + + "fragmentResolution" : "slash-delimited" + }); + + HYPERSCHEMA_02_JSON = JSV.inherits(HYPERSCHEMA_01_JSON, { + "id" : "http://json-schema.org/draft-02/hyper-schema#", + + "properties" : { + "fragmentResolution" : { + "default" : "slash-delimited" + } + } + }); + + LINKS_02_JSON = JSV.inherits(LINKS_01_JSON, { + "$schema" : "http://json-schema.org/draft-02/hyper-schema#", + "id" : "http://json-schema.org/draft-02/links#", + + "properties" : { + "targetSchema" : { + "$ref" : "hyper-schema#", + + //need this here because parsers are run before links are resolved + "parser" : HYPERSCHEMA_01.getAttribute("parser") + } + } + }); + + ENVIRONMENT.setOption("defaultFragmentDelimiter", "/"); + ENVIRONMENT.setOption("defaultSchemaURI", "http://json-schema.org/draft-02/schema#"); //update later + + SCHEMA_02 = ENVIRONMENT.createSchema(SCHEMA_02_JSON, true, "http://json-schema.org/draft-02/schema#"); + HYPERSCHEMA_02 = ENVIRONMENT.createSchema(JSV.inherits(SCHEMA_02, ENVIRONMENT.createSchema(HYPERSCHEMA_02_JSON, true, "http://json-schema.org/draft-02/hyper-schema#"), true), true, "http://json-schema.org/draft-02/hyper-schema#"); + + ENVIRONMENT.setOption("defaultSchemaURI", "http://json-schema.org/draft-02/hyper-schema#"); + + LINKS_02 = ENVIRONMENT.createSchema(LINKS_02_JSON, HYPERSCHEMA_02, "http://json-schema.org/draft-02/links#"); + + // + // draft-03 + // + + function getMatchedPatternProperties(instance, schema, report, self) { + var matchedProperties = {}, patternProperties, pattern, regexp, properties, key; + + if (instance.getType() === "object") { + patternProperties = schema.getAttribute("patternProperties"); + properties = instance.getProperties(); + for (pattern in patternProperties) { + if (patternProperties[pattern] !== O[pattern]) { + regexp = null; + try { + regexp = new RegExp(pattern); + } catch (e) { + if (report) { + report.addError(schema, self, "patternProperties", "Invalid pattern", pattern); + } + } + + if (regexp) { + for (key in properties) { + if (properties[key] !== O[key] && regexp.test(key)) { + matchedProperties[key] = matchedProperties[key] ? JSV.pushUnique(matchedProperties[key], patternProperties[pattern]) : [ patternProperties[pattern] ]; + } + } + } + } + } + } + + return matchedProperties; + } + + SCHEMA_03_JSON = JSV.inherits(SCHEMA_02_JSON, { + "$schema" : "http://json-schema.org/draft-03/schema#", + "id" : "http://json-schema.org/draft-03/schema#", + + "properties" : { + "patternProperties" : { + "type" : "object", + "additionalProperties" : {"$ref" : "#"}, + "default" : {}, + + "parser" : SCHEMA_02.getValueOfProperty("properties")["properties"]["parser"], + + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var matchedProperties, key, x; + if (instance.getType() === "object") { + matchedProperties = getMatchedPatternProperties(instance, schema, report, self); + for (key in matchedProperties) { + if (matchedProperties[key] !== O[key]) { + x = matchedProperties[key].length; + while (x--) { + matchedProperties[key][x].validate(instance.getProperty(key), report, instance, schema, key); + } + } + } + } + } + }, + + "additionalProperties" : { + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var additionalProperties, propertySchemas, properties, matchedProperties, key; + if (instance.getType() === "object") { + additionalProperties = schema.getAttribute("additionalProperties"); + propertySchemas = schema.getAttribute("properties") || {}; + properties = instance.getProperties(); + matchedProperties = getMatchedPatternProperties(instance, schema); + for (key in properties) { + if (properties[key] !== O[key] && properties[key] && propertySchemas[key] === O[key] && matchedProperties[key] === O[key]) { + if (JSV.isJSONSchema(additionalProperties)) { + additionalProperties.validate(properties[key], report, instance, schema, key); + } else if (additionalProperties === false) { + report.addError(instance, schema, "additionalProperties", "Additional properties are not allowed", additionalProperties); + } + } + } + } + } + }, + + "items" : { + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var properties, items, x, xl, itemSchema, additionalItems; + + if (instance.getType() === "array") { + properties = instance.getProperties(); + items = schema.getAttribute("items"); + additionalItems = schema.getAttribute("additionalItems"); + + if (JSV.typeOf(items) === "array") { + for (x = 0, xl = properties.length; x < xl; ++x) { + itemSchema = items[x] || additionalItems; + if (itemSchema !== false) { + itemSchema.validate(properties[x], report, instance, schema, x); + } else { + report.addError(instance, schema, "additionalItems", "Additional items are not allowed", itemSchema); + } + } + } else { + itemSchema = items || additionalItems; + for (x = 0, xl = properties.length; x < xl; ++x) { + itemSchema.validate(properties[x], report, instance, schema, x); + } + } + } + } + }, + + "additionalItems" : { + "type" : [{"$ref" : "#"}, "boolean"], + "default" : {}, + + "parser" : SCHEMA_02.getValueOfProperty("properties")["additionalProperties"]["parser"], + + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var additionalItems, properties, x, xl; + //only validate if the "items" attribute is undefined + if (instance.getType() === "array" && schema.getProperty("items").getType() === "undefined") { + additionalItems = schema.getAttribute("additionalItems"); + properties = instance.getProperties(); + + if (additionalItems !== false) { + for (x = 0, xl = properties.length; x < xl; ++x) { + additionalItems.validate(properties[x], report, instance, schema, x); + } + } else if (properties.length) { + report.addError(instance, schema, "additionalItems", "Additional items are not allowed", additionalItems); + } + } + } + }, + + "optional" : { + "validationRequired" : false, + "deprecated" : true + }, + + "required" : { + "type" : "boolean", + "default" : false, + + "parser" : function (instance, self) { + return !!instance.getValue(); + }, + + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + if (instance.getType() === "undefined" && schema.getAttribute("required")) { + report.addError(instance, schema, "required", "Property is required", true); + } + } + }, + + "requires" : { + "deprecated" : true + }, + + "dependencies" : { + "type" : "object", + "additionalProperties" : { + "type" : ["string", "array", {"$ref" : "#"}], + "items" : { + "type" : "string" + } + }, + "default" : {}, + + "parser" : function (instance, self, arg) { + function parseProperty(property) { + var type = property.getType(); + if (type === "string" || type === "array") { + return property.getValue(); + } else if (type === "object") { + return property.getEnvironment().createSchema(property, self.getEnvironment().findSchema(self.resolveURI("#"))); + } + } + + if (instance.getType() === "object") { + if (arg) { + return parseProperty(instance.getProperty(arg)); + } else { + return JSV.mapObject(instance.getProperties(), parseProperty); + } + } + //else + return {}; + }, + + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var dependencies, key, dependency, type, x, xl; + if (instance.getType() === "object") { + dependencies = schema.getAttribute("dependencies"); + for (key in dependencies) { + if (dependencies[key] !== O[key] && instance.getProperty(key).getType() !== "undefined") { + dependency = dependencies[key]; + type = JSV.typeOf(dependency); + if (type === "string") { + if (instance.getProperty(dependency).getType() === "undefined") { + report.addError(instance, schema, "dependencies", 'Property "' + key + '" requires sibling property "' + dependency + '"', dependencies); + } + } else if (type === "array") { + for (x = 0, xl = dependency.length; x < xl; ++x) { + if (instance.getProperty(dependency[x]).getType() === "undefined") { + report.addError(instance, schema, "dependencies", 'Property "' + key + '" requires sibling property "' + dependency[x] + '"', dependencies); + } + } + } else if (JSV.isJSONSchema(dependency)) { + dependency.validate(instance, report); + } + } + } + } + } + }, + + "minimumCanEqual" : { + "deprecated" : true + }, + + "maximumCanEqual" : { + "deprecated" : true + }, + + "exclusiveMinimum" : { + "type" : "boolean", + "default" : false, + + "parser" : function (instance, self) { + return !!instance.getValue(); + } + }, + + "exclusiveMaximum" : { + "type" : "boolean", + "default" : false, + + "parser" : function (instance, self) { + return !!instance.getValue(); + } + }, + + "minimum" : { + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var minimum, exclusiveMinimum; + if (instance.getType() === "number") { + minimum = schema.getAttribute("minimum"); + exclusiveMinimum = schema.getAttribute("exclusiveMinimum") || (!instance.getEnvironment().getOption("strict") && !schema.getAttribute("minimumCanEqual")); + if (typeof minimum === "number" && (instance.getValue() < minimum || (exclusiveMinimum === true && instance.getValue() === minimum))) { + report.addError(instance, schema, "minimum", "Number is less than the required minimum value", minimum); + } + } + } + }, + + "maximum" : { + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { + var maximum, exclusiveMaximum; + if (instance.getType() === "number") { + maximum = schema.getAttribute("maximum"); + exclusiveMaximum = schema.getAttribute("exclusiveMaximum") || (!instance.getEnvironment().getOption("strict") && !schema.getAttribute("maximumCanEqual")); + if (typeof maximum === "number" && (instance.getValue() > maximum || (exclusiveMaximum === true && instance.getValue() === maximum))) { + report.addError(instance, schema, "maximum", "Number is greater than the required maximum value", maximum); + } + } + } + }, + + "contentEncoding" : { + "deprecated" : true + }, + + "divisibleBy" : { + "exclusiveMinimum" : true + }, + + "disallow" : { + "items" : { + "type" : ["string", {"$ref" : "#"}] + }, + + "parser" : SCHEMA_02_JSON["properties"]["type"]["parser"] + }, + + "id" : { + "type" : "string", + "format" : "uri" + }, + + "$ref" : { + "type" : "string", + "format" : "uri" + }, + + "$schema" : { + "type" : "string", + "format" : "uri" + } + }, + + "dependencies" : { + "exclusiveMinimum" : "minimum", + "exclusiveMaximum" : "maximum" + }, + + "initializer" : function (instance) { + var link, extension, extended, + schemaLink = instance.getValueOfProperty("$schema"), + refLink = instance.getValueOfProperty("$ref"), + idLink = instance.getValueOfProperty("id"); + + //if there is a link to a different schema, set reference + if (schemaLink) { + link = instance.resolveURI(schemaLink); + instance.setReference("describedby", link); + } + + //if instance has a URI link to itself, update it's own URI + if (idLink) { + link = instance.resolveURI(idLink); + if (JSV.typeOf(link) === "string") { + instance._uri = JSV.formatURI(link); + } + } + + //if there is a link to the full representation, set reference + if (refLink) { + link = instance.resolveURI(refLink); + instance.setReference("full", link); + } + + //extend schema + extension = instance.getAttribute("extends"); + if (JSV.isJSONSchema(extension)) { + extended = JSV.inherits(extension, instance, true); + instance = instance._env.createSchema(extended, instance._schema, instance._uri); + } + + return instance; + } + }); + + HYPERSCHEMA_03_JSON = JSV.inherits(HYPERSCHEMA_02_JSON, { + "$schema" : "http://json-schema.org/draft-03/hyper-schema#", + "id" : "http://json-schema.org/draft-03/hyper-schema#", + + "properties" : { + "links" : { + "selfReferenceVariable" : "@" + }, + + "root" : { + "deprecated" : true + }, + + "contentEncoding" : { + "deprecated" : false //moved from core to hyper + }, + + "alternate" : { + "deprecated" : true + } + } + }); + + LINKS_03_JSON = JSV.inherits(LINKS_02_JSON, { + "$schema" : "http://json-schema.org/draft-03/hyper-schema#", + "id" : "http://json-schema.org/draft-03/links#", + + "properties" : { + "href" : { + "required" : true, + "format" : "link-description-object-template" + }, + + "rel" : { + "required" : true + }, + + "properties" : { + "deprecated" : true + }, + + "schema" : {"$ref" : "http://json-schema.org/draft-03/hyper-schema#"} + } + }); + + ENVIRONMENT.setOption("defaultSchemaURI", "http://json-schema.org/draft-03/schema#"); //update later + + SCHEMA_03 = ENVIRONMENT.createSchema(SCHEMA_03_JSON, true, "http://json-schema.org/draft-03/schema#"); + HYPERSCHEMA_03 = ENVIRONMENT.createSchema(JSV.inherits(SCHEMA_03, ENVIRONMENT.createSchema(HYPERSCHEMA_03_JSON, true, "http://json-schema.org/draft-03/hyper-schema#"), true), true, "http://json-schema.org/draft-03/hyper-schema#"); + + ENVIRONMENT.setOption("defaultSchemaURI", "http://json-schema.org/draft-03/hyper-schema#"); + + LINKS_03 = ENVIRONMENT.createSchema(LINKS_03_JSON, true, "http://json-schema.org/draft-03/links#"); + + ENVIRONMENT.setOption("latestJSONSchemaSchemaURI", "http://json-schema.org/draft-03/schema#"); + ENVIRONMENT.setOption("latestJSONSchemaHyperSchemaURI", "http://json-schema.org/draft-03/hyper-schema#"); + ENVIRONMENT.setOption("latestJSONSchemaLinksURI", "http://json-schema.org/draft-03/links#"); + + // + //Latest JSON Schema + // + + //Hack, but WAY faster than instantiating a new schema + ENVIRONMENT._schemas["http://json-schema.org/schema#"] = SCHEMA_03; + ENVIRONMENT._schemas["http://json-schema.org/hyper-schema#"] = HYPERSCHEMA_03; + ENVIRONMENT._schemas["http://json-schema.org/links#"] = LINKS_03; + + // + //register environment + // + + JSV.registerEnvironment("json-schema-draft-03", ENVIRONMENT); + if (!JSV.getDefaultEnvironmentID() || JSV.getDefaultEnvironmentID() === "json-schema-draft-01" || JSV.getDefaultEnvironmentID() === "json-schema-draft-02") { + JSV.setDefaultEnvironmentID("json-schema-draft-03"); + } + +}()); diff --git a/lib/jsv.js b/lib/jsv.js index 2a6f1ed..fef02f8 100644 --- a/lib/jsv.js +++ b/lib/jsv.js @@ -1,1537 +1,1537 @@ -/** - * JSV: JSON Schema Validator - * - * @fileOverview A JavaScript implementation of a extendable, fully compliant JSON Schema validator. - * @author Gary Court - * @version 4.0.2 - * @see http://github.com/garycourt/JSV - */ - -/* - * Copyright 2010 Gary Court. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are - * permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of - * conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list - * of conditions and the following disclaimer in the documentation and/or other materials - * provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY GARY COURT ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND - * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GARY COURT OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * The views and conclusions contained in the software and documentation are those of the - * authors and should not be interpreted as representing official policies, either expressed - * or implied, of Gary Court or the JSON Schema specification. - */ - -/*jslint white: true, sub: true, onevar: true, undef: true, eqeqeq: true, newcap: true, immed: true, indent: 4 */ - -var exports = exports || this, - require = require || function () { - return exports; - }; - -(function () { - - var URI = require("./uri/uri").URI, - O = {}, - I2H = "0123456789abcdef".split(""), - mapArray, filterArray, searchArray, - - JSV; - - // - // Utility functions - // - - function typeOf(o) { - return o === undefined ? "undefined" : (o === null ? "null" : Object.prototype.toString.call(o).split(" ").pop().split("]").shift().toLowerCase()); - } - - /** @inner */ - function F() {} - - function createObject(proto) { - F.prototype = proto || {}; - return new F(); - } - - function mapObject(obj, func, scope) { - var newObj = {}, key; - for (key in obj) { - if (obj[key] !== O[key]) { - newObj[key] = func.call(scope, obj[key], key, obj); - } - } - return newObj; - } - - /** @ignore */ - mapArray = function (arr, func, scope) { - var x = 0, xl = arr.length, newArr = new Array(xl); - for (; x < xl; ++x) { - newArr[x] = func.call(scope, arr[x], x, arr); - } - return newArr; - }; - - if (Array.prototype.map) { - /** @ignore */ - mapArray = function (arr, func, scope) { - return Array.prototype.map.call(arr, func, scope); - }; - } - - /** @ignore */ - filterArray = function (arr, func, scope) { - var x = 0, xl = arr.length, newArr = []; - for (; x < xl; ++x) { - if (func.call(scope, arr[x], x, arr)) { - newArr[newArr.length] = arr[x]; - } - } - return newArr; - }; - - if (Array.prototype.filter) { - /** @ignore */ - filterArray = function (arr, func, scope) { - return Array.prototype.filter.call(arr, func, scope); - }; - } - - /** @ignore */ - searchArray = function (arr, o) { - var x = 0, xl = arr.length; - for (; x < xl; ++x) { - if (arr[x] === o) { - return x; - } - } - return -1; - }; - - if (Array.prototype.indexOf) { - /** @ignore */ - searchArray = function (arr, o) { - return Array.prototype.indexOf.call(arr, o); - }; - } - - function toArray(o) { - return o !== undefined && o !== null ? (o instanceof Array && !o.callee ? o : (typeof o.length !== "number" || o.split || o.setInterval || o.call ? [ o ] : Array.prototype.slice.call(o))) : []; - } - - function keys(o) { - var result = [], key; - - switch (typeOf(o)) { - case "object": - for (key in o) { - if (o[key] !== O[key]) { - result[result.length] = key; - } - } - break; - case "array": - for (key = o.length - 1; key >= 0; --key) { - result[key] = key; - } - break; - } - - return result; - } - - function pushUnique(arr, o) { - if (searchArray(arr, o) === -1) { - arr.push(o); - } - return arr; - } - - function popFirst(arr, o) { - var index = searchArray(arr, o); - if (index > -1) { - arr.splice(index, 1); - } - return arr; - } - - function randomUUID() { - return [ - I2H[Math.floor(Math.random() * 0x10)], - I2H[Math.floor(Math.random() * 0x10)], - I2H[Math.floor(Math.random() * 0x10)], - I2H[Math.floor(Math.random() * 0x10)], - I2H[Math.floor(Math.random() * 0x10)], - I2H[Math.floor(Math.random() * 0x10)], - I2H[Math.floor(Math.random() * 0x10)], - I2H[Math.floor(Math.random() * 0x10)], - "-", - I2H[Math.floor(Math.random() * 0x10)], - I2H[Math.floor(Math.random() * 0x10)], - I2H[Math.floor(Math.random() * 0x10)], - I2H[Math.floor(Math.random() * 0x10)], - "-4", //set 4 high bits of time_high field to version - I2H[Math.floor(Math.random() * 0x10)], - I2H[Math.floor(Math.random() * 0x10)], - I2H[Math.floor(Math.random() * 0x10)], - "-", - I2H[(Math.floor(Math.random() * 0x10) & 0x3) | 0x8], //specify 2 high bits of clock sequence - I2H[Math.floor(Math.random() * 0x10)], - I2H[Math.floor(Math.random() * 0x10)], - I2H[Math.floor(Math.random() * 0x10)], - "-", - I2H[Math.floor(Math.random() * 0x10)], - I2H[Math.floor(Math.random() * 0x10)], - I2H[Math.floor(Math.random() * 0x10)], - I2H[Math.floor(Math.random() * 0x10)], - I2H[Math.floor(Math.random() * 0x10)], - I2H[Math.floor(Math.random() * 0x10)], - I2H[Math.floor(Math.random() * 0x10)], - I2H[Math.floor(Math.random() * 0x10)], - I2H[Math.floor(Math.random() * 0x10)], - I2H[Math.floor(Math.random() * 0x10)], - I2H[Math.floor(Math.random() * 0x10)], - I2H[Math.floor(Math.random() * 0x10)] - ].join(""); - } - - function escapeURIComponent(str) { - return encodeURIComponent(str).replace(/!/g, '%21').replace(/'/g, '%27').replace(/\(/g, '%28').replace(/\)/g, '%29').replace(/\*/g, '%2A'); - } - - function formatURI(uri) { - if (typeof uri === "string" && uri.indexOf("#") === -1) { - uri += "#"; - } - return uri; - } - - function stripInstances(o) { - if (o instanceof JSONInstance) { - return o.getURI(); - } - - switch (typeOf(o)) { - case "undefined": - case "null": - case "boolean": - case "number": - case "string": - return o; //do nothing - - case "object": - return mapObject(o, stripInstances); - - case "array": - return mapArray(o, stripInstances); - - default: - return o.toString(); - } - } - - /** - * The exception that is thrown when a schema fails to be created. - * - * @name InitializationError - * @class - * @param {JSONInstance|String} instance The instance (or instance URI) that is invalid - * @param {JSONSchema|String} schema The schema (or schema URI) that was validating the instance - * @param {String} attr The attribute that failed to validated - * @param {String} message A user-friendly message on why the schema attribute failed to validate the instance - * @param {Any} details The value of the schema attribute - */ - - function InitializationError(instance, schema, attr, message, details) { - Error.call(this, message); - - this.uri = instance instanceof JSONInstance ? instance.getURI() : instance; - this.schemaUri = schema instanceof JSONInstance ? schema.getURI() : schema; - this.attribute = attr; - this.message = message; - this.description = message; //IE - this.details = details; - } - - InitializationError.prototype = new Error(); - InitializationError.prototype.constructor = InitializationError; - InitializationError.prototype.name = "InitializationError"; - - /** - * Defines an error, found by a schema, with an instance. - * This class can only be instantiated by {@link Report#addError}. - * - * @name ValidationError - * @class - * @see Report#addError - */ - - /** - * The URI of the instance that has the error. - * - * @name ValidationError.prototype.uri - * @type String - */ - - /** - * The URI of the schema that generated the error. - * - * @name ValidationError.prototype.schemaUri - * @type String - */ - - /** - * The name of the schema attribute that generated the error. - * - * @name ValidationError.prototype.attribute - * @type String - */ - - /** - * An user-friendly (English) message about what failed to validate. - * - * @name ValidationError.prototype.message - * @type String - */ - - /** - * The value of the schema attribute that generated the error. - * - * @name ValidationError.prototype.details - * @type Any - */ - - /** - * Reports are returned from validation methods to describe the result of a validation. - * - * @name Report - * @class - * @see JSONSchema#validate - * @see Environment#validate - */ - - function Report() { - /** - * An array of {@link ValidationError} objects that define all the errors generated by the schema against the instance. - * - * @name Report.prototype.errors - * @type Array - * @see Report#addError - */ - this.errors = []; - - /** - * A hash table of every instance and what schemas were validated against it. - *

- * The key of each item in the table is the URI of the instance that was validated. - * The value of this key is an array of strings of URIs of the schema that validated it. - *

- * - * @name Report.prototype.validated - * @type Object - * @see Report#registerValidation - * @see Report#isValidatedBy - */ - this.validated = {}; - - /** - * If the report is generated by {@link Environment#validate}, this field is the generated instance. - * - * @name Report.prototype.instance - * @type JSONInstance - * @see Environment#validate - */ - - /** - * If the report is generated by {@link Environment#validate}, this field is the generated schema. - * - * @name Report.prototype.schema - * @type JSONSchema - * @see Environment#validate - */ - - /** - * If the report is generated by {@link Environment#validate}, this field is the schema's schema. - * This value is the same as calling schema.getSchema(). - * - * @name Report.prototype.schemaSchema - * @type JSONSchema - * @see Environment#validate - * @see JSONSchema#getSchema - */ - } - - /** - * Adds a {@link ValidationError} object to the errors field. - * - * @param {JSONInstance|String} instance The instance (or instance URI) that is invalid - * @param {JSONSchema|String} schema The schema (or schema URI) that was validating the instance - * @param {String} attr The attribute that failed to validated - * @param {String} message A user-friendly message on why the schema attribute failed to validate the instance - * @param {Any} details The value of the schema attribute - */ - - Report.prototype.addError = function (instance, schema, attr, message, details) { - this.errors.push({ - uri : instance instanceof JSONInstance ? instance.getURI() : instance, - schemaUri : schema instanceof JSONInstance ? schema.getURI() : schema, - attribute : attr, - message : message, - details : stripInstances(details) - }); - }; - - /** - * Registers that the provided instance URI has been validated by the provided schema URI. - * This is recorded in the validated field. - * - * @param {String} uri The URI of the instance that was validated - * @param {String} schemaUri The URI of the schema that validated the instance - */ - - Report.prototype.registerValidation = function (uri, schemaUri) { - if (!this.validated[uri]) { - this.validated[uri] = [ schemaUri ]; - } else { - this.validated[uri].push(schemaUri); - } - }; - - /** - * Returns if an instance with the provided URI has been validated by the schema with the provided URI. - * - * @param {String} uri The URI of the instance - * @param {String} schemaUri The URI of a schema - * @returns {Boolean} If the instance has been validated by the schema. - */ - - Report.prototype.isValidatedBy = function (uri, schemaUri) { - return !!this.validated[uri] && searchArray(this.validated[uri], schemaUri) !== -1; - }; - - /** - * A wrapper class for binding an Environment, URI and helper methods to an instance. - * This class is most commonly instantiated with {@link Environment#createInstance}. - * - * @name JSONInstance - * @class - * @param {Environment} env The environment this instance belongs to - * @param {JSONInstance|Any} json The value of the instance - * @param {String} [uri] The URI of the instance. If undefined, the URI will be a randomly generated UUID. - * @param {String} [fd] The fragment delimiter for properties. If undefined, uses the environment default. - * @param {JSONInstance} parent The parent instance, if any - * @param {String|Number} parentKey The corresponding key on the parent instance, if any - */ - - function JSONInstance(env, json, uri, fd, parent, parentKey) { - if (json instanceof JSONInstance) { - if (typeof fd !== "string") { - fd = json._fd; - } - if (typeof uri !== "string") { - uri = json._uri; - } - json = json._value; - } - - if (typeof uri !== "string") { - uri = "urn:uuid:" + randomUUID() + "#"; - } else if (uri.indexOf(":") === -1) { - uri = formatURI(URI.resolve("urn:uuid:" + randomUUID() + "#", uri)); - } - - this._env = env; - this._value = json; - this._uri = uri; - this._fd = fd || this._env._options["defaultFragmentDelimiter"]; - this._parent = parent; - this._parentKey = parentKey; - } - - /** - * Returns the environment the instance is bound to. - * - * @returns {Environment} The environment of the instance - */ - - JSONInstance.prototype.getEnvironment = function () { - return this._env; - }; - - /** - * Returns the name of the type of the instance. - * - * @returns {String} The name of the type of the instance - */ - - JSONInstance.prototype.getType = function () { - return typeOf(this._value); - }; - - /** - * Returns the JSON value of the instance. - * - * @returns {Any} The actual JavaScript value of the instance - */ - - JSONInstance.prototype.getValue = function () { - return this._value; - }; - - /** - * Coerces the JSON value of the instance according to the matching type coercion function set in - * environment options, if any (option "typeCoercionFns"). - * - * @param {String} type - */ - JSONInstance.prototype.applyTypeCoercionIfSet = function (type) { - var coercionFns = this._env._options["typeCoercionFns"]; - if (coercionFns && coercionFns[type] && typeof coercionFns[type] === "function") { - this._value = coercionFns[type](this._value); - if (this._parent) { - this._parent.setValueOfProperty(this._parentKey, this._value); - } - } - }; - - /** - * Returns the URI of the instance. - * - * @returns {String} The URI of the instance - */ - - JSONInstance.prototype.getURI = function () { - return this._uri; - }; - - /** - * Returns a resolved URI of a provided relative URI against the URI of the instance. - * - * @param {String} uri The relative URI to resolve - * @returns {String} The resolved URI - */ - - JSONInstance.prototype.resolveURI = function (uri) { - return formatURI(URI.resolve(this._uri, uri)); - }; - - /** - * Returns an array of the names of all the properties. - * - * @returns {Array} An array of strings which are the names of all the properties - */ - - JSONInstance.prototype.getPropertyNames = function () { - return keys(this._value); - }; - - /** - * Returns a {@link JSONInstance} of the value of the provided property name. - * - * @param {String} key The name of the property to fetch - * @returns {JSONInstance} The instance of the property value - */ - - JSONInstance.prototype.getProperty = function (key) { - var value = this._value ? this._value[key] : undefined; - if (value instanceof JSONInstance) { - return value; - } - //else - return new JSONInstance(this._env, value, this._uri + this._fd + escapeURIComponent(key), - this._fd, this, key); - }; - - /** - * Returns all the property instances of the target instance. - *

- * If the target instance is an Object, then the method will return a hash table of {@link JSONInstance}s of all the properties. - * If the target instance is an Array, then the method will return an array of {@link JSONInstance}s of all the items. - *

- * - * @returns {Object|Array|undefined} The list of instances for all the properties - */ - - JSONInstance.prototype.getProperties = function () { - var type = typeOf(this._value), - self = this; - - if (type === "object") { - return mapObject(this._value, function (value, key) { - if (value instanceof JSONInstance) { - return value; - } - return new JSONInstance(self._env, value, self._uri + self._fd + escapeURIComponent(key), - self._fd, this, key); - }); - } else if (type === "array") { - return mapArray(this._value, function (value, key) { - if (value instanceof JSONInstance) { - return value; - } - return new JSONInstance(self._env, value, self._uri + self._fd + escapeURIComponent(key), - self._fd, this, key); - }); - } - }; - - /** - * Returns the JSON value of the provided property name. - * This method is a faster version of calling instance.getProperty(key).getValue(). - * - * @param {String} key The name of the property - * @returns {Any} The JavaScript value of the instance - * @see JSONInstance#getProperty - * @see JSONInstance#getValue - */ - - JSONInstance.prototype.getValueOfProperty = function (key) { - if (this._value) { - if (this._value[key] instanceof JSONInstance) { - return this._value[key]._value; - } - return this._value[key]; - } - }; - - /** - * Sets the JSON value of the provided property name. - * Used to persist value coercion results. - * - * @param {String} key The name of the property - * @see JSONInstance#applyTypeCoercionIfSet - */ - - JSONInstance.prototype.setValueOfProperty = function (key, value) { - if (this._value) { - if (this._value[key] instanceof JSONInstance) { - this._value[key]._value = value; - } - this._value[key] = value; - } - }; - - /** - * Return if the provided value is the same as the value of the instance. - * - * @param {JSONInstance|Any} instance The value to compare - * @returns {Boolean} If both the instance and the value match - */ - - JSONInstance.prototype.equals = function (instance) { - if (instance instanceof JSONInstance) { - return this._value === instance._value; - } - //else - return this._value === instance; - }; - - /** - * Warning: Not a generic clone function - * Produces a JSV acceptable clone - */ - - function clone(obj, deep) { - var newObj, x; - - if (obj instanceof JSONInstance) { - obj = obj.getValue(); - } - - switch (typeOf(obj)) { - case "object": - if (deep) { - newObj = {}; - for (x in obj) { - if (obj[x] !== O[x]) { - newObj[x] = clone(obj[x], deep); - } - } - return newObj; - } else { - return createObject(obj); - } - break; - case "array": - if (deep) { - newObj = new Array(obj.length); - x = obj.length; - while (--x >= 0) { - newObj[x] = clone(obj[x], deep); - } - return newObj; - } else { - return Array.prototype.slice.call(obj); - } - break; - default: - return obj; - } - } - - /** - * This class binds a {@link JSONInstance} with a {@link JSONSchema} to provided context aware methods. - * - * @name JSONSchema - * @class - * @param {Environment} env The environment this schema belongs to - * @param {JSONInstance|Any} json The value of the schema - * @param {String} [uri] The URI of the schema. If undefined, the URI will be a randomly generated UUID. - * @param {JSONSchema|Boolean} [schema] The schema to bind to the instance. If undefined, the environment's default schema will be used. If true, the instance's schema will be itself. - * @extends JSONInstance - */ - - function JSONSchema(env, json, uri, schema) { - var fr; - JSONInstance.call(this, env, json, uri); - - if (schema === true) { - this._schema = this; - } else if (json instanceof JSONSchema && !(schema instanceof JSONSchema)) { - this._schema = json._schema; //TODO: Make sure cross environments don't mess everything up - } else { - this._schema = schema instanceof JSONSchema ? schema : this._env.getDefaultSchema() || this._env.createEmptySchema(); - } - - //determine fragment delimiter from schema - fr = this._schema.getValueOfProperty("fragmentResolution"); - if (fr === "dot-delimited") { - this._fd = "."; - } else if (fr === "slash-delimited") { - this._fd = "/"; - } - - return this.rebuild(); //this works even when called with "new" - } - - JSONSchema.prototype = createObject(JSONInstance.prototype); - - /** - * Returns the schema of the schema. - * - * @returns {JSONSchema} The schema of the schema - */ - - JSONSchema.prototype.getSchema = function () { - var uri = this._refs && this._refs["describedby"], - newSchema; - - if (uri) { - newSchema = uri && this._env.findSchema(uri); - - if (newSchema) { - if (!newSchema.equals(this._schema)) { - this._schema = newSchema; - this.rebuild(); //if the schema has changed, the context has changed - so everything must be rebuilt - } - } else if (this._env._options["enforceReferences"]) { - throw new InitializationError(this, this._schema, "{describedby}", "Unknown schema reference", uri); - } - } - - return this._schema; - }; - - /** - * Returns the value of the provided attribute name. - *

- * This method is different from {@link JSONInstance#getProperty} as the named property - * is converted using a parser defined by the schema's schema before being returned. This - * makes the return value of this method attribute dependent. - *

- * - * @param {String} key The name of the attribute - * @param {Any} [arg] Some attribute parsers accept special arguments for returning resolved values. This is attribute dependent. - * @returns {JSONSchema|Any} The value of the attribute - */ - - JSONSchema.prototype.getAttribute = function (key, arg) { - var schemaProperty, parser, property, result, - schema = this.getSchema(); //we do this here to make sure the "describedby" reference has not changed, and that the attribute cache is up-to-date - - if (!arg && this._attributes && this._attributes.hasOwnProperty(key)) { - return this._attributes[key]; - } - - schemaProperty = schema.getProperty("properties").getProperty(key); - parser = schemaProperty.getValueOfProperty("parser"); - property = this.getProperty(key); - if (typeof parser === "function") { - result = parser(property, schemaProperty, arg); - if (!arg && this._attributes) { - this._attributes[key] = result; - } - return result; - } - //else - return property.getValue(); - }; - - /** - * Returns all the attributes of the schema. - * - * @returns {Object} A map of all parsed attribute values - */ - - JSONSchema.prototype.getAttributes = function () { - var properties, schemaProperties, key, schemaProperty, parser, - schema = this.getSchema(); //we do this here to make sure the "describedby" reference has not changed, and that the attribute cache is up-to-date - - if (!this._attributes && this.getType() === "object") { - properties = this.getProperties(); - schemaProperties = schema.getProperty("properties"); - this._attributes = {}; - for (key in properties) { - if (properties[key] !== O[key]) { - schemaProperty = schemaProperties && schemaProperties.getProperty(key); - parser = schemaProperty && schemaProperty.getValueOfProperty("parser"); - if (typeof parser === "function") { - this._attributes[key] = parser(properties[key], schemaProperty); - } else { - this._attributes[key] = properties[key].getValue(); - } - } - } - } - - return clone(this._attributes, false); - }; - - /** - * Convenience method for retrieving a link or link object from a schema. - * This method is the same as calling schema.getAttribute("links", [rel, instance])[0];. - * - * @param {String} rel The link relationship - * @param {JSONInstance} [instance] The instance to resolve any URIs from - * @returns {String|Object|undefined} If instance is provided, a string containing the resolve URI of the link is returned. - * If instance is not provided, a link object is returned with details of the link. - * If no link with the provided relationship exists, undefined is returned. - * @see JSONSchema#getAttribute - */ - - JSONSchema.prototype.getLink = function (rel, instance) { - var schemaLinks = this.getAttribute("links", [rel, instance]); - if (schemaLinks && schemaLinks.length && schemaLinks[schemaLinks.length - 1]) { - return schemaLinks[schemaLinks.length - 1]; - } - }; - - /** - * Validates the provided instance against the target schema and returns a {@link Report}. - * - * @param {JSONInstance|Any} instance The instance to validate; may be a {@link JSONInstance} or any JavaScript value - * @param {Report} [report] A {@link Report} to concatenate the result of the validation to. If undefined, a new {@link Report} is created. - * @param {JSONInstance} [parent] The parent/containing instance of the provided instance - * @param {JSONSchema} [parentSchema] The schema of the parent/containing instance - * @param {String} [name] The name of the parent object's property that references the instance - * @returns {Report} The result of the validation - */ - - JSONSchema.prototype.validate = function (instance, report, parent, parentSchema, name) { - var schemaSchema = this.getSchema(), - validator = schemaSchema.getValueOfProperty("validator"); - - if (!(instance instanceof JSONInstance)) { - instance = this.getEnvironment().createInstance(instance); - } - - if (!(report instanceof Report)) { - report = new Report(); - } - - if (this._env._options["validateReferences"] && this._refs) { - if (this._refs["describedby"] && !this._env.findSchema(this._refs["describedby"])) { - report.addError(this, this._schema, "{describedby}", "Unknown schema reference", this._refs["describedby"]); - } - if (this._refs["full"] && !this._env.findSchema(this._refs["full"])) { - report.addError(this, this._schema, "{full}", "Unknown schema reference", this._refs["full"]); - } - } - - if (typeof validator === "function" && !report.isValidatedBy(instance.getURI(), this.getURI())) { - report.registerValidation(instance.getURI(), this.getURI()); - validator(instance, this, schemaSchema, report, parent, parentSchema, name); - } - - return report; - }; - - /** @inner */ - function createFullLookupWrapper(func) { - return /** @inner */ function fullLookupWrapper() { - var scope = this, - stack = [], - uri = scope._refs && scope._refs["full"], - schema; - - while (uri) { - schema = scope._env.findSchema(uri); - if (schema) { - if (schema._value === scope._value) { - break; - } - scope = schema; - stack.push(uri); - uri = scope._refs && scope._refs["full"]; - if (stack.indexOf(uri) > -1) { - break; //stop infinite loop - } - } else if (scope._env._options["enforceReferences"]) { - throw new InitializationError(scope, scope._schema, "{full}", "Unknown schema reference", uri); - } else { - uri = null; - } - } - return func.apply(scope, arguments); - }; - } - - /** - * Wraps all JSONInstance methods with a function that resolves the "full" reference. - * - * @inner - */ - - (function () { - var key; - for (key in JSONSchema.prototype) { - if (JSONSchema.prototype[key] !== O[key] && typeOf(JSONSchema.prototype[key]) === "function") { - JSONSchema.prototype[key] = createFullLookupWrapper(JSONSchema.prototype[key]); - } - } - }()); - - /** - * Reinitializes/re-registers/rebuilds the schema. - *
- * This is used internally, and should only be called when a schema's private variables are modified directly. - * - * @private - * @return {JSONSchema} The newly rebuilt schema - */ - - JSONSchema.prototype.rebuild = function () { - var instance = this, - initializer = instance.getSchema().getValueOfProperty("initializer"); - - //clear previous built values - instance._refs = null; - instance._attributes = null; - - if (typeof initializer === "function") { - instance = initializer(instance); - } - - //register schema - instance._env._schemas[instance._uri] = instance; - - //build & cache the rest of the schema - instance.getAttributes(); - - return instance; - }; - - /** - * Set the provided reference to the given value. - *
- * References are used for establishing soft-links to other {@link JSONSchema}s. - * Currently, the following references are natively supported: - *
- *
full
- *
The value is the URI to the full instance of this instance.
- *
describedby
- *
The value is the URI to the schema of this instance.
- *
- * - * @param {String} name The name of the reference - * @param {String} uri The URI of the schema to refer to - */ - - JSONSchema.prototype.setReference = function (name, uri) { - if (!this._refs) { - this._refs = {}; - } - this._refs[name] = this.resolveURI(uri); - }; - - /** - * Returns the value of the provided reference name. - * - * @param {String} name The name of the reference - * @return {String} The value of the provided reference name - */ - - JSONSchema.prototype.getReference = function (name) { - return this._refs && this._refs[name]; - }; - - /** - * Merges two schemas/instances together. - */ - - function inherits(base, extra, extension) { - var baseType = typeOf(base), - extraType = typeOf(extra), - child, x; - - if (extraType === "undefined") { - return clone(base, true); - } else if (baseType === "undefined" || extraType !== baseType) { - return clone(extra, true); - } else if (extraType === "object") { - if (base instanceof JSONSchema) { - base = base.getAttributes(); - } - if (extra instanceof JSONSchema) { - extra = extra.getAttributes(); - if (extra["extends"] && extension && extra["extends"] instanceof JSONSchema) { - extra["extends"] = [ extra["extends"] ]; - } - } - child = clone(base, true); //this could be optimized as some properties get overwritten - for (x in extra) { - if (extra[x] !== O[x]) { - child[x] = inherits(base[x], extra[x], extension); - } - } - return child; - } else { - return clone(extra, true); - } - } - - /** - * An Environment is a sandbox of schemas thats behavior is different from other environments. - * - * @name Environment - * @class - */ - - function Environment() { - this._id = randomUUID(); - this._schemas = {}; - this._options = {}; - - this.createSchema({}, true, "urn:jsv:empty-schema#"); - } - - /** - * Returns a clone of the target environment. - * - * @returns {Environment} A new {@link Environment} that is a exact copy of the target environment - */ - - Environment.prototype.clone = function () { - var env = new Environment(); - env._schemas = createObject(this._schemas); - env._options = createObject(this._options); - - return env; - }; - - /** - * Returns a new {@link JSONInstance} of the provided data. - * - * @param {JSONInstance|Any} data The value of the instance - * @param {String} [uri] The URI of the instance. If undefined, the URI will be a randomly generated UUID. - * @returns {JSONInstance} A new {@link JSONInstance} from the provided data - */ - - Environment.prototype.createInstance = function (data, uri) { - uri = formatURI(uri); - - if (data instanceof JSONInstance && (!uri || data.getURI() === uri)) { - return data; - } - - return new JSONInstance(this, data, uri); - }; - - /** - * Creates a new {@link JSONSchema} from the provided data, and registers it with the environment. - * - * @param {JSONInstance|Any} data The value of the schema - * @param {JSONSchema|Boolean} [schema] The schema to bind to the instance. If undefined, the environment's default schema will be used. If true, the instance's schema will be itself. - * @param {String} [uri] The URI of the schema. If undefined, the URI will be a randomly generated UUID. - * @returns {JSONSchema} A new {@link JSONSchema} from the provided data - * @throws {InitializationError} If a schema that is not registered with the environment is referenced - */ - - Environment.prototype.createSchema = function (data, schema, uri) { - uri = formatURI(uri); - - if (data instanceof JSONSchema && (!uri || data._uri === uri) && (!schema || data.getSchema().equals(schema))) { - return data; - } - - return new JSONSchema(this, data, uri, schema); - }; - - /** - * Creates an empty schema. - * - * @returns {JSONSchema} The empty schema, who's schema is itself. - */ - - Environment.prototype.createEmptySchema = function () { - return this._schemas["urn:jsv:empty-schema#"]; - }; - - /** - * Returns the schema registered with the provided URI. - * - * @param {String} uri The absolute URI of the required schema - * @returns {JSONSchema|undefined} The request schema, or undefined if not found - */ - - Environment.prototype.findSchema = function (uri) { - return this._schemas[formatURI(uri)]; - }; - - /** - * Sets the specified environment option to the specified value. - * - * @param {String} name The name of the environment option to set - * @param {Any} value The new value of the environment option - */ - - Environment.prototype.setOption = function (name, value) { - this._options[name] = value; - }; - - /** - * Returns the specified environment option. - * - * @param {String} name The name of the environment option to set - * @returns {Any} The value of the environment option - */ - - Environment.prototype.getOption = function (name) { - return this._options[name]; - }; - - /** - * Sets the default fragment delimiter of the environment. - * - * @deprecated Use {@link Environment#setOption} with option "defaultFragmentDelimiter" - * @param {String} fd The fragment delimiter character - */ - - Environment.prototype.setDefaultFragmentDelimiter = function (fd) { - if (typeof fd === "string" && fd.length > 0) { - this._options["defaultFragmentDelimiter"] = fd; - } - }; - - /** - * Returns the default fragment delimiter of the environment. - * - * @deprecated Use {@link Environment#getOption} with option "defaultFragmentDelimiter" - * @returns {String} The fragment delimiter character - */ - - Environment.prototype.getDefaultFragmentDelimiter = function () { - return this._options["defaultFragmentDelimiter"]; - }; - - /** - * Sets the URI of the default schema for the environment. - * - * @deprecated Use {@link Environment#setOption} with option "defaultSchemaURI" - * @param {String} uri The default schema URI - */ - - Environment.prototype.setDefaultSchemaURI = function (uri) { - if (typeof uri === "string") { - this._options["defaultSchemaURI"] = formatURI(uri); - } - }; - - /** - * Returns the default schema of the environment. - * - * @returns {JSONSchema} The default schema - */ - - Environment.prototype.getDefaultSchema = function () { - return this.findSchema(this._options["defaultSchemaURI"]); - }; - - /** - * Validates both the provided schema and the provided instance, and returns a {@link Report}. - * If the schema fails to validate, the instance will not be validated. - * - * @param {JSONInstance|Any} instanceJSON The {@link JSONInstance} or JavaScript value to validate. - * @param {JSONSchema|Any} schemaJSON The {@link JSONSchema} or JavaScript value to use in the validation. This will also be validated againt the schema's schema. - * @returns {Report} The result of the validation - */ - - Environment.prototype.validate = function (instanceJSON, schemaJSON) { - var instance, - schema, - schemaSchema, - report = new Report(); - - try { - instance = this.createInstance(instanceJSON); - report.instance = instance; - } catch (e) { - report.addError(e.uri, e.schemaUri, e.attribute, e.message, e.details); - } - - try { - schema = this.createSchema(schemaJSON); - report.schema = schema; - - schemaSchema = schema.getSchema(); - report.schemaSchema = schemaSchema; - } catch (f) { - report.addError(f.uri, f.schemaUri, f.attribute, f.message, f.details); - } - - if (schemaSchema) { - schemaSchema.validate(schema, report); - } - - if (report.errors.length) { - return report; - } - - return schema.validate(instance, report); - }; - - /** - * @private - */ - - Environment.prototype._checkForInvalidInstances = function (stackSize, schemaURI) { - var result = [], - stack = [ - [schemaURI, this._schemas[schemaURI]] - ], - counter = 0, - item, uri, instance, properties, key; - - while (counter++ < stackSize && stack.length) { - item = stack.shift(); - uri = item[0]; - instance = item[1]; - - if (instance instanceof JSONSchema) { - if (this._schemas[instance._uri] !== instance) { - result.push("Instance " + uri + " does not match " + instance._uri); - } else { - //schema = instance.getSchema(); - //stack.push([uri + "/{schema}", schema]); - - properties = instance.getAttributes(); - for (key in properties) { - if (properties[key] !== O[key]) { - stack.push([uri + "/" + escapeURIComponent(key), properties[key]]); - } - } - } - } else if (typeOf(instance) === "object") { - properties = instance; - for (key in properties) { - if (properties.hasOwnProperty(key)) { - stack.push([uri + "/" + escapeURIComponent(key), properties[key]]); - } - } - } else if (typeOf(instance) === "array") { - properties = instance; - for (key = 0; key < properties.length; ++key) { - stack.push([uri + "/" + escapeURIComponent(key), properties[key]]); - } - } - } - - return result.length ? result : counter; - }; - - /** - * A globaly accessible object that provides the ability to create and manage {@link Environments}, - * as well as providing utility methods. - * - * @namespace - */ - - JSV = { - _environments : {}, - _defaultEnvironmentID : "", - - /** - * Returns if the provide value is an instance of {@link JSONInstance}. - * - * @param o The value to test - * @returns {Boolean} If the provide value is an instance of {@link JSONInstance} - */ - - isJSONInstance : function (o) { - return o instanceof JSONInstance; - }, - - /** - * Returns if the provide value is an instance of {@link JSONSchema}. - * - * @param o The value to test - * @returns {Boolean} If the provide value is an instance of {@link JSONSchema} - */ - - isJSONSchema : function (o) { - return o instanceof JSONSchema; - }, - - /** - * Creates and returns a new {@link Environment} that is a clone of the environment registered with the provided ID. - * If no environment ID is provided, the default environment is cloned. - * - * @param {String} [id] The ID of the environment to clone. If undefined, the default environment ID is used. - * @returns {Environment} A newly cloned {@link Environment} - * @throws {Error} If there is no environment registered with the provided ID - */ - - createEnvironment : function (id) { - id = id || this._defaultEnvironmentID; - - if (!this._environments[id]) { - throw new Error("Unknown Environment ID"); - } - //else - return this._environments[id].clone(); - }, - - Environment : Environment, - - /** - * Registers the provided {@link Environment} with the provided ID. - * - * @param {String} id The ID of the environment - * @param {Environment} env The environment to register - */ - - registerEnvironment : function (id, env) { - id = id || (env || 0)._id; - if (id && !this._environments[id] && env instanceof Environment) { - env._id = id; - this._environments[id] = env; - } - }, - - /** - * Sets which registered ID is the default environment. - * - * @param {String} id The ID of the registered environment that is default - * @throws {Error} If there is no registered environment with the provided ID - */ - - setDefaultEnvironmentID : function (id) { - if (typeof id === "string") { - if (!this._environments[id]) { - throw new Error("Unknown Environment ID"); - } - - this._defaultEnvironmentID = id; - } - }, - - /** - * Returns the ID of the default environment. - * - * @returns {String} The ID of the default environment - */ - - getDefaultEnvironmentID : function () { - return this._defaultEnvironmentID; - }, - - // - // Utility Functions - // - - /** - * Returns the name of the type of the provided value. - * - * @event //utility - * @param {Any} o The value to determine the type of - * @returns {String} The name of the type of the value - */ - typeOf : typeOf, - - /** - * Return a new object that inherits all of the properties of the provided object. - * - * @event //utility - * @param {Object} proto The prototype of the new object - * @returns {Object} A new object that inherits all of the properties of the provided object - */ - createObject : createObject, - - /** - * Returns a new object with each property transformed by the iterator. - * - * @event //utility - * @param {Object} obj The object to transform - * @param {Function} iterator A function that returns the new value of the provided property - * @param {Object} [scope] The value of this in the iterator - * @returns {Object} A new object with each property transformed - */ - mapObject : mapObject, - - /** - * Returns a new array with each item transformed by the iterator. - * - * @event //utility - * @param {Array} arr The array to transform - * @param {Function} iterator A function that returns the new value of the provided item - * @param {Object} scope The value of this in the iterator - * @returns {Array} A new array with each item transformed - */ - mapArray : mapArray, - - /** - * Returns a new array that only contains the items allowed by the iterator. - * - * @event //utility - * @param {Array} arr The array to filter - * @param {Function} iterator The function that returns true if the provided property should be added to the array - * @param {Object} scope The value of this within the iterator - * @returns {Array} A new array that contains the items allowed by the iterator - */ - filterArray : filterArray, - - /** - * Returns the first index in the array that the provided item is located at. - * - * @event //utility - * @param {Array} arr The array to search - * @param {Any} o The item being searched for - * @returns {Number} The index of the item in the array, or -1 if not found - */ - searchArray : searchArray, - - /** - * Returns an array representation of a value. - *
    - *
  • For array-like objects, the value will be casted as an Array type.
  • - *
  • If an array is provided, the function will simply return the same array.
  • - *
  • For a null or undefined value, the result will be an empty Array.
  • - *
  • For all other values, the value will be the first element in a new Array.
  • - *
- * - * @event //utility - * @param {Any} o The value to convert into an array - * @returns {Array} The value as an array - */ - toArray : toArray, - - /** - * Returns an array of the names of all properties of an object. - * - * @event //utility - * @param {Object|Array} o The object in question - * @returns {Array} The names of all properties - */ - keys : keys, - - /** - * Mutates the array by pushing the provided value onto the array only if it is not already there. - * - * @event //utility - * @param {Array} arr The array to modify - * @param {Any} o The object to add to the array if it is not already there - * @returns {Array} The provided array for chaining - */ - pushUnique : pushUnique, - - /** - * Mutates the array by removing the first item that matches the provided value in the array. - * - * @event //utility - * @param {Array} arr The array to modify - * @param {Any} o The object to remove from the array - * @returns {Array} The provided array for chaining - */ - popFirst : popFirst, - - /** - * Creates a copy of the target object. - *

- * This method will create a new instance of the target, and then mixin the properties of the target. - * If deep is true, then each property will be cloned before mixin. - *

- *

Warning: This is not a generic clone function, as it will only properly clone objects and arrays.

- * - * @event //utility - * @param {Any} o The value to clone - * @param {Boolean} [deep=false] If each property should be recursively cloned - * @returns A cloned copy of the provided value - */ - clone : clone, - - /** - * Generates a pseudo-random UUID. - * - * @event //utility - * @returns {String} A new universally unique ID - */ - randomUUID : randomUUID, - - /** - * Properly escapes a URI component for embedding into a URI string. - * - * @event //utility - * @param {String} str The URI component to escape - * @returns {String} The escaped URI component - */ - escapeURIComponent : escapeURIComponent, - - /** - * Returns a URI that is formated for JSV. Currently, this only ensures that the URI ends with a hash tag (#). - * - * @event //utility - * @param {String} uri The URI to format - * @returns {String} The URI formatted for JSV - */ - formatURI : formatURI, - - /** - * Merges two schemas/instance together. - * - * @event //utility - * @param {JSONSchema|Any} base The old value to merge - * @param {JSONSchema|Any} extra The new value to merge - * @param {Boolean} extension If the merge is a JSON Schema extension - * @return {Any} The modified base value - */ - - inherits : inherits, - - /** - * @private - * @event //utility - */ - - InitializationError : InitializationError - }; - - this.JSV = JSV; //set global object - exports.JSV = JSV; //export to CommonJS - - require("./environments"); //load default environments - -}()); +/** + * JSV: JSON Schema Validator + * + * @fileOverview A JavaScript implementation of a extendable, fully compliant JSON Schema validator. + * @author Gary Court + * @version 4.0.2 + * @see http://github.com/garycourt/JSV + */ + +/* + * Copyright 2010 Gary Court. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY GARY COURT ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GARY COURT OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of Gary Court or the JSON Schema specification. + */ + +/*jslint white: true, sub: true, onevar: true, undef: true, eqeqeq: true, newcap: true, immed: true, indent: 4 */ + +var exports = exports || this, + require = require || function () { + return exports; + }; + +(function () { + + var URI = require("./uri/uri").URI, + O = {}, + I2H = "0123456789abcdef".split(""), + mapArray, filterArray, searchArray, + + JSV; + + // + // Utility functions + // + + function typeOf(o) { + return o === undefined ? "undefined" : (o === null ? "null" : Object.prototype.toString.call(o).split(" ").pop().split("]").shift().toLowerCase()); + } + + /** @inner */ + function F() {} + + function createObject(proto) { + F.prototype = proto || {}; + return new F(); + } + + function mapObject(obj, func, scope) { + var newObj = {}, key; + for (key in obj) { + if (obj[key] !== O[key]) { + newObj[key] = func.call(scope, obj[key], key, obj); + } + } + return newObj; + } + + /** @ignore */ + mapArray = function (arr, func, scope) { + var x = 0, xl = arr.length, newArr = new Array(xl); + for (; x < xl; ++x) { + newArr[x] = func.call(scope, arr[x], x, arr); + } + return newArr; + }; + + if (Array.prototype.map) { + /** @ignore */ + mapArray = function (arr, func, scope) { + return Array.prototype.map.call(arr, func, scope); + }; + } + + /** @ignore */ + filterArray = function (arr, func, scope) { + var x = 0, xl = arr.length, newArr = []; + for (; x < xl; ++x) { + if (func.call(scope, arr[x], x, arr)) { + newArr[newArr.length] = arr[x]; + } + } + return newArr; + }; + + if (Array.prototype.filter) { + /** @ignore */ + filterArray = function (arr, func, scope) { + return Array.prototype.filter.call(arr, func, scope); + }; + } + + /** @ignore */ + searchArray = function (arr, o) { + var x = 0, xl = arr.length; + for (; x < xl; ++x) { + if (arr[x] === o) { + return x; + } + } + return -1; + }; + + if (Array.prototype.indexOf) { + /** @ignore */ + searchArray = function (arr, o) { + return Array.prototype.indexOf.call(arr, o); + }; + } + + function toArray(o) { + return o !== undefined && o !== null ? (o instanceof Array && !o.callee ? o : (typeof o.length !== "number" || o.split || o.setInterval || o.call ? [ o ] : Array.prototype.slice.call(o))) : []; + } + + function keys(o) { + var result = [], key; + + switch (typeOf(o)) { + case "object": + for (key in o) { + if (o[key] !== O[key]) { + result[result.length] = key; + } + } + break; + case "array": + for (key = o.length - 1; key >= 0; --key) { + result[key] = key; + } + break; + } + + return result; + } + + function pushUnique(arr, o) { + if (searchArray(arr, o) === -1) { + arr.push(o); + } + return arr; + } + + function popFirst(arr, o) { + var index = searchArray(arr, o); + if (index > -1) { + arr.splice(index, 1); + } + return arr; + } + + function randomUUID() { + return [ + I2H[Math.floor(Math.random() * 0x10)], + I2H[Math.floor(Math.random() * 0x10)], + I2H[Math.floor(Math.random() * 0x10)], + I2H[Math.floor(Math.random() * 0x10)], + I2H[Math.floor(Math.random() * 0x10)], + I2H[Math.floor(Math.random() * 0x10)], + I2H[Math.floor(Math.random() * 0x10)], + I2H[Math.floor(Math.random() * 0x10)], + "-", + I2H[Math.floor(Math.random() * 0x10)], + I2H[Math.floor(Math.random() * 0x10)], + I2H[Math.floor(Math.random() * 0x10)], + I2H[Math.floor(Math.random() * 0x10)], + "-4", //set 4 high bits of time_high field to version + I2H[Math.floor(Math.random() * 0x10)], + I2H[Math.floor(Math.random() * 0x10)], + I2H[Math.floor(Math.random() * 0x10)], + "-", + I2H[(Math.floor(Math.random() * 0x10) & 0x3) | 0x8], //specify 2 high bits of clock sequence + I2H[Math.floor(Math.random() * 0x10)], + I2H[Math.floor(Math.random() * 0x10)], + I2H[Math.floor(Math.random() * 0x10)], + "-", + I2H[Math.floor(Math.random() * 0x10)], + I2H[Math.floor(Math.random() * 0x10)], + I2H[Math.floor(Math.random() * 0x10)], + I2H[Math.floor(Math.random() * 0x10)], + I2H[Math.floor(Math.random() * 0x10)], + I2H[Math.floor(Math.random() * 0x10)], + I2H[Math.floor(Math.random() * 0x10)], + I2H[Math.floor(Math.random() * 0x10)], + I2H[Math.floor(Math.random() * 0x10)], + I2H[Math.floor(Math.random() * 0x10)], + I2H[Math.floor(Math.random() * 0x10)], + I2H[Math.floor(Math.random() * 0x10)] + ].join(""); + } + + function escapeURIComponent(str) { + return encodeURIComponent(str).replace(/!/g, '%21').replace(/'/g, '%27').replace(/\(/g, '%28').replace(/\)/g, '%29').replace(/\*/g, '%2A'); + } + + function formatURI(uri) { + if (typeof uri === "string" && uri.indexOf("#") === -1) { + uri += "#"; + } + return uri; + } + + function stripInstances(o) { + if (o instanceof JSONInstance) { + return o.getURI(); + } + + switch (typeOf(o)) { + case "undefined": + case "null": + case "boolean": + case "number": + case "string": + return o; //do nothing + + case "object": + return mapObject(o, stripInstances); + + case "array": + return mapArray(o, stripInstances); + + default: + return o.toString(); + } + } + + /** + * The exception that is thrown when a schema fails to be created. + * + * @name InitializationError + * @class + * @param {JSONInstance|String} instance The instance (or instance URI) that is invalid + * @param {JSONSchema|String} schema The schema (or schema URI) that was validating the instance + * @param {String} attr The attribute that failed to validated + * @param {String} message A user-friendly message on why the schema attribute failed to validate the instance + * @param {Any} details The value of the schema attribute + */ + + function InitializationError(instance, schema, attr, message, details) { + Error.call(this, message); + + this.uri = instance instanceof JSONInstance ? instance.getURI() : instance; + this.schemaUri = schema instanceof JSONInstance ? schema.getURI() : schema; + this.attribute = attr; + this.message = message; + this.description = message; //IE + this.details = details; + } + + InitializationError.prototype = new Error(); + InitializationError.prototype.constructor = InitializationError; + InitializationError.prototype.name = "InitializationError"; + + /** + * Defines an error, found by a schema, with an instance. + * This class can only be instantiated by {@link Report#addError}. + * + * @name ValidationError + * @class + * @see Report#addError + */ + + /** + * The URI of the instance that has the error. + * + * @name ValidationError.prototype.uri + * @type String + */ + + /** + * The URI of the schema that generated the error. + * + * @name ValidationError.prototype.schemaUri + * @type String + */ + + /** + * The name of the schema attribute that generated the error. + * + * @name ValidationError.prototype.attribute + * @type String + */ + + /** + * An user-friendly (English) message about what failed to validate. + * + * @name ValidationError.prototype.message + * @type String + */ + + /** + * The value of the schema attribute that generated the error. + * + * @name ValidationError.prototype.details + * @type Any + */ + + /** + * Reports are returned from validation methods to describe the result of a validation. + * + * @name Report + * @class + * @see JSONSchema#validate + * @see Environment#validate + */ + + function Report() { + /** + * An array of {@link ValidationError} objects that define all the errors generated by the schema against the instance. + * + * @name Report.prototype.errors + * @type Array + * @see Report#addError + */ + this.errors = []; + + /** + * A hash table of every instance and what schemas were validated against it. + *

+ * The key of each item in the table is the URI of the instance that was validated. + * The value of this key is an array of strings of URIs of the schema that validated it. + *

+ * + * @name Report.prototype.validated + * @type Object + * @see Report#registerValidation + * @see Report#isValidatedBy + */ + this.validated = {}; + + /** + * If the report is generated by {@link Environment#validate}, this field is the generated instance. + * + * @name Report.prototype.instance + * @type JSONInstance + * @see Environment#validate + */ + + /** + * If the report is generated by {@link Environment#validate}, this field is the generated schema. + * + * @name Report.prototype.schema + * @type JSONSchema + * @see Environment#validate + */ + + /** + * If the report is generated by {@link Environment#validate}, this field is the schema's schema. + * This value is the same as calling schema.getSchema(). + * + * @name Report.prototype.schemaSchema + * @type JSONSchema + * @see Environment#validate + * @see JSONSchema#getSchema + */ + } + + /** + * Adds a {@link ValidationError} object to the errors field. + * + * @param {JSONInstance|String} instance The instance (or instance URI) that is invalid + * @param {JSONSchema|String} schema The schema (or schema URI) that was validating the instance + * @param {String} attr The attribute that failed to validated + * @param {String} message A user-friendly message on why the schema attribute failed to validate the instance + * @param {Any} details The value of the schema attribute + */ + + Report.prototype.addError = function (instance, schema, attr, message, details) { + this.errors.push({ + uri : instance instanceof JSONInstance ? instance.getURI() : instance, + schemaUri : schema instanceof JSONInstance ? schema.getURI() : schema, + attribute : attr, + message : message, + details : stripInstances(details) + }); + }; + + /** + * Registers that the provided instance URI has been validated by the provided schema URI. + * This is recorded in the validated field. + * + * @param {String} uri The URI of the instance that was validated + * @param {String} schemaUri The URI of the schema that validated the instance + */ + + Report.prototype.registerValidation = function (uri, schemaUri) { + if (!this.validated[uri]) { + this.validated[uri] = [ schemaUri ]; + } else { + this.validated[uri].push(schemaUri); + } + }; + + /** + * Returns if an instance with the provided URI has been validated by the schema with the provided URI. + * + * @param {String} uri The URI of the instance + * @param {String} schemaUri The URI of a schema + * @returns {Boolean} If the instance has been validated by the schema. + */ + + Report.prototype.isValidatedBy = function (uri, schemaUri) { + return !!this.validated[uri] && searchArray(this.validated[uri], schemaUri) !== -1; + }; + + /** + * A wrapper class for binding an Environment, URI and helper methods to an instance. + * This class is most commonly instantiated with {@link Environment#createInstance}. + * + * @name JSONInstance + * @class + * @param {Environment} env The environment this instance belongs to + * @param {JSONInstance|Any} json The value of the instance + * @param {String} [uri] The URI of the instance. If undefined, the URI will be a randomly generated UUID. + * @param {String} [fd] The fragment delimiter for properties. If undefined, uses the environment default. + * @param {JSONInstance} parent The parent instance, if any + * @param {String|Number} parentKey The corresponding key on the parent instance, if any + */ + + function JSONInstance(env, json, uri, fd, parent, parentKey) { + if (json instanceof JSONInstance) { + if (typeof fd !== "string") { + fd = json._fd; + } + if (typeof uri !== "string") { + uri = json._uri; + } + json = json._value; + } + + if (typeof uri !== "string") { + uri = "urn:uuid:" + randomUUID() + "#"; + } else if (uri.indexOf(":") === -1) { + uri = formatURI(URI.resolve("urn:uuid:" + randomUUID() + "#", uri)); + } + + this._env = env; + this._value = json; + this._uri = uri; + this._fd = fd || this._env._options["defaultFragmentDelimiter"]; + this._parent = parent; + this._parentKey = parentKey; + } + + /** + * Returns the environment the instance is bound to. + * + * @returns {Environment} The environment of the instance + */ + + JSONInstance.prototype.getEnvironment = function () { + return this._env; + }; + + /** + * Returns the name of the type of the instance. + * + * @returns {String} The name of the type of the instance + */ + + JSONInstance.prototype.getType = function () { + return typeOf(this._value); + }; + + /** + * Returns the JSON value of the instance. + * + * @returns {Any} The actual JavaScript value of the instance + */ + + JSONInstance.prototype.getValue = function () { + return this._value; + }; + + /** + * Coerces the JSON value of the instance according to the matching type coercion function set in + * environment options, if any (option "typeCoercionFns"). + * + * @param {String} type + */ + JSONInstance.prototype.applyTypeCoercionIfSet = function (type) { + var coercionFns = this._env._options["typeCoercionFns"]; + if (coercionFns && coercionFns[type] && typeof coercionFns[type] === "function") { + this._value = coercionFns[type](this._value); + if (this._parent) { + this._parent.setValueOfProperty(this._parentKey, this._value); + } + } + }; + + /** + * Returns the URI of the instance. + * + * @returns {String} The URI of the instance + */ + + JSONInstance.prototype.getURI = function () { + return this._uri; + }; + + /** + * Returns a resolved URI of a provided relative URI against the URI of the instance. + * + * @param {String} uri The relative URI to resolve + * @returns {String} The resolved URI + */ + + JSONInstance.prototype.resolveURI = function (uri) { + return formatURI(URI.resolve(this._uri, uri)); + }; + + /** + * Returns an array of the names of all the properties. + * + * @returns {Array} An array of strings which are the names of all the properties + */ + + JSONInstance.prototype.getPropertyNames = function () { + return keys(this._value); + }; + + /** + * Returns a {@link JSONInstance} of the value of the provided property name. + * + * @param {String} key The name of the property to fetch + * @returns {JSONInstance} The instance of the property value + */ + + JSONInstance.prototype.getProperty = function (key) { + var value = this._value ? this._value[key] : undefined; + if (value instanceof JSONInstance) { + return value; + } + //else + return new JSONInstance(this._env, value, this._uri + this._fd + escapeURIComponent(key), + this._fd, this, key); + }; + + /** + * Returns all the property instances of the target instance. + *

+ * If the target instance is an Object, then the method will return a hash table of {@link JSONInstance}s of all the properties. + * If the target instance is an Array, then the method will return an array of {@link JSONInstance}s of all the items. + *

+ * + * @returns {Object|Array|undefined} The list of instances for all the properties + */ + + JSONInstance.prototype.getProperties = function () { + var type = typeOf(this._value), + self = this; + + if (type === "object") { + return mapObject(this._value, function (value, key) { + if (value instanceof JSONInstance) { + return value; + } + return new JSONInstance(self._env, value, self._uri + self._fd + escapeURIComponent(key), + self._fd, this, key); + }); + } else if (type === "array") { + return mapArray(this._value, function (value, key) { + if (value instanceof JSONInstance) { + return value; + } + return new JSONInstance(self._env, value, self._uri + self._fd + escapeURIComponent(key), + self._fd, this, key); + }); + } + }; + + /** + * Returns the JSON value of the provided property name. + * This method is a faster version of calling instance.getProperty(key).getValue(). + * + * @param {String} key The name of the property + * @returns {Any} The JavaScript value of the instance + * @see JSONInstance#getProperty + * @see JSONInstance#getValue + */ + + JSONInstance.prototype.getValueOfProperty = function (key) { + if (this._value) { + if (this._value[key] instanceof JSONInstance) { + return this._value[key]._value; + } + return this._value[key]; + } + }; + + /** + * Sets the JSON value of the provided property name. + * Used to persist value coercion results. + * + * @param {String} key The name of the property + * @see JSONInstance#applyTypeCoercionIfSet + */ + + JSONInstance.prototype.setValueOfProperty = function (key, value) { + if (this._value) { + if (this._value[key] instanceof JSONInstance) { + this._value[key]._value = value; + } + this._value[key] = value; + } + }; + + /** + * Return if the provided value is the same as the value of the instance. + * + * @param {JSONInstance|Any} instance The value to compare + * @returns {Boolean} If both the instance and the value match + */ + + JSONInstance.prototype.equals = function (instance) { + if (instance instanceof JSONInstance) { + return this._value === instance._value; + } + //else + return this._value === instance; + }; + + /** + * Warning: Not a generic clone function + * Produces a JSV acceptable clone + */ + + function clone(obj, deep) { + var newObj, x; + + if (obj instanceof JSONInstance) { + obj = obj.getValue(); + } + + switch (typeOf(obj)) { + case "object": + if (deep) { + newObj = {}; + for (x in obj) { + if (obj[x] !== O[x]) { + newObj[x] = clone(obj[x], deep); + } + } + return newObj; + } else { + return createObject(obj); + } + break; + case "array": + if (deep) { + newObj = new Array(obj.length); + x = obj.length; + while (--x >= 0) { + newObj[x] = clone(obj[x], deep); + } + return newObj; + } else { + return Array.prototype.slice.call(obj); + } + break; + default: + return obj; + } + } + + /** + * This class binds a {@link JSONInstance} with a {@link JSONSchema} to provided context aware methods. + * + * @name JSONSchema + * @class + * @param {Environment} env The environment this schema belongs to + * @param {JSONInstance|Any} json The value of the schema + * @param {String} [uri] The URI of the schema. If undefined, the URI will be a randomly generated UUID. + * @param {JSONSchema|Boolean} [schema] The schema to bind to the instance. If undefined, the environment's default schema will be used. If true, the instance's schema will be itself. + * @extends JSONInstance + */ + + function JSONSchema(env, json, uri, schema) { + var fr; + JSONInstance.call(this, env, json, uri); + + if (schema === true) { + this._schema = this; + } else if (json instanceof JSONSchema && !(schema instanceof JSONSchema)) { + this._schema = json._schema; //TODO: Make sure cross environments don't mess everything up + } else { + this._schema = schema instanceof JSONSchema ? schema : this._env.getDefaultSchema() || this._env.createEmptySchema(); + } + + //determine fragment delimiter from schema + fr = this._schema.getValueOfProperty("fragmentResolution"); + if (fr === "dot-delimited") { + this._fd = "."; + } else if (fr === "slash-delimited") { + this._fd = "/"; + } + + return this.rebuild(); //this works even when called with "new" + } + + JSONSchema.prototype = createObject(JSONInstance.prototype); + + /** + * Returns the schema of the schema. + * + * @returns {JSONSchema} The schema of the schema + */ + + JSONSchema.prototype.getSchema = function () { + var uri = this._refs && this._refs["describedby"], + newSchema; + + if (uri) { + newSchema = uri && this._env.findSchema(uri); + + if (newSchema) { + if (!newSchema.equals(this._schema)) { + this._schema = newSchema; + this.rebuild(); //if the schema has changed, the context has changed - so everything must be rebuilt + } + } else if (this._env._options["enforceReferences"]) { + throw new InitializationError(this, this._schema, "{describedby}", "Unknown schema reference", uri); + } + } + + return this._schema; + }; + + /** + * Returns the value of the provided attribute name. + *

+ * This method is different from {@link JSONInstance#getProperty} as the named property + * is converted using a parser defined by the schema's schema before being returned. This + * makes the return value of this method attribute dependent. + *

+ * + * @param {String} key The name of the attribute + * @param {Any} [arg] Some attribute parsers accept special arguments for returning resolved values. This is attribute dependent. + * @returns {JSONSchema|Any} The value of the attribute + */ + + JSONSchema.prototype.getAttribute = function (key, arg) { + var schemaProperty, parser, property, result, + schema = this.getSchema(); //we do this here to make sure the "describedby" reference has not changed, and that the attribute cache is up-to-date + + if (!arg && this._attributes && this._attributes.hasOwnProperty(key)) { + return this._attributes[key]; + } + + schemaProperty = schema.getProperty("properties").getProperty(key); + parser = schemaProperty.getValueOfProperty("parser"); + property = this.getProperty(key); + if (typeof parser === "function") { + result = parser(property, schemaProperty, arg); + if (!arg && this._attributes) { + this._attributes[key] = result; + } + return result; + } + //else + return property.getValue(); + }; + + /** + * Returns all the attributes of the schema. + * + * @returns {Object} A map of all parsed attribute values + */ + + JSONSchema.prototype.getAttributes = function () { + var properties, schemaProperties, key, schemaProperty, parser, + schema = this.getSchema(); //we do this here to make sure the "describedby" reference has not changed, and that the attribute cache is up-to-date + + if (!this._attributes && this.getType() === "object") { + properties = this.getProperties(); + schemaProperties = schema.getProperty("properties"); + this._attributes = {}; + for (key in properties) { + if (properties[key] !== O[key]) { + schemaProperty = schemaProperties && schemaProperties.getProperty(key); + parser = schemaProperty && schemaProperty.getValueOfProperty("parser"); + if (typeof parser === "function") { + this._attributes[key] = parser(properties[key], schemaProperty); + } else { + this._attributes[key] = properties[key].getValue(); + } + } + } + } + + return clone(this._attributes, false); + }; + + /** + * Convenience method for retrieving a link or link object from a schema. + * This method is the same as calling schema.getAttribute("links", [rel, instance])[0];. + * + * @param {String} rel The link relationship + * @param {JSONInstance} [instance] The instance to resolve any URIs from + * @returns {String|Object|undefined} If instance is provided, a string containing the resolve URI of the link is returned. + * If instance is not provided, a link object is returned with details of the link. + * If no link with the provided relationship exists, undefined is returned. + * @see JSONSchema#getAttribute + */ + + JSONSchema.prototype.getLink = function (rel, instance) { + var schemaLinks = this.getAttribute("links", [rel, instance]); + if (schemaLinks && schemaLinks.length && schemaLinks[schemaLinks.length - 1]) { + return schemaLinks[schemaLinks.length - 1]; + } + }; + + /** + * Validates the provided instance against the target schema and returns a {@link Report}. + * + * @param {JSONInstance|Any} instance The instance to validate; may be a {@link JSONInstance} or any JavaScript value + * @param {Report} [report] A {@link Report} to concatenate the result of the validation to. If undefined, a new {@link Report} is created. + * @param {JSONInstance} [parent] The parent/containing instance of the provided instance + * @param {JSONSchema} [parentSchema] The schema of the parent/containing instance + * @param {String} [name] The name of the parent object's property that references the instance + * @returns {Report} The result of the validation + */ + + JSONSchema.prototype.validate = function (instance, report, parent, parentSchema, name) { + var schemaSchema = this.getSchema(), + validator = schemaSchema.getValueOfProperty("validator"); + + if (!(instance instanceof JSONInstance)) { + instance = this.getEnvironment().createInstance(instance); + } + + if (!(report instanceof Report)) { + report = new Report(); + } + + if (this._env._options["validateReferences"] && this._refs) { + if (this._refs["describedby"] && !this._env.findSchema(this._refs["describedby"])) { + report.addError(this, this._schema, "{describedby}", "Unknown schema reference", this._refs["describedby"]); + } + if (this._refs["full"] && !this._env.findSchema(this._refs["full"])) { + report.addError(this, this._schema, "{full}", "Unknown schema reference", this._refs["full"]); + } + } + + if (typeof validator === "function" && !report.isValidatedBy(instance.getURI(), this.getURI())) { + report.registerValidation(instance.getURI(), this.getURI()); + validator(instance, this, schemaSchema, report, parent, parentSchema, name); + } + + return report; + }; + + /** @inner */ + function createFullLookupWrapper(func) { + return /** @inner */ function fullLookupWrapper() { + var scope = this, + stack = [], + uri = scope._refs && scope._refs["full"], + schema; + + while (uri) { + schema = scope._env.findSchema(uri); + if (schema) { + if (schema._value === scope._value) { + break; + } + scope = schema; + stack.push(uri); + uri = scope._refs && scope._refs["full"]; + if (stack.indexOf(uri) > -1) { + break; //stop infinite loop + } + } else if (scope._env._options["enforceReferences"]) { + throw new InitializationError(scope, scope._schema, "{full}", "Unknown schema reference", uri); + } else { + uri = null; + } + } + return func.apply(scope, arguments); + }; + } + + /** + * Wraps all JSONInstance methods with a function that resolves the "full" reference. + * + * @inner + */ + + (function () { + var key; + for (key in JSONSchema.prototype) { + if (JSONSchema.prototype[key] !== O[key] && typeOf(JSONSchema.prototype[key]) === "function") { + JSONSchema.prototype[key] = createFullLookupWrapper(JSONSchema.prototype[key]); + } + } + }()); + + /** + * Reinitializes/re-registers/rebuilds the schema. + *
+ * This is used internally, and should only be called when a schema's private variables are modified directly. + * + * @private + * @return {JSONSchema} The newly rebuilt schema + */ + + JSONSchema.prototype.rebuild = function () { + var instance = this, + initializer = instance.getSchema().getValueOfProperty("initializer"); + + //clear previous built values + instance._refs = null; + instance._attributes = null; + + if (typeof initializer === "function") { + instance = initializer(instance); + } + + //register schema + instance._env._schemas[instance._uri] = instance; + + //build & cache the rest of the schema + instance.getAttributes(); + + return instance; + }; + + /** + * Set the provided reference to the given value. + *
+ * References are used for establishing soft-links to other {@link JSONSchema}s. + * Currently, the following references are natively supported: + *
+ *
full
+ *
The value is the URI to the full instance of this instance.
+ *
describedby
+ *
The value is the URI to the schema of this instance.
+ *
+ * + * @param {String} name The name of the reference + * @param {String} uri The URI of the schema to refer to + */ + + JSONSchema.prototype.setReference = function (name, uri) { + if (!this._refs) { + this._refs = {}; + } + this._refs[name] = this.resolveURI(uri); + }; + + /** + * Returns the value of the provided reference name. + * + * @param {String} name The name of the reference + * @return {String} The value of the provided reference name + */ + + JSONSchema.prototype.getReference = function (name) { + return this._refs && this._refs[name]; + }; + + /** + * Merges two schemas/instances together. + */ + + function inherits(base, extra, extension) { + var baseType = typeOf(base), + extraType = typeOf(extra), + child, x; + + if (extraType === "undefined") { + return clone(base, true); + } else if (baseType === "undefined" || extraType !== baseType) { + return clone(extra, true); + } else if (extraType === "object") { + if (base instanceof JSONSchema) { + base = base.getAttributes(); + } + if (extra instanceof JSONSchema) { + extra = extra.getAttributes(); + if (extra["extends"] && extension && extra["extends"] instanceof JSONSchema) { + extra["extends"] = [ extra["extends"] ]; + } + } + child = clone(base, true); //this could be optimized as some properties get overwritten + for (x in extra) { + if (extra[x] !== O[x]) { + child[x] = inherits(base[x], extra[x], extension); + } + } + return child; + } else { + return clone(extra, true); + } + } + + /** + * An Environment is a sandbox of schemas thats behavior is different from other environments. + * + * @name Environment + * @class + */ + + function Environment() { + this._id = randomUUID(); + this._schemas = {}; + this._options = {}; + + this.createSchema({}, true, "urn:jsv:empty-schema#"); + } + + /** + * Returns a clone of the target environment. + * + * @returns {Environment} A new {@link Environment} that is a exact copy of the target environment + */ + + Environment.prototype.clone = function () { + var env = new Environment(); + env._schemas = createObject(this._schemas); + env._options = createObject(this._options); + + return env; + }; + + /** + * Returns a new {@link JSONInstance} of the provided data. + * + * @param {JSONInstance|Any} data The value of the instance + * @param {String} [uri] The URI of the instance. If undefined, the URI will be a randomly generated UUID. + * @returns {JSONInstance} A new {@link JSONInstance} from the provided data + */ + + Environment.prototype.createInstance = function (data, uri) { + uri = formatURI(uri); + + if (data instanceof JSONInstance && (!uri || data.getURI() === uri)) { + return data; + } + + return new JSONInstance(this, data, uri); + }; + + /** + * Creates a new {@link JSONSchema} from the provided data, and registers it with the environment. + * + * @param {JSONInstance|Any} data The value of the schema + * @param {JSONSchema|Boolean} [schema] The schema to bind to the instance. If undefined, the environment's default schema will be used. If true, the instance's schema will be itself. + * @param {String} [uri] The URI of the schema. If undefined, the URI will be a randomly generated UUID. + * @returns {JSONSchema} A new {@link JSONSchema} from the provided data + * @throws {InitializationError} If a schema that is not registered with the environment is referenced + */ + + Environment.prototype.createSchema = function (data, schema, uri) { + uri = formatURI(uri); + + if (data instanceof JSONSchema && (!uri || data._uri === uri) && (!schema || data.getSchema().equals(schema))) { + return data; + } + + return new JSONSchema(this, data, uri, schema); + }; + + /** + * Creates an empty schema. + * + * @returns {JSONSchema} The empty schema, who's schema is itself. + */ + + Environment.prototype.createEmptySchema = function () { + return this._schemas["urn:jsv:empty-schema#"]; + }; + + /** + * Returns the schema registered with the provided URI. + * + * @param {String} uri The absolute URI of the required schema + * @returns {JSONSchema|undefined} The request schema, or undefined if not found + */ + + Environment.prototype.findSchema = function (uri) { + return this._schemas[formatURI(uri)]; + }; + + /** + * Sets the specified environment option to the specified value. + * + * @param {String} name The name of the environment option to set + * @param {Any} value The new value of the environment option + */ + + Environment.prototype.setOption = function (name, value) { + this._options[name] = value; + }; + + /** + * Returns the specified environment option. + * + * @param {String} name The name of the environment option to set + * @returns {Any} The value of the environment option + */ + + Environment.prototype.getOption = function (name) { + return this._options[name]; + }; + + /** + * Sets the default fragment delimiter of the environment. + * + * @deprecated Use {@link Environment#setOption} with option "defaultFragmentDelimiter" + * @param {String} fd The fragment delimiter character + */ + + Environment.prototype.setDefaultFragmentDelimiter = function (fd) { + if (typeof fd === "string" && fd.length > 0) { + this._options["defaultFragmentDelimiter"] = fd; + } + }; + + /** + * Returns the default fragment delimiter of the environment. + * + * @deprecated Use {@link Environment#getOption} with option "defaultFragmentDelimiter" + * @returns {String} The fragment delimiter character + */ + + Environment.prototype.getDefaultFragmentDelimiter = function () { + return this._options["defaultFragmentDelimiter"]; + }; + + /** + * Sets the URI of the default schema for the environment. + * + * @deprecated Use {@link Environment#setOption} with option "defaultSchemaURI" + * @param {String} uri The default schema URI + */ + + Environment.prototype.setDefaultSchemaURI = function (uri) { + if (typeof uri === "string") { + this._options["defaultSchemaURI"] = formatURI(uri); + } + }; + + /** + * Returns the default schema of the environment. + * + * @returns {JSONSchema} The default schema + */ + + Environment.prototype.getDefaultSchema = function () { + return this.findSchema(this._options["defaultSchemaURI"]); + }; + + /** + * Validates both the provided schema and the provided instance, and returns a {@link Report}. + * If the schema fails to validate, the instance will not be validated. + * + * @param {JSONInstance|Any} instanceJSON The {@link JSONInstance} or JavaScript value to validate. + * @param {JSONSchema|Any} schemaJSON The {@link JSONSchema} or JavaScript value to use in the validation. This will also be validated againt the schema's schema. + * @returns {Report} The result of the validation + */ + + Environment.prototype.validate = function (instanceJSON, schemaJSON) { + var instance, + schema, + schemaSchema, + report = new Report(); + + try { + instance = this.createInstance(instanceJSON); + report.instance = instance; + } catch (e) { + report.addError(e.uri, e.schemaUri, e.attribute, e.message, e.details); + } + + try { + schema = this.createSchema(schemaJSON); + report.schema = schema; + + schemaSchema = schema.getSchema(); + report.schemaSchema = schemaSchema; + } catch (f) { + report.addError(f.uri, f.schemaUri, f.attribute, f.message, f.details); + } + + if (schemaSchema) { + schemaSchema.validate(schema, report); + } + + if (report.errors.length) { + return report; + } + + return schema.validate(instance, report); + }; + + /** + * @private + */ + + Environment.prototype._checkForInvalidInstances = function (stackSize, schemaURI) { + var result = [], + stack = [ + [schemaURI, this._schemas[schemaURI]] + ], + counter = 0, + item, uri, instance, properties, key; + + while (counter++ < stackSize && stack.length) { + item = stack.shift(); + uri = item[0]; + instance = item[1]; + + if (instance instanceof JSONSchema) { + if (this._schemas[instance._uri] !== instance) { + result.push("Instance " + uri + " does not match " + instance._uri); + } else { + //schema = instance.getSchema(); + //stack.push([uri + "/{schema}", schema]); + + properties = instance.getAttributes(); + for (key in properties) { + if (properties[key] !== O[key]) { + stack.push([uri + "/" + escapeURIComponent(key), properties[key]]); + } + } + } + } else if (typeOf(instance) === "object") { + properties = instance; + for (key in properties) { + if (properties.hasOwnProperty(key)) { + stack.push([uri + "/" + escapeURIComponent(key), properties[key]]); + } + } + } else if (typeOf(instance) === "array") { + properties = instance; + for (key = 0; key < properties.length; ++key) { + stack.push([uri + "/" + escapeURIComponent(key), properties[key]]); + } + } + } + + return result.length ? result : counter; + }; + + /** + * A globaly accessible object that provides the ability to create and manage {@link Environments}, + * as well as providing utility methods. + * + * @namespace + */ + + JSV = { + _environments : {}, + _defaultEnvironmentID : "", + + /** + * Returns if the provide value is an instance of {@link JSONInstance}. + * + * @param o The value to test + * @returns {Boolean} If the provide value is an instance of {@link JSONInstance} + */ + + isJSONInstance : function (o) { + return o instanceof JSONInstance; + }, + + /** + * Returns if the provide value is an instance of {@link JSONSchema}. + * + * @param o The value to test + * @returns {Boolean} If the provide value is an instance of {@link JSONSchema} + */ + + isJSONSchema : function (o) { + return o instanceof JSONSchema; + }, + + /** + * Creates and returns a new {@link Environment} that is a clone of the environment registered with the provided ID. + * If no environment ID is provided, the default environment is cloned. + * + * @param {String} [id] The ID of the environment to clone. If undefined, the default environment ID is used. + * @returns {Environment} A newly cloned {@link Environment} + * @throws {Error} If there is no environment registered with the provided ID + */ + + createEnvironment : function (id) { + id = id || this._defaultEnvironmentID; + + if (!this._environments[id]) { + throw new Error("Unknown Environment ID"); + } + //else + return this._environments[id].clone(); + }, + + Environment : Environment, + + /** + * Registers the provided {@link Environment} with the provided ID. + * + * @param {String} id The ID of the environment + * @param {Environment} env The environment to register + */ + + registerEnvironment : function (id, env) { + id = id || (env || 0)._id; + if (id && !this._environments[id] && env instanceof Environment) { + env._id = id; + this._environments[id] = env; + } + }, + + /** + * Sets which registered ID is the default environment. + * + * @param {String} id The ID of the registered environment that is default + * @throws {Error} If there is no registered environment with the provided ID + */ + + setDefaultEnvironmentID : function (id) { + if (typeof id === "string") { + if (!this._environments[id]) { + throw new Error("Unknown Environment ID"); + } + + this._defaultEnvironmentID = id; + } + }, + + /** + * Returns the ID of the default environment. + * + * @returns {String} The ID of the default environment + */ + + getDefaultEnvironmentID : function () { + return this._defaultEnvironmentID; + }, + + // + // Utility Functions + // + + /** + * Returns the name of the type of the provided value. + * + * @event //utility + * @param {Any} o The value to determine the type of + * @returns {String} The name of the type of the value + */ + typeOf : typeOf, + + /** + * Return a new object that inherits all of the properties of the provided object. + * + * @event //utility + * @param {Object} proto The prototype of the new object + * @returns {Object} A new object that inherits all of the properties of the provided object + */ + createObject : createObject, + + /** + * Returns a new object with each property transformed by the iterator. + * + * @event //utility + * @param {Object} obj The object to transform + * @param {Function} iterator A function that returns the new value of the provided property + * @param {Object} [scope] The value of this in the iterator + * @returns {Object} A new object with each property transformed + */ + mapObject : mapObject, + + /** + * Returns a new array with each item transformed by the iterator. + * + * @event //utility + * @param {Array} arr The array to transform + * @param {Function} iterator A function that returns the new value of the provided item + * @param {Object} scope The value of this in the iterator + * @returns {Array} A new array with each item transformed + */ + mapArray : mapArray, + + /** + * Returns a new array that only contains the items allowed by the iterator. + * + * @event //utility + * @param {Array} arr The array to filter + * @param {Function} iterator The function that returns true if the provided property should be added to the array + * @param {Object} scope The value of this within the iterator + * @returns {Array} A new array that contains the items allowed by the iterator + */ + filterArray : filterArray, + + /** + * Returns the first index in the array that the provided item is located at. + * + * @event //utility + * @param {Array} arr The array to search + * @param {Any} o The item being searched for + * @returns {Number} The index of the item in the array, or -1 if not found + */ + searchArray : searchArray, + + /** + * Returns an array representation of a value. + *
    + *
  • For array-like objects, the value will be casted as an Array type.
  • + *
  • If an array is provided, the function will simply return the same array.
  • + *
  • For a null or undefined value, the result will be an empty Array.
  • + *
  • For all other values, the value will be the first element in a new Array.
  • + *
+ * + * @event //utility + * @param {Any} o The value to convert into an array + * @returns {Array} The value as an array + */ + toArray : toArray, + + /** + * Returns an array of the names of all properties of an object. + * + * @event //utility + * @param {Object|Array} o The object in question + * @returns {Array} The names of all properties + */ + keys : keys, + + /** + * Mutates the array by pushing the provided value onto the array only if it is not already there. + * + * @event //utility + * @param {Array} arr The array to modify + * @param {Any} o The object to add to the array if it is not already there + * @returns {Array} The provided array for chaining + */ + pushUnique : pushUnique, + + /** + * Mutates the array by removing the first item that matches the provided value in the array. + * + * @event //utility + * @param {Array} arr The array to modify + * @param {Any} o The object to remove from the array + * @returns {Array} The provided array for chaining + */ + popFirst : popFirst, + + /** + * Creates a copy of the target object. + *

+ * This method will create a new instance of the target, and then mixin the properties of the target. + * If deep is true, then each property will be cloned before mixin. + *

+ *

Warning: This is not a generic clone function, as it will only properly clone objects and arrays.

+ * + * @event //utility + * @param {Any} o The value to clone + * @param {Boolean} [deep=false] If each property should be recursively cloned + * @returns A cloned copy of the provided value + */ + clone : clone, + + /** + * Generates a pseudo-random UUID. + * + * @event //utility + * @returns {String} A new universally unique ID + */ + randomUUID : randomUUID, + + /** + * Properly escapes a URI component for embedding into a URI string. + * + * @event //utility + * @param {String} str The URI component to escape + * @returns {String} The escaped URI component + */ + escapeURIComponent : escapeURIComponent, + + /** + * Returns a URI that is formated for JSV. Currently, this only ensures that the URI ends with a hash tag (#). + * + * @event //utility + * @param {String} uri The URI to format + * @returns {String} The URI formatted for JSV + */ + formatURI : formatURI, + + /** + * Merges two schemas/instance together. + * + * @event //utility + * @param {JSONSchema|Any} base The old value to merge + * @param {JSONSchema|Any} extra The new value to merge + * @param {Boolean} extension If the merge is a JSON Schema extension + * @return {Any} The modified base value + */ + + inherits : inherits, + + /** + * @private + * @event //utility + */ + + InitializationError : InitializationError + }; + + this.JSV = JSV; //set global object + exports.JSV = JSV; //export to CommonJS + + require("./environments"); //load default environments + +}());