Skip to content

Latest commit

 

History

History
277 lines (193 loc) · 8.02 KB

README.md

File metadata and controls

277 lines (193 loc) · 8.02 KB

e-java

This is the main implementation of e in Java. 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-java:@VERSION@')
}

If you use Maven, add following to your pom.xml:

<dependencies>
  <dependency>
    <groupId>dev.akif</groupId>
    <artifactId>e-java</artifactId>
    <version>@VERSION@</version>
  </dependency>
</dependencies>

If you use SBT, add following to your build.sbt:

libraryDependencies += "dev.akif" % "e-java" % "@VERSION@"

Contents

Below are some details and examples of e-java's content. For more, please check corresponding automated tests and e-java's documentation.

To get started, add following import which will cover all your needs:

import e.java.*;

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 Optional<Integer> A numeric code identifying the error Optional.empty()
name Optional<String> A name identifying the error, usually enum-like Optional.empty()
message Optional<String> A message about the error, usually human-readable Optional.empty()
causes List<E> Underlying cause(s) of the error, if any new LinkedList<>()
data Map<String, String> Arbitrary data related to the error as a key-value map new LinkedHashMap<>()
time Optional<Long> Time when this error occurred as milliseconds since Epoch Optional.empty()

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

E empty = E.empty;
// {}

E notSoEmpty = new E(1, "error-name", "Error Message", null, null, null);
// {"code":1,"name":"error-name","message":"Error Message"}

E e1 = E.fromName("test-error").message("Test");
// {"name":"test-error","message":"Test"}

E unexpectedError = E.fromMessage("Unexpected Error").code(-1).now();
// {"code":-1,"message":"Unexpected Error","time":1595936239845}

E errorWithDataAndCause = unexpectedError.data("action", "test").cause(notSoEmpty);
// {"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 restricted POJO, you can access its fields by accessor methods. There are additional methods as well.

import e.java.*;

E databaseError = E.fromName("database").code(1);
// {"code":1,"name":"database"}

E error = E.fromMessage("Cannot get user!").name("Unknown").cause(databaseError);
// {"name":"Unknown","message":"Cannot get user!","causes":[{"code":1,"name":"database"}]}

Optional<String> message = error.message();
// "Cannot get user!"

Integer code = error.code().orElseGet(
  () -> error.causes
             .stream()
             .findFirst()
             .flatMap(E::code)
);
// 1

boolean hasData = error.hasData();
// false

1.3. Converting E

You can convert your E into an Exception or an EOr.

import e.java.*;

E error = E.fromName("test").message("Test");
// {"name":"test","message":"Test"}

EException ee = error.toException();
// {"name":"test","message":"Test"}

EOr<Integer> eor = error.toEOr<Integer>();
// {"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 utility methods
import e.java.*;

EOr<Boolean> eor1 = new EOr<>(E.fromCode(0));
// {"code":0}

EOr<String> eor2 = new EOr<>("hello");
// hello

EOr<Boolean> eor3 = new EOr.Failure<>(E.fromCode(1));
// {"code":1}

EOr<String> eor4 = new EOr.Success<>("test");
// test

EOr<Integer> eor5 = E.fromMessage("test").toEOr<>();
// {"message":"test"}

EOr<String> eor6 = EOr.from("hello");
// hello

EOr<String> eor7 = EOr.from(E.fromCode(2));
// {"code":2}

EOr<String> eor8 = EOr.fromNullable(null, () -> E.fromCode(3));
// {"code":3}

EOr<String> eor9 = EOr.fromNullable("test", () -> E.fromCode(3));
// test

2.2. Accessing Content of an EOr

The error or the value inside an EOr can be accessed safely.

import e.java.*;

EOr<Integer> eor1 = E.fromMessage("test").toEOr<>();
// {"message":"test"}

EOr<String> eor2 = EOr.from("hello");
// eor2: EOr<String> = hello

boolean hasValue = eor1.hasValue();
// false

Optional<E> e = eor1.error();
// {"message":"test"}

boolean hasError = eor2.hasError();
// false

Optional<String> value = eor2.value();
// hello

2.3. Working With EOr

There are many methods in EOr for modifying, composing, handling the error etc.

import e.java.*;

EOr<Integer> eor1 = E.fromMessage("test").toEOr<>();
// {"message":"test"}

EOr<String> eor2 = EOr.from("hello");
// hello

EOr<String> eor3 = eor2.map(String::toUpperCase);
// res27: EOr<String> = HELLO

EOr<Integer> eor4 = eor1.mapError(e -> e.code(1));
// {"code":1,"message":"test"}

EOr<Integer> eor5 = eor2.flatMap(s -> eor1);
// {"message":"test"}

EOr<String> eor6 = eor2.filter(s -> s.length() < 3);
// {"name":"filtered","message":"Condition does not hold!","data":{"value":"hello"}}

Integer i1 = eor1.getOrElse(0);
// 0

Integer i2 = eor1.fold(e -> e.code().orElse(0), i -> i);
// 0

class Person {
    public String name;
    public int age;
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    @Override public String toString() {
        return "Person(" + name + "," + age + ")";
    }
}

EOr<Person> makePerson(String name, int age) {
    return new EOr(name)
        .filter(n -> n.length() > 0)
        .flatMap(n ->
            new EOr(age)
                .filter(
                    a -> a > 0,
                    // custom error for filtering
                    invalidAge -> E.fromName("invalid-age")
                                   .data("value", invalidAge)
                )
                .map(a -> new Person(n, a))
        );
  }

EOr<Person> p1 = makePerson("", 5);
// {"name":"filtered","message":"Condition does not hold!","data":{"value":""}}

EOr<Person> p2 = makePerson("Akif", -1);
// {"name":"invalid-age","data":{"value":"-1"}}

EOr<Person> p3 = makePerson("Akif", 29);
// Person(Akif,29)

3. Codec, Decoder and Encoder

e-java 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