UNPKG

ts-results-es

Version:

A TypeScript implementation of Rust's Result and Option objects.

325 lines (268 loc) 10.4 kB
import { Err, None, Ok, Option, Result, ResultErrType, ResultErrTypes, ResultOkType, ResultOkTypes, Some, } from '../src/index.js'; import { eq, notSupposedToBeCalled } from './util.js'; test('Err<E> | Ok<T> should be Result<T, E>', () => { const r1 = Err(0); const r2 = new Ok(''); const r = Math.random() ? r1 : r2; expect(Result.isResult(r1)).toEqual(true); expect(Result.isResult(r2)).toEqual(true); expect(Result.isResult(Some(3))).toEqual(false); eq<typeof r, Result<string, number>>(true); }); test('Type can be narrowed using ok & err', () => { const r1 = Ok(0) as Result<number, string>; if (r1.isOk()) { eq<Ok<number>, typeof r1>(true); } else { eq<Err<string>, typeof r1>(true); } if (r1.isErr()) { eq<Err<string>, typeof r1>(true); } else { eq<Ok<number>, typeof r1>(true); } }); test('map', () => { const r = new Err(0) as Result<string, number>; const r2 = r.map(Symbol); eq<typeof r2, Result<symbol, number>>(true); }); test('andThen', () => { const result = new Ok('Ok') as Result<string, boolean>; const then = result.andThen(() => new Err('broke') as Result<boolean, string>); expect(then).toMatchResult(new Err('broke')); function takesResult(result: Result<boolean, string | boolean>): void {} takesResult(then); }); test('mapErr', () => { const r = new Err(0) as Result<string, number>; const r2 = r.mapErr(Symbol); eq<typeof r2, Result<string, symbol>>(true); }); test('Iterable', () => { const r1 = new Ok([true, false]) as Result<boolean[], number>; const r1Iter = r1[Symbol.iterator](); eq<Iterator<boolean[]>, typeof r1Iter>(true); const r2 = new Ok(32) as Result<number, string>; const r2Iter = r2[Symbol.iterator](); eq<Iterator<number>, typeof r2Iter>(true); }); test('ResultOkType', () => { type a = ResultOkType<Ok<string>>; eq<string, a>(true); type b = ResultOkType<Err<string>>; eq<never, b>(true); type c = ResultOkType<Result<string, number>>; eq<string, c>(true); }); test('ResultErrType', () => { type a = ResultErrType<Ok<string>>; eq<never, a>(true); type b = ResultErrType<Err<string>>; eq<string, b>(true); type c = ResultErrType<Result<string, number>>; eq<number, c>(true); }); test('ResultOkTypes & ResultErrTypes', () => { type a = ResultOkTypes< [Ok<string>, Err<string>, Result<symbol, number>, Result<never, string>, Ok<32> | Err<boolean>] >; eq<[string, never, symbol, never, 32], a>(true); type b = ResultErrTypes< [Ok<string>, Err<string>, Result<symbol, number>, Result<never, symbol>, Ok<boolean> | Err<32>] >; eq<[never, string, number, symbol, 32], b>(true); }); test('Result.all', () => { const ok0 = Ok(3); const ok1 = new Ok(true); const ok2 = new Ok(8 as const) as Result<8, boolean>; const err0 = Err(Symbol()); const err1 = new Err(Error()); const err2 = new Err(9 as const) as Result<boolean, 9>; const all0_array = Result.all([]); expect(all0_array).toMatchResult(Ok([])); eq<typeof all0_array, Result<[], never>>(true); const all1Array = Result.all([ok0, ok1]); expect(all1Array).toMatchResult(Ok([3, true])); eq<typeof all1Array, Result<[number, boolean], never>>(true); const all2Array = Result.all([err0, err1]); expect(all2Array).toMatchResult(Err(err0.error)); eq<typeof all2Array, Result<[never, never], symbol | Error>>(true); const all3Array = Result.all([] as Result<string, number>[]); eq<typeof all3Array, Result<string[], number>>(true); const all4Array = Result.all([ok0, ok1, ok2, err2]); expect(all4Array).toMatchResult(Err(9)); eq<typeof all4Array, Result<[number, boolean, 8, boolean], boolean | 9>>(true); }); test('Result.any', () => { const ok0 = new Ok(3); const ok1 = new Ok(true); const ok2 = new Ok(8 as const) as Result<8, boolean>; const err0 = new Err(Symbol()); const err1 = new Err(Error()); const err2 = new Err(9 as const) as Result<boolean, 9>; const any0Array = Result.any([]); expect(any0Array).toMatchResult(Err([])); eq<typeof any0Array, Result<never, []>>(true); const any1Array = Result.any([ok0, ok1]); expect(any1Array).toMatchResult(Ok(3)); eq<typeof any1Array, Result<number | boolean, [never, never]>>(true); const any2Array = Result.any([err0, err1]); expect(any2Array).toMatchResult(Err([err0.error, err1.error])); eq<typeof any2Array, Result<never, [symbol, Error]>>(true); const any3Array = Result.any([] as Result<string, number>[]); eq<typeof any3Array, Result<string, number[]>>(true); const any4Array = Result.any([err0, err1, err2, ok2]); expect(any4Array).toMatchResult(Ok(8)); eq<typeof any4Array, Result<boolean | 8, [symbol, Error, 9, boolean]>>(true); }); test('Result.wrap', () => { const a = Result.wrap(() => 1); expect(a).toMatchResult(Ok(1)); eq<typeof a, Result<number, unknown>>(true); class CustomError { readonly message = 'hi'; } const err = new CustomError(); const b = Result.wrap<number, CustomError>(() => { throw err; }); expect(b).toMatchResult(Err(err)); eq<typeof b, Result<number, CustomError>>(true); }); test('Result.wrapAsync', async () => { const a = await Result.wrapAsync(async () => 1); expect(a).toMatchResult(Ok(1)); eq<typeof a, Result<number, unknown>>(true); class CustomError { readonly message = 'hi'; } const err = new CustomError(); const b = await Result.wrapAsync<number, CustomError>(async () => { throw err; }); expect(b).toMatchResult(Err(err)); eq<typeof b, Result<number, CustomError>>(true); const c = await Result.wrapAsync<number, string>(() => { throw 'thrown before promise'; return Promise.resolve(3); }); expect(c).toMatchResult(Err('thrown before promise')); eq<typeof c, Result<number, string>>(true); }); test('Result.partition', async () => { const ok0 = Ok(3); const ok1 = new Ok(true); const err0 = Err(Symbol()); const err1 = new Err(Error()); const result0 = Ok(3) as unknown as Result<number, symbol>; const result1 = new Ok(true) as unknown as Result<boolean, Error>; const all0 = Result.partition([]); expect(all0).toEqual([[], []]); eq<typeof all0, [never[], never[]]>(true); const all1 = Result.partition([ok0, ok1, err0, err1]); expect(all1).toEqual([ [ok0.value, ok1.value], [err0.error, err1.error], ]); eq<typeof all1, [(number | boolean)[], (symbol | Error)[]]>(true); const all2 = Result.partition([ok0, ok1]); expect(all2).toEqual([[ok0.value, ok1.value], []]); eq<typeof all2, [(number | boolean)[], never[]]>(true); const all3 = Result.partition([err0, err1]); expect(all3).toEqual([[], [err0.error, err1.error]]); eq<typeof all3, [never[], (symbol | Error)[]]>(true); const all4 = Result.partition([1, 2, 3, 4].map((num) => Ok(num) as Result<number, Error>)); expect(all4).toEqual([[1, 2, 3, 4], []]); eq<typeof all4, [number[], Error[]]>(true); const all5 = Result.partition([result0, result1]); expect(all5).toEqual([[(result0 as Ok<number>).value, (result1 as Ok<boolean>).value], []]); eq<typeof all5, [(number | boolean)[], (symbol | Error)[]]>(true); }); test('safeUnwrap', () => { const ok1 = new Ok(3).safeUnwrap(); expect(ok1).toEqual(3); eq<typeof ok1, number>(true); const err = new Err('hi'); const result = new Ok(1) as Result<number, string>; expect(() => { // @ts-expect-error err.safeUnwrap(); }).toThrowError(); // @ts-expect-error result.safeUnwrap(); if (result.isOk()) { const val = result.safeUnwrap(); eq<typeof val, number>(true); expect(val).toEqual(1); } else { // @ts-expect-error result.safeUnwrap(); } }); test('Issue #24', () => { const getStatus = (payload: boolean): Result<boolean, Error> => { if (payload) { return Ok(payload); } return Err(new Error('Payload is false')); }; const processStatus = (): Result<boolean, Error> => { return getStatus(true) .andThen((result) => Ok(result)) .map((data) => data); }; }); test('To option', () => { const result = new Ok('hello') as Result<string, number>; const option = result.toOption(); eq<typeof option, Option<string>>(true); expect(option).toEqual(Some('hello')); const result2: Result<string, number> = new Err(32); const option2 = result2.toOption(); expect(option2).toEqual(None); }); test('or / orElse', () => { const result = Err('boo') as Result<number, string>; const afterOrElseAlwaysErr = result.orElse((error) => Err(error === 'boo')); eq<typeof afterOrElseAlwaysErr, Result<number, boolean>>(true); const afterOrElseAlwaysOk = result.orElse((_error) => Ok(1)); eq<typeof afterOrElseAlwaysOk, Result<number, never>>(true); const afterOrElseAnyResult = result.orElse((error) => (error === 'foo' ? Ok(1) : Err('bar'))); eq<typeof afterOrElseAnyResult, Result<number, string>>(true); const afterOrErr = result.or(Err(true)); eq<typeof afterOrErr, Result<number, boolean>>(true); const afterOrOk = result.or(Ok(1)); eq<typeof afterOrOk, Result<number, never>>(true); const afterOrResult = result.or(Err(true) as Result<number, boolean>); eq<typeof afterOrResult, Result<number, boolean>>(true); expect(Err('error').or(Ok(1))).toEqual(Ok(1)); expect(Err('error').orElse((error) => Ok(error.length))).toEqual(Ok(5)); expect(Ok(1).or(Ok(2))).toEqual(Ok(1)); expect( Ok(1).orElse((_error) => { throw new Error('Call unexpected'); }), ).toEqual(Ok(1)); }); test('toAsyncResult()', async () => { expect(await Ok(1).toAsyncResult().promise).toEqual(Ok(1)); const err = Err('error'); expect(await err.toAsyncResult().promise).toEqual(err); }); test('unwrapOrElse', () => { expect(Ok({ data: 'user data' }).unwrapOrElse(notSupposedToBeCalled)).toEqual({ data: 'user data' }); expect(Err('bad error').unwrapOrElse((error) => ({ error }))).toEqual({ error: 'bad error' }); });