diff --git a/src/main/java/com/johnnywey/flipside/failable/Failable.java b/src/main/java/com/johnnywey/flipside/failable/Failable.java index 0fee2f3..5086b8d 100644 --- a/src/main/java/com/johnnywey/flipside/failable/Failable.java +++ b/src/main/java/com/johnnywey/flipside/failable/Failable.java @@ -2,6 +2,11 @@ import com.johnnywey.flipside.marker.DidItWork; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; + /** * This is a simple wrapper for communicating failure data. It is loosely based on a Scala Option but provides * a common interface for failure cases that can be easily mapped to things like HTTP codes, etc. @@ -12,4 +17,27 @@ public interface Failable { Fail getReason(); String getDetail(); DidItWork toDidItWork(); + default void ifSuccess(Consumer consumer) { + if (isSuccess() && get() != null) { + consumer.accept(get()); + } else { + throw new FailableException(this); + } + } + default void ifFailed(Consumer consumer) { + if (!isSuccess()) { + consumer.accept(getReason()); + } + } + Failable filter(Predicate predicate); + Failable map(Function mapper); + Failable flatMap(Function> mapper); + default T orElse(T other) { + return get() != null ? get() : other; + } + default T orElseGet(Supplier other) { + return get() != null ? get() : other.get(); + } + + } diff --git a/src/main/java/com/johnnywey/flipside/failable/Failed.java b/src/main/java/com/johnnywey/flipside/failable/Failed.java index 3d983f1..92e3f59 100644 --- a/src/main/java/com/johnnywey/flipside/failable/Failed.java +++ b/src/main/java/com/johnnywey/flipside/failable/Failed.java @@ -2,11 +2,14 @@ import com.johnnywey.flipside.marker.DidNotWork; import com.johnnywey.flipside.marker.DidItWork; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; /** * Something failed. */ -public class Failed implements Failable { +public final class Failed implements Failable { private final Fail reason; private final String detail; @@ -16,6 +19,10 @@ public Failed(final Fail reason, final String detail) { this.detail = detail; } + public static Failed of(final Fail reason, final String detail) { + return new Failed<>(reason, detail); + } + @Override public T get() { throw new FailableException(this); @@ -45,4 +52,29 @@ public String toString() { public DidItWork toDidItWork() { return new DidNotWork(this.reason, this.detail); } + + @Override + public Failable filter(Predicate predicate) { + return this; + } + + @Override + public Failable map(Function mapper) { + throw new FailableException(this); + } + + @Override + public Failable flatMap(Function> mapper) { + throw new FailableException(this); + } + + @Override + public T orElse(T other) { + throw new FailableException(this); + } + + @Override + public T orElseGet(Supplier other) { + throw new FailableException(this); + } } diff --git a/src/main/java/com/johnnywey/flipside/failable/Success.java b/src/main/java/com/johnnywey/flipside/failable/Success.java index 2749dbb..3171f17 100644 --- a/src/main/java/com/johnnywey/flipside/failable/Success.java +++ b/src/main/java/com/johnnywey/flipside/failable/Success.java @@ -3,11 +3,16 @@ import com.johnnywey.flipside.marker.Worked; import com.johnnywey.flipside.marker.DidItWork; +import java.util.Objects; +import java.util.function.Function; +import java.util.function.Predicate; + /** * Something succeeded. */ public class Success implements Failable { private final T result; + private static final Success EMPTY = new Success<>(null); public Success(final T result) { this.result = result; @@ -18,6 +23,16 @@ public T get() { return result; } + public static Success of(final T result) { + return new Success<>(result); + } + + public static Success empty() { + @SuppressWarnings("unchecked") + Success t = (Success) EMPTY; + return t; + } + @Override public Boolean isSuccess() { return true; @@ -37,4 +52,34 @@ public String getDetail() { public DidItWork toDidItWork() { return new Worked(); } + + @Override + public Failable filter(Predicate predicate) { + Objects.requireNonNull(predicate); + if (!isSuccess()) { + return this; + } else { + return predicate.test(get()) ? this : empty(); + } + } + + @Override + public Failable map(Function mapper) { + Objects.requireNonNull(mapper); + if (!isSuccess()) + return empty(); + else { + return Success.of(mapper.apply(get())); + } + } + + @Override + public Failable flatMap(Function> mapper) { + Objects.requireNonNull(mapper); + if (!isSuccess()) + return empty(); + else { + return Objects.requireNonNull(mapper.apply(get())); + } + } } diff --git a/src/test/groovy/com/johnnywey/flipside/FailedSpec.groovy b/src/test/groovy/com/johnnywey/flipside/FailedSpec.groovy new file mode 100644 index 0000000..fbee258 --- /dev/null +++ b/src/test/groovy/com/johnnywey/flipside/FailedSpec.groovy @@ -0,0 +1,38 @@ +package com.johnnywey.flipside + +import com.johnnywey.flipside.failable.Fail +import com.johnnywey.flipside.failable.FailableException +import com.johnnywey.flipside.failable.Failed +import spock.lang.Specification + +class FailedSpec extends Specification { + + def "test failed"() { + setup: + def errMsg = "This is a test failed" + def unit = Failed.of(Fail.BAD_REQUEST, errMsg) + + expect: + !unit.isSuccess() + unit.detail.matches(errMsg) + unit.reason.equals(Fail.BAD_REQUEST) + unit.toString().equals(Failed.of(Fail.BAD_REQUEST, errMsg).toString()) + unit.reason.httpResponseCode.equals(Fail.BAD_REQUEST.httpResponseCode) + + unit.toDidItWork().detail.matches(errMsg) + unit.toDidItWork().reason.equals(Fail.BAD_REQUEST) + } + + def "test exception"() { + setup: + def errMsg = "This is a test failed" + def unit = Failed.of(Fail.BAD_REQUEST, errMsg) + + when: + unit.get() + + then: + thrown(FailableException) + } + +} diff --git a/src/test/groovy/com/johnnywey/flipside/SuccessSpec.groovy b/src/test/groovy/com/johnnywey/flipside/SuccessSpec.groovy new file mode 100644 index 0000000..71883db --- /dev/null +++ b/src/test/groovy/com/johnnywey/flipside/SuccessSpec.groovy @@ -0,0 +1,40 @@ +package com.johnnywey.flipside + +import com.johnnywey.flipside.failable.Fail +import com.johnnywey.flipside.failable.Success +import spock.lang.Specification + +class SuccessSpec extends Specification { + + def "test success"() { + setup: + def successMsg = "mySuccess" + def unit = Success.of(successMsg) + + expect: + unit.isSuccess() + unit.reason.equals(Fail.SUCCESS) + unit.detail.equals(Fail.SUCCESS.name()) + unit.get().equals(successMsg) + unit.toDidItWork().isSuccess() + } + + def "test success with consumer"() { + setup: + def successMsg = "mySuccess" + def result = "not set" + Success.of(successMsg).ifSuccess({ m -> result = successMsg }) + + expect: + result == successMsg + } + + def "test success with mapping"() { + setup: + def successMsg = "mySuccess" + Double result = Success.of(successMsg).map({s -> 23d}).orElse(24d); + + expect: + result == 23d + } +}