From 1ea86e6ad16c218714b39209bac3be99a0c598dd Mon Sep 17 00:00:00 2001 From: Nicolas Morel Date: Sun, 24 Jul 2016 15:08:51 +0200 Subject: [PATCH] Make coercion happen earlier in the validation Fixes #960. --- lib/any.js | 11 ++++++++ lib/index.js | 39 +++++++++++++++----------- test/index.js | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 110 insertions(+), 16 deletions(-) diff --git a/lib/any.js b/lib/any.js index 192742cbb..917faccc0 100755 --- a/lib/any.js +++ b/lib/any.js @@ -475,6 +475,17 @@ module.exports = internals.Any = class { }; }; + if (this._coerce) { + const coerced = this._coerce.call(this, value, state, options); + if (coerced.errors) { + value = coerced.value; + errors = errors.concat(coerced.errors); + return finish(); // Coerced error always aborts early + } + + value = coerced.value; + } + // Check presence requirements const presence = this._flags.presence || options.presence; diff --git a/lib/index.js b/lib/index.js index 3ed003de0..0ed19155e 100755 --- a/lib/index.js +++ b/lib/index.js @@ -213,19 +213,30 @@ internals.root = function () { }; - if (extension.coerce || extension.pre) { - type.prototype._base = function (value, state, options) { + if (extension.coerce) { + type.prototype._coerce = function (value, state, options) { + + if (ctor.prototype._coerce) { + const baseRet = ctor.prototype._coerce.call(this, value, state, options); - let ret; - if (extension.coerce) { - ret = extension.coerce.call(this, value, state, options); - if (ret instanceof Errors.Err) { - return { value, errors: ret }; + if (baseRet.errors) { + return baseRet; } - value = ret; + value = baseRet.value; + } + + const ret = extension.coerce.call(this, value, state, options); + if (ret instanceof Errors.Err) { + return { value, errors: ret }; } + return { value: ret }; + }; + } + if (extension.pre) { + type.prototype._base = function (value, state, options) { + if (ctor.prototype._base) { const baseRet = ctor.prototype._base.call(this, value, state, options); @@ -236,16 +247,12 @@ internals.root = function () { value = baseRet.value; } - if (extension.pre) { - ret = extension.pre.call(this, value, state, options); - if (ret instanceof Errors.Err) { - return { value, errors: ret }; - } - - return { value: ret }; + const ret = extension.pre.call(this, value, state, options); + if (ret instanceof Errors.Err) { + return { value, errors: ret }; } - return { value }; + return { value: ret }; }; } diff --git a/test/index.js b/test/index.js index 71cd66421..cc63fe26b 100755 --- a/test/index.js +++ b/test/index.js @@ -2329,6 +2329,82 @@ describe('Joi', () => { done(); }); + it('defines a custom type coercing its input value that runs early enough', (done) => { + + const customJoi = Joi.extend({ + base: Joi.string(), + coerce(value, state, options) { + + return 'foobar'; + }, + name: 'myType' + }); + + const schema = customJoi.myType(); + const result = schema.validate(''); + expect(result.error).to.be.null(); + expect(result.value).to.equal('foobar'); + + done(); + }); + + it('defines multiple levels of coercion', (done) => { + + const customJoi = Joi.extend({ + base: Joi.string(), + coerce(value, state, options) { + + return 'foobar'; + }, + name: 'myType' + }); + + const customJoi2 = customJoi.extend({ + base: customJoi.myType(), + coerce(value, state, options) { + + expect(value).to.equal('foobar'); + return 'baz'; + }, + name: 'myType' + }); + + const schema = customJoi2.myType(); + const result = schema.validate(''); + expect(result.error).to.be.null(); + expect(result.value).to.equal('baz'); + + done(); + }); + + it('defines multiple levels of coercion where base fails', (done) => { + + const customJoi = Joi.extend({ + base: Joi.string(), + coerce(value, state, options) { + + return this.createError('any.invalid', null, state, options); + }, + name: 'myType' + }); + + const customJoi2 = customJoi.extend({ + base: customJoi.myType(), + coerce(value, state, options) { + + expect(value).to.equal('foobar'); + return 'baz'; + }, + name: 'myType' + }); + + const schema = customJoi2.myType(); + const result = schema.validate(''); + expect(result.error).to.an.error('"value" contains an invalid value'); + + done(); + }); + it('defines a custom type casting its input value', (done) => { const customJoi = Joi.extend({