From f6eb27450f5f68e70a747d2a2595c3b5253f417a Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Mon, 9 Jul 2018 08:00:53 +0100 Subject: [PATCH] Add `thenWith(Function)` --- CHANGELOG | 1 + src/main/java/net/kemitix/mon/result/Err.java | 5 ++ .../java/net/kemitix/mon/result/Result.java | 23 +++++++- .../java/net/kemitix/mon/result/Success.java | 5 ++ .../mon/result/WithResultContinuation.java | 49 ++++++++++++++++ src/test/java/net/kemitix/mon/ResultTest.java | 57 ++++++++++++++++++- 6 files changed, 137 insertions(+), 3 deletions(-) create mode 100644 src/main/java/net/kemitix/mon/result/WithResultContinuation.java diff --git a/CHANGELOG b/CHANGELOG index a8d6a08..843d704 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -5,6 +5,7 @@ CHANGELOG ------ * Add `andThen(Function)` +* Add `thenWith(Function)` 0.9.0 ----- diff --git a/src/main/java/net/kemitix/mon/result/Err.java b/src/main/java/net/kemitix/mon/result/Err.java index ce7ae6d..65fcb96 100644 --- a/src/main/java/net/kemitix/mon/result/Err.java +++ b/src/main/java/net/kemitix/mon/result/Err.java @@ -96,6 +96,11 @@ class Err implements Result { return Result.error(error); } + @Override + public Result thenWith(final Function> f) { + return this; + } + @Override public boolean equals(final Object other) { return other instanceof Err && Objects.equals(error, ((Err) other).error); diff --git a/src/main/java/net/kemitix/mon/result/Result.java b/src/main/java/net/kemitix/mon/result/Result.java index fb15a36..45259e6 100644 --- a/src/main/java/net/kemitix/mon/result/Result.java +++ b/src/main/java/net/kemitix/mon/result/Result.java @@ -202,7 +202,6 @@ public interface Result extends Functor> { * *

Combination of {@link #flatMap(Function)} and {@link #of(Callable)}.

* - *

Syntax is:

*

      *     Integer doSomething() {...}
      *     String doSomethingElse(final Integer value) {...}
@@ -212,9 +211,29 @@ public interface Result extends Functor> {
      *
      * 

When the Result is an Err, then the original error is carried over and the Callable is never called.

* - * @param f the function to map the Success value to the Callable + * @param f the function to map the Success value into the Callable * @param the type of the final Result * @return a new Result */ Result andThen(Function> f); + + /** + * Perform the continuation with the current Result value then return the current Result, assuming there was no + * error in the continuation. + * + *

+     *     Integer doSomething() {...}
+     *     void doSomethingElse(final Integer value) {...}
+     *     Result<Integer> r = Result.of(() -> doSomething())
+     *                              .thenWith(value -> () -> doSomethingElse(value));
+     * 
+ * + *

Where the Result is an Err, then the Result is returned immediately and the continuation is ignored.

+ *

Where the Result is a Success, then if an exception is thrown by the continuation the Result returned will be + * a new error Result containing that exception, otherwise the original Result will be returned.

+ * + * @param f the function to map the Success value into the result continuation + * @return the Result or a new error Result + */ + Result thenWith(Function> f); } diff --git a/src/main/java/net/kemitix/mon/result/Success.java b/src/main/java/net/kemitix/mon/result/Success.java index ab605c3..b7d0ef3 100644 --- a/src/main/java/net/kemitix/mon/result/Success.java +++ b/src/main/java/net/kemitix/mon/result/Success.java @@ -100,6 +100,11 @@ class Success implements Result { return Result.of(f.apply(value)); } + @Override + public Result thenWith(final Function> f) { + return f.apply(value).call(this); + } + @Override public boolean equals(final Object other) { return other instanceof Success && Objects.equals(value, ((Success) other).value); diff --git a/src/main/java/net/kemitix/mon/result/WithResultContinuation.java b/src/main/java/net/kemitix/mon/result/WithResultContinuation.java new file mode 100644 index 0000000..e20cc90 --- /dev/null +++ b/src/main/java/net/kemitix/mon/result/WithResultContinuation.java @@ -0,0 +1,49 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2017 Paul Campbell + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies + * or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE + * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package net.kemitix.mon.result; + +/** + * A Callable-like interface for performing an action with a Result that, if there are no errors is returned as-is, but + * if there is an error then a new error Result is returned. + * + * @param the type of the current Result value + */ +@FunctionalInterface +public interface WithResultContinuation { + + /** + * Method to make use of the Result value. + * + * @throws Exception to replace the current Result with an error + */ + void run() throws Exception; + + @SuppressWarnings({"illegalcatch", "javadocmethod"}) + default Result call(final Result currentResult) { + try { + run(); + } catch (Throwable e) { + return Result.error(e); + } + return currentResult; + } +} diff --git a/src/test/java/net/kemitix/mon/ResultTest.java b/src/test/java/net/kemitix/mon/ResultTest.java index 98e5a4e..920ba02 100644 --- a/src/test/java/net/kemitix/mon/ResultTest.java +++ b/src/test/java/net/kemitix/mon/ResultTest.java @@ -540,7 +540,7 @@ public class ResultTest implements WithAssertions { } @Test - public void error_andThenError_thenError() { + public void error_andThenError_thenOriginalError() { //given final RuntimeException exception1 = new RuntimeException(); final Result error = Result.error(exception1); @@ -553,6 +553,61 @@ public class ResultTest implements WithAssertions { result.onError(e -> assertThat(e).isSameAs(exception1)); } + @Test + public void success_whenThenWith_whenOkay_thenSuccess() { + //given + final Result ok = Result.ok(1); + //when + final Result result = ok.thenWith(v -> () -> { + // do something with v + }); + //then + assertThat(result.isOkay()).isTrue(); + result.peek(v -> assertThat(v).isEqualTo(1)); + } + + @Test + public void success_whenThenWith_whenError_thenError() { + //given + final Result ok = Result.ok(1); + final RuntimeException exception = new RuntimeException(); + //when + final Result result = ok.thenWith(v -> () -> { + throw exception; + }); + //then + assertThat(result.isError()).isTrue(); + result.onError(e -> assertThat(e).isSameAs(exception)); + } + + @Test + public void error_whenThenWith_whenOkay_thenError() { + //given + final RuntimeException exception = new RuntimeException(); + final Result error = Result.error(exception); + //when + final Result result = error.thenWith(v -> () -> { + // do something with v + }); + //then + assertThat(result.isError()).isTrue(); + result.onError(e -> assertThat(e).isSameAs(exception)); + } + + @Test + public void error_whenThenWith_whenError_thenOriginalError() { + //given + final RuntimeException exception = new RuntimeException(); + final Result error = Result.error(exception); + //when + final Result result = error.thenWith(v -> () -> { + throw new RuntimeException(); + }); + //then + assertThat(result.isError()).isTrue(); + result.onError(e -> assertThat(e).isSameAs(exception)); + } + @RequiredArgsConstructor private static class UseCase {