import {hasValueResult} from '../internal/value/has'; import type {Constructor, NestedKeys, NestedValue, PlainObject} from '../models'; // #region Types /** * Asserter for a property of a value */ export type AssertProperty< Value extends PlainObject, Path extends NestedKeys, Asserted extends NestedPick = NestedPick, > = Asserter; /** * A function that asserts a value is of a specific type, throwing an error if it is not */ export type Asserter = (value: unknown) => asserts value is Value; type NestedPick = Value extends PlainObject ? Path extends `${infer Head}.${infer Rest}` ? Head extends keyof Value ? {[Key in Head]: NestedPick} : never : Path extends keyof Value ? {[Key in Path]: Value[Key]} : never : never; // #endregion // #region Functions /** * Asserts that a condition is true, throwing an error if it is not * * @param condition Condition to assert * @param message Error message * @param error Error constructor _(defaults to `Error`)_ */ export function assert boolean>( condition: Condition, message: string, error?: ErrorConstructor, ): asserts condition { if (!condition()) { throw new (error ?? Error)(message); } } assert.condition = assertCondition; assert.defined = assertDefined; assert.instanceOf = assertInstanceOf; assert.is = assertIs; assert.property = assertProperty; /** * Creates an _Asserter_ that asserts a condition is true, throwing an error if it is not * * _Available as `assertCondition` and `assert.condition`_ * * @param condition Condition to assert * @param message Error message * @param error Error constructor _(defaults to `Error`)_ * @returns _Asserter_ */ export function assertCondition( condition: (value: unknown) => boolean, message: string, error?: ErrorConstructor, ): Asserter { return value => { assert(() => condition(value), message, error); }; } /** * Asserts that a value is defined, throwing an error if it is not * * _Available as `assertDefined` and `assert.defined`_ * * @param value Value to assert * @param message Error message * @param error Error constructor _(defaults to `Error`)_ */ export function assertDefined( value: unknown, message?: string, error?: ErrorConstructor, ): asserts value is Exclude { assert(() => value != null, message ?? MESSAGE_VALUE_DEFINED, error); } /** * Creates an _Asserter_ that asserts a value is an instance of a constructor, throwing an error if it is not * * _Available as `assertInstanceOf` and `assert.instanceOf`_ * * @param constructor Constructor to check against * @param message Error message * @param error Error constructor _(defaults to `Error`)_ * @returns _Asserter_ */ export function assertInstanceOf( constructor: Constructor, message: string, error?: ErrorConstructor, ): Asserter { return value => { assert(() => value instanceof constructor, message, error); }; } /** * Creates an _Asserter_ that asserts a value is of a specific type, throwing an error if it is not * * _Available as `assertIs` and `assert.is`_ * * @param condition Type guard function to check the value * @param message Error message * @param error Error constructor _(defaults to `Error`)_ * @returns _Asserter_ */ export function assertIs( condition: (value: unknown) => value is Value, message: string, error?: ErrorConstructor, ): Asserter { return value => { assert(() => condition(value), message, error); }; } /** * Creates an _Asserter_ that asserts a property of a value exists and satisfies a condition, throwing an error if it does not * * _Available as `assertProperty` and `assert.property`_ * * @param path Path to the property to check, e.g., `foo.bar.baz` for a nested property * @param condition Condition to assert for the property * @param message Error message * @param error Error constructor _(defaults to `Error`)_ * @returns _Asserter_ */ export function assertProperty< Value extends PlainObject, Path extends NestedKeys, Asserted = NestedPick, >( path: Path, condition: (value: NestedValue) => boolean, message: string, error?: ErrorConstructor, ): Asserter { return (value: unknown): asserts value is Asserted => { assert( () => { const result = hasValueResult(value as never, path, false); return result.ok && condition(result.value as never); }, message, error, ); }; } // #endregion // #region Variables const MESSAGE_VALUE_DEFINED = 'Expected value to be defined'; // #endregion