Skip to content

Instantly share code, notes, and snippets.

@arleighdickerson
Last active December 28, 2021 03:43
Show Gist options
  • Save arleighdickerson/2f0e795cf916d52c2843113c745ac018 to your computer and use it in GitHub Desktop.
Save arleighdickerson/2f0e795cf916d52c2843113c745ac018 to your computer and use it in GitHub Desktop.
typed invariant utils
import _ from 'lodash';
const DEFAULT_ERROR_MESSAGE = 'Invariant Type Violation';
export class InvariantError extends Error {
constructor(message = DEFAULT_ERROR_MESSAGE) {
super(message);
}
}
export class InvariantTypeError extends InvariantError {
constructor(public readonly value: any, message: string) {
super(message);
}
}
export default function invariant(
condition: any,
message?: string,
): asserts condition {
if (!condition) {
throw new InvariantError(message);
}
}
type TypeGuard<T> = (value: T) => value is T;
type ErrorProvider = string | ((value: any) => string);
function resolveErrorMessage(provider: ErrorProvider, value: any): string {
return _.isFunction(provider) ? provider(value) : provider;
}
function invariantType<T>(guard: TypeGuard<T>) {
return function (
value: unknown,
message: ErrorProvider = DEFAULT_ERROR_MESSAGE,
): T {
if (!guard(value as any)) {
console.error(resolveErrorMessage(message, value), value);
throw new InvariantTypeError(value, resolveErrorMessage(message, value));
}
return value as T;
};
}
function invariantArray<T>(guard: TypeGuard<T>) {
return function (
value: unknown,
message: ErrorProvider = DEFAULT_ERROR_MESSAGE,
): T[] {
if (!_.isArray(value)) {
throw new InvariantTypeError(
value,
'(hint: value is not an array) >>' + message,
);
}
for (const v of value) {
if (!guard(v as any)) {
console.error(resolveErrorMessage(message, value), value);
throw new InvariantTypeError(
value,
resolveErrorMessage(message, value),
);
}
}
return value as T[];
};
}
export function isInteger(value: any): value is number {
return _.isNumber(value) && _.isInteger(value);
}
interface InvariantChecker<T> {
message(message: ErrorProvider): InvariantChecker<T>;
value(value: any): T;
optional(value: any): T | undefined;
nullable(value: any): T | null;
array(value: any): T[];
}
class InvariantCheckerImpl<T> implements InvariantChecker<T> {
private _message: ErrorProvider = DEFAULT_ERROR_MESSAGE;
constructor(private readonly typeGuard: TypeGuard<T>) {}
public message(message: ErrorProvider): InvariantChecker<T> {
this._message = message;
return this;
}
value(value: any): T {
return invariantType(this.typeGuard)(value, this._message);
}
optional(value: any): T | undefined {
if (_.isUndefined(value)) {
return invariantType(_.isUndefined)(value, this._message);
} else {
return this.value(value);
}
}
nullable(value: any): T | null {
if (_.isNull(value)) {
return invariantType(_.isNull)(value, this._message);
} else {
return this.value(value);
}
}
array(value: any): T[] {
return invariantArray(this.typeGuard)(value, this._message);
}
}
export function cast<T>(typeGuard: TypeGuard<T>): InvariantChecker<T> {
return new InvariantCheckerImpl(typeGuard);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment