diff --git a/lib/util/result.spec.ts b/lib/util/result.spec.ts index 71cc995519..1d213025d3 100644 --- a/lib/util/result.spec.ts +++ b/lib/util/result.spec.ts @@ -107,6 +107,16 @@ describe('util/result', () => { .unwrap() ).toThrow('oops'); }); + + it('returns ok-value for unwrapOrThrow', () => { + const res = Result.ok(42); + expect(res.unwrapOrThrow()).toBe(42); + }); + + it('throws error for unwrapOrThrow on error result', () => { + const res = Result.err('oops'); + expect(() => res.unwrapOrThrow()).toThrow('oops'); + }); }); describe('Transforming', () => { @@ -140,6 +150,36 @@ describe('util/result', () => { ); }); }); + + describe('Catch', () => { + it('bypasses ok result', () => { + const res = Result.ok(42); + expect(res.catch(() => Result.ok(0))).toEqual(Result.ok(42)); + expect(res.catch(() => Result.ok(0))).toBe(res); + }); + + it('bypasses uncaught transform errors', () => { + const res = Result.ok(42).transform(() => { + throw 'oops'; + }); + expect(res.catch(() => Result.ok(0))).toEqual(Result._uncaught('oops')); + expect(res.catch(() => Result.ok(0))).toBe(res); + }); + + it('converts error to Result', () => { + const result = Result.err('oops').catch(() => + Result.ok(42) + ); + expect(result).toEqual(Result.ok(42)); + }); + + it('handles error thrown in catch function', () => { + const result = Result.err('oops').catch(() => { + throw 'oops'; + }); + expect(result).toEqual(Result._uncaught('oops')); + }); + }); }); describe('AsyncResult', () => { @@ -222,6 +262,16 @@ describe('util/result', () => { const res = Result.wrap(Promise.reject('oops')); await expect(res.unwrap(42)).resolves.toBe(42); }); + + it('returns ok-value for unwrapOrThrow', async () => { + const res = Result.wrap(Promise.resolve(42)); + await expect(res.unwrapOrThrow()).resolves.toBe(42); + }); + + it('rejects for error for unwrapOrThrow', async () => { + const res = Result.wrap(Promise.reject('oops')); + await expect(res.unwrapOrThrow()).rejects.toBe('oops'); + }); }); describe('Transforming', () => { @@ -367,5 +417,33 @@ describe('util/result', () => { expect(res).toEqual(Result.ok('F-O-O')); }); }); + + describe('Catch', () => { + it('converts error to AsyncResult', async () => { + const result = await Result.err('oops').catch(() => + AsyncResult.ok(42) + ); + expect(result).toEqual(Result.ok(42)); + }); + + it('converts error to Promise', async () => { + const fallback = Promise.resolve(Result.ok(42)); + const result = await Result.err('oops').catch(() => fallback); + expect(result).toEqual(Result.ok(42)); + }); + + it('handles error thrown in Promise result', async () => { + const fallback = Promise.reject('oops'); + const result = await Result.err('oops').catch(() => fallback); + expect(result).toEqual(Result._uncaught('oops')); + }); + + it('converts AsyncResult error to Result', async () => { + const result = await AsyncResult.err('oops').catch(() => + AsyncResult.ok(42) + ); + expect(result).toEqual(Result.ok(42)); + }); + }); }); }); diff --git a/lib/util/result.ts b/lib/util/result.ts index 1069dc7858..19f31e1704 100644 --- a/lib/util/result.ts +++ b/lib/util/result.ts @@ -226,6 +226,17 @@ export class Result { return this.res; } + /** + * Returns the ok-value or throw the error. + */ + unwrapOrThrow(): NonNullable { + if (this.res.ok) { + return this.res.val; + } + + throw this.res.err; + } + /** * Transforms the ok-value, sync or async way. * @@ -301,6 +312,48 @@ export class Result { return Result._uncaught(err); } } + + catch( + fn: (err: NonNullable) => Result + ): Result; + catch( + fn: (err: NonNullable) => AsyncResult + ): AsyncResult; + catch( + fn: (err: NonNullable) => Promise> + ): AsyncResult; + catch( + fn: ( + err: NonNullable + ) => Result | AsyncResult | Promise> + ): Result | AsyncResult { + if (this.res.ok) { + return this; + } + + if (this.res._uncaught) { + return this; + } + + try { + const result = fn(this.res.err); + + if (result instanceof Promise) { + return AsyncResult.wrap(result, (err) => { + logger.warn( + { err }, + 'Result: unexpected error in async catch handler' + ); + return Result._uncaught(err); + }); + } + + return result; + } catch (err) { + logger.warn({ err }, 'Result: unexpected error in catch handler'); + return Result._uncaught(err); + } + } } /** @@ -401,6 +454,14 @@ export class AsyncResult implements PromiseLike> { : this.asyncResult.then>((res) => res.unwrap(fallback)); } + /** + * Returns the ok-value or throw the error. + */ + async unwrapOrThrow(): Promise> { + const result = await this.asyncResult; + return result.unwrapOrThrow(); + } + /** * Transforms the ok-value, sync or async way. * @@ -484,4 +545,24 @@ export class AsyncResult implements PromiseLike> { }) ); } + + catch( + fn: (err: NonNullable) => Result + ): AsyncResult; + catch( + fn: (err: NonNullable) => AsyncResult + ): AsyncResult; + catch( + fn: (err: NonNullable) => Promise> + ): AsyncResult; + catch( + fn: ( + err: NonNullable + ) => Result | AsyncResult | Promise> + ): AsyncResult { + const caughtAsyncResult = this.asyncResult.then((result) => + result.catch(fn as never) + ); + return AsyncResult.wrap(caughtAsyncResult); + } }