From 308f4273d15649f81a671de267db5108a7135efb Mon Sep 17 00:00:00 2001 From: Ryan Rauh Date: Thu, 7 May 2026 15:10:40 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20wrap=20non-Error=20causes=20pass?= =?UTF-8?q?ed=20to=20Err()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/action.ts | 2 +- lib/box.ts | 2 +- lib/delimiter.ts | 2 +- lib/race.ts | 2 +- lib/reducer.ts | 2 +- lib/result.ts | 19 ++++++++++++++++++- lib/scope-internal.ts | 2 +- test/result.test.ts | 28 ++++++++++++++++++++++++++++ test/until.test.ts | 8 +++++--- 9 files changed, 57 insertions(+), 10 deletions(-) create mode 100644 test/result.test.ts diff --git a/lib/action.ts b/lib/action.ts index 2ee27fb28..05a09fd6d 100644 --- a/lib/action.ts +++ b/lib/action.ts @@ -63,7 +63,7 @@ export function action(executor: Executor, desc?: string): Operation { discard(); discarded(Ok()); } catch (error) { - discarded(Err(error as Error)); + discarded(Err(error)); } }; }, diff --git a/lib/box.ts b/lib/box.ts index ac14f01de..202135ae7 100644 --- a/lib/box.ts +++ b/lib/box.ts @@ -5,6 +5,6 @@ export function* box(op: () => Operation): Operation> { try { return Ok(yield* op()); } catch (error) { - return Err(error as Error); + return Err(error); } } diff --git a/lib/delimiter.ts b/lib/delimiter.ts index a262961e8..e16db182f 100644 --- a/lib/delimiter.ts +++ b/lib/delimiter.ts @@ -86,7 +86,7 @@ export class Delimiter } } catch (error) { this.computed = true; - this.outcome = Just(Err(error as Error)); + this.outcome = Just(Err(error)); } finally { this.finalized = true; this.outcome = this.outcome ?? Nothing(); diff --git a/lib/race.ts b/lib/race.ts index fe60f4b80..f64312603 100644 --- a/lib/race.ts +++ b/lib/race.ts @@ -45,7 +45,7 @@ export function* race>( let value = yield* operation; winner.resolve(Ok(value as Yielded)); } catch (error) { - winner.resolve(Err(error as Error)); + winner.resolve(Err(error)); } }), ); diff --git a/lib/reducer.ts b/lib/reducer.ts index 45822a468..919ab5ec5 100644 --- a/lib/reducer.ts +++ b/lib/reducer.ts @@ -48,7 +48,7 @@ export class Reducer { throw result.error; } } catch (error) { - routine.next(Err(error as Error)); + routine.next(Err(error)); } item = queue.dequeue(); } diff --git a/lib/result.ts b/lib/result.ts index 8649a2468..9fbbdf81f 100644 --- a/lib/result.ts +++ b/lib/result.ts @@ -24,7 +24,24 @@ export function Ok(value?: T): Result { /** * @ignore */ -export const Err = (error: Error): Result => ({ ok: false, error }); +export const Err = (cause: unknown): Result => ({ + ok: false, + error: toError(cause), +}); + +class ThrowValueError extends Error { + constructor(message: string, options?: ErrorOptions) { + super(message, options); + this.name = "ThrowValueError"; + } +} + +function toError(cause: unknown): Error { + if (cause instanceof Error) { + return cause; + } + return new ThrowValueError(String(cause), { cause }); +} /** * @ignore diff --git a/lib/scope-internal.ts b/lib/scope-internal.ts index 0e505f4f6..326ec0451 100644 --- a/lib/scope-internal.ts +++ b/lib/scope-internal.ts @@ -79,7 +79,7 @@ export function createScopeInternal( destructors.delete(destructor); yield* destructor(); } catch (error) { - outcome = Err(error as Error); + outcome = Err(error); } } } finally { diff --git a/test/result.test.ts b/test/result.test.ts new file mode 100644 index 000000000..71df4d99f --- /dev/null +++ b/test/result.test.ts @@ -0,0 +1,28 @@ +import { describe, expect, it } from "./suite.ts"; + +import { Err, Ok } from "../mod.ts"; + +describe("Result", () => { + it("constructs successful results with Ok()", () => { + expect(Ok("hello")).toEqual({ ok: true, value: "hello" }); + }); + + it("preserves Error instances passed to Err()", () => { + let error = new Error("oh no"); + + expect(Err(error)).toEqual({ ok: false, error }); + }); + + it("wraps non-Error causes passed to Err()", () => { + let result = Err("oh no"); + + if (result.ok) { + throw new Error("expected Err() to produce a failed result"); + } + + expect(result.error).toBeInstanceOf(Error); + expect(result.error.name).toEqual("ThrowValueError"); + expect(result.error.message).toEqual("oh no"); + expect(result.error.cause).toEqual("oh no"); + }); +}); diff --git a/test/until.test.ts b/test/until.test.ts index a15ca61a1..89b4b8057 100644 --- a/test/until.test.ts +++ b/test/until.test.ts @@ -9,13 +9,15 @@ describe("until", () => { expect(yield* until(Promise.resolve(42))).toEqual(42); }); }); - it("throws on error", async () => { - expect.assertions(1); + it("wraps non-Error promise rejections", async () => { + expect.assertions(3); await run(function* () { try { yield* until(Promise.reject("error")); } catch (error) { - expect(error).toBe("error"); + expect(error).toBeInstanceOf(Error); + expect((error as Error).message).toBe("error"); + expect((error as Error).cause).toBe("error"); } }); });