From 279eced7ef32c8989f228f0f94ce7591cca45b55 Mon Sep 17 00:00:00 2001 From: Martin Wittlinger Date: Tue, 19 Dec 2023 15:16:12 +0100 Subject: [PATCH] feat: Add result/error return type (#1369) --- .../laughing_train/commons/result/Error.java | 99 +++++++++++++++++++ .../laughing_train/commons/result/Ok.java | 89 +++++++++++++++++ .../laughing_train/commons/result/Result.java | 77 +++++++++++++++ 3 files changed, 265 insertions(+) create mode 100644 commons/src/main/java/io/github/martinwitt/laughing_train/commons/result/Error.java create mode 100644 commons/src/main/java/io/github/martinwitt/laughing_train/commons/result/Ok.java create mode 100644 commons/src/main/java/io/github/martinwitt/laughing_train/commons/result/Result.java diff --git a/commons/src/main/java/io/github/martinwitt/laughing_train/commons/result/Error.java b/commons/src/main/java/io/github/martinwitt/laughing_train/commons/result/Error.java new file mode 100644 index 000000000..eae2416d1 --- /dev/null +++ b/commons/src/main/java/io/github/martinwitt/laughing_train/commons/result/Error.java @@ -0,0 +1,99 @@ +package io.github.martinwitt.laughing_train.commons.result; + +import static java.util.Objects.requireNonNull; + +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +class Error implements Result { + + private final Throwable throwable; + + Error(Throwable throwable) { + this.throwable = throwable; + } + + @SuppressWarnings("unchecked") + private T propagate(Throwable throwable) throws E { + throw (E) throwable; + } + + @Override + public boolean isOk() { + return false; + } + + @Override + public void ifOk(Consumer consumer) { + // Do nothing when trying to consume the value of an Error result. + } + + @Override + public boolean isError() { + return true; + } + + @Override + public void ifError(Consumer consumer) { + requireNonNull(consumer, "The error consumer cannot be null"); + consumer.accept(throwable); + } + + @Override + public Result switchIfError(Function> fallbackMethod) { + requireNonNull(fallbackMethod, "The fallback method cannot be null"); + return fallbackMethod.apply(throwable); + } + + @Override + public Result map(Function mapper) { + return new Error<>(throwable); + } + + @Override + public Result flatMap(Function> mapper) { + return new Error<>(throwable); + } + + @Override + public Result mapError(Function mapper) { + requireNonNull(mapper, "The error mapper cannot be null"); + return new Error<>(mapper.apply(throwable)); + } + + @Override + public T get() { + return propagate(throwable); + } + + @Override + public T getOrElse(Supplier supplier) { + requireNonNull(supplier); + return supplier.get(); + } + + @Override + public Throwable getError() { + return throwable; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (null == o || getClass() != o.getClass()) return false; + Error error = (Error) o; + return Objects.equals(throwable, error.throwable); + } + + @Override + public int hashCode() { + return Objects.hash(throwable); + } + + @Override + public String toString() { + return "Error{" + "throwable=" + throwable + '}'; + } +} diff --git a/commons/src/main/java/io/github/martinwitt/laughing_train/commons/result/Ok.java b/commons/src/main/java/io/github/martinwitt/laughing_train/commons/result/Ok.java new file mode 100644 index 000000000..add3f81ec --- /dev/null +++ b/commons/src/main/java/io/github/martinwitt/laughing_train/commons/result/Ok.java @@ -0,0 +1,89 @@ +package io.github.martinwitt.laughing_train.commons.result; + +import static java.util.Objects.requireNonNull; + +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +class Ok implements Result { + + private final T value; + + Ok(T value) { + this.value = value; + } + + @Override + public boolean isOk() { + return true; + } + + @Override + public void ifOk(Consumer consumer) { + requireNonNull(consumer, "The value consumer cannot be null"); + consumer.accept(value); + } + + @Override + public boolean isError() { + return false; + } + + @Override + public void ifError(Consumer consumer) { + // Do nothing when trying to consume the error of an Ok result. + } + + @Override + public Result switchIfError(Function> fallbackMethod) { + return new Ok<>(value); + } + + @Override + public Result map(Function mapper) { + requireNonNull(mapper, "The value mapper cannot be null"); + return new Ok<>(mapper.apply(value)); + } + + @Override + public Result flatMap(Function> mapper) { + requireNonNull(mapper, "The value flat-mapper cannot be null"); + return mapper.apply(value); + } + + @Override + public Result mapError(Function mapper) { + return new Ok<>(value); + } + + @Override + public T get() { + return value; + } + + @Override + public T getOrElse(Supplier supplier) { + return value; + } + + @Override + public Throwable getError() { + throw new NoSuchElementException("Result contains a value: " + value.toString()); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (null == obj || getClass() != obj.getClass()) return false; + Ok ok = (Ok) obj; + return Objects.equals(value, ok.value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } +} diff --git a/commons/src/main/java/io/github/martinwitt/laughing_train/commons/result/Result.java b/commons/src/main/java/io/github/martinwitt/laughing_train/commons/result/Result.java new file mode 100644 index 000000000..d6759ef1d --- /dev/null +++ b/commons/src/main/java/io/github/martinwitt/laughing_train/commons/result/Result.java @@ -0,0 +1,77 @@ +package io.github.martinwitt.laughing_train.commons.result; + +import static java.util.Objects.nonNull; +import static java.util.Objects.requireNonNull; + +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +public interface Result { + + static Result ok(T value) { + requireNonNull(value, "The value of a Result cannot be null"); + return new Ok<>(value); + } + + static Result error(E throwable) { + requireNonNull(throwable, "The error of a Result cannot be null"); + return new Error<>(throwable); + } + + static Result of(Supplier supplier) { + requireNonNull(supplier, "The value supplier cannot be null"); + + try { + return ok(supplier.get()); + } catch (Exception error) { + return error(error); + } + } + + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") + static Result of(Optional optional) { + requireNonNull(optional, "The optional value cannot be null"); + + return optional + .map(Result::ok) + .orElseGet( + () -> + error(new NoSuchElementException("No value present when unwrapping the optional"))); + } + + static Result ofNullable(T value) { + return ofNullable( + value, () -> new NullPointerException("The result was initialized with a null value")); + } + + static Result ofNullable(T value, Supplier errorSupplier) { + requireNonNull(errorSupplier, "The error supplier cannot be null"); + + return nonNull(value) ? ok(value) : error(errorSupplier.get()); + } + + boolean isOk(); + + void ifOk(Consumer consumer); + + boolean isError(); + + void ifError(Consumer consumer); + + Result switchIfError(Function> fallbackMethod); + + Result map(Function mapper); + + Result flatMap(Function> mapper); + + Result mapError(Function mapper); + + T get(); + + T getOrElse(Supplier supplier); + + Throwable getError(); +}