export enum AssertionMode { SKIP = "SKIP", LOG = "LOG", THROW = "THROW", } /** Configurations for the different assertion modes. */ export namespace AssertionConfig { /** The types of the assertion configurations. */ export type Type = typeof Skip|typeof Throw|ReturnType /** An assertion configuration suitable for production, that skips assertions without evaluating them. */ export const Skip = {mode: AssertionMode.SKIP} as const /** An assertion configuration suitable for testing, that throws an exception when an assertion fails. */ export const Throw = {mode: AssertionMode.THROW} as const /** An assertion configuration suitable for debugging, that logs an exception when an assertion fails but allows the program to continue. */ export function Log(logger: (message?: unknown, ...params: unknown[]) => void) { return {mode: AssertionMode.LOG, logger} as const } } /** Assertion class for test-only checks on preconditions and postconditions. */ export class Assertions { config: AssertionConfig.Type = AssertionConfig.Skip /** General assert method. Checks that the condition is true. */ check(value: T, condition: (value: T) => boolean, message: (value: T) => string): T { if (this.config.mode === AssertionMode.SKIP) { return value } const result = condition(value) if (result) { return value } const err = Error(message(value)) if (this.config.mode === AssertionMode.THROW) { throw err } else { // All we can do is stand by and watch in horror... this.config.logger(err) return value } } /** Checks that the value is an integer. */ checkInteger(value: number, message: (value: number) => string): number { return this.check(value, Number.isSafeInteger, message) } /** Checks that the value is an integer and either positive or zero. */ checkPositiveIntegerOrZero(value: number, message: (value: number) => string): number { return this.check(value, (value) => { return Number.isSafeInteger(value) && value >= 0 }, message) } } /** Global assertion object for use by other modules. */ export const assertion = new Assertions();