-
Notifications
You must be signed in to change notification settings - Fork 65
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add runSuspendCatching and T#runSuspendCatching
Closes #67
- Loading branch information
1 parent
623a360
commit 2667273
Showing
3 changed files
with
113 additions
and
6 deletions.
There are no files selected for viewing
42 changes: 42 additions & 0 deletions
42
...ines/src/commonMain/kotlin/com/github/michaelbull/result/coroutines/RunSuspendCatching.kt
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
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 | ||
} | ||
} |
65 changes: 65 additions & 0 deletions
65
...nes/src/jvmTest/kotlin/com/github/michaelbull/result/coroutines/RunSuspendCatchingTest.kt
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
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 | ||
) | ||
} | ||
} |
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