From c736b90067dbc73f715ad4eb81cee3168fa8c6e5 Mon Sep 17 00:00:00 2001 From: Ricardo Lopes Date: Wed, 7 Sep 2016 11:11:14 +0100 Subject: [PATCH] Add `NullOrAssert` to allow any assert to be nullable --- README.md | 11 +++ src/asserts/null-or-assert.js | 41 ++++++++++++ src/index.js | 2 + test/asserts/null-or-assert.test.js | 100 ++++++++++++++++++++++++++++ test/index.test.js | 3 +- 5 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 src/asserts/null-or-assert.js create mode 100644 test/asserts/null-or-assert.test.js diff --git a/README.md b/README.md index 8f0066b..925e608 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,7 @@ The following set of extra asserts are provided by this package: - [Iso3166Country](#iso3166country) (requires `isoc`) - [Json](#json) - [NotEmpty](#notempty) +- [NullOr](#nullor) - [NullOrDate](#nullordate) - [NullOrBoolean](#nullorboolean) - [NullOrString](#nullorstring) @@ -196,6 +197,9 @@ Tests if the value is valid json. ### NotEmpty Tests if the value is not an empty (empty object, empty array, empty string, etc). +### NullOr +Tests if the value is a `null` or validates agains the assert received as an argument. + ### NullOrBoolean Tests if the value is a `null` or `boolean`. @@ -264,6 +268,13 @@ if (true !== violation) { console.log('"foo" is not a valid IP. Violation:', violation[0].show()); // => "foo" is not a valid IP. Violation: { assert: 'Ip', value: 'foo' } } + +// Make the validation nullable. +violation = validator.validate(null, is.nullOr(is.ip())); + +if (true === violation) { + console.log('null is null or a valid IP'); // => null is null or a valid IP +} ``` ## Tests diff --git a/src/asserts/null-or-assert.js b/src/asserts/null-or-assert.js new file mode 100644 index 0000000..4c29381 --- /dev/null +++ b/src/asserts/null-or-assert.js @@ -0,0 +1,41 @@ +'use strict'; + +/** + * Export `NullOrAssert`. + */ + +module.exports = function nullOrAssert(assert) { + /** + * Class name. + */ + + this.__class__ = 'NullOr'; + + if (typeof assert !== 'object') { + throw new Error('Assert must be an object.'); + } + + if (typeof assert.validate !== 'function') { + throw new Error('Assert must have a validate function.'); + } + + /** + * Nullable assert. + */ + + this.assert = assert; + + /** + * Validation algorithm. + */ + + this.validate = value => { + if (value === null) { + return true; + } + + return this.assert.validate(value); + }; + + return this; +}; diff --git a/src/index.js b/src/index.js index c412ecb..6675139 100644 --- a/src/index.js +++ b/src/index.js @@ -30,6 +30,7 @@ const Ip = require('./asserts/ip-assert.js'); const Iso3166Country = require('./asserts/iso-3166-country-assert.js'); const Json = require('./asserts/json-assert.js'); const NotEmpty = require('./asserts/not-empty-assert.js'); +const NullOr = require('./asserts/null-or-assert.js'); const NullOrBoolean = require('./asserts/null-or-boolean-assert.js'); const NullOrDate = require('./asserts/null-or-date-assert.js'); const NullOrString = require('./asserts/null-or-string-assert.js'); @@ -73,6 +74,7 @@ module.exports = { Iso3166Country, Json, NotEmpty, + NullOr, NullOrBoolean, NullOrDate, NullOrString, diff --git a/test/asserts/null-or-assert.test.js b/test/asserts/null-or-assert.test.js new file mode 100644 index 0000000..5741683 --- /dev/null +++ b/test/asserts/null-or-assert.test.js @@ -0,0 +1,100 @@ +'use strict'; + +/** + * Module dependencies. + */ + +const { Assert: BaseAssert, Violation } = require('validator.js'); +const NullOrAssert = require('../../src/asserts/null-or-assert'); +const UuidAssert = require('../../src/asserts/uuid-assert'); + +/** + * Extend `Assert` with `NullOrAssert`. + */ + +const Assert = BaseAssert.extend({ + NullOr: NullOrAssert, + Uuid: UuidAssert +}); + +/** + * Test `NullOrAssert`. + */ + +describe('NullOrAssert', () => { + it('should throw an error if the specified assert is missing', () => { + try { + Assert.nullOr('foo').validate(); + + fail(); + } catch (e) { + expect(e).toBeInstanceOf(Error); + expect(e.message).toBe('Assert must be an object.'); + } + }); + + it('should throw an error if the specified assert is not valid', () => { + try { + Assert.nullOr('foo').validate('bar'); + + fail(); + } catch (e) { + expect(e).toBeInstanceOf(Error); + expect(e.message).toBe('Assert must be an object.'); + } + }); + + it('should throw an error if the specified assert has no `validate` function', () => { + try { + Assert.nullOr({}).validate(123); + + fail(); + } catch (e) { + expect(e).toBeInstanceOf(Error); + expect(e.message).toBe('Assert must have a validate function.'); + } + }); + + it('should throw an error if the specified assert has a `validate` property that is not a function', () => { + try { + Assert.nullOr({ validate: true }).validate(123); + + fail(); + } catch (e) { + expect(e).toBeInstanceOf(Error); + expect(e.message).toBe('Assert must have a validate function.'); + } + }); + + it('should throw an error if the value is not null and is not valid for the specified assert', () => { + try { + Assert.nullOr(Assert.string()).validate(123); + + fail(); + } catch (e) { + expect(e).toBeInstanceOf(Violation); + expect(e.show().assert).toBe('IsString'); + expect(e.violation.value).toBe('must_be_a_string'); + } + }); + + it('should include the arguments of the specified assert', () => { + try { + Assert.nullOr(Assert.uuid(4)).validate('foobar'); + + fail(); + } catch (e) { + expect(e).toBeInstanceOf(Object); + expect(e.show().assert).toBe('Uuid'); + expect(e.violation.version).toBe(4); + } + }); + + it('should accept a null value', () => { + Assert.nullOr(Assert.string()).validate(null); + }); + + it('should accept a value that is valid for the specified assert', () => { + Assert.nullOr(Assert.string()).validate('foobar'); + }); +}); diff --git a/test/index.test.js b/test/index.test.js index 91c52f6..6704d3d 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -14,7 +14,7 @@ describe('validator.js-asserts', () => { it('should export all asserts', () => { const assertNames = Object.keys(asserts); - expect(assertNames).toHaveLength(37); + expect(assertNames).toHaveLength(38); expect(assertNames).toEqual( expect.arrayContaining([ 'AbaRoutingNumber', @@ -43,6 +43,7 @@ describe('validator.js-asserts', () => { 'Iso3166Country', 'Json', 'NotEmpty', + 'NullOr', 'NullOrBoolean', 'NullOrDate', 'NullOrString',