UNPKG

unwrap-or

Version:

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

1,094 lines (1,014 loc) 25 kB
// Copyright 2025 Roman Hnatiuk. All rights reserved. MIT license. /** * # Module Result * * Error handling. * * ```ts * Result<T, E> { * Ok(T), * Err(E), * } * ``` * * ## Overview * * Type `Result` is used for returning and propagating errors. There are variants, * `Ok(T)`, representing success and containing a value, and `Err(E)`, representing * error and containing an error value. * * Commonly, `Result` is most prominently used for I/O. * * ### Results must be used * * A common problem with using return values to indicate errors is that it is easy * to ignore the return value, thus failing to handle the error. `Result` * especially useful with functions that may encounter errors but don’t otherwise * return a useful value. * * ### Querying the variant * * The `is_ok` and `is_err` methods return `true` if the `Result` is `Ok` or `Err`, * respectively. * * The `is_ok_and` and `is_err_and` methods apply the provided function to the * contents of the `Result` to produce a boolean value. If the `Result` does not * have the expected variant then `false` is returned instead without executing the * function. * * ### Extracting contained values * * These methods extract the contained value in a `Result<T, E>` when it is the * `Ok` variant. * * If the `Result` is `Err`: * * - `expect` panics with a provided custom message * - `unwrap` panics with a generic message * - `unwrap_or` returns the provided default value * - `unwrap_or_else` returns the result of evaluating the provided function * * If the `Result` is `Ok`: * * - `expect_err` panics with a provided custom message * - `unwrap_err` panics with a generic message * * ### Transforming contained values * * These methods transform `Result` to `Option`: * * - `err` transforms `Result<T, E>` into `Option<E>`, mapping `Err(e)` to * `Some(e)` and `Ok(v)` to `None` * - `ok` transforms `Result<T, E>` into `Option<T>`, mapping `Ok(v)` to `Some(v)` * and `Err(e)` to `None` * - `transpose` transposes a `Result` of an `Option` into an `Option` of a * `Result` * * These methods transform the contained value of the `Ok` variant: * * - `map` transforms `Result<T, E>` into `Result<U, E>` by applying the provided * function to the contained value of `Ok` and leaving `Err` values unchanged * - `inspect` takes ownership of the `Result`, applies the provided function to * the contained value by reference, and then returns the `Result` * * These methods transform the contained value of the `Err` variant: * * - `map_err` transforms `Result<T, E>` into `Result<T, F>` by applying the * provided function to the contained value of `Err` and leaving `Ok` values * unchanged * - `inspect_err` takes ownership of the `Result`, applies the provided function * to the contained value of `Err` by reference, and then returns the `Result` * * These methods transform a `Result<T, E>` into a value of a possibly different * type `U`: * * - `map_or` applies the provided function to the contained value of `Ok`, or * returns the provided default value if the `Result` is `Err` * - `map_or_else` applies the provided function to the contained value of `Ok`, or * applies the provided default fallback function to the contained value of `Err` * * ### Boolean operators * * These methods treat the `Result` as a boolean value, where `Ok` acts like `true` * and `Err` acts like `false`. There are two categories of these methods: ones * that take a `Result` as input, and ones that take a function as input (to be * lazily evaluated). * * The `and` and `or` methods take another `Result` as input, and produce a * `Result` as output. The `and` method can produce a `Result<U, E>` value having a * different inner type `U` than `Result<T, E>`. The `or` method can produce a * `Result<T, F>` value having a different error type `F` than `Result<T, E>`. * * | method | input | output | * | ------ | --------- | -------- | * | `and` | (ignored) | `Err(e)` | * | `and` | `Err(d)` | `Err(d)` | * | `and` | `Ok(y)` | `Ok(y)` | * | `or` | `Err(d)` | `Err(d)` | * | `or` | `Ok(y)` | `Ok(y)` | * | `or` | (ignored) | `Ok(x)` | * * The `and_then` and `or_else` methods take a function as input, and only evaluate * the function when they need to produce a new value. The `and_then` method can * produce a `Result<U, E>` value having a different inner type `U` than * `Result<T, E>`. The `or_else` method can produce a `Result<T, F>` value having a * different error type `F` than `Result<T, E>`. * * | method | self | function input | function result | output | * | ---------- | -------- | -------------- | --------------- | -------- | * | `and_then` | `Err(e)` | (not provided) | (not evaluated) | `Err(e)` | * | `and_then` | `Ok(x)` | `x` | `Err(d)` | `Err(d)` | * | `and_then` | `Ok(x)` | `x` | `Ok(y)` | `Ok(y)` | * | `or_else` | `Err(e)` | `e` | `Err(d)` | `Err(d)` | * | `or_else` | `Err(e)` | `e` | `Ok(y)` | `Ok(y)` | * | `or_else` | `Ok(x)` | (not provided) | (not evaluated) | `Ok(x)` | * * ## Variants * * ### Ok * * ```ts * Ok(T); * ``` * * Contains the success value. * * #### Examples * * ```ts * let x: Result<number, string> = Ok(42); * ``` * * ### Err * * ```ts * Err(E); * ``` * * Contains the error value. * * #### Examples * * ```ts * let x: Result<number, string> = Err("Not found"); * ``` * * ## Import * * ```ts * import { Err, Ok, type Result } from "unwrap-or/result"; * ``` * * @module Result */ /** * Type `Result` is a type that represents either success `Ok(T)` * and containing a value, or `Err(E)`, representing error * and containing an error value. * * ### Example * * ```ts * let x: Result<number, string> * * x = Ok(42) * assert_eq!(x, Ok(42)) * * x = Err('empty') * assert_eq!(x, Err('empty')) * ``` * * @since 0.4.0-alpha */ export interface Result<T, E> { /** * Returns `res` if the result is `Ok`, otherwise returns the `Err` value. * * Arguments passed to and are eagerly evaluated; * if you are passing the result of a function call, * it is recommended to use `and_then`, which is lazily evaluated. * * ### Example * * ```ts * let x: Result<number, string> * let y: Result<string, string> * * x = Ok(2) * y = Err("late error") * assert_eq!(x.and(y), Err("late error")) * * x = Err("early error") * y = Ok("foo") * assert_eq!(x.and(y), Err("early error")) * * x = Err("not a 2") * y = Err("late error") * assert_eq!(x.and(y), Err("not a 2")) * * x = Ok(2) * y = Ok("different result type") * assert_eq!(x.and(y), Ok("different result type")) * ``` * * @since 0.4.0-alpha */ and<U>(res: Result<U, E>): Result<T | U, E>; /** * Calls `op` if the result is `Ok`, otherwise returns the `Err` value. * * This function can be used for control flow based on `Result` values. * * Often used to chain fallible operations that may return `Err`. * * ### Example * * ```ts * let x: Result<number, string>; * let y: Result<string, string>; * * x = Ok(2) * y = Err("late error") * assert_eq!( * x.and_then(() => y), * Err("late error"), * ) * * x = Err("early error") * y = Ok("foo") * assert_eq!( * x.and_then(() => y), * Err("early error"), * ) * * x = Err("not a 2") * y = Err("late error") * assert_eq!( * x.and_then(() => y), * Err("not a 2"), * ) * * x = Ok(2) * y = Ok("different result type") * assert_eq!( * x.and_then(() => y), * Ok("different result type"), * ) * ``` * * @since 0.4.0-alpha */ and_then<U>(op: (value: T) => Result<U, E>): Result<T | U, E>; // TODO: public err() {} /** * Returns the contained `Ok` value. * * Because this method may throw, its use is generally discouraged. * Instead, prefer to use pattern matching and handle the `Err` case explicitly, * or call `unwrap_or`, `unwrap_or_else`, or `unwrap_or_default`. * * Recommend that expect messages are used to describe the reason * you expect the `Result` should be `Ok`. * * Panics if the value is an `Err`, * with a panic message including the passed message, and the value of the `Err`. * * ### Example * * ```ts * let x: Result<number, string> * * x = Ok(42) * assert_eq!(x.expect("should return 42"), 42) * * x = Err("unknown value") * assert_err!( * () => x.expect("should return 42"), * 'should return 42: "unknown value"', * ) * ``` * * @since 0.4.0-alpha */ expect(msg: string): T; /** * Returns the contained `Err` value. * * Panics if the value is an `Ok`, with a panic message * including the passed message, and the content of the Ok. * * ### Example * * ```ts * let x: Result<number, string> * * x = Ok(42) * assert_err!( * () => x.expect_err("should return unknown error value"), * "should return unknown error value: 42", * ) * * x = Err("unknown error value") * assert_eq!( * x.expect_err("should return unknown error value"), * "unknown error value", * ) * ``` * * @since 0.4.0-alpha */ expect_err(msg: string): E; // TODO: public flatten() {} /** * Calls a function with a reference to the contained value if `Ok`. * * Returns the original result. * * ### Example * * ```ts * function get<T>(arr: T[], idx: number): Result<T, string> { * const item = arr.at(idx) * return item !== undefined ? Ok(item) : Err("Not found") * } * * const list = [1, 2, 3, 4, 5] * * let has_inspected = false * * let x = get(list, 2).inspect((_v) => { * has_inspected = true * }) * * assert_eq!(x, Ok(3)) * assert_eq!(has_inspected, true) * ``` * * @since 0.4.0-alpha */ inspect(f: (value: T) => void): Result<T, E>; /** * Calls a function with a reference to the contained value if `Err`. * * Returns the original result. * * ### Example * * ```ts * function get<T>(arr: T[], idx: number): Result<T, string> { * const item = arr.at(idx) * * return item !== undefined ? Ok(item) : Err("Not found") * } * * const list = [1, 2, 3, 4, 5] * * let has_inspected = false * * let x = get(list, 9).inspect_err((_e) => { * has_inspected = true * }) * * assert_eq!(x, Err("Not found")) * assert_eq!(has_inspected, true) * ``` * * @since 0.4.0-alpha */ inspect_err(f: (err: E) => void): Result<T, E>; /** * Returns `true` if the result is `Err`. * * ### Example * * ```ts * let x: Result<number, string> * * x = Ok(42) * assert_eq!(x.is_err(), false) * * x = Err("Not found") * assert_eq!(x.is_err(), true) * ``` * * @since 0.4.0-alpha */ is_err(): boolean; /** * Returns `true` if the result is `Err` and the value inside of it matches a predicate. * * ### Example * * ```ts * let x: Result<{ html: string }, { statusCode: number }> * * x = Err({ statusCode: 500 }) * assert_eq!( * x.is_err_and((err) => err.statusCode === 404), * false, * ) * * x = Err({ statusCode: 404 }) * assert_eq!( * x.is_err_and((err) => err.statusCode === 404), * true, * ) * * x = Ok({ html: "value" }) * assert_eq!( * x.is_err_and((err) => err.statusCode === 404), * false, * ) * ``` * * @since 0.4.0-alpha */ is_err_and(f: (err: E) => boolean): boolean; /** * Returns `true` if the result is `Ok`. * * ### Example * * ```ts * let x: Result<number, string> * * x = Ok(42) * assert_eq!(x.is_ok(), true) * * x = Err("Not found") * assert_eq!(x.is_ok(), false) * ``` * * @since 0.4.0-alpha */ is_ok(): boolean; /** * Returns `true` if the result is `Ok` and the value inside of it matches a predicate. * * ### Example * * ```ts * let x: Result<number, string> * * x = Ok(0) * assert_eq!( * x.is_ok_and((value) => value > 10), * false, * ) * * x = Ok(42) * assert_eq!( * x.is_ok_and((value) => value > 10), * true, * ) * * x = Err("Not found") * assert_eq!( * x.is_ok_and((value) => value > 10), * false, * ) * ``` * * @since 0.4.0-alpha */ is_ok_and(f: (value: T) => boolean): boolean; /** * Maps a `Result<T, E>` to `Result<U, E>` by applying a function * to a contained `Ok` value, leaving an `Err` value untouched. * * This function can be used to compose the results of two functions. * * ### Example * * ```ts * let x: Result<string, { statusCode: number }> * * x = Ok("42") * assert_eq!( * x.map((value) => Number.parseInt(value, 10)), * Ok(42), * ) * * x = Err({ statusCode: 404 }) * assert_eq!( * x.map((value) => Number.parseInt(value, 10)), * Err({ statusCode: 404 }), * ) * ``` * * @since 0.4.0-alpha */ map<U>(f: (value: T) => U): Result<T | U, E>; /** * Returns the provided default (if `Err`), * or applies a function to the contained value (if `Ok`). * * Arguments passed to `map_or` are eagerly evaluated; * if you are passing the result of a function call, * it is recommended to use `map_or_else`, which is lazily evaluated. * * ### Example * * ```ts * let x: Result<string, string> * * x = Ok("foo") * assert_eq!( * x.map_or(42, (v) => v.length), * 3, * ) * * x = Err("bar") * assert_eq!( * x.map_or(42, (v) => v.length), * 42, * ) * ``` * * @since 0.4.0-alpha */ map_or<U>(default_value: U, f: (value: T) => U): U; /** * Maps a `Result<T, E>` to `U` by applying fallback function `default_f` * to a contained `Err` value, or function `f` to a contained `Ok` value. * * ### Example * * ```ts * const k = 21 * let x: Result<string, string> * * x = Ok("foo") * assert_eq!( * x.map_or_else( * () => 2 * k, * (v) => v.length, * ), * 3, * ) * * x = Err("bar") * assert_eq!( * x.map_or_else( * () => 2 * k, * (v) => v.length, * ), * 42, * ) * ``` * * @since 0.4.0-alpha */ map_or_else<U>(default_f: () => U, f: (value: T) => U): U; // TODO: public ok() {} /** * Returns `res` if the result is `Err`, otherwise returns the `Ok` value. * * Arguments passed to or are eagerly evaluated; * if you are passing the result of a function call, * it is recommended to use `or_else`, which is lazily evaluated. * * ### Example * * ```ts * let x: Result<number, string> * let y: Result<number, string> * * x = Ok(2) * y = Err("Not found") * assert_eq!(x.or(y), Ok(2)) * * x = Err("Not found") * y = Ok(100) * assert_eq!(x.or(y), Ok(100)) * * x = Ok(2) * y = Ok(100) * assert_eq!(x.or(y), Ok(2)) * * x = Err("Not found") * y = Err("Not found") * assert_eq!(x.or(y), Err("Not found")) * ``` * * @since 0.4.0-alpha */ or(res: Result<T, E>): Result<T, E>; /** * Calls `f` if the result is `Err`, otherwise returns the `Ok` value. * * This function can be used for control flow based on result values. * * ### Example * * ```ts * let x: Result<string, { statusCode: number }> * let y: Result<string, { statusCode: number }> * * x = Ok("barbarians") * y = Ok("vikings") * assert_eq!( * x.or_else(() => y), * Ok("barbarians"), * ) * * x = Err({ statusCode: 404 }) * y = Ok("vikings") * assert_eq!( * x.or_else(() => y), * Ok("vikings"), * ) * * x = Err({ statusCode: 404 }) * y = Err({ statusCode: 404 }) * assert_eq!( * x.or_else(() => y), * Err({ statusCode: 404 }), * ) * ``` * * @since 0.4.0-alpha */ or_else(f: () => Result<T, E>): Result<T, E>; /** * @ignore * * Returns a string representing this object. * This method is meant to be overridden by derived JS objects * for custom type coercion logic. * * ### Example * * ```ts * let x: Result<unknown, unknown> * * x = Ok(true) * assert_eq!(x.toString(), "Ok(true)") * * x = Ok(42) * assert_eq!(x.toString(), "Ok(42)") * * x = Ok("hello") * assert_eq!(x.toString(), "Ok(hello)") * * x = Ok([1, 2]) * assert_eq!(x.toString(), "Ok(1,2)") * * x = Ok({}) * assert_eq!(x.toString(), "Ok([object Object])") * * x = Ok(() => 2 * 4) * assert_eq!(x.toString(), "Ok(() => 2 * 4)") * * x = Err(true) * assert_eq!(x.toString(), "Err(true)") * * x = Err(42) * assert_eq!(x.toString(), "Err(42)") * * x = Err("hello") * assert_eq!(x.toString(), "Err(hello)") * * x = Err([1, 2]) * assert_eq!(x.toString(), "Err(1,2)") * * x = Err({}) * assert_eq!(x.toString(), "Err([object Object])") * * x = Err(() => 2 * 4) * assert_eq!(x.toString(), "Err(() => 2 * 4)") * ``` * * @since 0.4.0-alpha */ toString(): string; // TODO: public transpose() {} /** * Returns the contained `Ok` value. * * Because this function may throw, its use is generally discouraged. * Prefer to call inside `try/catch` statement, or handle the `Err` case explicitly, * or call `unwrap_or`, `unwrap_or_else`, or `unwrap_or_default`. * * Panics if the value is an `Err`, with a message provided by the `Err`’s value. * * ### Example * * ```ts * let x: Result<number, string> * * x = Ok(42) * assert_eq!(x.unwrap(), 42) * * x = Err("Not found") * assert_err!( * () => x.unwrap(), * TypeError, * "Called Result.unwrap() on an Err(E) value", * ) * ``` * * @since 0.4.0-alpha */ unwrap(): T; /** * Returns the contained `Err` value. * * Panics if the value is an `Ok`, with a custom panic message provided by the `Ok`’s value. * * ### Example * * ```ts * let x: Result<number, string> * * x = Ok(42) * assert_err!( * () => x.unwrap_err(), * TypeError, * "Called Result.unwrap_err() on an Ok value", * ) * * x = Err("Not found") * assert_eq!(x.unwrap_err(), "Not found") * ``` * * @since 0.4.0-alpha */ unwrap_err(): E; /** * Returns the contained `Ok` value or a provided default. * * Arguments passed to `unwrap_or` are eagerly evaluated; * if you are passing the result of a function call, * it is recommended to use `unwrap_or_else`, which is lazily evaluated. * * ### Example * * ```ts * let x: Result<number, string>; * * x = Ok(42); * assert_eq!(x.unwrap_or(0), 42); * * x = Err("Not found"); * assert_eq!(x.unwrap_or(0), 0); * ``` * * @since 0.4.0-alpha */ unwrap_or(default_value: T): T; /** * Returns the contained `Ok` value or computes it from a closure. * * Useful for expensive default computations. * * ### Example * * ```ts * let x: Result<number, string>; * * x = Ok(42); * assert_eq!( * x.unwrap_or_else((err) => err.length), * 42, * ); * * x = Err("foo"); * assert_eq!( * x.unwrap_or_else((err) => err.length), * 3, * ); * ``` * * @since 0.4.0-alpha */ unwrap_or_else(f: (err: E) => T): T; } /** * @internal * * Unique id for Ok */ const oid = Symbol.for("@@result/ok"); /** * @internal * * Unique id for Err */ const eid = Symbol.for("@@result/err"); /** * @internal * * Result constructor */ class ResultConstructor<T, E> implements Result<T, E> { /** * @internal * @private */ private _extract(): T { if (this.is_err()) { throw new TypeError("Prevent taking value from `Err(E)`."); } return (this as any)[oid] as T; } /** * @internal * @private */ private _extract_err(): E { if (this.is_ok()) { throw new TypeError("Prevent taking err from `Ok(T)`."); } return (this as any)[eid] as E; } /** * @internal * @private */ public constructor(_id: typeof oid, value: T); public constructor(_id: typeof eid, err: E); public constructor(_id: typeof oid | typeof eid, value?: T | E) { (this as any)[_id] = value; } /** @inheritdoc */ public and<U>(res: Result<U, E>): Result<T | U, E> { if (this.is_ok()) { return res; } return new ResultConstructor<T, E>(eid, this._extract_err()); } /** @inheritdoc */ public and_then<U>(op: (value: T) => Result<U, E>): Result<T | U, E> { if (this.is_ok()) { return op(this._extract()); } return new ResultConstructor<T, E>(eid, this._extract_err()); } // TODO: public err() {} /** @inheritdoc */ public expect(msg: string): T { if (this.is_ok()) { return this._extract(); } const err = this._extract_err(); const str_err = JSON.stringify(err); throw new Error(`${msg}: ${str_err}`); } /** @inheritdoc */ public expect_err(msg: string): E { if (this.is_err()) { return this._extract_err(); } const value = this._extract(); const str_value = JSON.stringify(value); throw new Error(`${msg}: ${str_value}`); } // TODO: public flatten() {} /** @inheritdoc */ public inspect(f: (value: T) => void): Result<T, E> { if (this.is_ok()) { f(this._extract()); } return this; } /** @inheritdoc */ public inspect_err(f: (err: E) => void): Result<T, E> { if (this.is_err()) { f(this._extract_err()); } return this; } /** @inheritdoc */ public is_err(): boolean { return eid in this; } /** @inheritdoc */ public is_err_and(f: (err: E) => boolean): boolean { if (this.is_err()) { return f(this._extract_err()); } return false; } /** @inheritdoc */ public is_ok(): boolean { return oid in this; } /** @inheritdoc */ public is_ok_and(f: (value: T) => boolean): boolean { if (this.is_ok()) { return f(this._extract()); } return false; } /** @inheritdoc */ public map<U>(f: (value: T) => U): Result<T | U, E> { if (this.is_ok()) { return new ResultConstructor<U, E>(oid, f(this._extract())); } return new ResultConstructor<T, E>(eid, this._extract_err()); } /** @inheritdoc */ public map_or<U>(default_value: U, f: (value: T) => U): U { if (this.is_ok()) { return f(this._extract()); } return default_value; } /** @inheritdoc */ public map_or_else<U>(default_f: () => U, f: (value: T) => U): U { if (this.is_ok()) { return f(this._extract()); } return default_f(); } // TODO: public ok() {} /** @inheritdoc */ public or(res: Result<T, E>): Result<T, E> { if (this.is_ok()) { return new ResultConstructor<T, E>(oid, this._extract()); } return res; } /** @inheritdoc */ public or_else(f: () => Result<T, E>): Result<T, E> { if (this.is_ok()) { return new ResultConstructor<T, E>(oid, this._extract()); } return f(); } /** @inheritdoc */ public toString(): string { if (this.is_ok()) { return `Ok(${this._extract()})`; } return `Err(${this._extract_err()})`; } /** * Overrides Node.js object inspection. * * @see toString * * @ignore */ public [Symbol.for("nodejs.util.inspect.custom")](): string { return this.toString(); } // TODO: public transpose() {} /** @inheritdoc */ public unwrap(): T { if (this.is_ok()) { return this._extract(); } throw new TypeError("Called Result.unwrap() on an Err value"); } /** @inheritdoc */ public unwrap_err(): E { if (this.is_err()) { return this._extract_err(); } throw new TypeError("Called Result.unwrap_err() on an Ok value"); } /** @inheritdoc */ public unwrap_or(default_value: T): T { if (this.is_ok()) { return this._extract(); } return default_value; } /** @inheritdoc */ public unwrap_or_else(f: (err: E) => T): T { if (this.is_ok()) { return this._extract(); } return f(this._extract_err()); } } /** * Contains the success value. * * ### Example * * ```ts * let x: Result<number, string> = Ok(42) * ``` * * @since 0.4.0-alpha */ export function Ok<T>(value: T): Result<T, any> { return new ResultConstructor<T, any>(oid, value); } /** * Contains the error value. * * ### Example * * ```ts * let x: Result<number, string> = Err("Not found") * ``` * * @since 0.4.0-alphag */ export function Err<E>(err: E): Result<any, E> { return new ResultConstructor<any, E>(eid, err); }