This is the main implementation of e in Kotlin. It contains two main types E and EOr. It also contains definitions of decoding and encoding for these. Implementations of decoding and encoding are provided in separate modules.
If you use Gradle, add following to your project's build.gradle
:
dependencies {
implementation('dev.akif:e-kotlin:3.0.1')
}
If you use Maven, add following to your pom.xml
:
<dependencies>
<dependency>
<groupId>dev.akif</groupId>
<artifactId>e-kotlin</artifactId>
<version>3.0.1</version>
</dependency>
</dependencies>
If you use SBT, add following to your build.sbt
:
libraryDependencies += "dev.akif" % "e-kotlin" % "3.0.1"
Below are some details and examples of e-kotlin's content. For more, please check corresponding automated tests and e-kotlin's documentation.
To get started, add following import which will cover all your needs:
import e.kotlin.*
E (short for error) is the main error type used to represent an error. It is an immutable object with a fluent API. It contains following data about the error.
Field | Type | Description | Default Value |
---|---|---|---|
code | Int? |
A numeric code identifying the error | null |
name | String? |
A name identifying the error, usually enum-like | null |
message | String? |
A message about the error, usually human-readable | null |
causes | List<E> |
Underlying cause(s) of the error, if any | emptyList() |
data | Map<String, String> |
Arbitrary data related to the error as a key-value map | emptyMap() |
time | Long? |
Time when this error occurred as milliseconds since Epoch | null |
An instance of E can be created by
- directly creating an instance
- modifying an existing instance
- using static constructor methods
import e.kotlin.*
E.empty
// res1: E = {}
val notSoEmpty = E(1, "error-name", "Error Message")
// notSoEmpty: E = {"code":1,"name":"error-name","message":"Error Message"}
E.name("test-error").message("Test")
// res2: E = {"name":"test-error","message":"Test"}
val unexpectedError = E(message = "Unexpected Error", code = -1).now()
// unexpectedError: E = {"code":-1,"message":"Unexpected Error","time":1595936239845}
val errorWithDataAndCause = unexpectedError.data("action" to "test").cause(notSoEmpty)
// errorWithDataAndCause: E = {"code":-1,"message":"Unexpected Error","causes":[{"code":1,"name":"error-name","message":"Error Message"}],"data":{"action":"test"},"time":1595936239845}
Since E is a case class, you can directly access its fields. There are additional methods as well.
import e.kotlin.*
val databaseError = E.name("database").code(1)
// databaseError: E = {"code":1,"name":"database"}
val error = E(message = "Cannot get user!", name = "Unknown").cause(databaseError)
// error: E = {"name":"Unknown","message":"Cannot get user!","causes":[{"code":1,"name":"database"}]}
error.message
// res4: String? = "Cannot get user!"
error.code ?: error.causes.firstOrNull()?.let { it.code }
// res5: Int? = 1
error.hasData()
// res6: Boolean = false
You can convert your E into an Exception or an EOr.
import e.kotlin.*
val error = E.name("test").message("Test")
// error: E = {"name":"test","message":"Test"}
error.toException()
// res8: EException = {"name":"test","message":"Test"}
error.toEOr<Int>()
// res9: EOr<Int> = {"name":"test","message":"Test"}
EOr<A> is a container that can either be a Failure
containing an E or Success
containing a value of type A
.
An instance of EOr can be created by
- directly creating an instance of Failure or Success
- modifying an existing instance
- using static constructor methods
- constructing from an E or a value
- converting from other types by extension methods
import e.kotlin.*
EOr<Boolean>(E.code(0))
// res11: EOr<Boolean> = {"code":0}
EOr("hello")
// res12: EOr<String> = hello
EOr.Failure(E.code(1))
// res13: EOr.Failure = {"code":1}
EOr.Success("test")
// res14: EOr.Success<String> = test
E.message("test").toEOr<Int>()
// res15: EOr<Int> = {"message":"test"}
"hello".toEOr()
// res16: EOr<String> = hello
(true as Boolean?).toEOr(E.code(2))
// res17: EOr<Boolean> = true
(null as String?).toEOr(E.code(3))
// res18: EOr<String> = {"code":3}
The error or the value inside an EOr can be accessed safely.
import e.kotlin.*
val eor1 = E.message("test").toEOr<Int>()
// eor1: EOr<Int> = {"message":"test"}
val eor2 = "hello".toEOr()
// eor2: EOr<String> = hello
eor1.hasValue()
// res22: Boolean = false
eor1.error
// res23: E? = Some({"message":"test"})
eor2.hasError()
// res24: Boolean = false
eor2.value
// res25: String? = Some(hello)
There are many methods in EOr for modifying, composing, handling the error etc.
import e.kotlin.*
val eor1 = E.message("test").toEOr<Int>()
// eor1: EOr<Int> = {"message":"test"}
val eor2 = "hello".toEOr()
// eor2: EOr<String> = hello
eor2.map { it.toUpperCase() }
// res27: EOr<String> = HELLO
eor1.mapError { it.code(1) }
// res28: EOr<Int> = {"code":1,"message":"test"}
eor2.flatMap { s -> eor1 }
// res29: EOr<Int> = {"message":"test"}
eor2.filter { it.length < 3 }
// res30: EOr<String> = {"name":"filtered","message":"Condition does not hold!","data":{"value":"hello"}}
eor1.getOrElse(0)
// res31: Int = 0
eor1.fold({ e -> e.code }, { i -> i }) ?: 0
// res32: Int = 0
data class Person(val name: String, val age: Int)
fun makePerson(name: String, age: Int): EOr<Person> =
EOr(name).filter { it.isNotEmpty() }.flatMap { n ->
// custom error for filtering
EOr(age).filter({ it > 0 }) { invalidAge -> E.name("invalid-age").data("value" to invalidAge) }.map { a ->
Person(n, a)
}
}
makePerson("", 5)
// res33: EOr<Person> = {"name":"filtered","message":"Condition does not hold!","data":{"value":""}}
makePerson("Akif", -1)
// res34: EOr<Person> = {"name":"invalid-age","data":{"value":"-1"}}
makePerson("Akif", 29)
// res35: EOr<Person> = Person(Akif,29)
e-kotlin provides definitions for implementing decoding/encoding mechanism for E and EOr.
- Decoder<I, O> is for building an
O
(output) from anI
(input) while handling the decoding failures with E - Encoder<I, O> is for building an
O
(output) from anI
(input) - Codec<S, T> is a combination of Decoder and Encoder for an
S
(source) type where output of Encoder and input of Decoder is the sameT
(target) type