Skip to content

Commit

Permalink
Add runSuspendCatching and T#runSuspendCatching
Browse files Browse the repository at this point in the history
Closes #67
  • Loading branch information
Joseph Cooper authored and michaelbull committed Oct 27, 2021
1 parent 623a360 commit 2667273
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -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
}
}
Original file line number Diff line number Diff line change
@@ -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
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import kotlin.contracts.InvocationKind
import kotlin.contracts.contract

/**
* Calls the specified function [block] and returns its encapsulated result if
* invocation was successful, catching and encapsulating any thrown exception
* as a failure.
* 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.
*/
public inline fun <V> runCatching(block: () -> V): Result<V, Throwable> {
contract {
Expand All @@ -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
* returns its encapsulated result if invocation was successful, catching and
* encapsulating any thrown exception as a failure.
* 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.
*/
public inline infix fun <T, V> T.runCatching(block: T.() -> V): Result<V, Throwable> {
contract {
Expand Down

0 comments on commit 2667273

Please sign in to comment.