Implement Result

This commit is contained in:
2024-05-21 15:40:31 +02:00
parent 3877ff75c1
commit ede55607e9
3 changed files with 292 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
export * as result from './src/result'
+202
View File
@@ -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
}
+89
View File
@@ -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}`)
})