Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ The following set of extra asserts are provided by this package:
| Assert | Peer Dependency |
| :------------------------------------------------------------------------------ | :--------------------------------------------------- |
| [AbaRoutingNumber](#abaroutingnumber) | [`abavalidator`][abavalidator-url] |
| [AnyOf](#anyof) | |
| [BankIdentifierCode](#bankidentifiercode-bic) (_BIC_) | |
| [BigNumber](#bignumber) | [`bignumber.js`][bignumber-url] |
| [BigNumberEqualTo](#bignumberequalto) | [`bignumber.js`][bignumber-url] |
Expand Down Expand Up @@ -75,6 +76,14 @@ The following set of extra asserts are provided by this package:

Tests if the value is a valid [ABA Routing Number](http://www.accuity.com/PageFiles/255/ROUTING_NUMBER_POLICY.pdf).

### AnyOf

Tests if the value matches at least one of the provided constraint sets. Throws a violation if the value matches none of the constraint sets.

#### Arguments

- `...constraintSets` (required) - two or more constraint sets to test the value against. Each constraint set can be either a plain object mapping field names to arrays of asserts, or an assert instance.

### BankIdentifierCode (_BIC_)

Tests if the value is a valid Bank Identifier Code (_BIC_) as defined in the [ISO-9362](http://www.iso.org/iso/home/store/catalogue_tc/catalogue_detail.htm?csnumber=60390) standard.
Expand Down
58 changes: 58 additions & 0 deletions src/asserts/any-of-assert.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
'use strict';

/**
* Module dependencies.
*/

const { Constraint, Validator, Violation } = require('validator.js');

/**
* Export `AnyOfAssert`.
*/

module.exports = function anyOfAssert(...constraintSets) {
if (constraintSets.length < 2) {
throw new Error('AnyOf constraint requires at least two constraint sets');
}

/**
* Class name.
*/

this.__class__ = 'AnyOf';

/**
* Validator instance.
*/

this.validator = new Validator();

/**
* Validation algorithm.
*/

this.validate = value => {
const violations = [];

for (const constraintSet of constraintSets) {
try {
const result =
typeof constraintSet.validate === 'function'
? constraintSet.validate(value)
: this.validator.validate(value, new Constraint(constraintSet, { deepRequired: true }));

if (result === true) {
return true;
}

violations.push(result);
} catch (violation) {
violations.push(violation);
}
}

throw new Violation(this, value, violations);
};

return this;
};
2 changes: 2 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/

const AbaRoutingNumber = require('./asserts/aba-routing-number-assert.js');
const AnyOf = require('./asserts/any-of-assert.js');
const BankIdentifierCode = require('./asserts/bank-identifier-code-assert.js');
const BigNumber = require('./asserts/big-number-assert.js');
const BigNumberEqualTo = require('./asserts/big-number-equal-to-assert.js');
Expand Down Expand Up @@ -53,6 +54,7 @@ const Uuid = require('./asserts/uuid-assert.js');

module.exports = {
AbaRoutingNumber,
AnyOf,
BankIdentifierCode,
BigNumber,
BigNumberEqualTo,
Expand Down
3 changes: 3 additions & 0 deletions src/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ export interface ValidatorJSAsserts {
*/
abaRoutingNumber(): AssertInstance;

/** Value matches one or more of the provided constraint sets. */
anyOf(...constraintSets: Record<string, AssertInstance[]>[]): AssertInstance;

/** Valid BIC (Bank Identifier Code) used for international wire transfers. */
bankIdentifierCode(): AssertInstance;

Expand Down
152 changes: 152 additions & 0 deletions test/asserts/any-of-assert.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
'use strict';

/**
* Module dependencies.
*/

const { Assert: BaseAssert, Violation } = require('validator.js');
const { describe, it } = require('node:test');
const AnyOfAssert = require('../../src/asserts/any-of-assert.js');

/**
* Extend `Assert` with `AnyOfAssert`.
*/

const Assert = BaseAssert.extend({
AnyOf: AnyOfAssert
});

/**
* Test `AnyOfAssert`.
*/

describe('AnyOfAssert', () => {
it('should throw an error if no constraint sets are provided', ({ assert }) => {
try {
Assert.anyOf();

assert.fail();
} catch (e) {
assert.equal(e.message, 'AnyOf constraint requires at least two constraint sets');
}
});

it('should throw an error if only one constraint set is provided', ({ assert }) => {
try {
Assert.anyOf({ bar: [Assert.equalTo('foo')] });

assert.fail();
} catch (e) {
assert.equal(e.message, 'AnyOf constraint requires at least two constraint sets');
}
});

it('should throw an error if value does not match any constraint set', ({ assert }) => {
try {
Assert.anyOf({ bar: [Assert.equalTo('foo')] }, { bar: [Assert.equalTo('baz')] }).validate({ bar: 'biz' });

assert.fail();
} catch (e) {
assert.ok(e instanceof Violation);
assert.equal(e.show().assert, 'AnyOf');
}
});

it('should include all violations in the error when no constraint set matches', ({ assert }) => {
try {
Assert.anyOf({ bar: [Assert.equalTo('biz')] }, { bar: [Assert.equalTo('baz')] }).validate({ bar: 'qux' });

assert.fail();
} catch (e) {
const { violation } = e.show();

assert.equal(violation.length, 2);
assert.ok(violation[0].bar[0] instanceof Violation);
assert.equal(violation[0].bar[0].show().assert, 'EqualTo');
assert.equal(violation[0].bar[0].show().violation.value, 'biz');
assert.ok(violation[1].bar[0] instanceof Violation);
assert.equal(violation[1].bar[0].show().assert, 'EqualTo');
assert.equal(violation[1].bar[0].show().violation.value, 'baz');
}
});

it('should validate required fields using `deepRequired`', ({ assert }) => {
try {
Assert.anyOf(
{ bar: [Assert.required(), Assert.notBlank()] },
{ baz: [Assert.required(), Assert.notBlank()] }
).validate({});

assert.fail();
} catch (e) {
assert.ok(e instanceof Violation);
assert.equal(e.show().assert, 'AnyOf');
}
});

it('should throw an error if a constraint set with an extra assert does not match', ({ assert }) => {
try {
Assert.anyOf(
{
bar: [Assert.equalTo('biz')],
baz: [Assert.anyOf({ qux: [Assert.equalTo('corge')] }, { qux: [Assert.equalTo('grault')] })]
},
{ bar: [Assert.equalTo('baz')] }
).validate({ bar: 'biz', baz: { qux: 'wrong' } });

assert.fail();
} catch (e) {
assert.ok(e instanceof Violation);
assert.equal(e.show().assert, 'AnyOf');
}
});

it('should throw an error if value does not match any assert instance constraint set', ({ assert }) => {
try {
Assert.anyOf(Assert.ofLength({ max: 1 }), Assert.ofLength({ min: 5 })).validate('foo');

assert.fail();
} catch (e) {
assert.ok(e instanceof Violation);
assert.equal(e.show().assert, 'AnyOf');
}
});

it('should pass if value matches more than one constraint set', () => {
Assert.anyOf({ bar: [Assert.equalTo('biz')] }, { bar: [Assert.equalTo('biz')] }).validate({ bar: 'biz' });
});

it('should pass if value matches more than one constraint set with different constraints', () => {
Assert.anyOf({ bar: [Assert.notBlank()] }, { bar: [Assert.equalTo('biz')] }).validate({ bar: 'biz' });
});

it('should pass if value matches the first constraint set', () => {
Assert.anyOf({ bar: [Assert.equalTo('biz')] }, { bar: [Assert.equalTo('baz')] }).validate({ bar: 'biz' });
});

it('should pass if value matches the second constraint set', () => {
Assert.anyOf({ bar: [Assert.equalTo('biz')] }, { bar: [Assert.equalTo('baz')] }).validate({ bar: 'baz' });
});

it('should support more than two constraint sets', () => {
Assert.anyOf(
{ bar: [Assert.equalTo('biz')] },
{ bar: [Assert.equalTo('baz')] },
{ bar: [Assert.equalTo('qux')] }
).validate({ bar: 'qux' });
});

it('should pass if a constraint set contains an extra assert', () => {
Assert.anyOf(
{
bar: [Assert.equalTo('biz')],
baz: [Assert.anyOf({ qux: [Assert.equalTo('corge')] }, { qux: [Assert.equalTo('grault')] })]
},
{ bar: [Assert.equalTo('baz')] }
).validate({ bar: 'biz', baz: { qux: 'corge' } });
});

it('should pass if value matches when using assert instances as constraint sets', () => {
Assert.anyOf(Assert.ofLength({ max: 2 }), Assert.ofLength({ min: 3 })).validate('foo');
});
});
3 changes: 2 additions & 1 deletion test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ describe('validator.js-asserts', () => {
it('should export all asserts', ({ assert }) => {
const assertNames = Object.keys(asserts);

assert.equal(assertNames.length, 42);
assert.equal(assertNames.length, 43);
assert.deepEqual(assertNames, [
'AbaRoutingNumber',
'AnyOf',
'BankIdentifierCode',
'BigNumber',
'BigNumberEqualTo',
Expand Down