diff --git a/util.ts b/util.ts index b9987fb..65f26ac 100644 --- a/util.ts +++ b/util.ts @@ -1,5 +1,19 @@ import type { Predicate } from "./is.ts"; +export type AssertMessageFactory = ( + x: unknown, + pred: Predicate, +) => string; + +export const defaultAssertMessageFactory: AssertMessageFactory = (x, pred) => { + const p = pred.name || "anonymous predicate"; + const t = typeof x; + const v = JSON.stringify(x, null, 2); + return `Expected a value that satisfies the predicate ${p}, got ${t}: ${v}`; +}; + +let assertMessageFactory = defaultAssertMessageFactory; + /** * Represents an error that occurs when an assertion fails. */ @@ -19,6 +33,31 @@ export class AssertError extends Error { } } +/** + * Sets the factory function used to generate assertion error messages. + * @param factory The factory function. + * @example + * ```ts + * import { setAssertMessageFactory } from "./util.ts"; + * import is from "./is.ts"; + * + * 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}`; + * } + * }); + * ``` + */ +export function setAssertMessageFactory(factory: AssertMessageFactory): void { + assertMessageFactory = factory; +} + /** * Asserts that the given value satisfies the provided predicate. * @@ -44,7 +83,7 @@ export function assert( ): asserts x is T { if (!pred(x)) { throw new AssertError( - options.message ?? "The value is not the expected type", + options.message ?? assertMessageFactory(x, pred), ); } } diff --git a/util_test.ts b/util_test.ts index 4100123..38abd00 100644 --- a/util_test.ts +++ b/util_test.ts @@ -2,24 +2,51 @@ import { assertStrictEquals, assertThrows, } from "https://deno.land/std@0.200.0/testing/asserts.ts"; -import { assert, AssertError, ensure, maybe } from "./util.ts"; +import { + assert, + AssertError, + defaultAssertMessageFactory, + ensure, + maybe, + setAssertMessageFactory, +} from "./util.ts"; const x: unknown = Symbol("x"); +function truePredicate(_x: unknown): _x is string { + return true; +} + +function falsePredicate(_x: unknown): _x is string { + return false; +} + Deno.test("assert", async (t) => { await t.step("does nothing on true predicate", () => { - assert(x, (_x): _x is string => true); + assert(x, truePredicate); }); await t.step("throws an `AssertError` on false predicate", () => { - assertThrows(() => assert(x, (_x): _x is string => false), AssertError); + assertThrows( + () => assert(x, falsePredicate), + AssertError, + `Expected a value that satisfies the predicate falsePredicate, got symbol: undefined`, + ); + }); + + await t.step("throws an `AssertError` on false predicate", () => { + assertThrows( + () => assert(x, falsePredicate), + AssertError, + `Expected a value that satisfies the predicate falsePredicate, got symbol: undefined`, + ); }); await t.step( "throws an `AssertError` with a custom message on false predicate", () => { assertThrows( - () => assert(x, (_x): _x is string => false, { message: "Hello" }), + () => assert(x, falsePredicate, { message: "Hello" }), AssertError, "Hello", ); @@ -29,18 +56,22 @@ Deno.test("assert", async (t) => { Deno.test("ensure", async (t) => { await t.step("returns `x` as-is on true predicate", () => { - assertStrictEquals(ensure(x, (_x): _x is string => true), x); + assertStrictEquals(ensure(x, truePredicate), x); }); await t.step("throws an `AssertError` on false predicate", () => { - assertThrows(() => ensure(x, (_x): _x is string => false), AssertError); + assertThrows( + () => ensure(x, falsePredicate), + AssertError, + `Expected a value that satisfies the predicate falsePredicate, got symbol: undefined`, + ); }); await t.step( "throws an `AssertError` with a custom message on false predicate", () => { assertThrows( - () => ensure(x, (_x): _x is string => false, { message: "Hello" }), + () => ensure(x, falsePredicate, { message: "Hello" }), AssertError, "Hello", ); @@ -50,10 +81,32 @@ Deno.test("ensure", async (t) => { Deno.test("maybe", async (t) => { await t.step("returns `x` as-is on true predicate", () => { - assertStrictEquals(maybe(x, (_x): _x is string => true), x); + assertStrictEquals(maybe(x, truePredicate), x); }); await t.step("returns `undefined` on false predicate", () => { - assertStrictEquals(maybe(x, (_x): _x is string => false), undefined); + assertStrictEquals(maybe(x, falsePredicate), undefined); }); }); + +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", + ); + }); + + await t.step("change `AssertError` message on `ensure` failure", () => { + assertThrows( + () => ensure(x, falsePredicate), + AssertError, + "Hello symbol falsePredicate", + ); + }); + + setAssertMessageFactory(defaultAssertMessageFactory); +});