KResult
An opinionated, functional Result type for Kotlin
KResult is a functional library that provides an opinionated and feature-rich extension / alternative to Kotlin's Result<T>
type. While the core library is as lean and dependency-free as possible, seamless integrations with common frameworks are provided via integration packages or plugins. KResult is heavily inspired by Scala's Try[T]
type, Rust's Result<T, E>
and Arrow's Either<A, B>
.
Features
-
Functional result type KResult to express Success or Failure of any action
-
Rich functional transformation functions, e.g. map / flatMap, filter, fold, merge, combine
-
Failure-side mapping and recovery, e.g. apFailure, flatMapFailure, getOrDefault
-
Nullable and empty success handling, see fromNullable / Success.unit
-
Smooth integration with Kotlin's Result
-
Java interoperability, see kresult-java
-
Provides RFC7807 compliant HTTP Problem Detail support using kresult-problem
-
Integration with various frameworks and libraries, e.g. Arrow
-
100% Kotlin, 100% open source
Find full documentation on kresult.io.
Usage
KResult is hosted on Maven Central. Use your favorite build tool to add a dependency. Replace VERSION
with the current version number:
Using Gradle Kotlin DSL:
dependencies {
implementation("io.kresult:kresult-core:VERSION")
}
Using Gradle Groovy DSL:
implementation group: 'io.kresult', name: 'kresult-core', version: 'VERSION'
Using Maven:
<dependency>
<groupId>io.kresult</groupId>
<artifactId>kresult-core</artifactId>
<version>VERSION</version>
</dependency>
Core Modules
Module | Description | Link |
---|---|---|
kresult-core |
Core module providing the functional KResult type as well as tooling and core functionality around it |
Docs / Maven |
kresult-java |
Java interoperability for KResult and its transformations. |
Docs / Maven |
kresult-problem |
RFC7807 compliant Problem Details JSON support for KResult. | Docs / Maven |
Integrations
Module | Description | Link |
---|---|---|
kresult-arrow |
Supports seamless integration with types of the Arrow functional programming library. | Docs / Maven |
Hello, World
import io.kresult.core.KResult
import io.kresult.core.filter
fun test() {
val greeting = KResult.Success("World")
val res: KResult<String, String> = greeting
// transform result, if successful
.map {
"""Hello, $it!"""
}
// transform to IllegalArgumentException on failure side, if value is blank
.filter(IllegalArgumentException("Greeting should not be blank")) {
it.isNotBlank()
}
// execute side-effect, if successful
.onSuccess {
print("log: $it is successful")
}
// transform failure from Exception to a string
.mapFailure {
"${it.javaClass}: ${it.localizedMessage}"
}
// fold result to string
res.fold(
{ error -> "Result failed with error: $error" },
{ value -> "Result was successful with value: $value" },
)
}
While strings are simple for showcasing, a more real world solution would have strongly typed result values. One common practice is to encode:
-
Success
side value as a product type (data class
in Kotlin) -
Failure
side value as a sum type / tagged union (enum
orsealed class
in Kotlin)
The following example demonstrates a very simplified case, where a greeting message is validated for correctness, and the resulting KResult<Failure, Greeting>
is folded to an exemplary Response
object again. In a more practical setup, that Response
and the mechanics to produce it would of course be provided by a framework integration:
import io.kresult.core.*
import io.kotest.matchers.shouldBe
// Product to encode success
data class Greeting(val name: String)
// SUm to encode a failure
sealed class Failure(val msg: String) {
// Indicates that Failure was caused by invalid input
interface InputValidationProblem
data object BlankName : Failure("Name should not be blank"), InputValidationProblem
data object IllegalCharacters : Failure("Name contains illegal characters"), InputValidationProblem
data object Unknown : Failure("An unknown error occured")
}
// Product to encode a response
data class Response(val status: Int, val content: String)
fun test() {
val greeting: KResult<Failure, Greeting> = Greeting("World")
// Wraps any type into a KResult.Success
.asSuccess()
// Filter, transforming to failure if greeting name is blank
.filter(Failure.BlankName) {
it.name.isNotBlank()
}
.filter(Failure.IllegalCharacters) {
!it.name.contains("/")
}
val response: Response = greeting
// Transforms Success side to a Response
.map {
Response(status = 200, content = """Hello, ${it.name}!""")
}
// Transforms Failure side to a Response
.mapFailure { failure ->
when (failure) {
is Failure.InputValidationProblem ->
Response(status = 400, content = failure.msg)
else ->
Response(status = 500, content = failure.msg)
}
}
// When Success and Failure side are of the same type, we can merge them (long syntax for fold)
.merge()
response.status shouldBe 200
response.content shouldBe "Hello, World!"
}
License
Copyright 2024 kresult.io authors
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
All modules:
The integration integration module provides functionality to integrate with the famous FP library for Kotlin, which was an inspiration for this project as well.
The core module contains all basic definitions and functionality, commonly used in KResult. Virtually all other modules depend on this either implicitly or explicitly.
Provides interoperability helpers to integrate with Java libraries.
Provides RFC7807 compliant Problem JSON support