mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-04-23 03:00:31 -04:00
feat(ui): revised Result implementation
Use classes with `isOk` and `isErr` methods instead of separate type guard functions. Update tests and usage.
This commit is contained in:
@@ -3,7 +3,7 @@ import { enqueueRequested } from 'app/store/actions';
|
||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||
import type { SerializableObject } from 'common/types';
|
||||
import type { Result } from 'common/util/result';
|
||||
import { isErr, withResult, withResultAsync } from 'common/util/result';
|
||||
import { withResult, withResultAsync } from 'common/util/result';
|
||||
import { $canvasManager } from 'features/controlLayers/store/canvasSlice';
|
||||
import {
|
||||
selectIsStaging,
|
||||
@@ -66,7 +66,7 @@ export const addEnqueueRequestedLinear = (startAppListening: AppStartListening)
|
||||
assert(false, `No graph builders for base ${base}`);
|
||||
}
|
||||
|
||||
if (isErr(buildGraphResult)) {
|
||||
if (buildGraphResult.isErr()) {
|
||||
log.error({ error: serializeError(buildGraphResult.error) }, 'Failed to build graph');
|
||||
abortStaging();
|
||||
return;
|
||||
@@ -80,7 +80,7 @@ export const addEnqueueRequestedLinear = (startAppListening: AppStartListening)
|
||||
prepareLinearUIBatch(state, g, prepend, noise, posCond, 'generation', destination)
|
||||
);
|
||||
|
||||
if (isErr(prepareBatchResult)) {
|
||||
if (prepareBatchResult.isErr()) {
|
||||
log.error({ error: serializeError(prepareBatchResult.error) }, 'Failed to prepare batch');
|
||||
abortStaging();
|
||||
return;
|
||||
@@ -95,7 +95,7 @@ export const addEnqueueRequestedLinear = (startAppListening: AppStartListening)
|
||||
|
||||
const enqueueResult = await withResultAsync(() => req.unwrap());
|
||||
|
||||
if (isErr(enqueueResult)) {
|
||||
if (enqueueResult.isErr()) {
|
||||
log.error({ error: serializeError(enqueueResult.error) }, 'Failed to enqueue batch');
|
||||
abortStaging();
|
||||
return;
|
||||
|
||||
@@ -2,8 +2,7 @@ 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
|
||||
import { Err, ErrResult, Ok, OkResult, withResult, withResultAsync } from './result';
|
||||
|
||||
const promiseify = <T>(fn: () => T): (() => Promise<T>) => {
|
||||
return () =>
|
||||
@@ -13,28 +12,30 @@ const promiseify = <T>(fn: () => T): (() => Promise<T>) => {
|
||||
};
|
||||
|
||||
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('OkResult() should create an Ok result', () => {
|
||||
const result = OkResult(42);
|
||||
expect(result).toBeInstanceOf(Ok);
|
||||
expect(result.isOk()).toBe(true);
|
||||
expect(result.isErr()).toBe(false);
|
||||
expect(result.value).toBe(42);
|
||||
assert<Equals<Ok<number>, typeof result>>(result);
|
||||
});
|
||||
|
||||
it('Err() should create an ErrResult', () => {
|
||||
it('ErrResult() should create an Err result', () => {
|
||||
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);
|
||||
const result = ErrResult(error);
|
||||
expect(result).toBeInstanceOf(Err);
|
||||
expect(result.isOk()).toBe(false);
|
||||
expect(result.isErr()).toBe(true);
|
||||
expect(result.error).toBe(error);
|
||||
assert<Equals<Err<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.isOk()).toBe(true);
|
||||
if (result.isOk()) {
|
||||
expect(result.value).toBe(42);
|
||||
}
|
||||
});
|
||||
@@ -44,8 +45,8 @@ describe('Result Utility Functions', () => {
|
||||
throw new Error('Failure');
|
||||
};
|
||||
const result = withResult(fn);
|
||||
expect(isErr(result)).toBe(true);
|
||||
if (isErr(result)) {
|
||||
expect(result.isErr()).toBe(true);
|
||||
if (result.isErr()) {
|
||||
expect(result.error.message).toBe('Failure');
|
||||
}
|
||||
});
|
||||
@@ -53,8 +54,8 @@ describe('Result Utility Functions', () => {
|
||||
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.isOk()).toBe(true);
|
||||
if (result.isOk()) {
|
||||
expect(result.value).toBe(42);
|
||||
}
|
||||
});
|
||||
@@ -64,8 +65,8 @@ describe('Result Utility Functions', () => {
|
||||
throw new Error('Async failure');
|
||||
});
|
||||
const result = await withResultAsync(fn);
|
||||
expect(isErr(result)).toBe(true);
|
||||
if (isErr(result)) {
|
||||
expect(result.isErr()).toBe(true);
|
||||
if (result.isErr()) {
|
||||
expect(result.error.message).toBe('Async failure');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2,39 +2,81 @@
|
||||
* Represents a successful result.
|
||||
* @template T The type of the value.
|
||||
*/
|
||||
export type OkResult<T> = { type: 'Ok'; value: T };
|
||||
export class Ok<T> {
|
||||
readonly value: T;
|
||||
constructor(value: T) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard to check if this result is an `Ok` result.
|
||||
* @returns {this is Ok<T>} `true` if the result is an `Ok` result, otherwise `false`.
|
||||
*/
|
||||
isOk(): this is Ok<T> {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard to check if this result is an `Err` result.
|
||||
* @returns {this is Err<never>} `true` if the result is an `Err` result, otherwise `false`.
|
||||
*/
|
||||
isErr(): this is Err<never> {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a failed result.
|
||||
* @template E The type of the error.
|
||||
*/
|
||||
export type ErrResult<E> = { type: 'Err'; error: E };
|
||||
export class Err<E> {
|
||||
readonly error: E;
|
||||
constructor(error: E) {
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard to check if this result is an `Ok` result.
|
||||
* @returns {this is Ok<never>} `true` if the result is an `Ok` result, otherwise `false`.
|
||||
*/
|
||||
isOk(): this is Ok<never> {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard to check if this result is an `Err` result.
|
||||
* @returns {this is Err<E>} `true` if the result is an `Err` result, otherwise `false`.
|
||||
*/
|
||||
isErr(): this is Err<E> {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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>;
|
||||
export type Result<T, E = Error> = Ok<T> | Err<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.
|
||||
* @returns {Ok<T>} The `Ok` result containing the value.
|
||||
*/
|
||||
export function Ok<T>(value: T): OkResult<T> {
|
||||
return { type: 'Ok', value };
|
||||
export function OkResult<T>(value: T): Ok<T> {
|
||||
return new 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.
|
||||
* @returns {Err<E>} The `Err` result containing the error.
|
||||
*/
|
||||
export function Err<E>(error: E): ErrResult<E> {
|
||||
return { type: 'Err', error };
|
||||
export function ErrResult<E>(error: E): Err<E> {
|
||||
return new Err(error);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -45,9 +87,9 @@ export function Err<E>(error: E): ErrResult<E> {
|
||||
*/
|
||||
export function withResult<T>(fn: () => T): Result<T> {
|
||||
try {
|
||||
return Ok(fn());
|
||||
return new Ok(fn());
|
||||
} catch (error) {
|
||||
return Err(error instanceof Error ? error : new Error(String(error)));
|
||||
return new Err(error instanceof Error ? error : new Error(String(error)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,30 +102,8 @@ export function withResult<T>(fn: () => T): Result<T> {
|
||||
export async function withResultAsync<T>(fn: () => Promise<T>): Promise<Result<T>> {
|
||||
try {
|
||||
const result = await fn();
|
||||
return Ok(result);
|
||||
return new Ok(result);
|
||||
} catch (error) {
|
||||
return Err(error instanceof Error ? error : new Error(String(error)));
|
||||
return new 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';
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { IconButton } from '@invoke-ai/ui-library';
|
||||
import { logger } from 'app/logging/logger';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { isOk, withResultAsync } from 'common/util/result';
|
||||
import { withResultAsync } from 'common/util/result';
|
||||
import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
|
||||
import { useCanvasIsBusy } from 'features/controlLayers/hooks/useCanvasIsBusy';
|
||||
import { useEntityTypeCount } from 'features/controlLayers/hooks/useEntityTypeCount';
|
||||
@@ -33,7 +33,7 @@ export const CanvasEntityMergeVisibleButton = memo(({ type }: Props) => {
|
||||
canvasManager.compositor.rasterizeAndUploadCompositeRasterLayer(rect, false)
|
||||
);
|
||||
|
||||
if (isOk(result)) {
|
||||
if (result.isOk()) {
|
||||
dispatch(
|
||||
rasterLayerAdded({
|
||||
isSelected: true,
|
||||
@@ -55,7 +55,7 @@ export const CanvasEntityMergeVisibleButton = memo(({ type }: Props) => {
|
||||
canvasManager.compositor.rasterizeAndUploadCompositeInpaintMask(rect, false)
|
||||
);
|
||||
|
||||
if (isOk(result)) {
|
||||
if (result.isOk()) {
|
||||
dispatch(
|
||||
inpaintMaskAdded({
|
||||
isSelected: true,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { logger } from 'app/logging/logger';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { isOk, withResultAsync } from 'common/util/result';
|
||||
import { withResultAsync } from 'common/util/result';
|
||||
import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
|
||||
import { selectDefaultControlAdapter, selectDefaultIPAdapter } from 'features/controlLayers/hooks/addLayerHooks';
|
||||
import { getPrefixedId } from 'features/controlLayers/konva/util';
|
||||
@@ -50,7 +50,7 @@ const useSaveCanvas = ({ region, saveToGallery, onSave }: UseSaveCanvasArg) => {
|
||||
canvasManager.compositor.rasterizeAndUploadCompositeRasterLayer(rect, saveToGallery)
|
||||
);
|
||||
|
||||
if (isOk(result)) {
|
||||
if (result.isOk()) {
|
||||
if (onSave) {
|
||||
onSave(result.value, rect);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { SerializableObject } from 'common/types';
|
||||
import { isErr, withResultAsync } from 'common/util/result';
|
||||
import { withResultAsync } from 'common/util/result';
|
||||
import type { CanvasEntityAdapterControlLayer } from 'features/controlLayers/konva/CanvasEntity/CanvasEntityAdapterControlLayer';
|
||||
import type { CanvasEntityAdapterRasterLayer } from 'features/controlLayers/konva/CanvasEntity/CanvasEntityAdapterRasterLayer';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
@@ -153,7 +153,7 @@ export class CanvasEntityFilterer extends CanvasModuleBase {
|
||||
this.$isProcessing.set(true);
|
||||
const req = this.manager.stateApi.enqueueBatch(batch);
|
||||
const result = await withResultAsync(req.unwrap);
|
||||
if (isErr(result)) {
|
||||
if (result.isErr()) {
|
||||
this.$isProcessing.set(false);
|
||||
}
|
||||
req.reset();
|
||||
|
||||
Reference in New Issue
Block a user