diff --git a/src/result-async.ts b/src/result-async.ts index 20120d2b..5e8f675d 100644 --- a/src/result-async.ts +++ b/src/result-async.ts @@ -98,6 +98,19 @@ export class ResultAsync implements PromiseLike> { ) } + inspect(f: (t: T) => void | Promise): ResultAsync { + return new ResultAsync( + this._promise.then(async (res: Result) => { + if (res.isErr()) { + return new Err(res.error) + } + + await f(res.value) + return new Ok(res.value) + }), + ) + } + andThrough(f: (t: T) => Result | ResultAsync): ResultAsync { return new ResultAsync( this._promise.then(async (res: Result) => { @@ -158,6 +171,19 @@ export class ResultAsync implements PromiseLike> { ) } + inspectErr(f: (e: E) => void | Promise): ResultAsync { + return new ResultAsync( + this._promise.then(async (res: Result) => { + if (res.isOk()) { + return new Ok(res.value) + } + + await f(res.error) + return new Err(res.error) + }), + ) + } + andThen>( f: (t: T) => R, ): ResultAsync, InferErrTypes | E> diff --git a/src/result.ts b/src/result.ts index ad447caa..6d57b3f2 100644 --- a/src/result.ts +++ b/src/result.ts @@ -156,6 +156,15 @@ interface IResult { */ map(f: (t: T) => A): Result + /** + * Calls a function with the contained `Ok` value, leaving both `Ok` and `Err` untouched. + * + * This function can be used to perform side-effects without transforming the contained value. + * + * @param f a callback function that receives the contained `Ok` value + */ + inspect(f: (t: T) => void): Result + /** * Maps a `Result` to `Result` by applying a function to a * contained `Err` value, leaving an `Ok` value untouched. @@ -167,6 +176,15 @@ interface IResult { */ mapErr(f: (e: E) => U): Result + /** + * Calls a function with the contained `Err` error, leaving both `Ok` and `Err` untouched. + * + * This function can be used to perform side-effects without transforming the contained error. + * + * @param f a callback function that receives the contained `Err` value + */ + inspectErr(f: (e: E) => void): Result + /** * Similar to `map` Except you must return a new `Result`. * @@ -324,11 +342,21 @@ export class Ok implements IResult { return ok(f(this.value)) } + inspect(f: (t: T) => void): Result { + f(this.value) + return ok(this.value) + } + // eslint-disable-next-line @typescript-eslint/no-unused-vars mapErr(_f: (e: E) => U): Result { return ok(this.value) } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + inspectErr(_f: (e: E) => void): Result { + return ok(this.value) + } + andThen>( f: (t: T) => R, ): Result, InferErrTypes | E> @@ -436,6 +464,16 @@ export class Err implements IResult { return err(f(this.error)) } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + inspect(_f: (t: T) => void): Result { + return err(this.error) + } + + inspectErr(f: (e: E) => void): Result { + f(this.error) + return err(this.error) + } + andThrough(_f: (t: T) => Result): Result { return err(this.error) } diff --git a/tests/index.test.ts b/tests/index.test.ts index 9f089a9d..68c49fa4 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -58,6 +58,19 @@ describe('Result.Ok', () => { expect(mapFn).toHaveBeenCalledTimes(1) }) + it('Inspects an Ok value', () => { + const okVal = ok(12) + const inspectorFn = vitest.fn((number) => { + console.log("got OK value", number) + }) + + const inspected = okVal.inspect(inspectorFn) + + expect(inspected.isOk()).toBe(true) + expect(inspected._unsafeUnwrap()).toBe(12) + expect(inspectorFn).toHaveBeenCalledTimes(1) + }) + it('Skips `mapErr`', () => { const mapErrorFunc = vitest.fn((_error) => 'mapped error value') @@ -67,6 +80,15 @@ describe('Result.Ok', () => { expect(mapErrorFunc).not.toHaveBeenCalledTimes(1) }) + it('Skips `inspectErr`', () => { + const inspectFn = vitest.fn((_error) => 'mapped error value') + + const notMapped = ok(12).inspectErr(inspectFn) + + expect(notMapped.isOk()).toBe(true) + expect(inspectFn).not.toHaveBeenCalledTimes(1) + }) + describe('andThen', () => { it('Maps to an Ok', () => { const okVal = ok(12) @@ -137,7 +159,7 @@ describe('Result.Ok', () => { describe('andTee', () => { it('Calls the passed function but returns an original ok', () => { const okVal = ok(12) - const passedFn = vitest.fn((_number) => {}) + const passedFn = vitest.fn((_number) => { }) const teed = okVal.andTee(passedFn) @@ -162,7 +184,7 @@ describe('Result.Ok', () => { describe('orTee', () => { it('Calls the passed function but returns an original err', () => { const errVal = err(12) - const passedFn = vitest.fn((_number) => {}) + const passedFn = vitest.fn((_number) => { }) const teed = errVal.orTee(passedFn) @@ -332,6 +354,20 @@ describe('Result.Err', () => { expect(hopefullyNotMapped._unsafeUnwrapErr()).toEqual(errVal._unsafeUnwrapErr()) }) + it('Skips `inspect`', () => { + const errVal = err('I am your father') + + const inspectorFn = vitest.fn((_value) => { + console.log("got value") + }) + + const notInspected = errVal.inspect(inspectorFn) + + expect(notInspected.isErr()).toBe(true) + expect(inspectorFn).not.toHaveBeenCalled() + expect(notInspected._unsafeUnwrapErr()).toEqual(errVal._unsafeUnwrapErr()) + }) + it('Maps over an Err', () => { const errVal = err('Round 1, Fight!') @@ -344,6 +380,20 @@ describe('Result.Err', () => { expect(mapped._unsafeUnwrapErr()).not.toEqual(errVal._unsafeUnwrapErr()) }) + it('Inspects an Err', () => { + const errVal = err('Round 1, Fight!') + + const inspectorFn = vitest.fn((error: string) => { + console.error("Error happened", error) + }) + + const inspected = errVal.inspectErr(inspectorFn) + + expect(inspected.isErr()).toBe(true) + expect(inspectorFn).toHaveBeenCalledTimes(1) + expect(inspected._unsafeUnwrapErr()).toEqual(errVal._unsafeUnwrapErr()) + }) + it('unwrapOr and return the default value', () => { const okVal = err('Oh nooo') expect(okVal.unwrapOr(1)).toEqual(1) @@ -376,7 +426,7 @@ describe('Result.Err', () => { it('Skips over andTee', () => { const errVal = err('Yolo') - const mapper = vitest.fn((_val) => {}) + const mapper = vitest.fn((_val) => { }) const hopefullyNotFlattened = errVal.andTee(mapper) @@ -877,6 +927,118 @@ describe('ResultAsync', () => { }) }) + describe("inspect", () => { + it('Inspects a value using a synchronous function', async () => { + const asyncVal = okAsync(12) + + const inspectorFn = vitest.fn((number) => { + console.log("got value", number) + }) + + const inspected = asyncVal.inspect(inspectorFn) + + expect(inspected).toBeInstanceOf(ResultAsync) + + const newVal = await inspected + + expect(newVal.isOk()).toBe(true) + expect(newVal._unsafeUnwrap()).toBe(12) + expect(inspectorFn).toHaveBeenCalledTimes(1) + }) + + it('Inspects a value using a asynchronous function', async () => { + const asyncVal = okAsync(12) + + const inspectorFn = vitest.fn(async (number) => { + console.log("got value", number) + }) + + const inspected = asyncVal.inspect(inspectorFn) + + expect(inspected).toBeInstanceOf(ResultAsync) + + const newVal = await inspected + + expect(newVal.isOk()).toBe(true) + expect(newVal._unsafeUnwrap()).toBe(12) + expect(inspectorFn).toHaveBeenCalledTimes(1) + }) + + it('Skips an error', async () => { + const asyncErr = errAsync('Wrong format') + + const inspectorFn = vitest.fn((number) => { + console.log("got value", number) + }) + + const notInspected = asyncErr.inspect(inspectorFn) + + expect(notInspected).toBeInstanceOf(ResultAsync) + + const newVal = await notInspected + + expect(newVal.isErr()).toBe(true) + expect(newVal._unsafeUnwrapErr()).toBe('Wrong format') + expect(inspectorFn).toHaveBeenCalledTimes(0) + }) + }) + + describe("inspectErr", () => { + it('Inspects an error using an synchronous function', async () => { + const asyncErr = errAsync('Wrong format') + + const inspectorFn = vitest.fn((str) => { + console.error("error happened", str) + }) + + const inspectedErr = asyncErr.inspectErr(inspectorFn) + + expect(inspectedErr).toBeInstanceOf(ResultAsync) + + const newVal = await inspectedErr + + expect(newVal.isErr()).toBe(true) + expect(newVal._unsafeUnwrapErr()).toBe('Wrong format') + expect(inspectorFn).toHaveBeenCalledTimes(1) + }) + + it('Inspects an error using an asynchronous function', async () => { + const asyncErr = errAsync('Wrong format') + + const inspectorFn = vitest.fn(async (str) => { + console.error("error happened", str) + }) + + const inspectedErr = asyncErr.inspectErr(inspectorFn) + + expect(inspectedErr).toBeInstanceOf(ResultAsync) + + const newVal = await inspectedErr + + expect(newVal.isErr()).toBe(true) + expect(newVal._unsafeUnwrapErr()).toBe('Wrong format') + expect(inspectorFn).toHaveBeenCalledTimes(1) + }) + + it('Skips a value', async () => { + const asyncVal = okAsync(12) + + const inspectorFn = vitest.fn((str) => { + console.error("got error", str) + }) + + const notInspected = asyncVal.inspectErr(inspectorFn) + + expect(notInspected).toBeInstanceOf(ResultAsync) + + const newVal = await notInspected + + expect(newVal.isOk()).toBe(true) + expect(newVal._unsafeUnwrap()).toBe(12) + expect(inspectorFn).toHaveBeenCalledTimes(0) + }) + }) + describe('mapErr', () => { it('Maps an error using a synchronous function', async () => { const asyncErr = errAsync('Wrong format') @@ -1067,7 +1229,7 @@ describe('ResultAsync', () => { describe('andTee', () => { it('Calls the passed function but returns an original ok', async () => { const okVal = okAsync(12) - const passedFn = vitest.fn((_number) => {}) + const passedFn = vitest.fn((_number) => { }) const teed = await okVal.andTee(passedFn) @@ -1092,7 +1254,7 @@ describe('ResultAsync', () => { describe('orTee', () => { it('Calls the passed function but returns an original err', async () => { const errVal = errAsync(12) - const passedFn = vitest.fn((_number) => {}) + const passedFn = vitest.fn((_number) => { }) const teed = await errVal.orTee(passedFn)