Skip to content
This repository has been archived by the owner on Jul 16, 2024. It is now read-only.

Commit

Permalink
add Effect.catchIf api
Browse files Browse the repository at this point in the history
  • Loading branch information
tim-smart committed Aug 28, 2023
1 parent 5329ef0 commit a7886c6
Show file tree
Hide file tree
Showing 8 changed files with 152 additions and 44 deletions.
5 changes: 5 additions & 0 deletions .changeset/wet-colts-work.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@effect/io": patch
---

add Effect.catchIf api
33 changes: 33 additions & 0 deletions docs/modules/Effect.ts.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ Added in v1.0.0
- [catchAll](#catchall)
- [catchAllCause](#catchallcause)
- [catchAllDefect](#catchalldefect)
- [catchIf](#catchif)
- [catchSome](#catchsome)
- [catchSomeCause](#catchsomecause)
- [catchSomeDefect](#catchsomedefect)
Expand Down Expand Up @@ -2176,6 +2177,38 @@ export declare const catchAllDefect: {
Added in v1.0.0
## catchIf
Recovers from errors that match the given predicate.
**Signature**
```ts
export declare const catchIf: {
<E, EA extends E, EB extends EA, R2, E2, A2>(refinement: Refinement<EA, EB>, f: (e: EB) => Effect<R2, E2, A2>): <
R,
A
>(
self: Effect<R, E, A>
) => Effect<R2 | R, E2 | Exclude<E, EB>, A2 | A>
<E, EX extends E, R2, E2, A2>(predicate: Predicate<EX>, f: (e: EX) => Effect<R2, E2, A2>): <R, A>(
self: Effect<R, E, A>
) => Effect<R2 | R, E | E2, A2 | A>
<R, E, A, EA extends E, EB extends EA, R2, E2, A2>(
self: Effect<R, E, A>,
refinement: Refinement<EA, EB>,
f: (e: EB) => Effect<R2, E2, A2>
): Effect<R | R2, E2 | Exclude<E, EB>, A | A2>
<R, E, A, EX extends E, R2, E2, A2>(
self: Effect<R, E, A>,
predicate: Predicate<EX>,
f: (e: EX) => Effect<R2, E2, A2>
): Effect<R | R2, E | E2, A | A2>
}
```
Added in v1.0.0
## catchSome
Recovers from some or all of the error cases.
Expand Down
27 changes: 27 additions & 0 deletions src/Effect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1474,6 +1474,33 @@ export const catchAllDefect: {
): Effect<R | R2, E | E2, A | A2>
} = effect.catchAllDefect

/**
* Recovers from errors that match the given predicate.
*
* @since 1.0.0
* @category error handling
*/
export const catchIf: {
<E, EA extends E, EB extends EA, R2, E2, A2>(
refinement: Refinement<EA, EB>,
f: (e: EB) => Effect<R2, E2, A2>
): <R, A>(self: Effect<R, E, A>) => Effect<R2 | R, E2 | Exclude<E, EB>, A2 | A>
<E, EX extends E, R2, E2, A2>(
predicate: Predicate<EX>,
f: (e: EX) => Effect<R2, E2, A2>
): <R, A>(self: Effect<R, E, A>) => Effect<R2 | R, E | E2, A2 | A>
<R, E, A, EA extends E, EB extends EA, R2, E2, A2>(
self: Effect<R, E, A>,
refinement: Refinement<EA, EB>,
f: (e: EB) => Effect<R2, E2, A2>
): Effect<R | R2, E2 | Exclude<E, EB>, A | A2>
<R, E, A, EX extends E, R2, E2, A2>(
self: Effect<R, E, A>,
predicate: Predicate<EX>,
f: (e: EX) => Effect<R2, E2, A2>
): Effect<R | R2, E | E2, A | A2>
} = core.catchIf

/**
* Recovers from some or all of the error cases.
*
Expand Down
69 changes: 55 additions & 14 deletions src/internal/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import * as List from "@effect/data/List"
import * as MutableRef from "@effect/data/MutableRef"
import * as Option from "@effect/data/Option"
import { pipeArguments } from "@effect/data/Pipeable"
import type { Predicate } from "@effect/data/Predicate"
import type { Predicate, Refinement } from "@effect/data/Predicate"
import * as ReadonlyArray from "@effect/data/ReadonlyArray"
import * as Cause from "@effect/io/Cause"
import type * as Deferred from "@effect/io/Deferred"
Expand Down Expand Up @@ -508,6 +508,47 @@ export const unified = <Args extends ReadonlyArray<any>, Ret extends Effect.Effe
) =>
(...args: Args): Effect.Effect.Unify<Ret> => f(...args)

/* @internal */
export const catchIf = dual<
{
<E, EA extends E, EB extends EA, R2, E2, A2>(
refinement: Refinement<EA, EB>,
f: (e: EB) => Effect.Effect<R2, E2, A2>
): <R, A>(self: Effect.Effect<R, E, A>) => Effect.Effect<R2 | R, Exclude<E, EB> | E2, A2 | A>
<E, EX extends E, R2, E2, A2>(
predicate: Predicate<EX>,
f: (e: EX) => Effect.Effect<R2, E2, A2>
): <R, A>(self: Effect.Effect<R, E, A>) => Effect.Effect<R2 | R, E | E2, A2 | A>
},
{
<R, E, A, EA extends E, EB extends EA, R2, E2, A2>(
self: Effect.Effect<R, E, A>,
refinement: Refinement<EA, EB>,
f: (e: EB) => Effect.Effect<R2, E2, A2>
): Effect.Effect<R2 | R, Exclude<E, EB> | E2, A2 | A>
<R, E, A, EX extends E, R2, E2, A2>(
self: Effect.Effect<R, E, A>,
predicate: Predicate<EX>,
f: (e: EX) => Effect.Effect<R2, E2, A2>
): Effect.Effect<R2 | R, E | E2, A2 | A>
}
>(3, <R, E, A, EX extends E, R2, E2, A2>(
self: Effect.Effect<R, E, A>,
predicate: Predicate<EX>,
f: (e: EX) => Effect.Effect<R2, E2, A2>
) =>
catchAllCause(self, (cause): Effect.Effect<R2 | R, E | E2, A2 | A> => {
const either = internalCause.failureOrCause(cause)
switch (either._tag) {
case "Left": {
return predicate(either.left as EX) ? f(either.left as EX) : failCause(cause)
}
case "Right": {
return failCause(either.right)
}
}
}))

/* @internal */
export const catchSome = dual<
<E, R2, E2, A2>(
Expand All @@ -517,20 +558,20 @@ export const catchSome = dual<
self: Effect.Effect<R, E, A>,
pf: (e: E) => Option.Option<Effect.Effect<R2, E2, A2>>
) => Effect.Effect<R2 | R, E | E2, A2 | A>
>(2, (self, pf) =>
matchCauseEffect(self, {
onFailure: unified((cause) => {
const either = internalCause.failureOrCause(cause)
switch (either._tag) {
case "Left": {
return pipe(pf(either.left), Option.getOrElse(() => failCause(cause)))
}
case "Right": {
return failCause(either.right)
}
>(2, <R, A, E, R2, E2, A2>(
self: Effect.Effect<R, E, A>,
pf: (e: E) => Option.Option<Effect.Effect<R2, E2, A2>>
) =>
catchAllCause(self, (cause): Effect.Effect<R2 | R, E | E2, A2 | A> => {
const either = internalCause.failureOrCause(cause)
switch (either._tag) {
case "Left": {
return pipe(pf(either.left), Option.getOrElse(() => failCause(cause)))
}
}),
onSuccess: succeed
case "Right": {
return failCause(either.right)
}
}
}))

/* @internal */
Expand Down
39 changes: 12 additions & 27 deletions src/internal/effect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,15 +246,7 @@ export const catchTag = dual<
k: K,
f: (e: Extract<E, { _tag: K }>) => Effect.Effect<R1, E1, A1>
) => Effect.Effect<R | R1, Exclude<E, { _tag: K }> | E1, A | A1>
>(3, (self, k, f) =>
core.catchAllCause(self, (e) => {
const failure = internalCause.find(e, (c) =>
internalCause.isFailType(c) && Predicate.isTagged(c.error, k) ? Option.some(c.error) : Option.none())
if (Option.isSome(failure)) {
return f(failure.value as any)
}
return core.fail(e as any)
}))
>(3, (self, k, f) => core.catchIf(self, Predicate.isTagged(k), f) as any)

/** @internal */
export const catchTags: {
Expand Down Expand Up @@ -305,24 +297,17 @@ export const catchTags: {
[K in keyof Cases]: Cases[K] extends ((...args: Array<any>) => Effect.Effect<any, any, infer A>) ? A : never
}[keyof Cases]
>
} = dual(2, (self, cases) =>
core.catchAllCause(self, (e) => {
const keys = Object.keys(cases)
const handler = internalCause.find(e, (c) => {
if (
internalCause.isFailType(c) && Predicate.isObject(c.error) && "_tag" in c.error &&
keys.includes(c.error["_tag"] as any)
) {
return Option.some(cases[c.error["_tag"] as any](c.error as any))
} else {
return Option.none()
}
})
if (Option.isSome(handler)) {
return handler.value
}
return core.failCause(e as any)
}))
} = dual(2, (self, cases) => {
let keys: Array<string>
return core.catchIf(
self,
(e): e is { readonly _tag: string } => {
keys ??= Object.keys(cases)
return Predicate.isObject(e) && "_tag" in e && keys.includes(e["_tag"] as any)
},
(e) => cases[e["_tag"] as any](e as any)
)
})

/* @internal */
export const cause = <R, E, A>(self: Effect.Effect<R, E, A>): Effect.Effect<R, never, Cause.Cause<E>> =>
Expand Down
17 changes: 17 additions & 0 deletions test/Effect/error-handling.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import * as Fiber from "@effect/io/Fiber"
import * as FiberId from "@effect/io/Fiber/Id"
import { causesArb } from "@effect/io/test/utils/cause"
import * as it from "@effect/io/test/utils/extend"
import { assertType, satisfies } from "@effect/io/test/utils/types"
import * as fc from "fast-check"
import { assert, describe } from "vitest"

Expand Down Expand Up @@ -257,6 +258,22 @@ describe.concurrent("Effect", () => {
)
assert.deepStrictEqual(Exit.unannotate(result), Exit.fail({ _tag: "ErrorB" as const }))
}))
it.effect("catchIf - does not recover from one of several tagged errors", () =>
Effect.gen(function*($) {
interface ErrorA {
readonly _tag: "ErrorA"
}
interface ErrorB {
readonly _tag: "ErrorB"
}
const effect: Effect.Effect<never, ErrorA | ErrorB, never> = Effect.fail({ _tag: "ErrorB" })
const result = yield* $(
Effect.catchIf(effect, (e): e is ErrorA => e._tag === "ErrorA", Effect.succeed),
Effect.exit
)
assert.deepStrictEqual(Exit.unannotate(result), Exit.fail({ _tag: "ErrorB" as const }))
satisfies<true>(assertType<Exit.Exit<ErrorB, ErrorA>>()(result))
}))
it.effect("catchTags - recovers from one of several tagged errors", () =>
Effect.gen(function*($) {
interface ErrorA {
Expand Down
4 changes: 1 addition & 3 deletions test/Effect/structural.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@ import * as Either from "@effect/data/Either"
import * as Option from "@effect/data/Option"
import * as Effect from "@effect/io/Effect"
import * as it from "@effect/io/test/utils/extend"
import { assertType } from "@effect/io/test/utils/types"
import { assertType, satisfies } from "@effect/io/test/utils/types"
import { describe } from "vitest"

const satisfies = <T>(type: T) => type

describe.concurrent("Effect", () => {
describe("all", () => {
it.effect("should work with one array argument", () =>
Expand Down
2 changes: 2 additions & 0 deletions test/utils/types.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export const assertType = <A>() => <B>(_: B): [A] extends [B] ? [B] extends [A] ? true : false : false => void 0 as any

export const satisfies = <T>(type: T) => type

0 comments on commit a7886c6

Please sign in to comment.