Skip to content

Instantly share code, notes, and snippets.

@adregan
Forked from duncan-rheaply/result.test.ts
Last active October 28, 2025 15:42
Show Gist options
  • Select an option

  • Save adregan/1508255a5e84525958f9b4d80caffa39 to your computer and use it in GitHub Desktop.

Select an option

Save adregan/1508255a5e84525958f9b4d80caffa39 to your computer and use it in GitHub Desktop.
Result
interface IResult<T, E> {
isOk(): boolean;
isErr(): boolean;
map<U>(fn: (value: T) => U): Result<U, E>;
flatMap<U, F>(fn: (value: T) => Result<U, F>): Result<U, E | F>;
match<U, F>(params: { onOk: (value: T) => U; onErr: (error: E) => F }): U | F;
unwrap(): T;
unwrapOr<D>(defaultValue: D): T | D;
}
class Ok<T> implements IResult<T, never> {
constructor(private readonly value: T) { }
isOk(): this is Ok<T> {
return true;
}
isErr(): this is never {
return false;
}
map<U>(fn: (value: T) => U): Result<U, never> {
return Result.ok(fn(this.value));
}
flatMap<U, F>(fn: (value: T) => Result<U, F>): Result<U, never | F> {
return fn(this.value);
}
match<U, F>({
onOk,
}: { onOk: (value: T) => U; onErr: (error: never) => F }): U | F {
return onOk(this.value);
}
unwrap(): T {
return this.value;
}
unwrapOr<D>(_: D): T {
return this.value;
}
}
class Err<E> implements IResult<never, E> {
constructor(private readonly error: E) { }
isOk(): this is never {
return false;
}
isErr(): this is Err<E> {
return true;
}
map<U>(_: (value: never) => U): Result<U, E> {
return this;
}
flatMap<U, F>(_: (value: never) => Result<U, F>): Result<never, E> {
return this;
}
match<U, F>({
onErr,
}: { onOk: (value: never) => U; onErr: (error: E) => F }): U | F {
return onErr(this.error);
}
unwrap(): never {
throw this.error;
}
unwrapOr<D>(defaultValue: D): D {
return defaultValue;
}
}
export type Result<T, E> = Ok<T> | Err<E>;
export const Result = {
ok: <T>(value: T): Ok<T> => new Ok(value),
err: <E>(error: E): Err<E> => new Err(error),
fromThrowable: <T>(fn: () => T): Result<T, Error> => {
try {
return Result.ok(fn());
} catch (error) {
const err = error instanceof Error ? error : new Error(String(error));
return Result.err(err);
}
},
};
import { Result } from "./result";
describe("Result", () => {
describe("creating", () => {
describe("Result.ok()", () => {
test("it returns an ok result", () => {
const result = Result.ok("I'm good");
expect(result.isOk()).toBeTruthy();
});
test("an ok cannot be errored", () => {
const result = Result.ok("I'm not bad");
expect(result.isErr()).toBeFalsy();
});
});
describe("Result.err()", () => {
test("it returns an error result", () => {
const result = Result.err("I'm bad");
expect(result.isErr()).toBeTruthy();
});
test("an error cannot be ok", () => {
const result = Result.err("I'm not good");
expect(result.isOk()).toBeFalsy();
});
});
});
describe("result.match(onOk, onErr)", () => {
test("it can pattern match the ok value to the onOk op", () => {
const result: Result<string, Error> = Result.ok("hello");
const actual = result.match(
(s: string) => `${s.toUpperCase()}!!`,
(_) => 2,
);
expect(actual).toEqual("HELLO!!");
});
test("it can pattern match the err value to the onErr op", () => {
const result: Result<string, Error> = Result.err(Error("KABOOM"));
expect(
result.match(
(_) => "I have a bad feeling about this.",
(e: Error) => `Something went ${e.message}`,
),
).toEqual("Something went KABOOM");
});
});
describe("result.map(fn)", () => {
test("an ok value can be transformed via map", () => {
const result = Result.ok(2);
const value = result.map((x): string => `${x * 2}`);
expect(value).toEqual(4);
});
test("an err value cannot be transformed via map", () => {
const result = Result.err(Error("OH NO"));
const value = result.map((x) => x * 3);
expect(value).toEqual(null);
});
});
});
export class Result<Ok, Err = Error> {
#ok: Ok | null;
#err: Err | null;
private constructor({ ok, err }: { ok?: Ok; err?: Err }) {
this.#ok = ok ?? null;
this.#err = err ?? null;
}
static ok = <Ok>(ok: Ok): Result<Ok, never> => new Result({ ok });
static err = <Err>(err: Err): Result<never, Err> => new Result({ err });
isOk = (): this is Result<Ok, never> => this.#ok !== null;
isErr = (): this is Result<never, Err> => this.#err !== null;
match = <S, T>(
onOk: (ok: Ok) => S,
onErr: (err: Err) => T,
): this extends Result<Ok, never> ? S : T =>
//@ts-ignore
this.isOk() ? onOk(this.#ok) : onErr(this.#err);
map = <B>(fn: (a: Ok) => B): Result<B, never> | this =>
this.isOk() ? Result.ok(fn(this.#ok)) : this;
getOr = <V extends unknown>(value: V = null): Ok | V => (this.isOk() ? this.#ok : value);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment