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 = (fn: () => T): (() => Promise) => { + 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, 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, 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 = { type: 'Ok'; value: T }; + +/** + * Represents a failed result. + * @template E The type of the error. + */ +export type ErrResult = { 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 = OkResult | ErrResult; + +/** + * Creates a successful result. + * @template T The type of the value. + * @param {T} value The value to wrap in an `Ok` result. + * @returns {OkResult} The `Ok` result containing the value. + */ +export function Ok(value: T): OkResult { + 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} The `Err` result containing the error. + */ +export function Err(error: E): ErrResult { + 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} An `Ok` result if the function succeeds, or an `Err` result if it throws an error. + */ +export function withResult(fn: () => T): Result { + 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} fn The asynchronous function to execute. + * @returns {Promise>} A `Promise` resolving to an `Ok` result if the function succeeds, or an `Err` result if it throws an error. + */ +export async function withResultAsync(fn: () => Promise): Promise> { + 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} result The result to check. + * @returns {result is OkResult} `true` if the result is an `Ok` result, otherwise `false`. + */ +export function isOk(result: Result): result is OkResult { + 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} result The result to check. + * @returns {result is ErrResult} `true` if the result is an `Err` result, otherwise `false`. + */ +export function isErr(result: Result): result is ErrResult { + return result.type === 'Err'; +}