Last active
August 23, 2019 15:16
-
-
Save atennapel/3c8c3ef7ed2bbc16a11490128a9690f6 to your computer and use it in GitHub Desktop.
Runtime type validation in TypeScript
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
type Validator<T> = (val: any) => T | Error; | |
type ValidatorExtract<V> = V extends Validator<infer T> ? T : never; | |
type ValidatorMap = { [key: string]: Validator<any> }; | |
const err = (ty: string) => new Error('validate failed: ' + ty); | |
const nullV: Validator<null> = (val: any) => | |
val === null ? val : err('null'); | |
const undefinedV: Validator<undefined> = (val: any) => | |
typeof val === 'undefined' ? val : err('undefined'); | |
const booleanV: Validator<boolean> = (val: any) => | |
typeof val === 'boolean' ? val : err('boolean'); | |
const numberV: Validator<number> = (val: any) => | |
typeof val === 'number' ? val : err('number'); | |
const stringV: Validator<string> = (val: any) => | |
typeof val === 'string' ? val : err('string'); | |
const functionAnyV: Validator<Function> = (val: any) => | |
typeof val === 'function' ? val : err('functionAny'); | |
const classV = <C>(cl: Function): Validator<C> => (val: any) => | |
val instanceof cl ? val : err('class ' + cl); | |
const dateV = classV<Date>(Date); | |
const functionV = <F extends (...args: any[]) => any>(): Validator<F> => (val: any) => | |
typeof val === 'function' ? val : err('function'); | |
const arrayV = <T>(elem: Validator<T>): Validator<T[]> => (val: any) => { | |
if (!Array.isArray(val)) return err('array'); | |
const l = val.length; | |
for (let i = 0; i < l; i++) { | |
const e = elem(val[i]); | |
if (e instanceof Error) return err('array ' + e); | |
} | |
return val; | |
}; | |
const objectV = <M extends ValidatorMap>(map: M, allowOtherProps: boolean = false): | |
Validator<{ [K in keyof M]: ValidatorExtract<M[K]> }> => | |
(val: any) => { | |
if (typeof val !== 'object' || val === null) return err('object'); | |
for (let k in val) { | |
if (!map[k]) { | |
if (!allowOtherProps) return err('object other prop ' + k); | |
} else { | |
const e = map[k](val[k]); | |
if (e instanceof Error) return err('object key ' + k); | |
} | |
} | |
return val; | |
}; | |
const unionV = <A, B>(a: Validator<A>, b: Validator<B>): Validator<A | B> => | |
(val: any) => a(val) instanceof Error ? b(val) : val; | |
const intersectionV = <A, B>(a: Validator<A>, b: Validator<B>): Validator<A & B> => | |
(val: any) => a(val) instanceof Error ? err('intersection') : b(val) as (A & B) | Error; | |
const optionalV = <T>(v: Validator<T>) => unionV(unionV(v, undefinedV), nullV); | |
const check = <T>(v: Validator<T>, val: any): T => { | |
const vret = v(val); | |
if (vret instanceof Error) throw vret; | |
return vret; | |
}; | |
const res = check( | |
objectV({ | |
a: numberV, | |
b: stringV, | |
c: functionV<(val: number) => number>() | |
}), | |
{ a: 1, b: 'a', c: (x: number) => x }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment