KResult
KResult and its inheritors Success and Failure provide an opinionated, functional result type. While Kotlin has its own kotlin.Result already, the intent of KResult is to be more functional, usable and better integrated.
Creating Results
Results can be created by constructing Success or Failure directly, but there is a variety of helpers and third party types to create results from:
import io.kresult.core.KResult
import io.kresult.core.asKResult
import io.kresult.core.asSuccess
import io.kotest.matchers.shouldBe
fun test() {
// by instance
KResult.Success("test")
.isSuccess() shouldBe true
// by extension function
"test".asSuccess()
.isSuccess() shouldBe true
// by catching exceptions
KResult
.catch {
throw RuntimeException("throws")
}
.isSuccess() shouldBe false
// from nullable
KResult
.fromNullable(null) {
RuntimeException("Value can not be null")
}
.isSuccess() shouldBe false
// from Kotlin Result
Result.success("test")
.asKResult()
.isSuccess() shouldBe true
}
From Extensions
There are more result builders with extensions, e.g. from kresult-arrow
:
import io.kresult.arrow.toKResult
import arrow.core.Either
fun test() {
Either.Right("test")
.toKResult()
}
Mapping & Transformation
Results can be transformed in several ways. At the core, KResult implements functional mapping with flatMap, map, flatten, filter, etc. Some of these are extension functions that need to be imported.:
import io.kresult.core.KResult
import io.kresult.core.filter
import io.kresult.core.flatMap
import io.kresult.core.flatten
import io.kotest.matchers.shouldBe
fun test() {
// map
KResult.Success(3)
.map { it - 1 }
.getOrNull() shouldBe 2
// flatMap
KResult.Success("some-p4ss!")
.flatMap {
if (it.length 8) {
KResult.Success(it)
} else {
KResult.Failure(RuntimeException("Password is too short"))
}
} shouldBe KResult.Success("some-p4ss!")
// filter
KResult.Success("some-p4ss!")
.filter(
{ it.isNotBlank() },
{ RuntimeException("String is empty") }
) shouldBe KResult.Success("some-p4ss!")
// flatten
KResult.Success(KResult.Success(2))
.flatten() shouldBe KResult.Success(2)
}
Getting values or errors
As a KResult is always either a Success or a Failure, a simple, kotlin-native when
expression or a destructuring declaration can be used to get the Success.value or Failure.error:
import io.kresult.core.KResult
import io.kresult.core.KResult.Failure
import io.kresult.core.KResult.Success
import io.kotest.matchers.shouldBe
fun test() {
val res: KResult<String, Int> = Success(2)
// when expression
when (res) {
is Success -> "success: ${res.value}"
is Failure -> "failure: ${res.error}"
} shouldBe "success: 2"
// destructuring call
val (failureValue, successValue) = res
failureValue shouldBe null
successValue shouldBe 2
}
Convenience Methods
Additionally, convenience methods like fold, getOrNull, getOrElse, or getOrThrow for results that have a Throwable on failure side.
import io.kresult.core.KResult
import io.kresult.core.getOrElse
import io.kresult.core.getOrThrow
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.matchers.shouldBe
fun test() {
// fold
KResult.Success(2).fold(
{ "failure: $it" },
{ "success: $it" }
) shouldBe "success: 2"
// getOrNull
KResult.Success(2).getOrNull() shouldBe 2
KResult.Failure("error").getOrNull() shouldBe null
// getOrElse
KResult.Success(2).getOrElse { -1 } shouldBe 2
KResult.Failure("error").getOrElse { -1 } shouldBe -1
// getOrThrow
KResult.Success(2).getOrThrow() shouldBe 2
shouldThrow<RuntimeException> {
KResult.Failure(RuntimeException("test")).getOrThrow()
}
}
Inheritors
Types
Functions
Failure component for destructuring declaration
Success component for destructuring declaration
Runs the Failure.error, if the KResult is a failure or null
otherwise
Runs the Success.value, if the KResult is a success or null
otherwise
If a KResult has a Throwable on failure side, this either returns the Success.value or throws the Failure.error