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';
+}