-
-
Save EXPEylazzari/dee99c40b5d68204756d3fd1c7a9f336 to your computer and use it in GitHub Desktop.
gRPC Effect Builder
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import arrow.core.continuations.Effect | |
import arrow.core.continuations.EffectScope | |
import arrow.core.continuations.effect | |
import arrow.core.nonFatalOrThrow | |
interface GrpcEffectContext<E> { | |
fun handleThrowable(t: Throwable): E | |
} | |
object DefaultGrpcEffectContext : GrpcEffectContext<String> { | |
override fun handleThrowable(t: Throwable): String = t.message ?: "Some error" | |
} | |
class GrpcEffectScope<R>( | |
private val context: GrpcEffectContext<R>, | |
private val continuation: EffectScope<R> | |
) : EffectScope<R> { | |
fun <A> call(f: suspend EffectScope<R>.() -> A): Effect<R, A> { | |
return effect { | |
try { | |
f() | |
} catch (t: Throwable) { | |
shift(context.handleThrowable(t.nonFatalOrThrow())) | |
} | |
} | |
} | |
override suspend fun <B> shift(r: R): B = continuation.shift(r) | |
} | |
suspend fun <R, A> grpc( | |
context: GrpcEffectContext<R>, | |
f: suspend GrpcEffectScope<R>.() -> A | |
): Effect<R, A> = effect { | |
f(GrpcEffectScope(context, this)) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import arrow.core.continuations.Effect | |
import arrow.core.continuations.effect | |
import kotlinx.coroutines.runBlocking | |
import org.junit.jupiter.api.Test | |
import kotlin.test.assertEquals | |
import kotlin.test.fail | |
class GrpcEffectTest { | |
class GrpcService1 { | |
suspend fun fun1(): String = "value_1" | |
suspend fun fun1ThatFails(): String = throw RuntimeException("kaboom_1") | |
} | |
class GrpcService2 { | |
suspend fun fun2(): String = "value_2" | |
suspend fun fun2ThatFails(): String = throw RuntimeException("kaboom_2") | |
} | |
class OtherService3 { | |
suspend fun fun3(): String = "value_3" | |
} | |
@Test | |
fun `should fold successfully when both calls succeed`() { | |
val service1 = GrpcService1() | |
val service2 = GrpcService2() | |
runBlocking { | |
val outerEffect: Effect<String, String> = grpc(DefaultGrpcEffectContext) { | |
val innerEffect1: Effect<String, String> = call { | |
service1.fun1() | |
} | |
val innerEffect2: Effect<String, String> = call { | |
service2.fun2() | |
} | |
val value1 = innerEffect1.bind() | |
val value2 = innerEffect2.bind() | |
"$value1 $value2" | |
} | |
outerEffect.fold( | |
{ fail("Should not have failed but failed with $it") }, | |
{ assertEquals("value_1 value_2", it) } | |
) | |
} | |
} | |
@Test | |
fun `should should shift correctly when first call fails`() { | |
val service1 = GrpcService1() | |
val service2 = GrpcService2() | |
runBlocking { | |
val outerEffect: Effect<String, String> = grpc(DefaultGrpcEffectContext) { | |
val innerEffect1: Effect<String, String> = call { | |
service1.fun1ThatFails() | |
} | |
val innerEffect2: Effect<String, String> = call { | |
service2.fun2() | |
} | |
val value1 = innerEffect1.bind() | |
val value2 = innerEffect2.bind() | |
"$value1 $value2" | |
} | |
outerEffect.fold( | |
{ assertEquals("kaboom_1", it) }, | |
{ fail("Should not have succeeded but succeeded with $it") } | |
) | |
} | |
} | |
@Test | |
fun `should should shift correctly when second call fails`() { | |
val service1 = GrpcService1() | |
val service2 = GrpcService2() | |
runBlocking { | |
val outerEffect: Effect<String, String> = grpc(DefaultGrpcEffectContext) { | |
val innerEffect1: Effect<String, String> = call { | |
service1.fun1() | |
} | |
val innerEffect2: Effect<String, String> = call { | |
service2.fun2ThatFails() | |
} | |
val value1 = innerEffect1.bind() | |
val value2 = innerEffect2.bind() | |
"$value1 $value2" | |
} | |
outerEffect.fold( | |
{ assertEquals("kaboom_2", it) }, | |
{ fail("Should not have succeeded but succeeded with $it") } | |
) | |
} | |
} | |
@Test | |
fun `should be able to nest effect block`() { | |
val service1 = GrpcService1() | |
val service2 = GrpcService2() | |
val service3 = OtherService3() | |
runBlocking { | |
val outerEffect: Effect<String, String> = grpc(DefaultGrpcEffectContext) { | |
val innerEffect1: Effect<String, String> = call { | |
service1.fun1() | |
} | |
val innerEffect2: Effect<String, String> = call { | |
service2.fun2() | |
} | |
val innerEffect3: Effect<String, String> = effect { | |
service3.fun3() | |
} | |
val value1 = innerEffect1.bind() | |
val value2 = innerEffect2.bind() | |
val value3 = innerEffect3.bind() | |
"$value1 $value2 $value3" | |
} | |
outerEffect.fold( | |
{ fail("Should not have failed but failed with $it") }, | |
{ assertEquals("value_1 value_2 value_3", it) } | |
) | |
} | |
} | |
@Test | |
fun `should should be able to nest gRPC calls inside effect block`() { | |
val service1 = GrpcService1() | |
runBlocking { | |
val outerEffect: Effect<String, String> = effect { | |
val innerEffect: Effect<String, String> = grpc(DefaultGrpcEffectContext) { | |
call { service1.fun1() }.bind() | |
} | |
innerEffect.bind() | |
} | |
outerEffect.fold( | |
{ fail("Should not have failed but failed with $it") }, | |
{ assertEquals("value_1", it) } | |
) | |
} | |
} | |
@Test | |
fun `should should be able to provide custom throwable handling function`() { | |
val service1 = GrpcService1() | |
val customGrpcEffectContext = object : GrpcEffectContext<Int> { | |
override fun handleThrowable(t: Throwable): Int = 0 | |
} | |
runBlocking { | |
val outerEffect: Effect<Int, String> = grpc(customGrpcEffectContext) { | |
val innerEffect: Effect<Int, String> = call { | |
service1.fun1ThatFails() | |
} | |
innerEffect.bind() | |
} | |
outerEffect.fold( | |
{ assertEquals(0, it) }, | |
{ fail("Should not have succeeded but succeeded with $it") } | |
) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment