Skip to content

Latest commit

 

History

History
250 lines (169 loc) · 7.46 KB

README.md

File metadata and controls

250 lines (169 loc) · 7.46 KB

e-kotlin

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.

Installation

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"

Contents

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.*

1. E

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

1.1. Creating an E

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}

1.2. Accessing Data in E

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

1.3. Converting E

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"}

2. EOr

EOr<A> is a container that can either be a Failure containing an E or Success containing a value of type A.

2.1. Creating an EOr

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}

2.2. Accessing Content of an EOr

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)

2.3. Working With EOr

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)

3. Codec, Decoder and Encoder

e-kotlin provides definitions for implementing decoding/encoding mechanism for E and EOr.

  • Decoder<I, O> is for building an O (output) from an I (input) while handling the decoding failures with E
  • Encoder<I, O> is for building an O (output) from an I (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 same T (target) type