Skip to content

Instantly share code, notes, and snippets.

@atennapel
Last active August 23, 2019 15:16
Show Gist options
  • Save atennapel/3c8c3ef7ed2bbc16a11490128a9690f6 to your computer and use it in GitHub Desktop.
Save atennapel/3c8c3ef7ed2bbc16a11490128a9690f6 to your computer and use it in GitHub Desktop.
Runtime type validation in TypeScript
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