src/predicates/or.ts
import { isArray } from './isArray';
import { isDefined } from './isDefined';
import { isGreaterThan } from './isGreaterThan';
import { Failure, Predicate, Result, Success } from './Predicate';
/**
* @desc Ensures that the `value` meets at least one of the provided {@link Predicate}s.
*
* @example
* import { ensure, isEqualTo, isGreaterThan, isLessThan, or } from 'tiny-type'l
*
* class Percentage extends TinyType {
* constructor(public readonly value: number) {
* ensure('Percentage', value, or(isEqualTo(0), isGreaterThan(0)), or(isLessThan(100), isEqualTo(100));
* }
* }
*
* @param {Predicate<T>} predicates
* @returns {Predicate<T>}
*/
export function or<T>(...predicates: Array<Predicate<T>>): Predicate<T> {
return new Or<T>(predicates);
}
/** @access private */
class Or<T> extends Predicate<T> {
constructor(private readonly predicates: Array<Predicate<T>>) {
super();
const results = [
_ => isDefined().check(_),
_ => isArray().check(_),
_ => isGreaterThan(0).check(_.length),
];
if (results.some(check => check(this.predicates) instanceof Failure)) {
throw new Error(`Looks like you haven't specified any predicates to check the value against?`);
}
}
/** @override */
check(value: T): Result<T> {
const results = this.predicates.map(predicate => predicate.check(value));
const anySuccess = results.some(result => result instanceof Success);
const failures = results.filter(_ => _ instanceof Failure)
.map((_: Result<T>) => (_ as Failure<T>).description);
return anySuccess
? new Success(value)
: new Failure(value, describe(failures));
}
}
function describe(issues: string[]): string {
return `either ${issues.join(', ').replace(/,([^,]*)$/, ' or$1')}`;
}