Skip to content

Commit

Permalink
Links to unregistered schemas will now throw an error. This can be di…
Browse files Browse the repository at this point in the history
…sabled by setting the environment option "validateReferences" to false.

Environment#validate now catches errors (such as InitializationErrors) and adds them to it's returned report.
Report#addError now accepts instance/schema URI strings.
Fixed bug where empty schemas would not be registered.
Added private method Environment#_checkForInvalidInstances, for testing the reliability of circular schemas.
Fixed bug where schemas in the "disallow" attribute would not validate. (Identified by henchan)
  • Loading branch information
garycourt committed Mar 9, 2011
1 parent 85902d6 commit e05e259
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 45 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
@@ -1,5 +1,14 @@
# Changelog

## 3.5 (2011/03/07)

* Links to unregistered schemas will now throw an error. This can be disabled by setting the environment option "validateReferences" to false.
* Environment#validate now catches errors (such as InitializationErrors) and adds them to it's returned report.
* Report#addError now accepts instance/schema URI strings.
* Fixed bug where empty schemas would not be registered.
* Added private method Environment#_checkForInvalidInstances, for testing the reliability of circular schemas.
* Fixed bug where schemas in the "disallow" attribute would not validate. (Identified by henchan)

## 3.4 (2011/03/06)

* Fixed bug with "id", "$ref", "$schema" attributes not resolving properly under the "http://json-schema.org/draft-03/schema#" schema. (Identified by dougtreder)
Expand Down
5 changes: 4 additions & 1 deletion README.md
Expand Up @@ -80,7 +80,10 @@ When creating an environment, you can optionally specify how you want that envir
* Schemas are now versioned under the URIs "http://json-schema.org/draft-XX/", where XX is the draft number

In addition to this, all schemas from the previous versions of the JSON Schema draft are included in this environment, and are backwards compatible (where possible) with it's previous version.
This backwards compatibility can be disabled with the new environment option "strict" is set to `true`.
This backwards compatibility can be disabled with the environment option `strict` is set to `true`.

This environment will also validate links/references to other schemas, and will throw an error if a referenced schema has not already been registered with the environment.
This feature can be disabled with the environment option `validateReferences` is set to `false`.

This is currently the default environment.

Expand Down
84 changes: 65 additions & 19 deletions lib/json-schema-draft-03.js
Expand Up @@ -3,7 +3,7 @@
*
* @fileOverview Implementation of the third revision of the JSON Schema specification draft.
* @author <a href="mailto:gary.court@gmail.com">Gary Court</a>
* @version 1.2
* @version 1.3
* @see http://github.com/garycourt/JSV
*/

Expand Down Expand Up @@ -41,6 +41,7 @@
(function () {
var O = {},
JSV = require('./jsv').JSV,
InitializationError,
TYPE_VALIDATORS,
ENVIRONMENT,
SCHEMA_00_JSON,
Expand Down Expand Up @@ -68,6 +69,20 @@
HYPERSCHEMA_03,
LINKS_03;

InitializationError = function InitializationError(instance, schema, attr, message, details) {
Error.call(this, message);

this.uri = instance.getURI();
this.schemaUri = schema.getURI();
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";

TYPE_VALIDATORS = {
"string" : function (instance, report) {
return instance.getType() === "string";
Expand Down Expand Up @@ -104,6 +119,7 @@

ENVIRONMENT = new JSV.Environment();
ENVIRONMENT.setOption("strict", false);
ENVIRONMENT.setOption("validateReferences", false); //updated later

//
// draft-00
Expand Down Expand Up @@ -895,19 +911,29 @@

//if there is a link to a different schema, update instance
link = instance._schema.getLink("describedby", instance);
if (link && instance._schema._uri !== link && instance._env._schemas[link]) {
instance._schema = instance._env._schemas[link];
initializer = instance._schema.getValueOfProperty("initializer");
if (typeof initializer === "function") {
return initializer(instance); //this function will finish initialization
if (link && instance._schema._uri !== link) {
if (instance._env._schemas[link]) {
instance._schema = instance._env._schemas[link];
initializer = instance._schema.getValueOfProperty("initializer");
if (typeof initializer === "function") {
return initializer(instance); //this function will finish initialization
} else {
return instance; //no further initialization
}
} else if (instance._env._options["validateReferences"]) {
throw new InitializationError(instance, instance._schema, "{link:describedby}", "Unknown schema reference", link);
}
}

//if there is a link to the full representation, replace instance
link = instance._schema.getLink("full", instance);
if (link && instance._uri !== link && instance._env._schemas[link]) {
instance = instance._env._schemas[link];
return instance; //retrieved schemas are guaranteed to be initialized
if (link && instance._uri !== link) {
if (instance._env._schemas[link]) {
instance = instance._env._schemas[link];
return instance; //retrieved schemas are guaranteed to be initialized
} else if (instance._env._options["validateReferences"]) {
throw new InitializationError(instance, instance._schema, "{link:full}", "Unknown schema reference", link);
}
}

//extend schema
Expand Down Expand Up @@ -1429,7 +1455,9 @@
"disallow" : {
"items" : {
"type" : ["string", {"$ref" : "#"}]
}
},

"parser" : SCHEMA_02_JSON["properties"]["type"]["parser"]
},

"id" : {
Expand Down Expand Up @@ -1462,21 +1490,31 @@
//if there is a link to a different schema, update instance
if (schemaLink) {
link = instance.resolveURI(schemaLink);
if (link && instance._schema._uri !== link && instance._env._schemas[link]) {
instance._schema = instance._env._schemas[link];
initializer = instance._schema.getValueOfProperty("initializer");
if (typeof initializer === "function") {
return initializer(instance); //this function will finish initialization
if (link && instance._schema._uri !== link) {
if (instance._env._schemas[link]) {
instance._schema = instance._env._schemas[link];
initializer = instance._schema.getValueOfProperty("initializer");
if (typeof initializer === "function") {
return initializer(instance); //this function will finish initialization
} else {
return instance; //no further initialization
}
} else if (instance._env._options["validateReferences"]) {
throw new InitializationError(instance, instance._schema, "$schema", "Unknown schema reference", link);
}
}
}

//if there is a link to the full representation, replace instance
if (refLink) {
link = instance.resolveURI(refLink);
if (link && instance._uri !== link && instance._env._schemas[link]) {
instance = instance._env._schemas[link];
return instance; //retrieved schemas are guaranteed to be initialized
if (link && instance._uri !== link) {
if (instance._env._schemas[link]) {
instance = instance._env._schemas[link];
return instance; //retrieved schemas are guaranteed to be initialized
} else if (instance._env._options["validateReferences"]) {
throw new InitializationError(instance, instance._schema, "$ref", "Unknown schema reference", link);
}
}
}

Expand Down Expand Up @@ -1544,14 +1582,22 @@
}
});

ENVIRONMENT.setOption("validateReferences", true);
ENVIRONMENT.setOption("defaultSchemaURI", "http://json-schema.org/draft-03/schema#"); //update later

//prevent reference errors
ENVIRONMENT.createSchema({}, true, "http://json-schema.org/draft-03/schema#");
ENVIRONMENT.createSchema({}, true, "http://json-schema.org/draft-03/hyper-schema#");
ENVIRONMENT.createSchema({}, true, "http://json-schema.org/draft-03/links#");

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#");
LINKS_03 = ENVIRONMENT.createSchema(LINKS_03_JSON, true, "http://json-schema.org/draft-03/links#");

ENVIRONMENT.setOption("defaultSchemaURI", "http://json-schema.org/draft-03/hyper-schema#");

LINKS_03 = ENVIRONMENT.createSchema(LINKS_03_JSON, HYPERSCHEMA_03, "http://json-schema.org/draft-03/links#");
//We need to reinitialize these schemas as they reference each other
HYPERSCHEMA_03 = ENVIRONMENT.createSchema(HYPERSCHEMA_03.getValue(), HYPERSCHEMA_03, "http://json-schema.org/draft-03/hyper-schema#");

ENVIRONMENT.setOption("latestJSONSchemaSchemaURI", "http://json-schema.org/draft-03/schema#");
ENVIRONMENT.setOption("latestJSONSchemaHyperSchemaURI", "http://json-schema.org/draft-03/hyper-schema#");
Expand Down
92 changes: 79 additions & 13 deletions lib/jsv.js
Expand Up @@ -3,7 +3,7 @@
*
* @fileOverview A JavaScript implementation of a extendable, fully compliant JSON Schema validator.
* @author <a href="mailto:gary.court@gmail.com">Gary Court</a>
* @version 3.4
* @version 3.5
* @see http://github.com/garycourt/JSV
*/

Expand Down Expand Up @@ -327,17 +327,17 @@ var exports = exports || this,
/**
* Adds a {@link ValidationError} object to the <a href="#errors"><code>errors</code></a> field.
*
* @param {JSONInstance} instance The instance that is invalid
* @param {JSONSchema} schema The schema that was validating the instance
* @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.getURI(),
schemaUri : schema.getURI(),
uri : instance instanceof JSONInstance ? instance.getURI() : instance,
schemaUri : instance instanceof JSONInstance ? schema.getURI() : schema,
attribute : attr,
message : message,
details : details
Expand Down Expand Up @@ -853,6 +853,7 @@ var exports = exports || this,
* @param {JSONSchema|Boolean} [schema] The schema to bind to the instance. If <code>undefined</code>, the environment's default schema will be used. If <code>true</code>, 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) {
Expand Down Expand Up @@ -889,7 +890,9 @@ var exports = exports || this,
*/

Environment.prototype.createEmptySchema = function () {
return JSONSchema.createEmptySchema(this);
var schema = JSONSchema.createEmptySchema(this);
this._schemas[schema._uri] = schema;
return schema;
};

/**
Expand Down Expand Up @@ -982,16 +985,31 @@ var exports = exports || this,
*/

Environment.prototype.validate = function (instanceJSON, schemaJSON) {
var instance = this.createInstance(instanceJSON),
schema = this.createSchema(schemaJSON),
schemaSchema = schema.getSchema(),
var instance,
schema,
schemaSchema,
report = new Report();

report.instance = instance;
report.schema = schema;
report.schemaSchema = schemaSchema;
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 (e) {
report.addError(e.uri, e.schemaUri, e.attribute, e.message, e.details);
}

schemaSchema.validate(schema, report);
if (schemaSchema) {
schemaSchema.validate(schema, report);
}

if (report.errors.length) {
return report;
Expand All @@ -1000,6 +1018,54 @@ var exports = exports || this,
return schema.validate(instance, report);
};

/**
* @private
*/

Environment.prototype._checkForInvalidInstances = function (stackSize, schemaURI) {
var result = [],
stack = [
[schemaURI, this._schemas[schemaURI]]
],
counter = 0,
item, uri, instance, schema, 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},
Expand Down
2 changes: 1 addition & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name" : "JSV",
"version" : "3.4.0",
"version" : "3.5.0",
"description" : "A JavaScript implementation of a extendable, fully compliant JSON Schema validator.",
"homepage" : "http://github.com/garycourt/JSV",
"author" : "Gary Court <gary.court@gmail.com>",
Expand Down

0 comments on commit e05e259

Please sign in to comment.