sarcastic
Version:
Cast unknown values to typed values
147 lines (127 loc) • 4.2 kB
JavaScript
// @flow
/*::
type Assertion<T> = (val: mixed, name: string) => T;
type AssertionMap = { [key: string]: Assertion<any> };
type ExtractAssertionType = <T>(Assertion<T>) => T;
type AssertionResultMap<T> = $ObjMap<T, ExtractAssertionType>;
export type AssertionType<A: Assertion<*>> = $Call<ExtractAssertionType, A>;
*/
class AssertionError extends Error {
/*::
kind: string;
target: string;
value: mixed;
*/
constructor(kind/*: string */, target/*: string */, value/*: mixed */) {
super(`${target} must be ${kind}`);
this.kind = kind;
this.target = target;
this.value = value;
}
}
let is = /*:: <T> */ (val/*: mixed */, assertion/*: Assertion<T> */, name/*: string */)/*: T */ => {
return assertion(val, name);
};
let boolean /*: Assertion<boolean> */ = (val, name) => {
if (typeof val === 'boolean') return val;
throw new AssertionError('a boolean', name, val);
};
let number /*: Assertion<number> */ = (val, name) => {
if (typeof val === 'number') return val;
throw new AssertionError('a number', name, val);
};
let string /*: Assertion<string> */ = (val, name) => {
if (typeof val === 'string') return val;
throw new AssertionError('a string', name, val);
};
let array /*: Assertion<Array<mixed>> */ = (val, name) => {
if (Array.isArray(val)) return val;
throw new AssertionError('an array', name, val);
};
let object /*: Assertion<{ [key: string]: mixed }> */ = (val, name) => {
if (typeof val === 'object' && val !== null && !Array.isArray(val)) return val;
throw new AssertionError('an object', name, val);
};
let arrayOf = /*:: <T> */ (assertion /*: Assertion<T> */) /*: Assertion<Array<T>> */ => {
return (val, name) => {
return is(val, array, name).map((item, index) => assertion(item, `${name}[${index}]`));
};
};
let arrayish = /*:: <T> */ (assertion /*: Assertion<T> */) /*: Assertion<Array<T>> */ => {
let arrAssertion = is.arrayOf(assertion);
return (val, name) => {
if (Array.isArray(val)) {
return arrAssertion(val, name);
} else {
try {
return [assertion(val, name)];
} catch (err) {
if (!(err instanceof AssertionError)) throw err;
throw new AssertionError(`${err.kind} or an array`, name, val);
}
}
};
};
let objectOf = /*::<T> */ (assertion/*: Assertion<T>*/)/*: Assertion<{ [key: string]: T }> */ => {
return (val, name) => {
let obj = is(val, object, name);
let res = {};
Object.keys(obj).forEach(key => {
res[key] = assertion(obj[key], `${name}.${key}`);
});
return res;
};
};
let shape = /*:: <T: AssertionMap> */ (assertions /*: T */) /*: Assertion<AssertionResultMap<T>> */ => {
return (val, name) => {
let obj = is(val, object, name);
let res = {};
Object.keys(assertions).forEach(key => {
res[key] = assertions[key](obj[key], `${name}.${key}`);
});
return res;
};
};
let maybe = /*:: <T> */ (assertion /*: Assertion<T> */) /*: Assertion<T | null> */ => {
return (val, name) => {
if (typeof val === 'undefined' || val === null) return null;
return assertion(val, name);
};
};
let _default = /*:: <T> */ (assertion /*: Assertion<T> */, defaultValue /*: T */) /*: Assertion<T> */ => {
return (val, name) => {
if (typeof val === 'undefined' || val === null) return defaultValue;
return assertion(val, name);
};
};
let either = /*:: <A, B> */ (assertionA /*: Assertion<A> */, assertionB /*: Assertion<B> */) /*: Assertion<A | B> */ => {
return (val, name) => {
try {
return assertionA(val, name)
} catch (errA) {
if (!(errA instanceof AssertionError)) throw errA;
try {
return assertionB(val, name);
} catch (errB) {
if (!(errA instanceof AssertionError)) throw errB;
throw new AssertionError(`${errA.kind} or ${errB.kind}`, name, val);
}
}
};
};
is.is = is;
is.boolean = boolean;
is.number = number;
is.string = string;
is.array = array;
is.object = object;
is.arrayOf = arrayOf;
is.arrayish = arrayish;
is.objectOf = objectOf;
is.shape = shape;
is.maybe = maybe;
is.default = _default;
is.either = either;
is.AssertionError = AssertionError;
module.exports = is;
;