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
42 changes: 42 additions & 0 deletions src/asserts/any-of-assert.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
'use strict';

/**
* Module dependencies.
*/

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

/**
* Export `AnyOfAssert`.
*/

module.exports = function anyOfAssert(...constraintSets) {
/**
* Class name.
*/

this.__class__ = 'AnyOf';

/**
* Validation algorithm.
*/

this.validate = value => {
const validator = new Validator();
const violations = [];

for (const constraintSet of constraintSets) {
const result = validator.validate(value, new Constraint(constraintSet, { deepRequired: true }));

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

violations.push(result);
}

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
153 changes: 153 additions & 0 deletions test/asserts/any-of-assert.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
'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().validate({ bar: 'biz' });

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

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

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

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')] })] },
{ 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 pass if value matches more than one constraint set', ({ assert }) => {
assert.doesNotThrow(() => {
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 }) => {
assert.doesNotThrow(() => {
Assert.anyOf({ bar: [Assert.notBlank()] }, { bar: [Assert.equalTo('biz')] }).validate({ bar: 'biz' });
});
});

it('should pass if value matches a single constraint set', ({ assert }) => {
assert.doesNotThrow(() => {
Assert.anyOf({ bar: [Assert.equalTo('biz')] }).validate({ bar: 'biz' });
});
});

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

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

it('should support more than two constraint sets', ({ assert }) => {
assert.doesNotThrow(() => {
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 }) => {
assert.doesNotThrow(() => {
Assert.anyOf(
{ bar: [Assert.equalTo('biz')], baz: [Assert.anyOf({ qux: [Assert.equalTo('corge')] })] },
{ bar: [Assert.equalTo('baz')] }
).validate({ bar: 'biz', baz: { qux: 'corge' } });
});
});
});
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
Loading