Created
July 21, 2017 04:09
-
-
Save mikeplus64/465c0ee54b7f9a5b752183439defc7b1 to your computer and use it in GitHub Desktop.
check if arbitrary objects match some schema, allowing them to be flowtyped; better than ((thing: any): Something)
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
/* @flow */ | |
export type ParseError = { | |
expected: string, | |
got: mixed, | |
at?: mixed, | |
children?: ParseError[], | |
}; | |
export type Result<T> = | |
{ type: 'OK', value: T } | | |
{ type: 'ERROR', error: ParseError } | |
export type Validator<I, T> = (value: I) => Result<T>; | |
// throws an exception if parse failed | |
export type Check<I, T> = (input: I) => T; | |
export default function check<I, T>(validator: Validator<I, T>): Check<I, T> { | |
return (input) => { | |
const r = validator(input); | |
if (r.type === 'ERROR') { | |
throw new Error(JSON.stringify(r.error, null, 2)); | |
} | |
return r.value; | |
}; | |
} | |
export const number: Validator<mixed, number> = | |
(value: mixed) => { | |
if (typeof value === 'number') { | |
return { type: 'OK', value }; | |
} | |
return { | |
type: 'ERROR', | |
error: { expected: 'number', got: value }, | |
}; | |
}; | |
export const string: Validator<mixed, string> = | |
(value: mixed) => { | |
if (typeof value === 'string') { | |
return { type: 'OK', value }; | |
} | |
return { | |
type: 'ERROR', | |
error: { expected: 'string', got: value }, | |
}; | |
}; | |
export const boolean: Validator<mixed, boolean> = | |
(value: mixed) => { | |
if (typeof value === 'boolean') { | |
return { type: 'OK', value }; | |
} | |
return { | |
type: 'ERROR', | |
error: { expected: 'boolean', got: value }, | |
}; | |
}; | |
export function nullable<T>(elem: Validator<mixed, T>): Validator<mixed, ?T> { | |
return (value: mixed) => { | |
if (value == null) { | |
return { type: 'OK', value }; | |
} | |
return (elem(value): any); | |
}; | |
} | |
export function shape<S: { [key: string]: <T>(value: mixed) => Result<T> }>( | |
objShape: S, | |
): Validator<mixed, $ObjMap<S, <Q>(() => Result<Q>) => Q>> { | |
const shapeKeys = Object.keys(objShape); | |
return (value: mixed) => { | |
if (typeof value !== 'object' || value == null) { | |
return { | |
type: 'ERROR', | |
error: { | |
expected: 'object', | |
got: value, | |
}, | |
}; | |
} | |
for (let i = 0; i < shapeKeys.length; i += 1) { | |
const key: string = shapeKeys[i]; | |
const shapeHere: <T>(value: mixed) => Result<T> = objShape[key]; | |
const elemHere: mixed = value[key]; | |
const result: Result<mixed> = shapeHere(elemHere); | |
if (result.type === 'ERROR') { | |
return { | |
type: 'ERROR', | |
error: { | |
expected: 'shape', | |
got: value, | |
children: [({ at: shapeKeys[i], ...(result: any) }: any)], | |
}, | |
}; | |
} | |
} | |
return { type: 'OK', value: (value: any) }; | |
}; | |
} | |
export function object<T>( | |
elem: Validator<mixed, T>, | |
): Validator<{ [key: string]: mixed }, T> { | |
return (value: { [key: string]: mixed }) => { | |
const keys: string[] = Object.keys(value); | |
const errors: ParseError[] = []; | |
let hasParseError: boolean = false; | |
for (let i = 0; i < keys.length; i += 1) { | |
const key: string = keys[i]; | |
const here: mixed = value[key]; | |
const test: Result<T> = elem(here); | |
if (test.type === 'ERROR') { | |
hasParseError = true; | |
errors.push(test.error); | |
} | |
} | |
if (hasParseError) { | |
return { | |
type: 'ERROR', | |
error: { | |
expected: 'object', | |
got: ((value: any): mixed), | |
children: errors, | |
}, | |
}; | |
} | |
return { type: 'OK', value: (value: any) }; | |
}; | |
} | |
export function array<T>(elem: Validator<mixed, T>): Validator<mixed[], T> { | |
return (value: mixed[]) => { | |
if (!Array.isArray(value)) { | |
return { | |
type: 'ERROR', | |
error: { expected: 'array', got: value }, | |
}; | |
} | |
for (let i = 0; i < value.length; i += 1) { | |
const here: mixed = value[i]; | |
const test: Result<T> = elem(here); | |
if (test.type === 'ERROR') { | |
return { | |
type: 'ERROR', | |
error: { | |
expected: 'array', | |
got: value, | |
children: [{ ...test.error, at: i }], | |
}, | |
}; | |
} | |
} | |
return { type: 'OK', value: (value: any) }; | |
}; | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment