UNPKG

ts-results-es

Version:

A TypeScript implementation of Rust's Result and Option objects.

353 lines (291 loc) 9.62 kB
import { AsyncOption } from './asyncoption.js'; import { toString } from './utils.js'; import { Result, Ok, Err } from './result.js'; interface BaseOption<T> extends Iterable<T> { /** `true` when the Option is Some */ isSome(): this is SomeImpl<T>; /** `true` when the Option is None */ isNone(): this is None; /** * Returns the contained `Some` value, if exists. Throws an error if not. * * If you know you're dealing with `Some` and the compiler knows it too (because you tested * `isSome()` or `isNone()`) you should use `value` instead. While `Some`'s `expect()` and `value` will * both return the same value using `value` is preferable because it makes it clear that * there won't be an exception thrown on access. * * @param msg the message to throw if no Some value. */ expect(msg: string): T; /** * Returns the contained `Some` value. * Because this function may throw, its use is generally discouraged. * Instead, prefer to handle the `None` case explicitly. * * If you know you're dealing with `Some` and the compiler knows it too (because you tested * `isSome()` or `isNone()`) you should use `value` instead. While `Some`'s `unwrap()` and `value` will * both return the same value using `value` is preferable because it makes it clear that * there won't be an exception thrown on access. * * Throws if the value is `None`. */ unwrap(): T; /** * Returns the contained `Some` value or a provided default. * * (This is the `unwrap_or` in rust) */ unwrapOr<T2>(val: T2): T | T2; /** * Returns the contained `Some` value or computes a value with a provided function. * * The function is called at most one time, only if needed. * * @example * ``` * Some('OK').unwrapOrElse( * () => { console.log('Called'); return 'UGH'; } * ) // => 'OK', nothing printed * * None.unwrapOrElse(() => 'UGH') // => 'UGH' * ``` */ unwrapOrElse<T2>(f: () => T2): T | T2; /** * Calls `mapper` if the Option is `Some`, otherwise returns `None`. * This function can be used for control flow based on `Option` values. */ andThen<T2>(mapper: (val: T) => Option<T2>): Option<T2>; /** * Maps an `Option<T>` to `Option<U>` by applying a function to a contained `Some` value, * leaving a `None` value untouched. * * This function can be used to compose the Options of two functions. */ map<U>(mapper: (val: T) => U): Option<U>; /** * Maps an `Option<T>` to `Option<U>` by either converting `T` to `U` using `mapper` (in case * of `Some`) or using the `default_` value (in case of `None`). * * If `default` is a result of a function call consider using `mapOrElse()` instead, it will * only evaluate the function when needed. */ mapOr<U>(default_: U, mapper: (val: T) => U): U; /** * Maps an `Option<T>` to `Option<U>` by either converting `T` to `U` using `mapper` (in case * of `Some`) or producing a default value using the `default` function (in case of `None`). */ mapOrElse<U>(default_: () => U, mapper: (val: T) => U): U; /** * Returns `Some()` if we have a value, otherwise returns `other`. * * `other` is evaluated eagerly. If `other` is a result of a function * call try `orElse()` instead – it evaluates the parameter lazily. * * @example * * Some(1).or(Some(2)) // => Some(1) * None.or(Some(2)) // => Some(2) */ or(other: Option<T>): Option<T>; /** * Returns `Some()` if we have a value, otherwise returns the result * of calling `other()`. * * `other()` is called *only* when needed. * * @example * * Some(1).orElse(() => Some(2)) // => Some(1) * None.orElse(() => Some(2)) // => Some(2) */ orElse(other: () => Option<T>): Option<T>; /** * Maps an `Option<T>` to a `Result<T, E>`. */ toResult<E>(error: E): Result<T, E>; /** * Creates an `AsyncOption` based on this `Option`. * * Useful when you need to compose results with asynchronous code. */ toAsyncOption(): AsyncOption<T>; } /** * Contains the None value */ class NoneImpl implements BaseOption<never> { isSome(): this is SomeImpl<never> { return false; } isNone(): this is NoneImpl { return true; } [Symbol.iterator](): Iterator<never, never, any> { return { next(): IteratorResult<never, never> { return { done: true, value: undefined! }; }, }; } unwrapOr<T2>(val: T2): T2 { return val; } unwrapOrElse<T2>(f: () => T2): T2 { return f(); } expect(msg: string): never { throw new Error(`${msg}`); } unwrap(): never { throw new Error(`Tried to unwrap None`); } map<T2>(_mapper: unknown): None { return this; } mapOr<T2>(default_: T2, _mapper: unknown): T2 { return default_; } mapOrElse<U>(default_: () => U, _mapper: unknown): U { return default_(); } or<T>(other: Option<T>): Option<T> { return other; } orElse<T>(other: () => Option<T>): Option<T> { return other(); } andThen<T2>(op: unknown): None { return this; } toResult<E>(error: E): Err<E> { return Err(error); } toString(): string { return 'None'; } toAsyncOption(): AsyncOption<never> { return new AsyncOption<never>(None); } } // Export None as a singleton, then freeze it so it can't be modified export const None = new NoneImpl(); export type None = NoneImpl; Object.freeze(None); /** * Contains the success value */ class SomeImpl<T> implements BaseOption<T> { static readonly EMPTY = new SomeImpl<void>(undefined); isSome(): this is SomeImpl<T> { return true; } isNone(): this is NoneImpl { return false; } readonly value!: T; [Symbol.iterator](): Iterator<T> { return [this.value][Symbol.iterator](); } constructor(val: T) { if (!(this instanceof SomeImpl)) { return new SomeImpl(val); } this.value = val; } unwrapOr(_val: unknown): T { return this.value; } unwrapOrElse(_f: unknown): T { return this.value; } expect(_msg: string): T { return this.value; } unwrap(): T { return this.value; } map<T2>(mapper: (val: T) => T2): Some<T2> { return Some(mapper(this.value)); } mapOr<T2>(_default_: T2, mapper: (val: T) => T2): T2 { return mapper(this.value); } mapOrElse<U>(_default_: () => U, mapper: (val: T) => U): U { return mapper(this.value); } or(_other: Option<T>): Option<T> { return this; } orElse(_other: () => Option<T>): Option<T> { return this; } andThen<T2>(mapper: (val: T) => Option<T2>): Option<T2> { return mapper(this.value); } toResult<E>(error: E): Ok<T> { return Ok(this.value); } toAsyncOption(): AsyncOption<T> { return new AsyncOption(this); } /** * Returns the contained `Some` value, but never throws. * Unlike `unwrap()`, this method doesn't throw and is only callable on an Some<T> * * Therefore, it can be used instead of `unwrap()` as a maintainability safeguard * that will fail to compile if the type of the Option is later changed to a None that can actually occur. * * (this is the `into_Some()` in rust) */ safeUnwrap(): T { return this.value; } toString(): string { return `Some(${toString(this.value)})`; } } // This allows Some to be callable - possible because of the es5 compilation target export const Some = SomeImpl as typeof SomeImpl & (<T>(val: T) => SomeImpl<T>); export type Some<T> = SomeImpl<T>; export type Option<T> = Some<T> | None; export type OptionSomeType<T extends Option<any>> = T extends Some<infer U> ? U : never; export type OptionSomeTypes<T extends Option<any>[]> = { [key in keyof T]: T[key] extends Option<any> ? OptionSomeType<T[key]> : never; }; export namespace Option { /** * Parse a set of `Option`s, returning an array of all `Some` values. * Short circuits with the first `None` found, if any */ export function all<T extends Option<any>[]>(...options: T): Option<OptionSomeTypes<T>> { const someOption = []; for (let option of options) { if (option.isSome()) { someOption.push(option.value); } else { return option as None; } } return Some(someOption as OptionSomeTypes<T>); } /** * Parse a set of `Option`s, short-circuits when an input value is `Some`. * If no `Some` is found, returns `None`. */ export function any<T extends Option<any>[]>(...options: T): Option<OptionSomeTypes<T>[number]> { // short-circuits for (const option of options) { if (option.isSome()) { return option as Some<OptionSomeTypes<T>[number]>; } else { continue; } } // it must be None return None; } export function isOption<T = any>(value: unknown): value is Option<T> { return value instanceof Some || value === None; } }