diff --git a/src/main/java/io/github/joselion/maybe/EffectHandler.java b/src/main/java/io/github/joselion/maybe/EffectHandler.java index 367e14d..aca31f6 100644 --- a/src/main/java/io/github/joselion/maybe/EffectHandler.java +++ b/src/main/java/io/github/joselion/maybe/EffectHandler.java @@ -5,6 +5,7 @@ import java.util.Optional; import java.util.function.Consumer; import java.util.function.Function; +import java.util.function.Supplier; import org.eclipse.jdt.annotation.Nullable; @@ -200,7 +201,7 @@ public EffectHandler effect(final ThrowingRunnableThe second callback receives the caught error. Both callbacks should * solve a value of the same type {@code T}, but only one of the callbacks is - * invoked. It depends on whether the previous effect threw an error or not. + * invoked. It depends on whether the previous effect(s) threw an error or not. * * @param the type of the value to be solved * @param the type of exception the callbacks may throw @@ -217,6 +218,45 @@ public SolveHandler solve( .orElseGet(() -> Maybe.from(onSuccess)); } + /** + * Terminal operation to supply a {@code T} value. If no error is present the + * {@code onSuccess} callback is used. Otherwise, the {@code onError} + * callback which receives the caught error. + * + *

Both callbacks should return a value of the same type {@code T}, but + * only one of the callbacks is invoked. It depends on whether the previous + * effect(s) threw an error or not. + * + * @param the type of the value to supplied + * @param onSuccess a supplier that provides a value + * @param onError a function that receives the error and returns a value + * @return either the success or the error mapped value + */ + public T thenGet(final Supplier onSuccess, final Function onError) { + return this.error + .map(onError) + .orElseGet(() -> Commons.cast(onSuccess.get())); + } + + /** + * Terminal operation to return a {@code T} value. If no error is present the + * {@code value} param is returned, {@code fallback} otherwise. + * + *

Both values should be of the same type {@code T}, but only one is + * returned. It depends on whether the previous effect(s) threw an error + * or not. + * + * @param the type of the value to return + * @param value the value to return on success + * @param fallback the value to return on error + * @return either the success or the error value + */ + public T thenReturn(final T value, final T fallback) { + return this.error.isEmpty() + ? value + : fallback; + } + /** * Terminal operation to handle the error if present. The error is passed in * the argument of the {@code effect} consumer. diff --git a/src/test/java/io/github/joselion/maybe/EffectHandlerTest.java b/src/test/java/io/github/joselion/maybe/EffectHandlerTest.java index 8bf0ad0..633951f 100644 --- a/src/test/java/io/github/joselion/maybe/EffectHandlerTest.java +++ b/src/test/java/io/github/joselion/maybe/EffectHandlerTest.java @@ -339,6 +339,67 @@ } } + @Nested class thenGet { + @Nested class when_the_error_is_not_present { + @Test void calls_only_the_success_callback_and_returns_the_supplied_value() { + final var successSpy = Spy.supplier(() -> "OK"); + final var errorSpy = Spy.function((Throwable e) -> "FAIL"); + final var value = Maybe.from(noop).thenGet(successSpy, errorSpy); + + assertThat(value).isEqualTo("OK"); + + verify(successSpy).get(); + verify(errorSpy, never()).apply(any()); + } + } + + @Nested class when_the_error_is_present { + @Nested class and_the_error_matches_the_type_of_the_arg { + @Test void calls_only_the_error_callback_and_returns_the_mapped_value() { + final var successSpy = Spy.supplier(() -> "OK"); + final var errorSpy = Spy.function((Throwable e) -> "FAIL"); + final var value = Maybe.from(throwingOp).thenGet(successSpy, errorSpy); + + assertThat(value).isEqualTo("FAIL"); + + verify(successSpy, never()).get(); + verify(errorSpy).apply(FAILURE); + } + } + + @Nested class and_the_error_does_not_match_the_type_of_the_arg { + @Test void calls_only_the_error_callback_and_returns_the_mapped_value() { + final var successSpy = Spy.supplier(() -> "OK"); + final var errorSpy = Spy.function((Throwable e) -> "FAIL"); + final var value = Maybe.from(throwingOp).effect(noop).thenGet(successSpy, errorSpy); + + assertThat(value).isEqualTo("FAIL"); + + verify(successSpy, never()).get(); + verify(errorSpy).apply(FAILURE); + } + } + } + } + + @Nested class thenReturn { + @Nested class when_the_error_is_not_present { + @Test void returns_the_success_value() { + final var value = Maybe.from(noop).thenReturn("OK", "FAIL"); + + assertThat(value).isEqualTo("OK"); + } + } + + @Nested class when_the_error_is_present { + @Test void returns_the_fallback_value() { + final var value = Maybe.from(throwingOp).thenReturn("OK", "FAIL"); + + assertThat(value).isEqualTo("FAIL"); + } + } + } + @Nested class orElse { @Nested class when_the_error_is_present { @Nested class and_the_error_matches_the_type_of_the_arg {