Implement Result
This commit is contained in:
+202
@@ -0,0 +1,202 @@
|
||||
import logger from 'log'
|
||||
import {Option} from './option'
|
||||
|
||||
const log = logger('result')
|
||||
|
||||
export class Result<T, E = []> {
|
||||
public constructor(
|
||||
protected readonly _data: T | E,
|
||||
private readonly _state: State,
|
||||
) {}
|
||||
|
||||
// Constructors
|
||||
static ok<T, E>(data: T) : Result<T, E> {
|
||||
log.trace('New Ok')
|
||||
return new Result<T, E>(data, State.OK)
|
||||
}
|
||||
static error<T, E>(err: E) : Result<T, E> {
|
||||
log.trace('New Error')
|
||||
log.debug(`Error : ${err}`)
|
||||
return new Result<T, E>(err, State.ERROR)
|
||||
}
|
||||
static fromObject<T, E>(res: Result<T, E>) : Result<T, E> {
|
||||
// @ts-expect-error
|
||||
return new Result(res._data, res._state)
|
||||
}
|
||||
public as_error() : Result<never, E> {
|
||||
return Result.error(this.unwrap_err())
|
||||
}
|
||||
|
||||
// Getters
|
||||
public state() : State {
|
||||
return this._state
|
||||
}
|
||||
|
||||
// TODO : implements Iterable<...>
|
||||
|
||||
// *****************************************************************************************************************
|
||||
// Ok
|
||||
public is_ok() : boolean {
|
||||
return this._state === State.OK
|
||||
}
|
||||
public is_ok_and(f: (data: T) => boolean) : boolean {
|
||||
return this._state === State.OK && f(this._data as T)
|
||||
}
|
||||
public expect(msg: string) : T {
|
||||
log.trace('expect')
|
||||
|
||||
if (this._state === State.ERROR) {
|
||||
log.error('unwrapped an Error')
|
||||
log.error(msg)
|
||||
log.debug(`Error : ${this._data}`)
|
||||
throw msg
|
||||
}
|
||||
|
||||
return this._data as T
|
||||
}
|
||||
public unwrap() : T {
|
||||
log.trace('unwrap')
|
||||
|
||||
if (this._state === State.ERROR) {
|
||||
log.error("Unwrapped an Error")
|
||||
log.debug(`Error : ${this._data}`)
|
||||
throw "Unwrapped an Error"
|
||||
}
|
||||
|
||||
return this._data as T
|
||||
}
|
||||
public ok() : Option<T> {
|
||||
if (this._state === State.OK) {
|
||||
return Option.some(this._data as T)
|
||||
} else {
|
||||
return Option.none()
|
||||
}
|
||||
}
|
||||
public map<U>(f: (data: T) => U) : Result<U, E> {
|
||||
if (this._state === State.OK) {
|
||||
return Result.ok(f(this._data as T))
|
||||
} else {
|
||||
return Result.error<never, E>(this._data as E)
|
||||
}
|
||||
}
|
||||
public map_or<U>(default_: U, f: (data: T) => U) : U {
|
||||
if (this._state === State.OK) {
|
||||
return f(this._data as T)
|
||||
} else {
|
||||
return default_
|
||||
}
|
||||
}
|
||||
public map_or_else<U>(fe: (err: E) => U, f: (data: T) => U) : U {
|
||||
if (this._state === State.OK) {
|
||||
return f(this._data as T)
|
||||
} else {
|
||||
return fe(this._data as E)
|
||||
}
|
||||
}
|
||||
public inspect(f: (data: T) => void) : Result<T, E> {
|
||||
if (this._state === State.OK) {
|
||||
f(this._data as T)
|
||||
}
|
||||
return this
|
||||
}
|
||||
public unwrap_or(default_: T) : T {
|
||||
if (this._state === State.OK) {
|
||||
return this._data as T
|
||||
} else {
|
||||
return default_
|
||||
}
|
||||
}
|
||||
public unwrap_or_else(f: (err: E) => T) : T {
|
||||
if (this._state === State.OK) {
|
||||
return this._data as T
|
||||
} else {
|
||||
return f(this._data as E)
|
||||
}
|
||||
}
|
||||
|
||||
// *****************************************************************************************************************
|
||||
// Errors
|
||||
public is_err() : boolean {
|
||||
return this._state === State.ERROR
|
||||
}
|
||||
public is_err_and(f: (err: E) => boolean) : boolean {
|
||||
return this._state === State.ERROR && f(this._data as E)
|
||||
}
|
||||
public expect_err(msg: string) : E {
|
||||
log.trace('expect_err')
|
||||
|
||||
if (this._state === State.OK) {
|
||||
log.error('expect_err on Ok')
|
||||
log.error(msg)
|
||||
throw msg
|
||||
}
|
||||
|
||||
return this._data as E
|
||||
}
|
||||
public unwrap_err() : E {
|
||||
log.trace('unwrap_err')
|
||||
|
||||
if (this._state === State.OK) {
|
||||
log.error('unwrap_err on Ok')
|
||||
throw "Result wasn't in error"
|
||||
}
|
||||
|
||||
return this._data as E
|
||||
}
|
||||
public err() : Option<E> {
|
||||
if (this._state === State.ERROR) {
|
||||
return Option.some(this._data as E)
|
||||
} else {
|
||||
return Option.none()
|
||||
}
|
||||
}
|
||||
public map_err<F>(f: (err: E) => F) : Result<T, F> {
|
||||
if (this._state === State.ERROR) {
|
||||
return Result.error(f(this._data as E))
|
||||
} else {
|
||||
return Result.ok(this._data as T)
|
||||
}
|
||||
}
|
||||
public inspect_err(f: (err: E) => void) : Result<T, E> {
|
||||
if (this._state === State.ERROR) {
|
||||
f(this._data as E)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
// *****************************************************************************************************************
|
||||
// Combine
|
||||
public and<U>(res: Result<U, E>) : Result<U, E> {
|
||||
if (this._state === State.OK) {
|
||||
return res
|
||||
} else {
|
||||
return Result.error(this._data as E)
|
||||
}
|
||||
}
|
||||
public and_then<U>(f: (data: T) => Result<U, E>) : Result<U, E> {
|
||||
if (this._state === State.OK) {
|
||||
return f(this._data as T)
|
||||
} else {
|
||||
return Result.error(this._data as E)
|
||||
}
|
||||
}
|
||||
public or<F>(res: Result<T, F>) : Result<T, F> {
|
||||
if (this._state === State.OK) {
|
||||
return new Result<T, F>(this._data as T, State.OK)
|
||||
} else {
|
||||
return res
|
||||
}
|
||||
}
|
||||
public or_else<F>(f: (err: E) => Result<T, F>) : Result<T, F> {
|
||||
if (this._state === State.OK) {
|
||||
return Result.ok(this._data as T)
|
||||
} else {
|
||||
return f(this._data as E)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const enum State {
|
||||
OK,
|
||||
ERROR
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
import {expect, test} from 'bun:test'
|
||||
|
||||
import {Result, State} from 'result'
|
||||
|
||||
test('base case Ok', () => {
|
||||
const value = 12
|
||||
|
||||
const res = Result.ok<number, never>(value)
|
||||
|
||||
expect(res.is_err()).toBeFalse()
|
||||
expect(res.is_ok()).toBeTrue()
|
||||
expect(res.unwrap()).toBe(value)
|
||||
expect(res.expect("Shouldn't happen")).toBe(value)
|
||||
expect(() => res.unwrap_err()).toThrow()
|
||||
expect(() => res.expect_err("Error")).toThrow()
|
||||
expect(res.state()).toBe(State.OK)
|
||||
expect(() => res.as_error()).toThrow()
|
||||
|
||||
expect(res.is_ok_and(v => v === value)).toBeTrue()
|
||||
expect(res.map(v => v + 1).unwrap()).toBe(value + 1)
|
||||
expect(res.ok().unwrap()).toBe(value)
|
||||
expect(res.map_or(0, v => v + 1)).toBe(value + 1)
|
||||
expect(res.map_or_else(() => 0, v => v + 1)).toBe(value + 1)
|
||||
|
||||
const handler = (v: number) => expect(v).toBe(value)
|
||||
res.inspect(handler)
|
||||
|
||||
expect(res.unwrap_or(0)).toBe(value)
|
||||
expect(res.unwrap_or_else(() => 0)).toBe(value)
|
||||
|
||||
expect(res.is_err_and(() => true)).toBeFalse()
|
||||
expect(res.err().is_none()).toBeTrue()
|
||||
|
||||
const handler_err = (_v: never) => {
|
||||
throw "Shouldn't happen"
|
||||
}
|
||||
expect(res.map_err(handler_err).unwrap()).toBe(value)
|
||||
expect(res.inspect_err(handler_err).unwrap()).toBe(value)
|
||||
|
||||
const value2 = value + 1
|
||||
const res2 = Result.ok<number, never>(value2)
|
||||
expect(res.and(res2).unwrap()).toBe(value2)
|
||||
expect(res.and_then(v => Result.ok(v + 2)).unwrap()).toBe(value + 2)
|
||||
expect(res.or(res2).unwrap()).toBe(value)
|
||||
expect(res.or_else(handler_err).unwrap()).toBe(value)
|
||||
})
|
||||
|
||||
test('base case Error', () => {
|
||||
const value = 12
|
||||
|
||||
const res = Result.error<string, number>(value)
|
||||
|
||||
expect(res.is_err()).toBeTrue()
|
||||
expect(res.is_ok()).toBeFalse()
|
||||
expect(res.unwrap_err()).toBe(value)
|
||||
expect(res.expect_err("Shouldn't happen")).toBe(value)
|
||||
expect(() => res.unwrap()).toThrow()
|
||||
expect(() => res.expect("Error")).toThrow()
|
||||
expect(res.state()).toBe(State.ERROR)
|
||||
expect(res.as_error().unwrap_err()).toBe(value)
|
||||
|
||||
const handle_throw = (_v: string) => {
|
||||
throw "Shouldn't happen"
|
||||
}
|
||||
expect(res.is_ok_and(handle_throw)).toBeFalse()
|
||||
expect(res.map(handle_throw).unwrap_err()).toBe(value)
|
||||
expect(res.err().unwrap()).toBe(value)
|
||||
expect(res.map_or(0, handle_throw)).toBe(0)
|
||||
expect(res.map_or_else(() => 0, handle_throw)).toBe(0)
|
||||
|
||||
const handler = (v: number) => expect(v).toBe(value)
|
||||
res.inspect_err(handler)
|
||||
|
||||
expect(res.unwrap_or("yes")).toBe("yes")
|
||||
expect(res.unwrap_or_else(() => "yes")).toBe("yes")
|
||||
|
||||
expect(res.is_err_and(() => true)).toBeTrue()
|
||||
expect(res.ok().is_none()).toBeTrue()
|
||||
|
||||
expect(res.map_err(v => v + 1).unwrap_err()).toBe(value + 1)
|
||||
res.inspect_err(v => expect(v).toBe(value))
|
||||
|
||||
const value2 = "yeet"
|
||||
const res2 = Result.ok<string, number>(value2)
|
||||
expect(res.and(res2).unwrap_err()).toBe(value)
|
||||
expect(res.and_then(handle_throw).unwrap_err()).toBe(value)
|
||||
expect(res.or(res2).unwrap()).toBe(value2)
|
||||
expect(res.or_else(v => Result.ok(`${v}`)).unwrap()).toBe(`${value}`)
|
||||
})
|
||||
Reference in New Issue
Block a user