Browse Source

Add `any` predicate (#56)

iss58
Sam Verschueren 7 years ago
committed by Sindre Sorhus
parent
commit
7bbfee02a5
  1. 35
      source/index.ts
  2. 31
      source/lib/predicates/any.ts
  3. 13
      source/lib/predicates/base-predicate.ts
  4. 19
      source/lib/predicates/predicate.ts
  5. 21
      source/test/any.ts

35
source/index.ts

@ -1,5 +1,6 @@
import {ArgumentError} from './lib/argument-error';
import {Predicate, validatorSymbol, Validator} from './lib/predicates/predicate';
import {Predicate} from './lib/predicates/predicate';
import {AnyPredicate} from './lib/predicates/any';
import {testSymbol} from './lib/predicates/base-predicate';
import {StringPredicate} from './lib/predicates/string';
import {NumberPredicate} from './lib/predicates/number';
import {BooleanPredicate} from './lib/predicates/boolean';
@ -25,6 +26,20 @@ export interface Ow {
* @param predicate Predicate used in the validator function.
*/
create<T>(predicate: Predicate<T>): (value: T) => void;
/**
* Test that the value matches at least one of the given predicates.
*/
any<T1>(p1: Predicate<T1>): Predicate<T1>;
any<T1, T2>(p1: Predicate<T1>, p2: Predicate<T2>): Predicate<T1 | T2>;
any<T1, T2, T3>(p1: Predicate<T1>, p2: Predicate<T2>, p3: Predicate<T3>): Predicate<T1 | T2 | T3>;
any<T1, T2, T3, T4>(p1: Predicate<T1>, p2: Predicate<T2>, p3: Predicate<T3>, p4: Predicate<T4>): Predicate<T1 | T2 | T3 | T4>;
any<T1, T2, T3, T4, T5>(p1: Predicate<T1>, p2: Predicate<T2>, p3: Predicate<T3>, p4: Predicate<T4>, p5: Predicate<T5>): Predicate<T1 | T2 | T3 | T4 | T5>;
any<T1, T2, T3, T4, T5, T6>(p1: Predicate<T1>, p2: Predicate<T2>, p3: Predicate<T3>, p4: Predicate<T4>, p5: Predicate<T5>, p6: Predicate<T6>): Predicate<T1 | T2 | T3 | T4 | T5 | T6>;
any<T1, T2, T3, T4, T5, T6, T7>(p1: Predicate<T1>, p2: Predicate<T2>, p3: Predicate<T3>, p4: Predicate<T4>, p5: Predicate<T5>, p6: Predicate<T6>, p7: Predicate<T7>): Predicate<T1 | T2 | T3 | T4 | T5 | T6 | T7>;
any<T1, T2, T3, T4, T5, T6, T7, T8>(p1: Predicate<T1>, p2: Predicate<T2>, p3: Predicate<T3>, p4: Predicate<T4>, p5: Predicate<T5>, p6: Predicate<T6>, p7: Predicate<T7>, p8: Predicate<T8>): Predicate<T1 | T2 | T3 | T4 | T5 | T6 | T7 | T8>;
any<T1, T2, T3, T4, T5, T6, T7, T8, T9>(p1: Predicate<T1>, p2: Predicate<T2>, p3: Predicate<T3>, p4: Predicate<T4>, p5: Predicate<T5>, p6: Predicate<T6>, p7: Predicate<T7>, p8: Predicate<T8>, p9: Predicate<T9>): Predicate<T1 | T2 | T3 | T4 | T5 | T6 | T7 | T8 | T9>;
any<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(p1: Predicate<T1>, p2: Predicate<T2>, p3: Predicate<T3>, p4: Predicate<T4>, p5: Predicate<T5>, p6: Predicate<T6>, p7: Predicate<T7>, p8: Predicate<T8>, p9: Predicate<T9>, p10: Predicate<T10>): Predicate<T1 | T2 | T3 | T4 | T5 | T6 | T7 | T8 | T9 | T10>;
any(...predicate: Predicate[]): Predicate;
/**
* Test the value to be a string.
*/
@ -159,23 +174,15 @@ export interface Ow {
readonly iterable: Predicate<Iterable<any>>;
}
const main = <T>(value: T, predicate: Predicate<T>) => {
const validators: Validator<any>[] = (predicate as any)[validatorSymbol];
for (const {validator, message} of validators) {
const result = validator(value);
if (typeof result !== 'boolean' || !result) {
// TODO: Modify the stack output to show the original `ow()` call instead of this `throw` statement
throw new ArgumentError(message(value, result), main);
}
}
};
const main = <T>(value: T, predicate: Predicate<T> | AnyPredicate<T>) => (predicate as any)[testSymbol](value, main);
Object.defineProperties(main, {
create: {
value: <T>(predicate: Predicate<T>) => (value: T) => main(value, predicate)
},
any: {
value: (...predicates: Predicate[]) => new AnyPredicate(predicates)
},
string: {
get: () => new StringPredicate()
},

31
source/lib/predicates/any.ts

@ -0,0 +1,31 @@
import {ArgumentError} from '../argument-error';
import {Predicate} from './predicate';
import {BasePredicate, testSymbol} from './base-predicate';
import {Ow} from '../..';
/**
* @hidden
*/
export class AnyPredicate<T> implements BasePredicate<T> {
constructor(
private readonly predicates: Predicate[]
) {}
[testSymbol](value: T, main: Ow) {
const errors = [
'Any predicate failed with the following errors:'
];
for (const predicate of this.predicates) {
try {
main(value, predicate);
return;
} catch (err) {
errors.push(`- ${err.message}`);
}
}
throw new ArgumentError(errors.join('\n'), main);
}
}

13
source/lib/predicates/base-predicate.ts

@ -0,0 +1,13 @@
import {Ow} from '../..';
/**
* @hidden
*/
export const testSymbol = Symbol('test');
/**
* @hidden
*/
export interface BasePredicate<T = any> {
[testSymbol](value: T, main: Ow): void;
}

19
source/lib/predicates/predicate.ts

@ -1,4 +1,7 @@
import is from '@sindresorhus/is';
import {Ow} from '../..';
import {ArgumentError} from '../argument-error';
import {BasePredicate, testSymbol} from './base-predicate';
import {not} from '../operators/not';
/**
@ -24,7 +27,7 @@ export const validatorSymbol = Symbol('validators');
/**
* @hidden
*/
export class Predicate<T = any> {
export class Predicate<T = any> implements BasePredicate<T> {
constructor(
type: string,
private readonly context: Context = {
@ -37,6 +40,20 @@ export class Predicate<T = any> {
});
}
/**
* @hidden
*/
[testSymbol](value: T, main: Ow) {
for (const {validator, message} of this.context.validators) {
const result = validator(value);
if (typeof result !== 'boolean' || !result) {
// TODO: Modify the stack output to show the original `ow()` call instead of this `throw` statement
throw new ArgumentError(message(value, result), main);
}
}
}
/**
* @hidden
*/

21
source/test/any.ts

@ -0,0 +1,21 @@
import test from 'ava';
import m from '..';
const createError = (...errors: string[]) => {
return [
'Any predicate failed with the following errors:',
...errors.map(error => `- ${error}`)
].join('\n');
}
test('any', t => {
t.notThrows(() => m(1, m.any(m.number)));
t.notThrows(() => m(1, m.any(m.number, m.string)));
t.notThrows(() => m(1, m.any(m.number, m.string)));
t.notThrows(() => m(true, m.any(m.number, m.string, m.boolean)));
t.throws(() => m(1 as any, m.any(m.string)), createError('Expected argument to be of type `string` but received type `number`'));
t.throws(() => m(true as any, m.any(m.number, m.string)), createError(
'Expected argument to be of type `number` but received type `boolean`',
'Expected argument to be of type `string` but received type `boolean`'
));
});
Loading…
Cancel
Save