Skip to content

Commit

Permalink
Implement catchError so we can take the input and error from a failed…
Browse files Browse the repository at this point in the history
… composable and recover from it
  • Loading branch information
diogob committed Mar 13, 2024
1 parent cfd59f0 commit 371d2fe
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 2 deletions.
26 changes: 25 additions & 1 deletion src/composable/composable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,30 @@ function map<T extends Composable, R>(
}) as Composable<(...args: Parameters<T>) => R>
}

/**
* Creates a new function that will try to recover from a resulting Failure. When the given function succeeds, its result is returned without changes.
* @example
* import { cf as C } from 'domain-functions'
*
* const increment = C.composable(({ id }: { id: number }) => id + 1)
* const negativeOnError = C.catchError(increment, (result, originalInput) => (
* originalInput.id * -1
* ))
*/
function catchError<T extends Composable, R>(
fn: T,
catcher: (
err: Omit<Failure, 'success'>,
...originalInput: Parameters<T>
) => UnpackResult<ReturnType<T>>,
) {
return (async (...args) => {
const res = await fn(...args)
if (res.success) return success(res.data)
return composable(catcher)(res, ...(args as any))
}) as T
}

/**
* Creates a new function that will apply a transformation over a resulting Failure from the given function. When the given function succeeds, its result is returned without changes.
* @example
Expand Down Expand Up @@ -216,6 +240,7 @@ function mapError<T extends Composable, R>(

export {
all,
catchError,
collect,
composable,
error,
Expand All @@ -226,4 +251,3 @@ export {
sequence,
success,
}

34 changes: 33 additions & 1 deletion src/composable/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { assertEquals, describe, it } from '../test-prelude.ts'
import { map, mapError, pipe, sequence } from './index.ts'
import type { Composable, ErrorWithMessage, Result } from './index.ts'
import { Equal, Expect } from './types.test.ts'
import { all, collect, composable } from './composable.ts'
import { all, catchError, collect, composable } from './composable.ts'

const voidFn = composable(() => {})
const toString = composable((a: unknown) => `${a}`)
Expand Down Expand Up @@ -383,3 +383,35 @@ describe('mapError', () => {
})
})

describe('catchError', () => {
it('receives an error as input to another composable', async () => {
const fn = catchError(faultyAdd, (_error, a, b) => a + b)
const res = await fn(1, 2)

type _FN = Expect<
Equal<typeof fn, Composable<(a: number, b: number) => number>>
>
type _R = Expect<Equal<typeof res, Result<number>>>

assertEquals(res, {
success: true,
data: 3,
errors: [],
})
})

it('fails when catcher fail', async () => {
const fn = catchError(faultyAdd, () => {
throw new Error('Catcher also has problems')
})
const res = await fn(1, 2)

type _FN = Expect<
Equal<typeof fn, Composable<(a: number, b: number) => number>>
>
type _R = Expect<Equal<typeof res, Result<number>>>

assertEquals(res.success, false)
assertEquals(res.errors![0].message, 'Catcher also has problems')
})
})

0 comments on commit 371d2fe

Please sign in to comment.