src/result.ts

Summary

Maintainability
C
1 day
Test Coverage
A
100%
import type { UnwrapOption } from "./option";

import { Option } from "./option";
import { SPECIES, SPECIES_RESULT } from "./utils";

export type UnwrapOk<T, Default = T> = T extends Result<infer U> ? U : Default;
export type UnwrapErr<E, Default = E> = E extends Result<infer _S, infer U>
  ? U
  : Default;

export interface ResultMatcher<T = any, E = any, U = any> {
  Ok: (value: T) => U;
  Err: (error: E) => U;
}

/**
 * The `Result` type is an immutable representation of either success (`Ok`) or failure (`Err`).
 */
export class Result<T = any, E = any> {
  /**
   * @param value - A value of type `T`
   * @returns Wrap a value into an `Result`.
   */
  public static readonly Ok = <T, E = any>(value: T): Result<T, E> =>
    Object.freeze(new Result<T, E>(value, true)) as Result<T, E>;

  /**
   * @param error - An error of type `E`
   * @returns Wrap an error into an `Result`.
   */
  public static readonly Err = <E, T = any>(error: E): Result<T, E> =>
    Object.freeze(new Result<T, E>(error, false)) as Result<T, E>;

  /**
   * `Err` if the value is an `Error`.
   *
   * @param value - A value of type `T`
   */
  public static from<T = any, E extends Error = Error>(
    source: T | E
  ): Result<T, E>;
  /**
   * `OK` if the value satisfies the predicate, otherwise `Err`
   *
   * @param source - Source value
   * @param predicate - A function that returns `true` if the value satisfies the predicate, otherwise `false`
   * @param thisArg - If provided, it will be used as the this value for each invocation of predicate. If it is not provided, `undefined` is used instead.
   */
  public static from<T = any, E = any>(
    source: T | E,
    predicate: (source: T | E) => source is T,
    thisArg?: any
  ): Result<T, E>;
  /**
   * `OK` if the value satisfies the predicate, otherwise `Err`
   *
   * @param source - Source value
   * @param predicate - A function that returns `true` if the value satisfies the predicate, otherwise `false`
   * @param thisArg - If provided, it will be used as the this value for each invocation of predicate. If it is not provided, `undefined` is used instead.
   */
  public static from<T = any, E = any>(
    source: T | E,
    predicate: (source: T | E) => boolean,
    thisArg?: any
  ): Result<T, E>;
  public static from<T = any, E = any>(
    source: T | E,
    predicate?: (source: T | E) => boolean,
    thisArg?: any
  ): Result<T> {
    return (
      predicate ? predicate.call(thisArg, source) : !(source instanceof Error)
    )
      ? Result.Ok(source as T)
      : Result.Err(source as E);
  }

  /**
   * `Ok` if the `fn` returns a value, `Err` if the `fn` throws.
   * @param fn
   * @returns `Ok` with the returned value or `Err` with the exception error.
   */
  public static try<T = any, E = any, TArgs extends any[] = []>(
    fn: (...args: TArgs) => T,
    ...args: TArgs
  ): Result<T, E> {
    try {
      return Result.Ok(fn(...args));
    } catch (error: any) {
      return Result.Err(error);
    }
  }

  /**
   * `Ok` if the `fn` returned Promise resolves a value, `Err` if the `fn` throws or the Promise rejected.
   * @param fn
   * @returns `Ok` with the resolved value or `Err` with the exception error or the rejected value.
   */
  public static async tryAsync<T = any, E = any, TArgs extends any[] = []>(
    fn: (...args: TArgs) => T,
    ...args: TArgs
  ): Promise<Result<T, E>> {
    try {
      return Result.Ok(await fn(...args));
    } catch (error: any) {
      return Result.Err(error);
    }
  }

  /**
   * @returns `true` if the given value is an `Result`.
   *
   * @param maybeResult - A value that might be an `Result`
   */
  public static isResult<T, E>(
    maybeResult: unknown
  ): maybeResult is Result<T, E> {
    return (
      !!maybeResult && (maybeResult as Result<T>)[SPECIES] === SPECIES_RESULT
    );
  }

  /**
   * @returns `true` if the both are `Result` and the `Ok` value or `Err` error are the same via `Object.is`.
   *
   * @param a - An `Result` or any value
   * @param b - An `Result` or any value
   */
  public static isSame(a: unknown, b: unknown): boolean {
    return Result.isResult(a) && Result.isResult(b)
      ? Object.is(a._value, b._value)
      : false;
  }

  private readonly [SPECIES] = SPECIES_RESULT;

  private readonly _value: T | E;
  private readonly _ok: boolean;

  private constructor(value: T | E, ok: boolean) {
    this._value = value;
    this._ok = ok;
  }

  /**
   * Returns an iterator over the possibly contained value.
   *
   * The iterator yields one value if the result is `Ok`, otherwise none.
   */
  *[Symbol.iterator]() {
    if (this._ok) {
      yield this._value as T;
    }
  }

  /**
   * @returns `true` if the `Result` is an `Ok`.
   */
  public isOk(): boolean {
    return this._ok;
  }

  /**
   * @returns `true` if the `Result` is an `Err`.
   */
  public isErr(): boolean {
    return !this._ok;
  }

  /**
   * @returns `true` if the `Result` is an `Ok` and and the value inside of it matches a predicate.
   * @param thisArg - If provided, it will be used as the this value for each invocation of predicate. If it is not provided, `undefined` is used instead.
   */
  public isOkAnd(predicate: (value: T) => boolean, thisArg?: any): boolean {
    return this._ok && predicate.call(thisArg, this._value as T);
  }

  /**
   * @returns `true` if the `Result` is an `Err` and and the error inside of it matches a predicate.
   * @param thisArg - If provided, it will be used as the this value for each invocation of predicate. If it is not provided, `undefined` is used instead.
   */
  public isErrAnd(predicate: (error: E) => boolean, thisArg?: any): boolean {
    return !this._ok && predicate.call(thisArg, this._value as E);
  }

  /**
   * Whether `this` `Ok` value or `Err` error is the same as the other `Result`.
   *
   * @param other - Another `Result` or any value
   * @returns `true` if the other is an `Result` and the `Ok` value or `Err` error is the same as `this` via `Object.is`.
   */
  public isSame(other: unknown): boolean {
    return Result.isResult(other)
      ? Object.is(this._value, other._value)
      : false;
  }

  public and<BT, BE = any>(resultB: Result<BT, BE>): Result<BT, E | BE> {
    return this._ok ? resultB : (this as Err<E | BE>);
  }

  /**
   * @returns `Err` if the `Result` is `Err`, otherwise calls `getOptionB` with the wrapped value and returns the result.
   *
   * @param getResultB - A function that returns a `Result`
   * @param thisArg - If provided, it will be used as the this value for each invocation of predicate. If it is not provided, `undefined` is used instead.
   */
  public andThen<BT, BE = any>(
    getResultB: (value: T) => Result<BT, BE>,
    thisArg?: any
  ): Result<BT, BE | E> {
    return this._ok
      ? getResultB.call(thisArg, this._value as T)
      : (this as Err<BE | E>);
  }

  /**
   * @returns the `Result` if it is `Ok`, otherwise returns `resultB`.
   *
   * Arguments passed to `or` are eagerly evaluated; if you are passing the result of a function call, it is recommended to use `orElse`, which is lazily evaluated.
   *
   * @param resultB - A `Result`
   */
  public or<BT, BE = any>(resultB: Result<BT, BE>): Result<T | BT, E | BE> {
    return this._ok ? this : resultB;
  }

  /**
   * @returns the `Result` if it contains a value, otherwise calls `getResultB` and returns the result.
   *
   * @param getResultB - A function that returns an `Result`
   * @param thisArg - If provided, it will be used as the this value for each invocation of predicate. If it is not provided, `undefined` is used instead.
   */
  public orElse<BT, BE = any>(
    getResultB: () => Result<BT, BE>,
    thisArg?: any
  ): Result<T | BT, E | BE> {
    return this._ok ? this : getResultB.call(thisArg);
  }

  /**
   * Converts from `Option<Option<T>>` to `Option<T>`
   */
  public flatten(): Result<UnwrapOk<T>, E | UnwrapErr<E>> {
    return this._ok && Result.isResult<UnwrapOk<T>, UnwrapErr<E>>(this._value)
      ? this._value
      : (this as Result<UnwrapOk<T>, E | UnwrapErr<E>>);
  }

  /**
   * Maps an `Result<T, E>` to `Result<U, E>` by applying a function to a contained `Ok` value, leaving an `Err` value untouched.
   *
   * @param fn - A function that maps a value to another value
   * @param thisArg - If provided, it will be used as the this value for each invocation of predicate. If it is not provided, `undefined` is used instead.
   * @returns `Err` if the `Result` is `Err`, otherwise returns `Ok(fn(value))`.
   */
  public map<U>(fn: (value: T) => U, thisArg?: any): Result<U, E> {
    return this._ok
      ? Result.Ok(fn.call(thisArg, this._value as T))
      : (this as Err<E>);
  }

  /**
   * Maps a `Result<T, E>` to `Result<T, F>` by applying a function to a contained `Err` value, leaving an `Ok` value untouched.
   *
   * This function can be used to pass through a successful result while handling an error.
   *
   * @param fn - A function that maps a error to another error
   * @param thisArg - If provided, it will be used as the this value for each invocation of predicate. If it is not provided, `undefined` is used instead.
   * @returns `Ok` if the `Result` is `Ok`, otherwise returns `Err(fn(error))`.
   */
  public mapErr<U>(fn: (error: E) => U, thisArg?: any): Result<T, U> {
    return this._ok
      ? (this as Ok<T>)
      : Result.Err(fn.call(thisArg, this._value as E));
  }

  /**
   * Transposes a `Result(Option)` into `Option(Result)`.
   *
   * - `Ok(Some(_))` will be mapped to `Some(Ok(_))
   * - `Err(_)` will be mapped to `Some(Err(_))`.
   * - `Ok(_)` will be mapped to `Some(Ok(_))`.
   */
  public transpose(): Option<Result<UnwrapOption<T>, E>> {
    return this._ok
      ? Option.isOption<UnwrapOption<T>>(this._value)
        ? this._value.map(Result.Ok)
        : Option.Some(this)
      : Option.None;
  }

  /**
   * Converts from `Result<T, E>` to `Option<T>` and discarding the error, if any.
   */
  public ok(): Option<T> {
    return this._ok ? Option.Some(this._value) : Option.None;
  }

  /**
   * Converts from `Result<T, E>` to `Option<E>` and discarding the value, if any.
   */
  public err(): Option<E> {
    return this._ok ? Option.None : Option.Some(this._value);
  }

  /**
   * @returns the contained `Ok` value.
   *
   * @throws if the value is an `Err`.
   *
   * @param message - Optional Error message
   */
  public unwrap(message = "called `Result.unwrap()` on an `Err`"): T {
    if (this._ok) {
      return this._value as T;
    }
    throw new Error(message);
  }

  /**
   * @returns the contained `Ok` value or `undefined` otherwise.
   */
  public unwrapOr(): T | undefined;
  /**
   * @returns the contained `Ok` value or a provided default.
   *
   * Arguments passed to `unwrapOr` are eagerly evaluated; if you are passing the result of a function call, it is recommended to use `unwrapOrElse`, which is lazily evaluated.
   *
   * @param defaultValue - default value
   */
  public unwrapOr<U>(defaultValue: U): T | U;
  public unwrapOr(defaultValue?: T): T | undefined {
    return this._ok ? (this._value as T) : defaultValue;
  }

  /**
   * @returns the contained `Ok` value or computes it from a closure.
   * @param fn - A function that computes a default value.
   * @param thisArg - If provided, it will be used as the this value for each invocation of predicate. If it is not provided, `undefined` is used instead.
   */
  public unwrapOrElse<U>(fn: () => U, thisArg?: any): T | U {
    return this._ok ? (this._value as T) : fn.call(thisArg);
  }

  /**
   * @returns the contained `Err` error.
   *
   * @throws if the error is an `Ok`.
   *
   * @param message - Optional Error message
   */
  public unwrapErr(
    message = "called `Result.unwrapErr()` on an `Ok` value"
  ): E {
    if (this._ok) {
      throw new Error(message);
    }
    return this._value as E;
  }

  /**
   * @returns the contained `Err` error or `undefined` otherwise.
   */
  public unwrapErrOr(): E | undefined;
  /**
   * @returns the contained `Err` error or a provided default.
   *
   * Arguments passed to `unwrapErrOr` are eagerly evaluated; if you are passing the result of a function call, it is recommended to use `unwrapErrOrElse`, which is lazily evaluated.
   *
   * @param defaultError - default error
   */
  public unwrapErrOr<U>(defaultError: U): E | U;
  public unwrapErrOr(defaultError?: E): E | undefined {
    return this._ok ? defaultError : (this._value as E);
  }

  /**
   * @returns the contained `Err` error or computes it from a closure.
   * @param fn - A function that computes a default value.
   * @param thisArg - If provided, it will be used as the this value for each invocation of predicate. If it is not provided, `undefined` is used instead.
   */
  public unwrapErrOrElse<U>(fn: () => U, thisArg?: any): E | U {
    return this._ok ? fn.call(thisArg) : (this._value as E);
  }

  /**
   * Extract the value from an `Result` in a way that handles both the `Ok` and `Err` cases.
   *
   * @param Ok - A function that returns a value if the `Result` is a `Ok`.
   * @param Err - A function that returns a value if the `Result` is a `Err`.
   * @returns The value returned by the provided function.
   */
  public match<U>(Ok: (value: T) => U, Err: (error: E) => U): U {
    return this._ok ? Ok(this._value as T) : Err(this._value as E);
  }

  public toString(): string {
    return `${this._ok ? "Ok" : "Err"}(${this._value})`;
  }
}

/**
 * @param value - A value of type `T`
 * @returns Wrap a value into an `Result`.
 */
export const Ok = /* @__PURE__ */ (() => Result.Ok)();
export type Ok<T> = Result<T, any>;

/**
 * @param error - An error of type `E`
 * @returns Wrap an error into an `Result`.
 */
export const Err = /* @__PURE__ */ (() => Result.Err)();
export type Err<E> = Result<any, E>;