Add runSuspendCatching and T#runSuspendCatching

Closes #67
This commit is contained in:
Joseph Cooper 2021-10-26 17:24:42 +01:00 committed by Michael Bull
parent 623a360e21
commit 2667273015
3 changed files with 113 additions and 6 deletions

View File

@ -0,0 +1,42 @@
package com.github.michaelbull.result.coroutines
import com.github.michaelbull.result.Result
import com.github.michaelbull.result.runCatching
import com.github.michaelbull.result.throwIf
import kotlinx.coroutines.CancellationException
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
/**
* Calls the specified function [block] and returns its encapsulated result if invocation was
* successful, catching any [Throwable] exception that was thrown from the [block] function
* execution and encapsulating it as a failure. If the encapsulated failure is a
* [CancellationException], the exception is thrown to indicate _normal_ cancellation of a
* coroutine.
*/
public inline fun <V> runSuspendCatching(block: () -> V): Result<V, Throwable> {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return runCatching(block).throwIf {
it is CancellationException
}
}
/**
* Calls the specified function [block] with [this] value as its receiver and returns its
* encapsulated result if invocation was successful, catching any [Throwable] exception that was
* thrown from the [block] function execution and encapsulating it as a failure. If the
* encapsulated failure is a [CancellationException], the exception is thrown to indicate _normal_
* cancellation of a coroutine.
*/
public inline infix fun <T, V> T.runSuspendCatching(block: T.() -> V): Result<V, Throwable> {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return runCatching(block).throwIf {
it is CancellationException
}
}

View File

@ -0,0 +1,65 @@
package com.github.michaelbull.result.coroutines
import com.github.michaelbull.result.Err
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.onSuccess
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runBlockingTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNull
@ExperimentalCoroutinesApi
class RunSuspendCatchingTest {
@Test
fun propagatesCoroutineCancellation() = runBlockingTest {
var value: String? = null
launch(CoroutineName("outer scope")) {
launch(CoroutineName("inner scope")) {
val result = runSuspendCatching {
delay(4_000)
"value"
}
// The coroutine should be cancelled before reaching here
result.onSuccess { value = it }
}
advanceTimeBy(2_000)
// Cancel outer scope, which should cancel inner scope
cancel()
}
assertNull(value)
}
@Test
fun returnsOkIfInvocationSuccessful() = runBlockingTest {
val block = { "example" }
val result = runSuspendCatching(block)
assertEquals(
expected = Ok("example"),
actual = result
)
}
@Test
fun returnsErrIfInvocationFailsWithAnythingOtherThanCancellationException() = runBlockingTest {
val exception = IllegalArgumentException("throw me")
val block = { throw exception }
val result = runSuspendCatching(block)
assertEquals(
expected = Err(exception),
actual = result
)
}
}

View File

@ -4,9 +4,9 @@ import kotlin.contracts.InvocationKind
import kotlin.contracts.contract import kotlin.contracts.contract
/** /**
* Calls the specified function [block] and returns its encapsulated result if * Calls the specified function [block] and returns its encapsulated result if invocation was
* invocation was successful, catching and encapsulating any thrown exception * successful, catching any [Throwable] exception that was thrown from the [block] function
* as a failure. * execution and encapsulating it as a failure.
*/ */
public inline fun <V> runCatching(block: () -> V): Result<V, Throwable> { public inline fun <V> runCatching(block: () -> V): Result<V, Throwable> {
contract { contract {
@ -21,9 +21,9 @@ public inline fun <V> runCatching(block: () -> V): Result<V, Throwable> {
} }
/** /**
* Calls the specified function [block] with [this] value as its receiver and * Calls the specified function [block] with [this] value as its receiver and returns its
* returns its encapsulated result if invocation was successful, catching and * encapsulated result if invocation was successful, catching any [Throwable] exception that was
* encapsulating any thrown exception as a failure. * thrown from the [block] function execution and encapsulating it as a failure.
*/ */
public inline infix fun <T, V> T.runCatching(block: T.() -> V): Result<V, Throwable> { public inline infix fun <T, V> T.runCatching(block: T.() -> V): Result<V, Throwable> {
contract { contract {