From c04608df953eca010fabd4ff5e1a9dacd2f7b4bc Mon Sep 17 00:00:00 2001 From: Steve Hu Date: Fri, 9 Nov 2018 16:37:20 -0500 Subject: [PATCH] fixes #325 add monad-result module to wrap success T and failure Status --- monad-result/pom.xml | 54 ++++++++++++++ .../java/com/networknt/monad/Failure.java | 38 ++++++++++ .../main/java/com/networknt/monad/Result.java | 73 +++++++++++++++++++ .../java/com/networknt/monad/Success.java | 53 ++++++++++++++ .../java/com/networknt/monad/ResultTest.java | 25 +++++++ .../src/test/resources/logback-test.xml | 72 ++++++++++++++++++ pom.xml | 6 ++ 7 files changed, 321 insertions(+) create mode 100644 monad-result/pom.xml create mode 100644 monad-result/src/main/java/com/networknt/monad/Failure.java create mode 100644 monad-result/src/main/java/com/networknt/monad/Result.java create mode 100644 monad-result/src/main/java/com/networknt/monad/Success.java create mode 100644 monad-result/src/test/java/com/networknt/monad/ResultTest.java create mode 100644 monad-result/src/test/resources/logback-test.xml diff --git a/monad-result/pom.xml b/monad-result/pom.xml new file mode 100644 index 0000000000..fe60835d0b --- /dev/null +++ b/monad-result/pom.xml @@ -0,0 +1,54 @@ + + + + 4.0.0 + + + com.networknt + light-4j + 1.5.22-SNAPSHOT + .. + + + monad-result + jar + A monad result wraps a T if success and Status if failure. + + + + com.networknt + status + + + org.slf4j + slf4j-api + + + + ch.qos.logback + logback-classic + test + + + junit + junit + test + + + + diff --git a/monad-result/src/main/java/com/networknt/monad/Failure.java b/monad-result/src/main/java/com/networknt/monad/Failure.java new file mode 100644 index 0000000000..e00f1a9394 --- /dev/null +++ b/monad-result/src/main/java/com/networknt/monad/Failure.java @@ -0,0 +1,38 @@ +package com.networknt.monad; + +import com.networknt.status.Status; + +import java.util.NoSuchElementException; + +public final class Failure implements Result { + + private final Status error; + + private Failure(Status error) { + this.error = error; + } + + public static Result of(Status error) { + return new Failure<>(error); + } + + @Override + public boolean isSuccess() { + return false; + } + + @Override + public Status getError() { + return error; + } + + @Override + public T getResult() { + throw new NoSuchElementException("There is no result is Failure"); + } + + @Override + public String toString() { + return String.format("Failure[%s]", error.toString()); + } +} diff --git a/monad-result/src/main/java/com/networknt/monad/Result.java b/monad-result/src/main/java/com/networknt/monad/Result.java new file mode 100644 index 0000000000..c3870ace19 --- /dev/null +++ b/monad-result/src/main/java/com/networknt/monad/Result.java @@ -0,0 +1,73 @@ +package com.networknt.monad; + +import com.networknt.status.Status; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; + +public interface Result { + + boolean isSuccess(); + + default boolean isFailure() { + return !isSuccess(); + } + + Status getError(); + + T getResult(); + + /* + Use this method when you want to apply a function on the insides of the Result + without unwrapping it. Mapper won't be invoked if the Result is Failure + */ + default Result map(Function mapper) { + return isSuccess() ? + Success.of(mapper.apply(getResult())) : + (Failure) this; + } + + /* + Use this method when you your mapping function is returning a Result (which will make + the return type the Result> using just 'map'). + */ + default Result flatMap(Function> mapper) { + return isSuccess() ? + mapper.apply(getResult()) : + (Failure) this; + } + + /* + Use this method if you want to reduce your Result to some type R. For example at the + end of the flow, you could convert it to two different client responses depending on + the insides. + */ + default R fold(Function successFunction, Function, ? extends R> failureFunction) { + return isSuccess() ? + successFunction.apply(getResult()) : + failureFunction.apply((Failure) this); + } + + /* + Use this method when you have two instances of Result and you want to invoke a function on + their insides without unwrapping them separately. If both of them are Failures then only + the first (this) Failure will be returned. + */ + default Result lift(Result other, BiFunction function) { + return flatMap(first -> other.map(second -> function.apply(first, second))); + } + + default Result ifSuccess(Consumer successConsumer) { + if (isSuccess()) { + successConsumer.accept(this.getResult()); + } + return this; + } + + default Result ifFailure(Consumer> failureConsumer) { + if (isFailure()) { + failureConsumer.accept((Failure) this); + } + return this; + } +} diff --git a/monad-result/src/main/java/com/networknt/monad/Success.java b/monad-result/src/main/java/com/networknt/monad/Success.java new file mode 100644 index 0000000000..5df2774608 --- /dev/null +++ b/monad-result/src/main/java/com/networknt/monad/Success.java @@ -0,0 +1,53 @@ +package com.networknt.monad; + +import com.networknt.status.Status; + +import java.util.NoSuchElementException; +import java.util.Optional; + +public final class Success implements Result { + + public static final Result SUCCESS = new Success<>(null); + + public static final Result OPTIONAL_SUCCESS = Success.ofOptional(null); + + @SuppressWarnings("unchecked") + static Result> emptyOptional() { + return (Result>) OPTIONAL_SUCCESS; + } + + private final T result; + + public static Result of(T result) { + return new Success<>(result); + } + + public static Result> ofOptional(T result) { + return new Success<>(Optional.ofNullable(result)); + } + + private Success(T result) { + this.result = result; + } + + @Override + public boolean isSuccess() { + return true; + } + + @Override + public Status getError() { + throw new NoSuchElementException("There is no error in Success"); + } + + @Override + public T getResult() { + return result; + } + + @Override + public String toString() { + final String value = result != null ? result.toString() : ""; + return String.format("Success[%s]", value); + } +} diff --git a/monad-result/src/test/java/com/networknt/monad/ResultTest.java b/monad-result/src/test/java/com/networknt/monad/ResultTest.java new file mode 100644 index 0000000000..862c12e687 --- /dev/null +++ b/monad-result/src/test/java/com/networknt/monad/ResultTest.java @@ -0,0 +1,25 @@ +package com.networknt.monad; + +import com.networknt.status.Status; +import org.junit.Assert; +import org.junit.Test; + +import static com.networknt.monad.Success.SUCCESS; + +public class ResultTest { + @Test + public void testResult() { + Status status = new Status(400, "ERR00000", "DEMO_STATUS", "This is an error", "ERROR"); + Assert.assertTrue(SUCCESS.isSuccess()); + Assert.assertTrue(!Failure.of(status).isSuccess()); + Assert.assertTrue(!SUCCESS.isFailure()); + Assert.assertTrue(Failure.of(status).isFailure()); + + Result result = Failure.of(status); + Assert.assertTrue(result.getError().equals(status)); + + String stringResult = "String result"; + result = Success.of(stringResult); + Assert.assertEquals(stringResult, result.getResult()); + } +} diff --git a/monad-result/src/test/resources/logback-test.xml b/monad-result/src/test/resources/logback-test.xml new file mode 100644 index 0000000000..07c9df25e1 --- /dev/null +++ b/monad-result/src/test/resources/logback-test.xml @@ -0,0 +1,72 @@ + + + + + TODO create logger for audit only. + http://stackoverflow.com/questions/2488558/logback-to-log-different-messages-to-two-files + + PROFILER + + NEUTRAL + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5marker %-5level %logger{36} - %msg%n + + + + + target/test.log + false + + %d{HH:mm:ss.SSS} [%thread] %-5level %class{36}:%L %M - %msg%n + + + + + + target/audit.log + + %-5level [%thread] %date{ISO8601} %F:%L - %msg%n + true + + + target/audit.log.%i.zip + 1 + 5 + + + 200MB + + + + + + + + + + + + + + + + diff --git a/pom.xml b/pom.xml index b0065b39c4..5f8714968f 100644 --- a/pom.xml +++ b/pom.xml @@ -142,6 +142,7 @@ deref-token ip-whitelist encode-decode + monad-result @@ -339,6 +340,11 @@ encode-decode ${project.version} + + com.networknt + monad-result + ${project.version} + com.networknt server