fixed #52 (compileAsync); loadSchema should be called only once for any schema; error is always passed via callback

master
Evgeny Poberezkin 2015-09-23 23:42:33 +01:00
parent d6ed06766c
commit a67b661e10
4 changed files with 157 additions and 24 deletions

View File

@ -30,6 +30,7 @@ function Ajv(opts) {
this._refs = {};
this._formats = formats(this.opts.format);
this._cache = this.opts.cache || new Cache;
this._loadingSchemas = {};
// this is done on purpose, so that methods are bound to the instance
// (without using bind) so that they can be used without the instance
@ -91,12 +92,16 @@ function Ajv(opts) {
* @param {Function} callback node-style callback, it is always called with 2 parameters: error (or null) and validating function.
*/
function compileAsync(schema, callback) {
var schemaObj = _addSchema(schema);
if (schemaObj.validate) {
setTimeout(function() {
callback(null, schemaObj.validate);
});
} else {
var schemaObj;
try {
schemaObj = _addSchema(schema);
} catch(e) {
setTimeout(function() { callback(e); });
return;
}
if (schemaObj.validate)
setTimeout(function() { callback(null, schemaObj.validate); });
else {
if (typeof self.opts.loadSchema != 'function')
throw new Error('options.loadSchema should be a function');
_compileAsync(schema, callback, true);
@ -109,27 +114,51 @@ function Ajv(opts) {
try { validate = compile(schema); }
catch(e) {
if (e.missingSchema) loadMissingSchema(e);
else if (firstCall) setTimeout(function() { callback(e); });
else callback(e);
return;
}
if (firstCall)
setTimeout(function() {
callback(null, validate);
});
else
callback(null, validate);
if (firstCall) setTimeout(function() { callback(null, validate); });
else callback(null, validate);
function loadMissingSchema(e) {
var ref = e.missingSchema;
if (self._refs[ref] || self._schemas[ref])
return callback(new Error('Schema ' + ref + ' is loaded but' + e.missingRef + 'cannot be resolved'));
self.opts.loadSchema(ref, function(err, sch) {
var _callbacks = self._loadingSchemas[ref];
if (_callbacks) {
if (typeof _callbacks == 'function')
self._loadingSchemas[ref] = [_callbacks, schemaLoaded];
else
_callbacks[_callbacks.length] = schemaLoaded;
} else {
self._loadingSchemas[ref] = schemaLoaded;
self.opts.loadSchema(ref, function (err, sch) {
var _callbacks = self._loadingSchemas[ref];
delete self._loadingSchemas[ref];
if (typeof _callbacks == 'function')
_callbacks(err, sch);
else
for (var i=0; i<_callbacks.length; i++)
_callbacks[i](err, sch);
});
}
function schemaLoaded(err, sch) {
if (err) callback(err);
else {
addSchema(sch, ref);
if (!(self._refs[ref] || self._schemas[ref])) {
try {
addSchema(sch, ref);
} catch(e) {
callback(e);
return;
}
}
_compileAsync(schema, callback);
}
});
}
}
}

View File

@ -1,6 +1,6 @@
{
"name": "ajv",
"version": "1.4.1",
"version": "1.4.2",
"description": "Another JSON Schema Validator",
"main": "lib/ajv.js",
"files": [

View File

@ -7,7 +7,7 @@ var Ajv = require(typeof window == 'object' ? 'ajv' : '../lib/ajv')
describe('compileAsync method', function() {
var ajv;
var ajv, loadCallCount;
var SCHEMAS = {
"http://example.com/object.json": {
@ -39,10 +39,18 @@ describe('compileAsync method', function() {
"b": { "$ref": "parent.json" }
},
"required": ["b"]
},
"http://example.com/invalid.json": {
"id": "http://example.com/recursive.json",
"properties": {
"invalid": { "type": "number" }
},
"required": "invalid"
}
}
beforeEach(function() {
loadCallCount = 0;
ajv = Ajv({ loadSchema: loadSchema });
});
@ -55,6 +63,7 @@ describe('compileAsync method', function() {
}
};
ajv.compileAsync(schema, function (err, validate) {
loadCallCount .should.equal(2);
should.not.exist(err);
validate .should.be.a('function');
validate({ a: { b: 2 } }) .should.equal(true);
@ -72,6 +81,7 @@ describe('compileAsync method', function() {
}
};
ajv.compileAsync(schema, function (err, validate) {
loadCallCount .should.equal(2);
should.not.exist(err);
validate .should.be.a('function');
validate({ a: 2 }) .should.equal(true);
@ -113,6 +123,7 @@ describe('compileAsync method', function() {
}
};
ajv.compileAsync(schema, function (err, validate) {
loadCallCount .should.equal(1);
should.not.exist(err);
validate .should.be.a('function');
var validData = { a: { b: { a: { b: {} } } } };
@ -124,11 +135,7 @@ describe('compileAsync method', function() {
});
it('should return compiled schema on the next tick if there are no references', function (done) {
var loadCalled = false;
var ajv = Ajv({ loadSchema: function() {
loadCalled = true;
} });
it('should return compiled schema on the next tick if there are no references (#51)', function (done) {
var schema = {
"id": "http://example.com/int2plus.json",
"type": "integer",
@ -148,7 +155,7 @@ describe('compileAsync method', function() {
function spec(err, validate) {
should.not.exist(err);
loadCalled .should.equal(false);
loadCallCount .should.equal(0);
validate .should.be.a('function');
var validData = 2;
var invalidData = 1;
@ -158,7 +165,104 @@ describe('compileAsync method', function() {
});
it('should queue calls so only one compileAsync executes at a time (#52)', function (done) {
var schema = {
"id": "http://example.com/parent.json",
"properties": {
"a": { "$ref": "object.json" }
}
};
var completedCount = 0;
ajv.compileAsync(schema, spec);
ajv.compileAsync(schema, spec);
ajv.compileAsync(schema, spec);
function spec(err, validate) {
should.not.exist(err);
validate .should.be.a('function');
validate({ a: { b: 2 } }) .should.equal(true);
validate({ a: { b: 1 } }) .should.equal(false);
completed();
}
function completed() {
completedCount++;
if (completedCount == 3) {
loadCallCount .should.equal(2);
done();
}
}
});
it('should throw exception if loadSchema is not passed', function (done) {
var schema = {
"id": "http://example.com/int2plus.json",
"type": "integer",
"minimum": 2
};
var ajv = Ajv();
should.throw(function() {
ajv.compileAsync(schema, function() {
done(new Error('it should have thrown exception'));
});
});
setTimeout(done);
});
describe('should return error via callback', function() {
it('if passed schema is invalid', function (done) {
var invalidSchema = {
"id": "http://example.com/int2plus.json",
"type": "integer",
"minimum": "invalid"
};
ajv.compileAsync(invalidSchema, function (err, validate) {
should.exist(err);
should.not.exist(validate);
done();
});
});
it('if loaded schema is invalid', function (done) {
var schema = {
"id": "http://example.com/parent.json",
"properties": {
"a": { "$ref": "invalid.json" }
}
};
ajv.compileAsync(schema, function (err, validate) {
should.exist(err);
should.not.exist(validate);
done();
});
});
it('if loadSchema returned error', function (done) {
var schema = {
"id": "http://example.com/parent.json",
"properties": {
"a": { "$ref": "object.json" }
}
};
var ajv = Ajv({ loadSchema: badLoadSchema });
ajv.compileAsync(schema, function (err, validate) {
should.exist(err);
should.not.exist(validate);
done();
});
function badLoadSchema(ref, callback) {
setTimeout(function() { callback(new Error('cant load')); });
}
});
});
function loadSchema(uri, callback) {
loadCallCount++;
setTimeout(function() {
if (SCHEMAS[uri]) callback(null, SCHEMAS[uri]);
else callback(new Error('404'));

View File

@ -142,7 +142,7 @@ describe('Validation errors', function () {
});
it('errors for items should include item index without quotes in dataPath', function() {
it('errors for items should include item index without quotes in dataPath (#48)', function() {
var schema1 = {
id: 'schema1',
type: 'array',