diff --git a/lib/json-schema-draft-03.js b/lib/json-schema-draft-03.js index e0e4843..354f6ea 100644 --- a/lib/json-schema-draft-03.js +++ b/lib/json-schema-draft-03.js @@ -1,6 +1,6 @@ /** * json-schema-draft-03 Environment - * + * * @fileOverview Implementation of the third revision of the JSON Schema specification draft. * @author Gary Court * @version 1.5.1 @@ -9,17 +9,17 @@ /* * 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 @@ -29,7 +29,7 @@ * 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. @@ -45,77 +45,77 @@ ENVIRONMENT, SCHEMA_00_JSON, HYPERSCHEMA_00_JSON, - LINKS_00_JSON, + LINKS_00_JSON, SCHEMA_00, HYPERSCHEMA_00, - LINKS_00, + LINKS_00, SCHEMA_01_JSON, HYPERSCHEMA_01_JSON, - LINKS_01_JSON, + LINKS_01_JSON, SCHEMA_01, HYPERSCHEMA_01, - LINKS_01, + LINKS_01, SCHEMA_02_JSON, HYPERSCHEMA_02_JSON, LINKS_02_JSON, SCHEMA_02, HYPERSCHEMA_02, - LINKS_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"], @@ -125,15 +125,15 @@ "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, + instance, self.getEnvironment().findSchema(self.resolveURI("#")) ); } else if (instance.getType() === "array") { @@ -145,16 +145,16 @@ //else return "any"; }, - + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { var requiredTypes = JSV.toArray(schema.getAttribute("type")), - x, xl, type, subreport, typeValidators; - + 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 + + //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)) { @@ -165,7 +165,9 @@ return true; //instance matches this schema } } else { - if (typeValidators[type] !== O[type] && typeof typeValidators[type] === "function") { + instance.applyTypeCoercionIfSet(type); + + if (typeValidators[type] !== O[type] && typeof typeValidators[type] === "function") { if (typeValidators[type](instance, report)) { return true; //type is valid } @@ -174,7 +176,7 @@ } } } - + //if we get to this point, type is invalid report.addError(instance, schema, "type", "Instance is not a required type", requiredTypes); return false; @@ -182,16 +184,16 @@ //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(); @@ -207,7 +209,7 @@ //else return {}; }, - + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { var propertySchemas, key; //this attribute is for object type instances only @@ -223,13 +225,13 @@ } } }, - + "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("#"))); @@ -241,15 +243,15 @@ //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; @@ -268,30 +270,30 @@ } } }, - + "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("#"))); @@ -301,7 +303,7 @@ //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 @@ -321,11 +323,11 @@ } } }, - + "requires" : { "type" : ["string", {"$ref" : "#"}], "optional" : true, - + "parser" : function (instance, self) { if (instance.getType() === "string") { return instance.getValue(); @@ -333,7 +335,7 @@ 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") { @@ -348,17 +350,17 @@ } } }, - + "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") { @@ -370,17 +372,17 @@ } } }, - + "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") { @@ -392,13 +394,13 @@ } } }, - + "minimumCanEqual" : { "type" : "boolean", "optional" : true, "requires" : "minimum", "default" : true, - + "parser" : function (instance, self) { if (instance.getType() === "boolean") { return instance.getValue(); @@ -407,13 +409,13 @@ return true; } }, - + "maximumCanEqual" : { "type" : "boolean", "optional" : true, "requires" : "maximum", "default" : true, - + "parser" : function (instance, self) { if (instance.getType() === "boolean") { return instance.getValue(); @@ -422,13 +424,13 @@ return true; } }, - + "minItems" : { "type" : "integer", "optional" : true, "minimum" : 0, "default" : 0, - + "parser" : function (instance, self) { if (instance.getType() === "number") { return instance.getValue(); @@ -436,7 +438,7 @@ //else return 0; }, - + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { var minItems; if (instance.getType() === "array") { @@ -447,18 +449,18 @@ } } }, - + "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") { @@ -469,18 +471,18 @@ } } }, - + "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 { @@ -493,13 +495,13 @@ } } }, - + "minLength" : { "type" : "integer", "optional" : true, "minimum" : 0, "default" : 0, - + "parser" : function (instance, self) { if (instance.getType() === "number") { return instance.getValue(); @@ -507,7 +509,7 @@ //else return 0; }, - + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { var minLength; if (instance.getType() === "string") { @@ -518,17 +520,17 @@ } } }, - + "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") { @@ -539,19 +541,19 @@ } } }, - + "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") { @@ -567,27 +569,27 @@ } } }, - + "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") { @@ -598,31 +600,31 @@ } } }, - + "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") { @@ -636,27 +638,27 @@ } } }, - + "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]; @@ -667,14 +669,14 @@ 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; + 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); @@ -682,23 +684,23 @@ } */ } - + //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("#"))); @@ -708,7 +710,7 @@ }); } }, - + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { var extensions = schema.getAttribute("extends"), x, xl; if (extensions) { @@ -723,24 +725,24 @@ } } }, - + "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(), + 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")) { @@ -751,7 +753,7 @@ } } } - + for (x = 0, xl = propNames.length; x < xl; ++x) { if (attributeSchemas[propNames[x]] !== O[propNames[x]]) { validator = attributeSchemas[propNames[x]].getValueOfProperty("validator"); @@ -762,17 +764,17 @@ } } }; - + 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"], @@ -780,7 +782,7 @@ 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); @@ -788,20 +790,20 @@ } 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; + var value; if (p1 === selfReferenceVariable) { value = instance.getValue(); } else { @@ -812,36 +814,36 @@ 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") { @@ -855,103 +857,103 @@ } } }, - + "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(), @@ -968,7 +970,7 @@ } } }, - + "parser" : function (instance, self) { var selfProperties = self.getProperty("properties"); if (instance.getType() === "object") { @@ -985,63 +987,63 @@ 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")) { @@ -1056,23 +1058,23 @@ } } }, - + "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") { @@ -1091,51 +1093,51 @@ } } }, - + "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(); @@ -1149,7 +1151,7 @@ report.addError(schema, self, "patternProperties", "Invalid pattern", pattern); } } - + if (regexp) { for (key in properties) { if (properties[key] !== O[key] && regexp.test(key)) { @@ -1160,22 +1162,22 @@ } } } - + 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") { @@ -1191,7 +1193,7 @@ } } }, - + "additionalProperties" : { "validator" : function (instance, schema, self, report, parent, parentSchema, name) { var additionalProperties, propertySchemas, properties, matchedProperties, key; @@ -1212,16 +1214,16 @@ } } }, - + "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; @@ -1240,20 +1242,20 @@ } } }, - + "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); @@ -1264,31 +1266,31 @@ } } }, - + "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" : { @@ -1298,7 +1300,7 @@ } }, "default" : {}, - + "parser" : function (instance, self, arg) { function parseProperty(property) { var type = property.getType(); @@ -1308,7 +1310,7 @@ return property.getEnvironment().createSchema(property, self.getEnvironment().findSchema(self.resolveURI("#"))); } } - + if (instance.getType() === "object") { if (arg) { return parseProperty(instance.getProperty(arg)); @@ -1319,7 +1321,7 @@ //else return {}; }, - + "validator" : function (instance, schema, self, report, parent, parentSchema, name) { var dependencies, key, dependency, type, x, xl; if (instance.getType() === "object") { @@ -1346,33 +1348,33 @@ } } }, - + "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; @@ -1385,7 +1387,7 @@ } } }, - + "maximum" : { "validator" : function (instance, schema, self, report, parent, parentSchema, name) { var maximum, exclusiveMaximum; @@ -1398,56 +1400,56 @@ } } }, - + "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); @@ -1455,98 +1457,98 @@ 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 + +}()); diff --git a/lib/jsv.js b/lib/jsv.js index 1ca04ae..fef02f8 100644 --- a/lib/jsv.js +++ b/lib/jsv.js @@ -1,6 +1,6 @@ /** * JSV: JSON Schema Validator - * + * * @fileOverview A JavaScript implementation of a extendable, fully compliant JSON Schema validator. * @author Gary Court * @version 4.0.2 @@ -9,17 +9,17 @@ /* * 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 @@ -29,7 +29,7 @@ * 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. @@ -43,30 +43,30 @@ var exports = exports || this, }; (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) { @@ -76,7 +76,7 @@ var exports = exports || this, } return newObj; } - + /** @ignore */ mapArray = function (arr, func, scope) { var x = 0, xl = arr.length, newArr = new Array(xl); @@ -85,14 +85,14 @@ var exports = exports || this, } 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 = []; @@ -103,14 +103,14 @@ var exports = exports || this, } 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; @@ -121,21 +121,21 @@ var exports = exports || this, } 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) { @@ -150,17 +150,17 @@ var exports = exports || this, } 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) { @@ -168,7 +168,7 @@ var exports = exports || this, } return arr; } - + function randomUUID() { return [ I2H[Math.floor(Math.random() * 0x10)], @@ -208,23 +208,23 @@ var exports = exports || this, 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": @@ -232,21 +232,21 @@ var exports = exports || this, 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 @@ -255,10 +255,10 @@ var exports = exports || this, * @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; @@ -266,125 +266,125 @@ var exports = exports || this, 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}. - * + * 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 callingschema.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,
@@ -394,15 +394,15 @@ var exports = exports || this,
details : stripInstances(details)
});
};
-
+
/**
- * Registers that the provided instance URI has been validated by the provided schema URI.
+ * 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 ];
@@ -410,32 +410,34 @@ var exports = exports || this,
this.validated[uri].push(schemaUri);
}
};
-
+
/**
- * Returns if an instance with the provided URI has been validated by the schema with the provided URI.
- *
+ * 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.
+ * 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} [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) {
+
+ function JSONInstance(env, json, uri, fd, parent, parentKey) {
if (json instanceof JSONInstance) {
if (typeof fd !== "string") {
fd = json._fd;
@@ -445,137 +447,158 @@ var exports = exports || this,
}
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.
- *
+ * 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);
+ 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 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); + 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); + 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. + * Returns the JSON value of the provided property name. * This method is a faster version of callinginstance.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) {
@@ -584,14 +607,31 @@ var exports = exports || this,
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;
@@ -599,19 +639,19 @@ var exports = exports || this,
//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) {
@@ -642,23 +682,23 @@ var exports = exports || this,
return obj;
}
}
-
+
/**
- * This class binds a {@link JSONInstance} with a {@link JSONSchema} to provided context aware methods.
- *
+ * 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 {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)) {
@@ -666,7 +706,7 @@ var exports = exports || this,
} 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") {
@@ -674,25 +714,25 @@ var exports = exports || this,
} 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;
@@ -702,31 +742,31 @@ var exports = exports || this,
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 + * 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); @@ -740,17 +780,17 @@ var exports = exports || this, //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"); @@ -767,14 +807,14 @@ var exports = exports || this, } } } - + return clone(this._attributes, false); }; - + /** - * Convenience method for retrieving a link or link object from a schema. + * Convenience method for retrieving a link or link object from a schema. * This method is the same as callingschema.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.
@@ -782,37 +822,37 @@ var exports = exports || this,
* 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 {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"]);
@@ -821,15 +861,15 @@ var exports = exports || this,
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() {
@@ -837,7 +877,7 @@ var exports = exports || this,
stack = [],
uri = scope._refs && scope._refs["full"],
schema;
-
+
while (uri) {
schema = scope._env.findSchema(uri);
if (schema) {
@@ -859,13 +899,13 @@ var exports = exports || this,
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) {
@@ -874,37 +914,37 @@ var exports = exports || this,
}
}
}());
-
+
/**
* Reinitializes/re-registers/rebuilds the schema.
* describedby
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.
+ * @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
+ * @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}.
+ * 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]) {
@@ -1219,71 +1259,71 @@ var exports = exports || this,
}
}
}
-
+
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) {
@@ -1291,38 +1331,38 @@ var exports = exports || this,
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.
*
@@ -1331,7 +1371,7 @@ var exports = exports || this,
* @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.
*
@@ -1340,7 +1380,7 @@ var exports = exports || this,
* @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.
*
@@ -1351,10 +1391,10 @@ var exports = exports || this,
* @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
@@ -1362,7 +1402,7 @@ var exports = exports || this,
* @returns {Array} A new array with each item transformed
*/
mapArray : mapArray,
-
+
/**
* Returns a new array that only contains the items allowed by the iterator.
*
@@ -1373,7 +1413,7 @@ var exports = exports || this,
* @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.
*
@@ -1383,7 +1423,7 @@ var exports = exports || this,
* @returns {Number} The index of the item in the array, or -1
if not found
*/
searchArray : searchArray,
-
+
/**
* Returns an array representation of a value.
*
@@ -1435,63 +1475,63 @@ var exports = exports || this,
* 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 {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
+
+}());
diff --git a/tests/tests3.js b/tests/tests3.js
index bf036e4..d8b556c 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,57 @@ 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",
+ subObject: {
+ integerProp: "789"
+ }
+ };
+ var schema = {
+ type: "object",
+ properties: {
+ "booleanProp": {type: "boolean"},
+ "numberProp": {type: "number"},
+ "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(typeof obj.numberProp, "number");
+ equal(obj.subObject.integerProp, 789);
+ equal(typeof obj.subObject.integerProp, "number");
+});
+
+}