// deno-lint-ignore-file no-explicit-any import { isError } from "../error/is-error.ts"; import type { TaggedError } from "../error/tagged-error.ts"; /** * A Result represents the outcome of an operation * that can be either a value of type `TValue` or an error `TError`. */ export class Result { static succeedWith(value: T): Result { return new Result(value); } static failWith(error: T): Result { return new Result(error); } static ok(): Result; static ok(value: T): Result; static ok(value?: any) { return new Result(value ?? undefined); } static tryFrom( fn: () => T, errorMapper: (error: any) => TError, ): Result { try { return Result.succeedWith(fn()); } catch (error) { return Result.failWith(errorMapper(error)); } } private constructor(readonly value: TValue | TError) {} /** * Unwrap the value of the result. * If the result is an error, it will throw the error. */ unwrapOrThrow(): TValue { if (isError(this.value)) { throw this.value; } return this.value as TValue; } /** * Throw the error if the result is an error. * otherwise, do nothing. */ orThrow(): void { if (isError(this.value)) { throw this.value; } } unwrapErrorOrThrow(): TError { if (!isError(this.value)) { throw new Error("Result is not an error"); } return this.value; } /** * Check if the result is a success. */ isOk(): this is Result { return !isError(this.value); } /** * Check if the result is an error. */ isError(): this is Result { return isError(this.value); } /** * Map a function over the value of the result. */ map( fn: (value: TValue) => TMappedValue, ): Result { if (!isError(this.value)) { return Result.succeedWith(fn(this.value as TValue)); } return this as any; } /** * Maps a function over the value of the result and flattens the result. */ flatMap( fn: (value: TValue) => Result, ): Result { if (!isError(this.value)) { return fn(this.value as TValue) as any; } return this as any; } /** * Try to map a function over the value of the result. * If the function throws an error, the result will be a failure. */ tryMap( fn: (value: TValue) => TMappedValue, errMapper: (error: any) => TError, ): Result { if (!isError(this.value)) { try { return Result.succeedWith(fn(this.value as TValue)); } catch (error) { return Result.failWith(errMapper(error)); } } return this as any; } /** * Map a function over the error of the result. */ errorMap( fn: (error: TError) => TMappedError, ): Result { if (isError(this.value)) { return Result.failWith(fn(this.value as TError)); } return this as unknown as Result; } /** * Taps a function if the result is a success. * This is useful for side effects that do not modify the result. */ tap(fn: (value: TValue) => void): Result { if (!isError(this.value)) { try { fn(this.value as TValue); } catch { // do nothing } } return this; } assert( fn: (value: TValue) => Result, ): Result { return this.flatMap((value) => fn(value).map(() => value)); } }