Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add runSuspendThrowing and T#runSuspendCatching #67

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
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

/**
* Calls the specified function [block] and returns its encapsulated result if
* invocation was successful, catching and encapsulating any thrown exception
* as a failure, excepting that any [CancellationException] will be rethrown
* in order to propagate cancellation from any parent
* [CoroutineContext][kotlin.coroutines.CoroutineContext].
*
* @throws CancellationException if is thrown from the block
*/
public suspend inline fun <V> runSuspendCatching(block: () -> V)
: Result<V, Throwable> =
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 and
* encapsulating any thrown exception as a failure, excepting that any
* [CancellationException] will be rethrown in order to propagate cancellation
* from any parent [CoroutineContext][kotlin.coroutines.CoroutineContext].
*
* @throws CancellationException if is thrown from the block
*/
public suspend inline fun <V,T> T.runSuspendCatching(block: T.() -> V)
: Result<V, Throwable> =
runCatching(block)
.throwIf { it is CancellationException }
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package com.github.michaelbull.result.coroutines

import com.github.michaelbull.result.get
import com.github.michaelbull.result.getError
import com.github.michaelbull.result.onSuccess
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.TestCoroutineDispatcher
import kotlinx.coroutines.test.TestCoroutineScope
import kotlinx.coroutines.test.runBlockingTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNull
import kotlin.test.assertSame

@ExperimentalCoroutinesApi
class RunSuspendCatchingTest {

@Test
fun propagatesCoroutineCancellation() {
val testDispatcher = TestCoroutineDispatcher()
val testScope = TestCoroutineScope(testDispatcher)

testScope.runBlockingTest {
var value: String? = null

launch { // Outer scope
launch { // Inner scope
val result = runSuspendCatching {
delay(4_000)
"value"
}

// The coroutine should be cancelled before reaching here
result.onSuccess { value = it }
}
testDispatcher.advanceTimeBy(2_000)

// Cancel outer scope, which should cancel inner scope
cancel()
}
assertNull(value)
}
}

@Test
fun returnsOkIfInvocationSuccessful() {
val testDispatcher = TestCoroutineDispatcher()
val testScope = TestCoroutineScope(testDispatcher)

testScope.runBlockingTest {
val callback = { "example" }
val result = runSuspendCatching(callback)

assertEquals(
expected = "example",
actual = result.get()
)
}
}

@Test
fun returnsErrIfInvocationFailsWithAnythingOtherThanCancellationException() {
val testDispatcher = TestCoroutineDispatcher()
val testScope = TestCoroutineScope(testDispatcher)

testScope.runBlockingTest {
val exception = IllegalArgumentException("throw me")
val callback = { throw exception }
val result = runSuspendCatching(callback)

assertSame(
expected = exception,
actual = result.getError()
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ 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.
*
* N.B. [runCatching] catches *all* exceptions thrown in the block, including
* [CancellationException][kotlinx.coroutines.CancellationException], preventing
* correct cancellation in structured concurrency. Use [runSuspendCatching] in
* such a context.
*/
public inline fun <V> runCatching(block: () -> V): Result<V, Throwable> {
contract {
Expand All @@ -24,6 +29,11 @@ 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.
*
* N.B. [runCatching] catches *all* exceptions thrown in the block, including
* [CancellationException][kotlinx.coroutines.CancellationException], preventing
* correct cancellation in structured concurrency. Use [runSuspendCatching] in
* such a context.
*/
public inline infix fun <T, V> T.runCatching(block: T.() -> V): Result<V, Throwable> {
contract {
Expand Down