import is from '@sindresorhus/is'; import {Ow} from '../..'; import {ArgumentError} from '../argument-error'; import {BasePredicate, testSymbol} from './base-predicate'; import {not} from '../operators/not'; /** * @hidden */ export interface Validator { message(value: T, result?: any): string; validator(value: T): any; } /** * @hidden */ export interface Context { validators: Validator[]; } /** * @hidden */ export const validatorSymbol = Symbol('validators'); /** * @hidden */ export class Predicate implements BasePredicate { constructor( type: string, private readonly context: Context = { validators: [] } ) { this.addValidator({ message: value => `Expected argument to be of type \`${type}\` but received type \`${is(value)}\``, validator: value => (is as any)[type](value) }); } /** * @hidden */ // tslint:disable completed-docs [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 */ get [validatorSymbol]() { return this.context.validators; } /** * Invert the following validators. */ get not(): this { return not(this); } /** * Test if the value matches a custom validation function. The validation function should return `true` if the value * passes the function. If the function either returns `false` or a string, the function fails and the string will be * used as error message. * * @param fn Validation function. */ is(fn: (value: T) => boolean | string) { return this.addValidator({ message: (value, error) => error || `Expected \`${value}\` to pass custom validation function`, validator: value => fn(value) }); } /** * Register a new validator. * * @internal * @hidden * @param validator Validator to register. */ addValidator(validator: Validator) { this.context.validators.push(validator); return this; } }