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 set objects to test the value against.

### 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
51 changes: 51 additions & 0 deletions src/asserts/any-of-assert.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
'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) {
const result = this.validator.validate(value, new Constraint(constraintSet, { deepRequired: true }));
Comment on lines +28 to +38
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
this.validator = new Validator();
/**
* Validation algorithm.
*/
this.validate = value => {
const violations = [];
for (const constraintSet of constraintSets) {
const result = this.validator.validate(value, new Constraint(constraintSet, { deepRequired: true }));
/**
* Validation algorithm.
*/
this.validate = value => {
const violations = [];
for (const constraintSet of constraintSets) {
const result = constraintSet.validate(value, new Constraint(constraintSet, { deepRequired: true }));

From my point of view, I’d use it like this, just a dummy example:

validate(someObject, { 
  foobar: is.anyOf(is.ofLength({ max: 254 }), is.ofLength({ min: 10 }))
})

So if I’m seeding an instance of is.ofLength({ max: 254 }) into the constraints set, this method returns an object that has a validate method, so no need to redeclare new Validator(); everytime we want to do any of.

I should be able to call it directly like this, right? is.ofLength({ max: 254 }).validate(value);


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
139 changes: 139 additions & 0 deletions test/asserts/any-of-assert.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
'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 }) => {
assert.throws(() => Assert.anyOf(), { message: 'AnyOf constraint requires at least two constraint sets' });
});

it('should throw an error if only one constraint set is provided', ({ assert }) => {
assert.throws(() => Assert.anyOf({ bar: [Assert.equalTo('foo')] }), {
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 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 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')] }, { qux: [Assert.equalTo('grault')] })]
},
{ 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