import AssertionError from "./AssertionError"; import Comparator from "./Comparator"; import nativeOrdering from "./nativeOrdering"; import type from "./type"; export class ResultUnwrapError extends Error {} // `E` should never be false-like. // Patterns: // const [err, res] = resultable(); // const {error, result} = resultable(); // try { resultable().unwrap() } catch (err) { ... } export default class Result { private constructor(private readonly pair: [true, T] | [false, E]) {} static Ok(result: T) { return new Result([true, result]); } static Err(error: E) { return new Result([false, error]); } get result(): T | undefined { return this.pair[0] ? this.pair[1] : undefined; } get error(): E | undefined { return this.pair[0] ? undefined : this.pair[1]; } readonly and = (other: Result): Result => { return this.pair[0] ? other : Result.Err(this.pair[1]); }; readonly andThen = (other: (val: T) => Result): Result => { return this.pair[0] ? other(this.pair[1]) : Result.Err(this.pair[1]); }; readonly contains = ( other: T, comparator: Comparator = nativeOrdering as any ) => { return this.pair[0] && comparator(this.pair[1], other) === 0; }; readonly containsErr = ( other: E, comparator: Comparator = nativeOrdering as any ) => { return !this.pair[0] && comparator(this.pair[1], other) === 0; }; readonly expect = (msg: string): T => { if (!this.pair[0]) { throw new AssertionError(msg); } return this.pair[1]; }; readonly expectErr = (msg: string): E => { if (this.pair[0]) { throw new AssertionError(msg); } return this.pair[1]; }; readonly flatten = (): T extends Result ? Result : Result => { if (this.pair[0] && type(this.pair[1]) == Result) { return this.pair[1] as any; } return this as any; }; readonly inspect = (fn: (val: T) => unknown): Result => { if (this.pair[0]) { fn(this.pair[1]); } return this; }; readonly inspectErr = (fn: (val: E) => unknown): Result => { if (!this.pair[0]) { fn(this.pair[1]); } return this; }; readonly isErr = () => { return !this.pair[0]; }; readonly isErrAnd = (pred: (err: E) => any) => { return !this.pair[0] && pred(this.pair[1]); }; readonly map = (op: (val: T) => U): Result => { return !this.pair[0] ? Result.Err(this.pair[1]) : Result.Ok(op(this.pair[1])); }; readonly mapErr = (op: (val: E) => F): Result => { return this.pair[0] ? Result.Ok(this.pair[1]) : Result.Err(op(this.pair[1])); }; readonly mapOr = (def: U, op: (val: T) => U): U => { return !this.pair[0] ? def : op(this.pair[1]); }; readonly mapOrElse = (def: (val: E) => U, op: (val: T) => U): U => { return !this.pair[0] ? def(this.pair[1]) : op(this.pair[1]); }; readonly or = (other: Result): Result => { return !this.pair[0] ? other : Result.Ok(this.pair[1]); }; readonly orElse = (other: (err: E) => Result): Result => { return !this.pair[0] ? other(this.pair[1]) : Result.Ok(this.pair[1]); }; readonly unwrap = (): T => { if (!this.pair[0]) { throw this.pair[1]; } return this.pair[1]; }; readonly unwrapErr = (): E => { if (this.pair[0]) { throw new ResultUnwrapError("Called unwrapErr on Ok"); } return this.pair[1]; }; readonly unwrapOr = (def: T): T => { return !this.pair[0] ? def : this.pair[1]; }; readonly unwrapOrElse = (def: (err: E) => T): T => { return !this.pair[0] ? def(this.pair[1]) : this.pair[1]; }; *[Symbol.iterator]() { if (this.pair[0]) { yield undefined; yield this.pair[1]; } else { yield this.pair[1]; yield undefined; } } } export const Ok = (val: T) => Result.Ok(val); export const Err = (err: E) => Result.Err(err);