diff --git a/README.md b/README.md index b9d92be..0a5602d 100644 --- a/README.md +++ b/README.md @@ -194,9 +194,22 @@ const a: unknown = "Hello"; // `assert` does nothing or throws an `AssertError` assert(a, is.String); // a is now narrowed to string +``` + +Use [`@core/errorutil/alter`](https://jsr.io/@core/errorutil/doc/alter/~/alter) +to throw a custom error: + +```typescript +import { alter } from "@core/errorutil/alter"; +import { assert, is } from "@core/unknownutil"; + +const a: unknown = 0; -// With custom message -assert(a, is.String, { message: "a must be a string" }); +// The following throws an Error("a is not a string") +alter( + () => assert(a, is.String), + new Error("a is not a string"), +); ``` ### ensure @@ -211,9 +224,22 @@ const a: unknown = "Hello"; // `ensure` returns `string` or throws an `AssertError` const _: string = ensure(a, is.String); +``` + +Use [`@core/errorutil/alter`](https://jsr.io/@core/errorutil/doc/alter/~/alter) +to throw a custom error: + +```typescript +import { alter } from "@core/errorutil/alter"; +import { ensure, is } from "@core/unknownutil"; + +const a: unknown = 0; -// With custom message -const __: string = ensure(a, is.String, { message: "a must be a string" }); +// The following throws an Error("a is not a string") +const _: string = alter( + () => ensure(a, is.String), + new Error("a is not a string"), +); ``` ### maybe diff --git a/assert.ts b/assert.ts index c76ad75..9c5415c 100644 --- a/assert.ts +++ b/assert.ts @@ -1,81 +1,49 @@ import type { Predicate } from "./type.ts"; -/** - * A factory function that generates assertion error messages. - */ -export type AssertMessageFactory = ( - x: unknown, - pred: Predicate, - name?: string, -) => string; - -/** - * The default factory function used to generate assertion error messages. - */ -export const defaultAssertMessageFactory: AssertMessageFactory = ( - x, - pred, - name, -) => { +const assertMessageFactory = (x: unknown, pred: Predicate) => { const p = pred.name || "anonymous predicate"; const t = typeof x; const v = JSON.stringify(x, null, 2); - return `Expected ${ - name ?? "a value" - } that satisfies the predicate ${p}, got ${t}: ${v}`; + return `Expected a value that satisfies the predicate ${p}, got ${t}: ${v}`; }; -let assertMessageFactory = defaultAssertMessageFactory; - /** * Represents an error that occurs when an assertion fails. */ export class AssertError extends Error { /** - * Constructs a new instance. - * @param message The error message. + * The value that failed the assertion. */ - constructor(message?: string) { - super(message); + readonly x: unknown; - if (Error.captureStackTrace) { - Error.captureStackTrace(this, AssertError); - } + /** + * The predicate that the value failed to satisfy. + */ + readonly pred: Predicate; + /** + * Constructs a new instance. + * + * @param x The value that failed the assertion. + * @param pred The predicate that the value failed to satisfy. + */ + constructor(x: unknown, pred: Predicate) { + super(assertMessageFactory(x, pred)); this.name = this.constructor.name; + this.x = x; + this.pred = pred; } } -/** - * Sets the factory function used to generate assertion error messages. - * - * ```ts - * import { is, setAssertMessageFactory } from "@core/unknownutil"; - * - * setAssertMessageFactory((x, pred) => { - * if (pred === is.String) { - * return `Expected a string, got ${typeof x}`; - * } else if (pred === is.Number) { - * return `Expected a number, got ${typeof x}`; - * } else if (pred === is.Boolean) { - * return `Expected a boolean, got ${typeof x}`; - * } else { - * return `Expected a value that satisfies the predicate, got ${typeof x}`; - * } - * }); - * ``` - * - * @param factory The factory function. - */ -export function setAssertMessageFactory(factory: AssertMessageFactory): void { - assertMessageFactory = factory; -} - /** * Asserts that the given value satisfies the provided predicate. * * It throws {@linkcode AssertError} if the value does not satisfy the predicate. * + * @param x The value to be asserted. + * @param pred The predicate function to test the value against. + * @returns The function has a return type of `asserts x is T` to help TypeScript narrow down the type of `x` after the assertion. + * * ```ts * import { assert, is } from "@core/unknownutil"; * @@ -84,19 +52,22 @@ export function setAssertMessageFactory(factory: AssertMessageFactory): void { * // a is now narrowed to string * ``` * - * @param x The value to be asserted. - * @param pred The predicate function to test the value against. - * @param options Optional configuration for the assertion. - * @returns The function has a return type of `asserts x is T` to help TypeScript narrow down the type of `x` after the assertion. + * Use {@linkcode https://jsr.io/@core/errorutil/doc/alter/~/alter|@core/errorutil/alter.alter} to alter error. + * + * ```ts + * import { alter } from "@core/errorutil/alter"; + * import { assert, is } from "@core/unknownutil"; + * + * const a: unknown = 42; + * alter(() => assert(a, is.String), new Error("a is not a string")); + * // Error: a is not a string + * ``` */ export function assert( x: unknown, pred: Predicate, - options: { message?: string; name?: string } = {}, ): asserts x is T { if (!pred(x)) { - throw new AssertError( - options.message ?? assertMessageFactory(x, pred, options.name), - ); + throw new AssertError(x, pred); } } diff --git a/assert_test.ts b/assert_test.ts index ee96eb2..f5bb7e6 100644 --- a/assert_test.ts +++ b/assert_test.ts @@ -1,10 +1,5 @@ import { assertThrows } from "@std/assert"; -import { - assert, - AssertError, - defaultAssertMessageFactory, - setAssertMessageFactory, -} from "./assert.ts"; +import { assert, AssertError } from "./assert.ts"; const x: unknown = Symbol("x"); @@ -39,40 +34,4 @@ Deno.test("assert", async (t) => { ); }, ); - - await t.step( - "throws an `AssertError` on false predicate with a custom name", - () => { - assertThrows( - () => assert(x, falsePredicate, { name: "hello world" }), - AssertError, - `Expected hello world that satisfies the predicate falsePredicate, got symbol: undefined`, - ); - }, - ); - - await t.step( - "throws an `AssertError` with a custom message on false predicate", - () => { - assertThrows( - () => assert(x, falsePredicate, { message: "Hello" }), - AssertError, - "Hello", - ); - }, - ); -}); - -Deno.test("setAssertMessageFactory", async (t) => { - setAssertMessageFactory((x, pred) => `Hello ${typeof x} ${pred.name}`); - - await t.step("change `AssertError` message on `assert` failure", () => { - assertThrows( - () => assert(x, falsePredicate), - AssertError, - "Hello symbol falsePredicate", - ); - }); - - setAssertMessageFactory(defaultAssertMessageFactory); }); diff --git a/deno.jsonc b/deno.jsonc index 7d37baf..3064849 100644 --- a/deno.jsonc +++ b/deno.jsonc @@ -69,6 +69,7 @@ ] }, "imports": { + "@core/errorutil": "jsr:@core/errorutil@^1.2.0", "@core/iterutil": "jsr:@core/iterutil@^0.3.0", "@core/unknownutil": "./mod.ts", "@deno/dnt": "jsr:@deno/dnt@^0.41.1", diff --git a/ensure.ts b/ensure.ts index 6de3e7a..1adbc4e 100644 --- a/ensure.ts +++ b/ensure.ts @@ -6,6 +6,10 @@ import { assert } from "./assert.ts"; * * It throws {@linkcode [assert].AssertError|AssertError} if the value does not satisfy the predicate. * + * @param x The value to be ensured. + * @param pred The predicate function to test the value against. + * @returns The input value `x`. + * * ```ts * import { ensure, is } from "@core/unknownutil"; * @@ -13,16 +17,21 @@ import { assert } from "./assert.ts"; * const _: string = ensure(a, is.String); * ``` * - * @param x The value to be ensured. - * @param pred The predicate function to test the value against. - * @param options Optional configuration for the assertion. - * @returns The input value `x`. + * Use {@linkcode https://jsr.io/@core/errorutil/doc/alter/~/alter|@core/errorutil/alter.alter} to alter error. + * + * ```ts + * import { alter } from "@core/errorutil/alter"; + * import { ensure, is } from "@core/unknownutil"; + * + * const a: unknown = 42; + * const _: string = alter(() => ensure(a, is.String), new Error("a is not a string")); + * // Error: a is not a string + * ``` */ export function ensure( x: unknown, pred: Predicate, - options: { message?: string; name?: string } = {}, ): T { - assert(x, pred, options); + assert(x, pred); return x; } diff --git a/ensure_test.ts b/ensure_test.ts index 8659fa0..a836ad8 100644 --- a/ensure_test.ts +++ b/ensure_test.ts @@ -1,9 +1,5 @@ import { assertStrictEquals, assertThrows } from "@std/assert"; -import { - AssertError, - defaultAssertMessageFactory, - setAssertMessageFactory, -} from "./assert.ts"; +import { AssertError } from "./assert.ts"; import { ensure } from "./ensure.ts"; const x: unknown = Symbol("x"); @@ -28,40 +24,4 @@ Deno.test("ensure", async (t) => { `Expected a value that satisfies the predicate falsePredicate, got symbol: undefined`, ); }); - - await t.step( - "throws an `AssertError` on false predicate with a custom name", - () => { - assertThrows( - () => ensure(x, falsePredicate, { name: "hello world" }), - AssertError, - `Expected hello world that satisfies the predicate falsePredicate, got symbol: undefined`, - ); - }, - ); - - await t.step( - "throws an `AssertError` with a custom message on false predicate", - () => { - assertThrows( - () => ensure(x, falsePredicate, { message: "Hello" }), - AssertError, - "Hello", - ); - }, - ); -}); - -Deno.test("setAssertMessageFactory", async (t) => { - setAssertMessageFactory((x, pred) => `Hello ${typeof x} ${pred.name}`); - - await t.step("change `AssertError` message on `ensure` failure", () => { - assertThrows( - () => ensure(x, falsePredicate), - AssertError, - "Hello symbol falsePredicate", - ); - }); - - setAssertMessageFactory(defaultAssertMessageFactory); });