Created
August 31, 2022 17:04
-
-
Save OliverBrotchie/101708c78cca74dec3c5e2254f00f1cc to your computer and use it in GitHub Desktop.
New implementation of Optionals
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* eslint-disable no-prototype-builtins */ | |
/** | |
* A Rust-like Result class. | |
* | |
* Note: Please use either Ok or Err to construct Results. | |
* | |
* @example | |
* ``` | |
* function divide(left: number, right: number): Result<number, Error> { | |
* if (right === 0) return Err("Divided by zero"); | |
* | |
* return Ok(left / right); | |
* } | |
* | |
* ``` | |
*/ | |
export class Result<T, E extends Error> { | |
private val: T | E; | |
/** | |
* A constructor for a Result | |
* | |
* Note: Please use either Ok or Err to construct Results. | |
*/ | |
constructor(input: T | E) { | |
this.val = input; | |
} | |
/** | |
* Returns true if contained value isnt an error. | |
*/ | |
isOk(): boolean { | |
return !( | |
this.val instanceof Error || | |
(this.val && | |
typeof this.val === "object" && | |
Error.isPrototypeOf(this.val)) | |
); | |
} | |
/** | |
* Returns true if contained value is an error. | |
*/ | |
isErr(): boolean { | |
return ( | |
this.val instanceof Error || | |
(this.val && | |
typeof this.val === "object" && | |
Error.isPrototypeOf(this.val)) | |
); | |
} | |
private formatError(err: Error) { | |
err.stack = `${err.message}: ${ | |
(this.val as E).stack | |
? "\n\t" + ((this.val as E).stack as string).split("\n").join("\n\t") | |
: (this.val as E).message | |
}`; | |
throw err; | |
} | |
/** | |
* Returns the contained Ok value, consuming the self value. | |
* Throws an Error with a given message if contained value is not Ok. | |
* | |
* @param msg An error message to throw if contained value is an Error. | |
*/ | |
expect(msg: string): T { | |
if (this.isErr()) { | |
this.formatError(new Error(msg)); | |
} | |
return this.val as T; | |
} | |
/** | |
* Returns the contained Ok value, consuming the self value. | |
* Throws an Error if contained value is not Ok. | |
*/ | |
unwrap(): T { | |
if (this.isErr()) { | |
this.formatError(new Error(`Unwrap called on ${(this.val as E).name}`)); | |
} | |
return this.val as T; | |
} | |
/** | |
* Returns the contained Error value, consuming the self value. | |
* Throws an Error if contained value is not an Error. | |
*/ | |
unwrapErr(): E { | |
if (this.isOk()) { | |
throw new Error( | |
`UnwrapError called on value - ${this.val as unknown as string}` | |
); | |
} | |
return this.val as E; | |
} | |
/** | |
* Returns the contained Ok value or a provided default. | |
* | |
* @param fallback A default value to return if contained value is an Error. | |
*/ | |
unwrapOr(fallback: T): T { | |
if (this.isErr()) { | |
return fallback; | |
} | |
return this.val as T; | |
} | |
/** | |
* Returns the contained Ok value or computes it from a closure. | |
* | |
* @param fn A function that computes a new value. | |
*/ | |
unwrapOrElse(fn: (input: E) => T): T { | |
if (this.isErr()) { | |
return fn(this.val as E); | |
} | |
return this.val as T; | |
} | |
/** | |
* Maps a Result<T, E> to Result<U, E> by applying a function to a contained Ok value, leaving an Error value untouched. | |
* | |
* @param fn A mapping function. | |
*/ | |
map<U>(fn: (input: T) => U): Result<U, E> { | |
if (this.isOk()) { | |
return new Result<U, E>(fn(this.val as T)); | |
} | |
return this as unknown as Result<U, E>; | |
} | |
/** | |
* Maps a Result<T, E> to Result<T, U> by applying a function to a contained Error value, leaving an Ok value untouched. | |
* | |
* @param fn A mapping function. | |
*/ | |
mapErr<U extends Error>(fn: (input: E) => U): Result<T, U> { | |
if (this.isOk()) { | |
return this as unknown as Result<T, U>; | |
} | |
return new Result<T, U>(fn(this.val as E)); | |
} | |
/** | |
* Returns the provided fallback (if Error), or applies a function to the contained value. | |
* | |
* @param fallback A defualt value | |
* @param fn A mapping function. | |
*/ | |
mapOr<U>(fallback: U, fn: (input: T) => U): U { | |
if (this.isOk()) { | |
return fn(this.val as T); | |
} | |
return fallback; | |
} | |
/** | |
* Returns `or` if the result is Error, otherwise returns self. | |
* | |
* @param or A alternative Result value | |
*/ | |
or(or: Result<T, E>): Result<T, E> { | |
if (this.isOk()) { | |
return this; | |
} | |
return or; | |
} | |
/** | |
* Returns contained value for use in matching. | |
* | |
* Note: Please only use this to match against in `if` or `swtich` statments. | |
* | |
* @example | |
* ``` | |
* function coolOrNice(input: Result<string, Error>): Result<void, Error> { | |
* switch (input.peek()) { | |
* case "cool": | |
* console.log("Input was the coolest!"); | |
* break; | |
* case "nice": | |
* console.log("Input was was the nicest!"); | |
* break | |
* default: | |
* return Err("Input neither cool nor nice."); | |
* } | |
* return Ok() | |
* } | |
* ``` | |
*/ | |
peek(): T | E { | |
return this.val; | |
} | |
/** | |
* Throws contained Errors, consuming the Result | |
*/ | |
throw(): void { | |
if (this.isErr()) { | |
throw this.val; | |
} | |
} | |
/** | |
* Run a closure in a `try`/`catch` and convert it into a Result | |
* @param fn The closure to run | |
* @returns The Result of the closure | |
*/ | |
static async from<T>( | |
fn: (() => T) | (() => Promise<T>) | |
): Promise<Result<T, Error>> { | |
try { | |
return new Result<T, Error>(await fn()); | |
} catch (e: unknown) { | |
return new Result<T, Error>(e as Error); | |
} | |
} | |
/** | |
* Partition an array of Results into Ok values and Errors | |
* | |
* @param input An array of Results | |
* @example | |
* ``` | |
* const results = [Ok(2),Ok(16),Err("Something went wrong!")] | |
* | |
* partition(results) // {ok:[2, 16], err:[Error("Something went wrong!")]} | |
* | |
* ``` | |
*/ | |
static partition<T, E extends Error>( | |
input: Array<Result<T, E>> | |
): { ok: Array<T>; err: Array<E> } { | |
return input.reduce( | |
(acc: { ok: Array<T>; err: Array<E> }, e) => { | |
if (e.isOk()) acc.ok.push(e.unwrap()); | |
else acc.err.push(e.unwrapErr()); | |
return acc; | |
}, | |
{ | |
ok: [], | |
err: [], | |
} | |
); | |
} | |
} | |
/** | |
* Return a non-error value result. | |
* | |
* @param input a value that does not extend the `Error` type. | |
* @example | |
* ``` | |
* function divide(left: number, right: number): Result<number, Error> { | |
* if (right === 0) return Err("Divided by zero"); | |
* | |
* return Ok(left / right); | |
* } | |
* | |
* ``` | |
*/ | |
export function Ok<T, E extends Error>(input?: Exclude<T, E>) { | |
return new Result<T, E>(input as T); | |
} | |
/** | |
* Return a error result. | |
* | |
* @param input a value that extends the `Error` type. | |
* @example | |
* ``` | |
* function divide(left: number, right: number): Result<number, Error> { | |
* if (right === 0) return Err("Divided by zero"); | |
* | |
* return Ok(left / right); | |
* } | |
* | |
* ``` | |
*/ | |
export function Err<T, E extends Error>(input: E | string): Result<T, E> { | |
if (typeof input === "string") { | |
return new Result<T, Error>(new Error(input)) as Result<T, E>; | |
} | |
return new Result<T, E>(input); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment