diff --git a/lib/api/lib.api b/lib/api/lib.api index a67a44a..043a89f 100644 --- a/lib/api/lib.api +++ b/lib/api/lib.api @@ -169,6 +169,7 @@ public final class app/cash/quiver/extensions/EitherKt { public static final fun flatTap (Larrow/core/Either;Lkotlin/jvm/functions/Function1;)Larrow/core/Either; public static final fun forEach (Larrow/core/Either;Lkotlin/jvm/functions/Function1;)V public static final fun getRightUnit ()Larrow/core/Either; + public static final fun handleErrorWith (Larrow/core/Either;Lkotlin/jvm/functions/Function1;)Larrow/core/Either; public static final fun leftAsOption (Larrow/core/Either;)Larrow/core/Option; public static final fun leftForEach (Larrow/core/Either;Lkotlin/jvm/functions/Function1;)V public static final fun mapOption (Larrow/core/Either;Lkotlin/jvm/functions/Function1;)Larrow/core/Either; diff --git a/lib/src/main/kotlin/app/cash/quiver/extensions/Either.kt b/lib/src/main/kotlin/app/cash/quiver/extensions/Either.kt index 83499c3..fa28176 100644 --- a/lib/src/main/kotlin/app/cash/quiver/extensions/Either.kt +++ b/lib/src/main/kotlin/app/cash/quiver/extensions/Either.kt @@ -8,8 +8,12 @@ import arrow.core.Some import arrow.core.flatMap import arrow.core.getOrElse import arrow.core.identity +import arrow.core.recover import arrow.core.right import arrow.core.toOption +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract import kotlin.experimental.ExperimentalTypeInference import app.cash.quiver.extensions.traverse as quiverTraverse @@ -287,3 +291,12 @@ inline fun Either.traverseOption(transform: (A) -> Option): O quiverTraverse(transform) fun Either>.sequence(): Option> = quiverTraverse(::identity) + +/** + * Recovers the left side of an Either with the supplied function + */ +@OptIn(ExperimentalContracts::class) +inline fun Either.handleErrorWith(f: (A) -> Either): Either { + contract { callsInPlace(f, InvocationKind.AT_MOST_ONCE) } + return recover { a -> f(a).bind() } +} diff --git a/lib/src/test/kotlin/app/cash/quiver/extensions/EitherTest.kt b/lib/src/test/kotlin/app/cash/quiver/extensions/EitherTest.kt index 6f8e9eb..67313d3 100644 --- a/lib/src/test/kotlin/app/cash/quiver/extensions/EitherTest.kt +++ b/lib/src/test/kotlin/app/cash/quiver/extensions/EitherTest.kt @@ -11,6 +11,8 @@ import io.kotest.assertions.arrow.core.shouldBeLeft import io.kotest.assertions.arrow.core.shouldBeRight import io.kotest.assertions.arrow.core.shouldBeSome import io.kotest.core.spec.style.StringSpec +import io.kotest.matchers.result.shouldBeFailure +import io.kotest.matchers.result.shouldBeSuccess import io.kotest.matchers.shouldBe import io.kotest.property.Arb import io.kotest.property.arbitrary.int @@ -197,6 +199,19 @@ class EitherTest : StringSpec({ ) } } + + "handleFailureWith recovers errors" { + 1.right().handleErrorWith { Either.Left(Throwable("never happens")) }.shouldBeRight(1) + Throwable("sad panda").left().handleErrorWith { Throwable("even sadder").left() } + .shouldBeLeft().message shouldBe "even sadder" + + Throwable("sad panda").left().handleErrorWith { 1.right() } + .shouldBeRight(1) + + Throwable("sad panda").left().handleErrorWith { it.left() } + .shouldBeLeft().message shouldBe "sad panda" + } + }) fun testParse(s: String): ErrorOr =