diff --git a/lib/ajv.js b/lib/ajv.js index b379508..3f71001 100644 --- a/lib/ajv.js +++ b/lib/ajv.js @@ -363,9 +363,11 @@ function _compile(schemaObj, root) { return v; + /* @this {*} - custom context, see passContext option */ function callValidate() { + /* jshint validthis: true */ var _validate = schemaObj.validate; - var result = _validate.apply(null, arguments); + var result = _validate.apply(this, arguments); callValidate.errors = _validate.errors; return result; } diff --git a/lib/compile/index.js b/lib/compile/index.js index 8af3a36..8e369db 100644 --- a/lib/compile/index.js +++ b/lib/compile/index.js @@ -69,9 +69,11 @@ function compile(schema, root, localRefs, baseId) { endCompiling.call(this, schema, root, baseId); } + /* @this {*} - custom context, see passContext option */ function callValidate() { + /* jshint validthis: true */ var validate = compilation.validate; - var result = validate.apply(null, arguments); + var result = validate.apply(this, arguments); callValidate.errors = validate.errors; return result; } diff --git a/spec/issues.spec.js b/spec/issues.spec.js index 87c21a4..0a6e570 100644 --- a/spec/issues.spec.js +++ b/spec/issues.spec.js @@ -703,3 +703,113 @@ describe('property __proto__ should be removed with removeAdditional option, iss Object.keys(data.obj) .should.eql(['a', 'b']); }); }); + + +describe('issue #768, fix passContext in recursive $ref', function() { + var ajv, contexts; + + beforeEach(function() { + contexts = []; + }); + + describe('passContext = true', function() { + it('should pass this value as context to custom keyword validation function', function() { + var validate = getValidate(true); + var self = {}; + validate.call(self, { bar: 'a', baz: { bar: 'b' } }); + contexts .should.have.length(2); + contexts.forEach(function(ctx) { + ctx .should.equal(self); + }); + }); + }); + + describe('passContext = false', function() { + it('should pass ajv instance as context to custom keyword validation function', function() { + var validate = getValidate(false); + validate({ bar: 'a', baz: { bar: 'b' } }); + contexts .should.have.length(2); + contexts.forEach(function(ctx) { + ctx .should.equal(ajv); + }); + }); + }); + + describe('ref is fragment and passContext = true', function() { + it('should pass this value as context to custom keyword validation function', function() { + var validate = getValidateFragments(true); + var self = {}; + validate.call(self, { baz: { corge: 'a', quux: { baz: { corge: 'b' } } } }); + contexts .should.have.length(2); + contexts.forEach(function(ctx) { + ctx .should.equal(self); + }); + }); + }); + + describe('ref is fragment and passContext = false', function() { + it('should pass ajv instance as context to custom keyword validation function', function() { + var validate = getValidateFragments(false); + validate({ baz: { corge: 'a', quux: { baz: { corge: 'b' } } } }); + contexts .should.have.length(2); + contexts.forEach(function(ctx) { + ctx .should.equal(ajv); + }); + }); + }); + + function getValidate(passContext) { + ajv = new Ajv({ passContext: passContext }); + ajv.addKeyword('testValidate', { validate: storeContext }); + + var schema = { + "$id" : "foo", + "type": "object", + "required": ["bar"], + "properties": { + "bar": { "testValidate": true }, + "baz": { + "$ref": "foo" + } + } + }; + + return ajv.compile(schema); + } + + + function getValidateFragments(passContext) { + ajv = new Ajv({ passContext: passContext }); + ajv.addKeyword('testValidate', { validate: storeContext }); + + ajv.addSchema({ + "$id" : "foo", + "definitions": { + "bar": { + "properties": { + "baz": { + "$ref": "boo" + } + } + } + } + }); + + ajv.addSchema({ + "$id" : "boo", + "type": "object", + "required": ["corge"], + "properties": { + "quux": { "$ref": "foo#/definitions/bar" }, + "corge": { "testValidate": true } + } + }); + + return ajv.compile({ "$ref": "foo#/definitions/bar" }); + } + + function storeContext() { + contexts.push(this); + return true; + } +});