Browse Source

Add optional predicate - fixes #58

iss58
Sam Verschueren 7 years ago
parent
commit
396813b792
  1. 91
      source/index.ts
  2. 14
      source/lib/operators/optional.ts
  3. 19
      source/lib/predicates/predicate.ts
  4. 7
      source/test/optional.ts

91
source/index.ts

@ -1,4 +1,4 @@
import {Predicate} from './lib/predicates/predicate'; import {Predicate, Context} from './lib/predicates/predicate';
import {AnyPredicate} from './lib/predicates/any'; import {AnyPredicate} from './lib/predicates/any';
import {testSymbol} from './lib/predicates/base-predicate'; import {testSymbol} from './lib/predicates/base-predicate';
import {StringPredicate} from './lib/predicates/string'; import {StringPredicate} from './lib/predicates/string';
@ -12,12 +12,20 @@ import {MapPredicate} from './lib/predicates/map';
import {WeakMapPredicate} from './lib/predicates/weak-map'; import {WeakMapPredicate} from './lib/predicates/weak-map';
import {SetPredicate} from './lib/predicates/set'; import {SetPredicate} from './lib/predicates/set';
import {WeakSetPredicate} from './lib/predicates/weak-set'; import {WeakSetPredicate} from './lib/predicates/weak-set';
import {optional} from './lib/operators/optional';
/** /**
* @hidden * @hidden
*/ */
export type TypedArray = Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array; export type TypedArray = Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array;
export interface Operators {
/**
* Make the value optional.
*/
readonly optional: Ow;
}
export interface Ow { export interface Ow {
/** /**
* Test if the value matches the predicate. * Test if the value matches the predicate.
@ -180,114 +188,121 @@ export interface Ow {
readonly iterable: Predicate<Iterable<any>>; readonly iterable: Predicate<Iterable<any>>;
} }
const main = <T>(value: T, predicate: Predicate<T> | AnyPredicate<T>) => (predicate as any)[testSymbol](value, main); const ow = (context?: Context<any>) => {
const main = <T>(value: T, predicate: Predicate<T> | AnyPredicate<T>) => (predicate as any)[testSymbol](value, main);
Object.defineProperties(main, { Object.defineProperties(main, {
create: { create: {
value: <T>(predicate: Predicate<T>) => (value: T) => main(value, predicate) value: <T>(predicate: Predicate<T>) => (value: T) => main(value, predicate)
}, },
any: { any: {
value: (...predicates: Predicate[]) => new AnyPredicate(predicates) value: (...predicates: Predicate[]) => new AnyPredicate(predicates)
}, },
optional: {
get: () => optional(ow, context)
},
string: { string: {
get: () => new StringPredicate() get: () => new StringPredicate(context)
}, },
number: { number: {
get: () => new NumberPredicate() get: () => new NumberPredicate(context)
}, },
boolean: { boolean: {
get: () => new BooleanPredicate() get: () => new BooleanPredicate(context)
}, },
undefined: { undefined: {
get: () => new Predicate('undefined') get: () => new Predicate('undefined', context)
}, },
null: { null: {
get: () => new Predicate('null') get: () => new Predicate('null', context)
}, },
nullOrUndefined: { nullOrUndefined: {
get: () => new Predicate('nullOrUndefined') get: () => new Predicate('nullOrUndefined', context)
}, },
nan: { nan: {
get: () => new Predicate('nan') get: () => new Predicate('nan', context)
}, },
symbol: { symbol: {
get: () => new Predicate('symbol') get: () => new Predicate('symbol', context)
}, },
array: { array: {
get: () => new ArrayPredicate() get: () => new ArrayPredicate(context)
}, },
object: { object: {
get: () => new ObjectPredicate() get: () => new ObjectPredicate(context)
}, },
date: { date: {
get: () => new DatePredicate() get: () => new DatePredicate(context)
}, },
error: { error: {
get: () => new ErrorPredicate() get: () => new ErrorPredicate(context)
}, },
map: { map: {
get: () => new MapPredicate() get: () => new MapPredicate(context)
}, },
weakMap: { weakMap: {
get: () => new WeakMapPredicate() get: () => new WeakMapPredicate(context)
}, },
set: { set: {
get: () => new SetPredicate() get: () => new SetPredicate(context)
}, },
weakSet: { weakSet: {
get: () => new WeakSetPredicate() get: () => new WeakSetPredicate(context)
}, },
function: { function: {
get: () => new Predicate('function') get: () => new Predicate('function', context)
}, },
buffer: { buffer: {
get: () => new Predicate('buffer') get: () => new Predicate('buffer', context)
}, },
regExp: { regExp: {
get: () => new Predicate('regExp') get: () => new Predicate('regExp', context)
}, },
promise: { promise: {
get: () => new Predicate('promise') get: () => new Predicate('promise', context)
}, },
typedArray: { typedArray: {
get: () => new Predicate('typedArray') get: () => new Predicate('typedArray', context)
}, },
int8Array: { int8Array: {
get: () => new Predicate('int8Array') get: () => new Predicate('int8Array', context)
}, },
uint8Array: { uint8Array: {
get: () => new Predicate('uint8Array') get: () => new Predicate('uint8Array', context)
}, },
uint8ClampedArray: { uint8ClampedArray: {
get: () => new Predicate('uint8ClampedArray') get: () => new Predicate('uint8ClampedArray', context)
}, },
int16Array: { int16Array: {
get: () => new Predicate('int16Array') get: () => new Predicate('int16Array', context)
}, },
uint16Array: { uint16Array: {
get: () => new Predicate('uint16Array') get: () => new Predicate('uint16Array', context)
}, },
int32Array: { int32Array: {
get: () => new Predicate('int32Array') get: () => new Predicate('int32Array', context)
}, },
uint32Array: { uint32Array: {
get: () => new Predicate('uint32Array') get: () => new Predicate('uint32Array', context)
}, },
float32Array: { float32Array: {
get: () => new Predicate('float32Array') get: () => new Predicate('float32Array', context)
}, },
float64Array: { float64Array: {
get: () => new Predicate('float64Array') get: () => new Predicate('float64Array', context)
}, },
arrayBuffer: { arrayBuffer: {
get: () => new Predicate('arrayBuffer') get: () => new Predicate('arrayBuffer', context)
}, },
dataView: { dataView: {
get: () => new Predicate('dataView') get: () => new Predicate('dataView', context)
}, },
iterable: { iterable: {
get: () => new Predicate('iterable') get: () => new Predicate('iterable', context)
} }
}); });
return main as Ow & Operators;
};
export default main as Ow; export default ow();

14
source/lib/operators/optional.ts

@ -0,0 +1,14 @@
import {Ow} from '../..';
import {Context} from '../predicates/predicate';
/**
* Operator which makes the predicate optional.
*
* @hidden
* @param ow Function to construct a new Ow instance.
* @param context Current context object.
*/
export const optional = (ow: (context: Context<any>) => Ow, context?: Context<any>) => ow({
...context,
optional: true
});

19
source/lib/predicates/predicate.ts

@ -16,7 +16,8 @@ export interface Validator<T> {
* @hidden * @hidden
*/ */
export interface Context<T> { export interface Context<T> {
validators: Validator<T>[]; validators?: Validator<T>[];
optional?: boolean;
} }
/** /**
@ -28,12 +29,14 @@ export const validatorSymbol = Symbol('validators');
* @hidden * @hidden
*/ */
export class Predicate<T = any> implements BasePredicate<T> { export class Predicate<T = any> implements BasePredicate<T> {
constructor( private readonly context: Context<T> & {validators: Validator<T>[]};
type: string,
private readonly context: Context<T> = { constructor(type: string, context?: Context<T>) {
this.context = {
...context,
validators: [] validators: []
} };
) {
this.addValidator({ this.addValidator({
message: value => `Expected argument to be of type \`${type}\` but received type \`${is(value)}\``, message: value => `Expected argument to be of type \`${type}\` but received type \`${is(value)}\``,
validator: value => (is as any)[type](value) validator: value => (is as any)[type](value)
@ -45,6 +48,10 @@ export class Predicate<T = any> implements BasePredicate<T> {
*/ */
// tslint:disable completed-docs // tslint:disable completed-docs
[testSymbol](value: T, main: Ow) { [testSymbol](value: T, main: Ow) {
if (this.context.optional && value === undefined) {
return;
}
for (const {validator, message} of this.context.validators) { for (const {validator, message} of this.context.validators) {
const result = validator(value); const result = validator(value);

7
source/test/optional.ts

@ -0,0 +1,7 @@
import test from 'ava';
import m from '..';
test('optional', t => {
t.notThrows(() => m('foo', m.optional.string));
t.notThrows(() => m(undefined, m.optional.string));
});
Loading…
Cancel
Save