Skip to content

Commit

Permalink
1.8.0: test extensions, no more arrow deps
Browse files Browse the repository at this point in the history
  • Loading branch information
JesusMcCloud committed Oct 12, 2024
1 parent 2652065 commit 5bad8ca
Show file tree
Hide file tree
Showing 14 changed files with 336 additions and 42 deletions.
7 changes: 5 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,13 @@
- add `callsInPlace` contracts
- return result fon `onFailure` and `onSuccess`

## 1.7.1
## 1.8.0
- migrate to dokka 2 for documentation
- multi-module project setup
- introduce `kmmresult-test`, enabling
- introduce `kmmresult-test`, featuring
- `result should succeed`
- `result shouldNot succeed`
- `result shouldSucceedWith expectedValue`
- `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
28 changes: 20 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,14 @@ var intResult = KmmResult.success(3)
intResult = KmmResult.failure (NotImplementedError("Not Implemented"))
```

Also provides `map()` to transform success types while passing through errors and `mapFailure` to transform error types
while passing through success cases.
In addition, the more generic `fold()` is available for conveniently operating on both success and failure branches.


There really is not much more to say, except for two things:
- `KmmResult` sports `unwrap()` to conveniently map it to the `kotlin.Result` equivalent
- It provides a `Result.wrap()` extension function to go the opposite way.
Convenience functions:
- `map()` transforms success types while passing through errors
- `mapFailure` transforms error types while passing through success cases
- the more generic `fold()` is available for conveniently operating on both success and failure branches
- `KmmResult` sports `unwrap()` to conveniently map it to the `kotlin.Result` equivalent
- `Result.wrap()` extension function goes the opposite way
- `mapCatching()` does what you'd expect
- `wrapping()` allows for wrapping the failure branch's exception unless it is of the specified type

Refer to the [full documentation](https://a-sit-plus.github.io/kmmresult/) for more info.

Expand Down Expand Up @@ -83,6 +83,18 @@ func funWithKotlin() -> KmmResult<NSString> {
}
```

## Non-Fatal-Only `catching`
KmmResult comes with `catching`. This is a non-fatal-only-catching version of stdlib's `runCatching`, directly returning a `KmmResult`.
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.
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.


Happy folding!

## Contributing
Expand Down
23 changes: 22 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import org.gradle.kotlin.dsl.support.listFilesOrdered

plugins {base
kotlin("multiplatform") version "2.0.0" apply false
id("org.jetbrains.dokka")
id("io.github.gradle-nexus.publish-plugin") version "1.3.0"
}
Expand All @@ -10,9 +11,29 @@ version = artifactVersion

dependencies {
dokka(project(":kmmresult"))
dokka(project(":kmmresult-test"))
}

dokka {
val moduleDesc = File("$rootDir/dokka-tmp.md").also { it.createNewFile() }
val readme =
File("${rootDir}/README.md").readText()
moduleDesc.writeText("\n\n$readme")
moduleName.set("KmmResult")

dokkaPublicationDirectory.set(file("${rootDir}/docs"))
dokkaPublications.html {
includes.from(moduleDesc)
}
}

tasks.dokkaGenerate {
doLast {
rootDir.listFilesOrdered { it.extension.lowercase() == "png" || it.extension.lowercase() == "svg" }
.forEach { it.copyTo(File("$rootDir/docs/html/${it.name}"), overwrite = true) }

}
}

nexusPublishing {
repositories {
Expand Down
1 change: 1 addition & 0 deletions buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ repositories {

dependencies {
implementation("org.jetbrains.dokka:dokka-gradle-plugin:2.0.0-Beta")
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.20")
}
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.7.1
artifactVersion = 1.8.0
org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled
175 changes: 175 additions & 0 deletions kmmresult-test/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import io.gitlab.arturbosch.detekt.Detekt
import org.gradle.kotlin.dsl.support.listFilesOrdered
import java.lang.management.ManagementFactory
import java.net.URI

plugins {
kotlin("multiplatform")
id("maven-publish")
id("signing")
id("org.jetbrains.dokka")
id("org.jetbrains.kotlinx.kover") version "0.8.0"
id("io.gitlab.arturbosch.detekt") version "1.23.6"
}

val artifactVersion: String by extra
group = "at.asitplus"
version = artifactVersion

repositories {
mavenCentral()
}

val dokkaOutputDir = "$projectDir/docs"
dokka {
dokkaSourceSets {

named("commonMain") {
sourceLink {
val path = "${projectDir}/src/$name/kotlin"
println(path)
localDirectory.set(file(path))
remoteUrl.set(
URI("https://github.com/a-sit-plus/kmmresult/tree/main/src/$name/kotlin")
)
// Suffix which is used to append the line number to the URL. Use #L for GitHub
remoteLineSuffix.set("#L")
}
}
}
pluginsConfiguration.html {
footerMessage = "&copy; 2024 A-SIT Plus GmbH"
}
}
val deleteDokkaOutputDir by tasks.register<Delete>("deleteDokkaOutputDirectory") {
delete(dokkaOutputDir)
}
val javadocJar = tasks.register<Jar>("javadocJar") {
dependsOn(deleteDokkaOutputDir, tasks.dokkaGenerate)
archiveClassifier.set("javadoc")
from(dokkaOutputDir)
}

tasks.getByName("check") {
dependsOn("detektMetadataMain")
}


//first sign everything, then publish!
tasks.withType<AbstractPublishToMaven>() {
tasks.withType<Sign>().forEach {
dependsOn(it)
}
}

kotlin {

macosArm64()
macosX64()
tvosArm64()
tvosX64()
tvosSimulatorArm64()
iosX64()
iosArm64()
iosSimulatorArm64()


jvmToolchain(11)
jvm {
compilations.all {
kotlinOptions {
freeCompilerArgs = listOf(
"-Xjsr305=strict"
)
}
}
withJava() //for Java Interop tests
}

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

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

tasks.withType<Detekt>().configureEach {
reports {
xml.required.set(true)
html.required.set(false)
txt.required.set(false)
sarif.required.set(true)
md.required.set(true)
}
}
}




dependencies {
detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.23.6")
}

repositories {
mavenCentral()
}

publishing {
publications {
withType<MavenPublication> {
artifact(javadocJar)
pom {
name.set("KmmResult Test")
description.set("Kotest helperrs for KmmResult")
url.set("https://github.com/a-sit-plus/kmmresult")
licenses {
license {
name.set("The Apache License, Version 2.0")
url.set("http://www.apache.org/licenses/LICENSE-2.0.txt")
}
}
developers {
developer {
id.set("JesusMcCloud")
name.set("Bernd Prünster")
email.set("[email protected]")
}
developer {
id.set("nodh")
name.set("Christian Kollmann")
email.set("[email protected]")
}
}
scm {
connection.set("scm:git:[email protected]:a-sit-plus/kmmresult.git")
developerConnection.set("scm:git:[email protected]:a-sit-plus/kmmresult.git")
url.set("https://github.com/a-sit-plus/kmmresult")
}
}
}
}
repositories {
mavenLocal() {
signing.isRequired = false
}
}
}


signing {
val signingKeyId: String? by project
val signingKey: String? by project
val signingPassword: String? by project
useInMemoryPgpKeys(signingKeyId, signingKey, signingPassword)
sign(publishing.publications)
}

40 changes: 40 additions & 0 deletions kmmresult-test/src/commonMain/kotlin/at/asitplus/TestExtensions.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright 2021 - 2023 A-SIT Plus GmbH. Obviously inspired and partially copy-pasted from kotlin.Result.
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
*/
package at.asitplus

import io.kotest.matchers.Matcher
import io.kotest.matchers.MatcherResult
import io.kotest.matchers.should
import io.kotest.matchers.shouldBe

/**
* Shorthand for `getOrThrow() shouldBe expected`
*/
infix fun <T> KmmResult<T>.shouldSucceedWith(expected: T): T = getOrThrow() shouldBe expected

/**
* [KmmResult] matcher. Use as follows: `okResult should succeed`, `errResult shouldNot succeed`
*/
@Suppress("ClassNaming")
object succeed : Matcher<KmmResult<*>> {
override fun test(value: KmmResult<*>) =
MatcherResult(
value.isSuccess,
failureMessageFn = {
"Should have succeeded, but failed:\n${
value.exceptionOrNull()!!.stackTraceToString()
}"
},
negatedFailureMessageFn = { "Should have failed, but succeeded with ${value.getOrNull()!!}" }
)
}

/**
* Asserts that this KmmResult should succeed and returns the contained value
*/
fun <T> KmmResult<T>.shouldSucceed(): T {
this should succeed
return getOrThrow()
}
Loading

0 comments on commit 5bad8ca

Please sign in to comment.