From bf0824b56d46b6826f3098f0cd615f5b711c9a27 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Sat, 24 Aug 2024 14:46:58 +1000 Subject: [PATCH] feat(ui): add Result type & helpers Wrappers to capture errors and turn into results: - `withResult` wraps a sync function - `withResultAsync` wraps an async function Comments, tests. --- .../web/src/common/util/result.test.ts | 72 +++++++++++++++ .../frontend/web/src/common/util/result.ts | 89 +++++++++++++++++++ 2 files changed, 161 insertions(+) create mode 100644 invokeai/frontend/web/src/common/util/result.test.ts create mode 100644 invokeai/frontend/web/src/common/util/result.ts diff --git a/invokeai/frontend/web/src/common/util/result.test.ts b/invokeai/frontend/web/src/common/util/result.test.ts new file mode 100644 index 0000000000..1237a59024 --- /dev/null +++ b/invokeai/frontend/web/src/common/util/result.test.ts @@ -0,0 +1,72 @@ +import type { Equals } from 'tsafe'; +import { assert } from 'tsafe'; +import { describe, expect, it } from 'vitest'; + +import type { ErrResult, OkResult } from './result'; +import { Err, isErr, isOk, Ok, withResult, withResultAsync } from './result'; // Adjust import as needed + +const promiseify = <T>(fn: () => T): (() => Promise<T>) => { + return () => + new Promise((resolve) => { + resolve(fn()); + }); +}; + +describe('Result Utility Functions', () => { + it('Ok() should create an OkResult', () => { + const result = Ok(42); + expect(result).toEqual({ type: 'Ok', value: 42 }); + expect(isOk(result)).toBe(true); + expect(isErr(result)).toBe(false); + assert<Equals<OkResult<number>, typeof result>>(result); + }); + + it('Err() should create an ErrResult', () => { + const error = new Error('Something went wrong'); + const result = Err(error); + expect(result).toEqual({ type: 'Err', error }); + expect(isOk(result)).toBe(false); + expect(isErr(result)).toBe(true); + assert<Equals<ErrResult<Error>, typeof result>>(result); + }); + + it('withResult() should return Ok on success', () => { + const fn = () => 42; + const result = withResult(fn); + expect(isOk(result)).toBe(true); + if (isOk(result)) { + expect(result.value).toBe(42); + } + }); + + it('withResult() should return Err on exception', () => { + const fn = () => { + throw new Error('Failure'); + }; + const result = withResult(fn); + expect(isErr(result)).toBe(true); + if (isErr(result)) { + expect(result.error.message).toBe('Failure'); + } + }); + + it('withResultAsync() should return Ok on success', async () => { + const fn = promiseify(() => 42); + const result = await withResultAsync(fn); + expect(isOk(result)).toBe(true); + if (isOk(result)) { + expect(result.value).toBe(42); + } + }); + + it('withResultAsync() should return Err on exception', async () => { + const fn = promiseify(() => { + throw new Error('Async failure'); + }); + const result = await withResultAsync(fn); + expect(isErr(result)).toBe(true); + if (isErr(result)) { + expect(result.error.message).toBe('Async failure'); + } + }); +}); diff --git a/invokeai/frontend/web/src/common/util/result.ts b/invokeai/frontend/web/src/common/util/result.ts new file mode 100644 index 0000000000..6a28e257be --- /dev/null +++ b/invokeai/frontend/web/src/common/util/result.ts @@ -0,0 +1,89 @@ +/** + * Represents a successful result. + * @template T The type of the value. + */ +export type OkResult<T> = { type: 'Ok'; value: T }; + +/** + * Represents a failed result. + * @template E The type of the error. + */ +export type ErrResult<E> = { type: 'Err'; error: E }; + +/** + * A union type that represents either a successful result (`Ok`) or a failed result (`Err`). + * @template T The type of the value in the `Ok` case. + * @template E The type of the error in the `Err` case. + */ +export type Result<T, E = Error> = OkResult<T> | ErrResult<E>; + +/** + * Creates a successful result. + * @template T The type of the value. + * @param {T} value The value to wrap in an `Ok` result. + * @returns {OkResult<T>} The `Ok` result containing the value. + */ +export function Ok<T>(value: T): OkResult<T> { + return { type: 'Ok', value }; +} + +/** + * Creates a failed result. + * @template E The type of the error. + * @param {E} error The error to wrap in an `Err` result. + * @returns {ErrResult<E>} The `Err` result containing the error. + */ +export function Err<E>(error: E): ErrResult<E> { + return { type: 'Err', error }; +} + +/** + * Wraps a synchronous function in a try-catch block, returning a `Result`. + * @template T The type of the value returned by the function. + * @param {() => T} fn The function to execute. + * @returns {Result<T>} An `Ok` result if the function succeeds, or an `Err` result if it throws an error. + */ +export function withResult<T>(fn: () => T): Result<T> { + try { + return Ok(fn()); + } catch (error) { + return Err(error instanceof Error ? error : new Error(String(error))); + } +} + +/** + * Wraps an asynchronous function in a try-catch block, returning a `Promise` of a `Result`. + * @template T The type of the value returned by the function. + * @param {() => Promise<T>} fn The asynchronous function to execute. + * @returns {Promise<Result<T>>} A `Promise` resolving to an `Ok` result if the function succeeds, or an `Err` result if it throws an error. + */ +export async function withResultAsync<T>(fn: () => Promise<T>): Promise<Result<T>> { + try { + const result = await fn(); + return Ok(result); + } catch (error) { + return Err(error instanceof Error ? error : new Error(String(error))); + } +} + +/** + * Type guard to check if a `Result` is an `Ok` result. + * @template T The type of the value in the `Ok` result. + * @template E The type of the error in the `Err` result. + * @param {Result<T, E>} result The result to check. + * @returns {result is OkResult<T>} `true` if the result is an `Ok` result, otherwise `false`. + */ +export function isOk<T, E>(result: Result<T, E>): result is OkResult<T> { + return result.type === 'Ok'; +} + +/** + * Type guard to check if a `Result` is an `Err` result. + * @template T The type of the value in the `Ok` result. + * @template E The type of the error in the `Err` result. + * @param {Result<T, E>} result The result to check. + * @returns {result is ErrResult<E>} `true` if the result is an `Err` result, otherwise `false`. + */ +export function isErr<T, E>(result: Result<T, E>): result is ErrResult<E> { + return result.type === 'Err'; +}