Skip to content

Commit

Permalink
1.9.0: More plain Result features
Browse files Browse the repository at this point in the history
  • Loading branch information
JesusMcCloud committed Oct 25, 2024
1 parent 78acabc commit e4b47de
Show file tree
Hide file tree
Showing 9 changed files with 83 additions and 27 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/build-jvm.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
name: Build JVM/JS/iOS/macOS/tvOS/MinGW/Linux
name: Build JVM/JS/iOS/macOS/tvOS/MinGW/wasmJS/wasmWasi/Linux
on: [push]
jobs:
build:
runs-on: ubuntu-latest
runs-on: macos-latest
steps:
- name: Checkout
uses: actions/checkout@v3
Expand Down
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,11 @@
- `result.shouldSucceed()` returning the contained value
- remove Arrow dependency and import arrow's list of Fatal exceptions directly into our code
- Introduce `Result.nonFatalOrThrow` to mimic KmmResult's non-fatal-only behaviour, but without the object instantiation overhead
- Introduce `carchingUnwrapped`, which mimics KmmResult's non-fatal-only behaviour, but without the object instantiation overhead
- Introduce `carchingUnwrapped`, which mimics KmmResult's non-fatal-only behaviour, but without the object instantiation overhead

## 1.9.0
- add WasmJS target
- add WasmWasi target (not or KmmResult-test, as Kotest does not support WASI yet)
- add `wrappingPlain`, which works just like `wrapping` but on a `Result` rather than a `KmmResult` to avoid instantiation overhead
- rename `catchingUnwrapped` to `catchingPlain` to avoid confusions with `wrapping` but keep the old name as deprecated alternative
- add `nonFatalOrThrow`
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ is *not* a value class (a sealed `Either` type also does not interop well with S

`KmmResult` comes to the rescue! → [Full documentation](https://a-sit-plus.github.io/KmmResult/).


## Using in your Projects

This library is available at maven central.
Expand Down Expand Up @@ -88,12 +87,12 @@ KmmResult comes with `catching`. This is a non-fatal-only-catching version of st
It re-throws any fatal exceptions, such as `OutOfMemoryError`. The underlying logic is borrowed from [Arrow's](https://arrow-kt.io)'s
[`nonFatalOrThrow`](https://apidocs.arrow-kt.io/arrow-core/arrow.core/non-fatal-or-throw.html).

The only downside of `catching` is that it incurs instatiation overhead, because it creates a `KmmResult` instance.
The only downside of `catching` is that it incurs instantiation overhead, because it creates a `KmmResult` instance.
Internally, though, only the behaviour is important, not Swift interop. Hence, you don't care for a `KmmResult` and you
certainly don't care for the cost of instantiating an object. Here, the `Result.nonFatalOrThrow()` extension shipped with KmmResult
comes to the rescue. It does exactly what the name suggest: It re-throws any fatal exception and leaved the `Result` object
untouched otherwise.

untouched otherwise.
In addition, there's `catchingPlain` which directly returns an stdlib `Result`

Happy folding!

Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ kotlin.mpp.enableCInteropCommonization=true
kotlin.mpp.stability.nowarn=true
kotlin.native.ignoreDisabledTargets=true

artifactVersion = 1.8.0
artifactVersion = 1.9.0
org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled
15 changes: 9 additions & 6 deletions kmmresult-test/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import io.gitlab.arturbosch.detekt.Detekt
import org.gradle.kotlin.dsl.support.listFilesOrdered
import java.lang.management.ManagementFactory
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
import java.net.URI

plugins {
Expand Down Expand Up @@ -86,18 +85,22 @@ kotlin {
withJava() //for Java Interop tests
}

js(IR) {
browser { testTask { enabled = false } }
nodejs()
listOf(
js(IR).apply { browser { testTask { enabled = false } } },
@OptIn(ExperimentalWasmDsl::class)
wasmJs().apply { browser { testTask { enabled = false } } }
).forEach {
it.nodejs()
}

linuxX64()
linuxArm64()
mingwX64()

sourceSets {
commonMain.dependencies {
implementation(project(":kmmresult"))
api("io.kotest:kotest-assertions-core:5.9.1")
api("io.kotest:kotest-assertions-core:6.0.0.M1")
}
}

Expand Down
14 changes: 10 additions & 4 deletions kmmresult/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import io.gitlab.arturbosch.detekt.Detekt
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
import java.net.URI

plugins {
Expand Down Expand Up @@ -78,7 +79,6 @@ kotlin {
it.binaries.framework {
baseName = "KmmResult"
binaryOption("bundleId", "at.asitplus.KmmResult")
embedBitcode("bitcode")
xcf.add(this)
isStatic = true
}
Expand All @@ -96,9 +96,15 @@ kotlin {
withJava() //for Java Interop tests
}

js(IR) {
browser { testTask { enabled = false } }
nodejs()

listOf(
js(IR).apply { browser { testTask { enabled = false } } },
@OptIn(ExperimentalWasmDsl::class)
wasmJs().apply { browser { testTask { enabled = false } } },
@OptIn(ExperimentalWasmDsl::class)
wasmWasi()
).forEach {
it.nodejs()
}

linuxX64()
Expand Down
36 changes: 30 additions & 6 deletions kmmresult/src/commonMain/kotlin/at/asitplus/KmmResult.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

package at.asitplus

import at.asitplus.KmmResult.Companion.wrap
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
import kotlin.experimental.ExperimentalObjCName
Expand Down Expand Up @@ -311,16 +312,26 @@ inline fun <T, R> T.catching(block: T.() -> R): KmmResult<R> {
* Usage: `wrapping(asA = ::ThrowableType) { block }`.
*/
@Suppress("TooGenericExceptionCaught")
inline fun <reified E : Throwable, R> wrapping(asA: (String?, Throwable) -> E, block: () -> R): KmmResult<R> {
inline fun <reified E : Throwable, R> wrapping(asA: (String?, Throwable) -> E, block: () -> R): KmmResult<R> =
wrappingPlain(asA, block).wrap()

/**
* Runs the specified function [block], returning a [Result].
* Any non-fatal exception will be wrapped as the specified exception, unless it is already the specified type.
*
* Usage: `wrapping(asA = ::ThrowableType) { block }`.
*/
@Suppress("TooGenericExceptionCaught")
inline fun <reified E : Throwable, R> wrappingPlain(asA: (String?, Throwable) -> E, block: () -> R): Result<R> {
contract {
callsInPlace(asA, InvocationKind.AT_MOST_ONCE)
// not EXACTLY_ONCE, because inside a try block!
callsInPlace(block, InvocationKind.AT_MOST_ONCE)
}
return try {
KmmResult.success(block())
Result.success(block())
} catch (e: Throwable) {
KmmResult.failure(
Result.failure(
when (e.nonFatalOrThrow()) {
is E -> e
else -> asA(e.message, e)
Expand All @@ -329,23 +340,36 @@ inline fun <reified E : Throwable, R> wrapping(asA: (String?, Throwable) -> E, b
}
}


/**
* Runs the specified function [block] with `this` as its receiver, returning a [KmmResult].
* Any non-fatal exception will be wrapped as the specified exception, unless it is already the specified type.
*
* Usage: `wrapping(asA = ::ThrowableType) { block }`.
*/
@Suppress("TooGenericExceptionCaught")
inline fun <reified E : Throwable, T, R> T.wrapping(asA: (String?, Throwable) -> E, block: T.() -> R): KmmResult<R> {
inline fun <reified E : Throwable, T, R> T.wrapping(
asA: (String?, Throwable) -> E,
block: T.() -> R
): KmmResult<R> = this.wrappingPlain(asA, block).wrap()

/**
* Runs the specified function [block] with `this` as its receiver, returning a [KmmResult].
* Any non-fatal exception will be wrapped as the specified exception, unless it is already the specified type.
*
* Usage: `wrapping(asA = ::ThrowableType) { block }`.
*/
@Suppress("TooGenericExceptionCaught")
inline fun <reified E : Throwable, T, R> T.wrappingPlain(asA: (String?, Throwable) -> E, block: T.() -> R): Result<R> {
contract {
callsInPlace(asA, InvocationKind.AT_MOST_ONCE)
// not EXACTLY_ONCE, because inside a try block!
callsInPlace(block, InvocationKind.AT_MOST_ONCE)
}
return try {
KmmResult.success(block())
Result.success(block())
} catch (e: Throwable) {
KmmResult.failure(
Result.failure(
when (e.nonFatalOrThrow()) {
is E -> e
else -> asA(e.message, e)
Expand Down
12 changes: 10 additions & 2 deletions kmmresult/src/commonMain/kotlin/at/asitplus/NonFatal.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ inline fun <T> Result<T>.nonFatalOrThrow(): Result<T> = this.onFailure { it.nonF
* logic to avoid a dependency on Arrow for a single function.
*/
@Suppress("NOTHING_TO_INLINE")
inline fun <T> catchingUnwrapped(block: () -> T): Result<T> = runCatching(block).nonFatalOrThrow()
inline fun <T> catchingPlain(block: () -> T): Result<T> = runCatching(block).nonFatalOrThrow()

@Suppress("NOTHING_TO_INLINE")
@Deprecated("This function was badly named", ReplaceWith("catchingPlain(block)"))
inline fun <T> catchingUnwrapped(block: () -> T): Result<T> = catchingPlain(block)

/**
* Non-fatal-only-catching version of stdlib's [runCatching] (calling the specified function [block] with `this` value
Expand All @@ -34,4 +38,8 @@ inline fun <T> catchingUnwrapped(block: () -> T): Result<T> = runCatching(block)
* logic to avoid a dependency on Arrow for a single function.
*/
@Suppress("NOTHING_TO_INLINE")
inline fun <T, R> T.catchingUnwrapped(block: T.() -> R): Result<R> = runCatching(block).nonFatalOrThrow()
inline fun <T, R> T.catchingPlain(block: T.() -> R): Result<R> = runCatching(block).nonFatalOrThrow()

@Suppress("NOTHING_TO_INLINE")
@Deprecated("This function was badly named", ReplaceWith("catchingPlain { }"))
inline fun <T, R> T.catchingUnwrapped(block: T.() -> R): Result<R> = catchingPlain(block)
11 changes: 10 additions & 1 deletion kmmresult/src/commonTest/kotlin/KmmResultTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -228,9 +228,18 @@ class KmmResultTest {
assertFailsWith(CancellationException::class) {
catching { throw CancellationException() }
}
assertFailsWith(CancellationException::class) {
"Receiver".catching { throw CancellationException() }
}

assertFailsWith(CancellationException::class) {
catchingPlain { throw CancellationException() }
}
assertFailsWith(CancellationException::class) {
"Receiver".catchingPlain { throw CancellationException() }
}

runCatching { throw IndexOutOfBoundsException() }.nonFatalOrThrow()
catching { throw IndexOutOfBoundsException() }

}
}

0 comments on commit e4b47de

Please sign in to comment.