diff --git a/src/main/java/io/github/joselion/maybe/EffectHandler.java b/src/main/java/io/github/joselion/maybe/EffectHandler.java index 500db44..f5c669c 100644 --- a/src/main/java/io/github/joselion/maybe/EffectHandler.java +++ b/src/main/java/io/github/joselion/maybe/EffectHandler.java @@ -104,7 +104,7 @@ public EffectHandler doOnError(final Class ofType, f * @param effect a consumer function that recieves the caught error * @return the same handler to continue chainning operations */ - public EffectHandler doOnError(final Consumer effect) { + public EffectHandler doOnError(final Consumer effect) { this.error.ifPresent(effect); return this; @@ -143,7 +143,7 @@ public EffectHandler catchError(final Class ofType, * @return an empty handler if the error is present. The same handler * instance otherwise */ - public EffectHandler catchError(final Consumer handler) { + public EffectHandler catchError(final Consumer handler) { return this.error .map(caught -> { handler.accept(caught); @@ -164,10 +164,17 @@ public EffectHandler catchError(final Consumer handler) { */ public EffectHandler effect( final ThrowingRunnable onSuccess, - final ThrowingConsumer onError + final ThrowingConsumer onError ) { return this.error - .map(Maybe.partial(onError)) + .map(e -> { + try { + onError.accept(e); + return EffectHandler.failure(Commons.cast(e)); + } catch (Throwable x) { // NOSONAR + return EffectHandler.failure(Commons.cast(x)); + } + }) .orElseGet(() -> Maybe.from(onSuccess)); } @@ -191,7 +198,7 @@ public EffectHandler effect(final ThrowingRunnable effect) { + public void orElse(final Consumer effect) { this.error.ifPresent(effect); } @@ -214,7 +221,7 @@ public void orThrow() throws E { * @param mapper a function that maps the new exception to throw * @throws X a mapped exception */ - public void orThrow(final Function mapper) throws X { + public void orThrow(final Function mapper) throws X { if (this.error.isPresent()) { throw mapper.apply(this.error.get()); } diff --git a/src/main/java/io/github/joselion/maybe/Maybe.java b/src/main/java/io/github/joselion/maybe/Maybe.java index 786fb28..af3b907 100644 --- a/src/main/java/io/github/joselion/maybe/Maybe.java +++ b/src/main/java/io/github/joselion/maybe/Maybe.java @@ -232,7 +232,7 @@ public static CloseableHandler return Maybe .from(supplier) .map(CloseableHandler::from) - .orElse(CloseableHandler::failure); + .orElse(x -> CloseableHandler.failure(Commons.cast(x))); } /** diff --git a/src/main/java/io/github/joselion/maybe/SolveHandler.java b/src/main/java/io/github/joselion/maybe/SolveHandler.java index 808259e..29d2751 100644 --- a/src/main/java/io/github/joselion/maybe/SolveHandler.java +++ b/src/main/java/io/github/joselion/maybe/SolveHandler.java @@ -124,7 +124,7 @@ public SolveHandler doOnError(final Class ofType, * @param effect a consumer function that receives the caught error * @return the same handler to continue chainning operations */ - public SolveHandler doOnError(final Consumer effect) { + public SolveHandler doOnError(final Consumer effect) { this.value.doOnLeft(effect); return this; @@ -164,7 +164,7 @@ public SolveHandler catchError( * @return a handler containing a new value if an error was caught. The same * handler instance otherwise */ - public SolveHandler catchError(final Function handler) { + public SolveHandler catchError(final Function handler) { return this.value .mapLeft(handler) .mapLeft(SolveHandler::from) @@ -186,7 +186,7 @@ public SolveHandler catchError(final Function hand * @return a new handler with either the solved value or the error */ public SolveHandler onErrorSolve( - final ThrowingFunction solver + final ThrowingFunction solver ) { return this.value .unwrap( @@ -202,21 +202,21 @@ public SolveHandler onErrorSolve( *

This is helpful to try to solve values in different ways, like when * nesting a try-catch in another catch block, but more functional. * - * @param the type of the error the new solver may throw * @param the type of the error to catch + * @param the type of the error the new solver may throw * @param ofType a class instance of the error type to catch * @param solver a throwing function that receives the previous error and * solves another value * @return a new handler with either the solved value or the error */ - public SolveHandler onErrorSolve( - final Class ofType, - final ThrowingFunction solver + public SolveHandler onErrorSolve( + final Class ofType, + final ThrowingFunction solver ) { return this.value .unwrap( x -> ofType.isInstance(x) - ? Maybe.of(x).solve(solver) + ? Maybe.of(x).map(Commons::cast).solve(solver) : SolveHandler.failure(Commons.cast(x)), SolveHandler::from ); @@ -241,7 +241,7 @@ public SolveHandler onErrorSolv */ public SolveHandler solve( final ThrowingFunction onSuccess, - final ThrowingFunction onError + final ThrowingFunction onError ) { return this.value.unwrap( Maybe.partial(onError), @@ -251,7 +251,7 @@ public SolveHandler solve( /** * Chain another solver function if the value was solved. Otherwise, - * returns a handler containing the error so it can be propagated upstream. + * returns a handler containing the error so it can be propagated downstream. * * @param the type of value returned by the next operation * @param the type of exception the new solver may throw @@ -282,7 +282,7 @@ public SolveHandler solve( */ public EffectHandler effect( final ThrowingConsumer onSuccess, - final ThrowingConsumer onError + final ThrowingConsumer onError ) { return this.value.unwrap( Maybe.partial(onError), @@ -293,7 +293,7 @@ public EffectHandler effect( /** * Chain the previous operation to an effect if the value was solved. * Otherwise, returns a handler containing the error so it can be propagated - * upstream. + * downstream. * * @param the type of the error the effect may throw * @param effect a consume checked function to run in case of succeess @@ -384,7 +384,7 @@ public T orElse(final T fallback) { * another value * @return the solved value if present. Another value otherwise */ - public T orElse(final Function mapper) { + public T orElse(final Function mapper) { return this.value.unwrap(mapper, Function.identity()); } @@ -434,7 +434,7 @@ public T orThrow() throws E { } /** - * Returns the value solved/handled if present. Throws another error otherwise. + * Returns the solved value if present. Throws another error otherwise. * * @param the new error type * @param mapper a function that receives the caught error and produces @@ -442,7 +442,7 @@ public T orThrow() throws E { * @return the solved/handled value if present * @throws X a mapped exception */ - public T orThrow(final Function mapper) throws X { + public T orThrow(final Function mapper) throws X { return this.value .rightToOptional() .orElseThrow(() -> mapper.apply(this.value.leftOrNull())); @@ -536,7 +536,7 @@ public CloseableHandler sol .of(prev) .solve(solver) .map(CloseableHandler::from) - .orElse(CloseableHandler::failure) + .orElse(x -> CloseableHandler.failure(Commons.cast(x))) ); } } diff --git a/src/main/java17/io/github/joselion/maybe/Maybe.java b/src/main/java17/io/github/joselion/maybe/Maybe.java index 1366615..3787a2c 100644 --- a/src/main/java17/io/github/joselion/maybe/Maybe.java +++ b/src/main/java17/io/github/joselion/maybe/Maybe.java @@ -233,7 +233,7 @@ public static CloseableHandler return Maybe .from(supplier) .map(CloseableHandler::from) - .orElse(CloseableHandler::failure); + .orElse(x -> CloseableHandler.failure(Commons.cast(x))); } /** diff --git a/src/test/java/io/github/joselion/maybe/EffectHandlerTest.java b/src/test/java/io/github/joselion/maybe/EffectHandlerTest.java index 58401dc..1fde56c 100644 --- a/src/test/java/io/github/joselion/maybe/EffectHandlerTest.java +++ b/src/test/java/io/github/joselion/maybe/EffectHandlerTest.java @@ -20,10 +20,10 @@ @UnitTest class EffectHandlerTest { - private static final FileSystemException FAIL_EXCEPTION = new FileSystemException("FAIL"); + private static final FileSystemException FAILURE = new FileSystemException("FAIL"); private final ThrowingRunnable throwingOp = () -> { - throw FAIL_EXCEPTION; + throw FAILURE; }; private final ThrowingRunnable noOp = () -> { }; @@ -39,9 +39,9 @@ @Nested class failure { @Nested class when_the_error_is_not_null { @Test void returns_a_handler_with_the_error() { - final var handler = EffectHandler.failure(FAIL_EXCEPTION); + final var handler = EffectHandler.failure(FAILURE); - assertThat(handler.error()).containsSame(FAIL_EXCEPTION); + assertThat(handler.error()).containsSame(FAILURE); } } @@ -86,10 +86,11 @@ @Test void calls_the_effect_callback() { final var consumerSpy = Spy.consumer(error -> { }); - Maybe.from(throwingOp) + Maybe + .from(throwingOp) .doOnError(FileSystemException.class, consumerSpy); - verify(consumerSpy).accept(FAIL_EXCEPTION); + verify(consumerSpy).accept(FAILURE); } } @@ -97,7 +98,8 @@ @Test void never_calls_the_effect_callback() { final var consumerSpy = Spy.consumer(error -> { }); - Maybe.from(throwingOp) + Maybe + .from(throwingOp) .doOnError(RuntimeException.class, consumerSpy); verify(consumerSpy, never()).accept(any()); @@ -106,26 +108,44 @@ } @Nested class and_the_error_type_is_not_provided { - @Test void calls_the_effect_callback() { - final var consumerSpy = Spy.consumer(error -> { }); + @Nested class and_the_error_matches_the_type_of_the_arg { + @Test void calls_the_effect_callback() { + final var consumerSpy = Spy.consumer(error -> { }); - Maybe.from(throwingOp) - .doOnError(consumerSpy); + Maybe + .from(throwingOp) + .doOnError(consumerSpy); - verify(consumerSpy).accept(FAIL_EXCEPTION); + verify(consumerSpy).accept(FAILURE); + } + } + + @Nested class and_the_error_does_not_match_the_type_of_the_arg { + @Test void calls_the_effect_callback() { + final var consumerSpy = Spy.consumer(error -> { }); + + Maybe + .from(throwingOp) + .effect(() -> { }) + .doOnError(consumerSpy); + + verify(consumerSpy).accept(FAILURE); + } } } } @Nested class when_the_error_is_not_present { @Test void never_calls_the_effect_callback() { - final var cunsumerSpy = Spy.consumer(error -> { }); + final var runtimeSpy = Spy.consumer(error -> { }); + final var throwableSpy = Spy.consumer(error -> { }); Maybe.from(noOp) - .doOnError(RuntimeException.class, cunsumerSpy) - .doOnError(cunsumerSpy); + .doOnError(RuntimeException.class, runtimeSpy) + .doOnError(throwableSpy); - verify(cunsumerSpy, never()).accept(any()); + verify(runtimeSpy, never()).accept(any()); + verify(throwableSpy, never()).accept(any()); } } } @@ -136,22 +156,24 @@ @Nested class and_the_error_is_an_instance_of_the_provided_type { @Test void calls_the_handler_function() { final var consumerSpy = Spy.consumer(e -> { }); - final var handler = Maybe.from(throwingOp) + final var handler = Maybe + .from(throwingOp) .catchError(FileSystemException.class, consumerSpy); assertThat(handler.error()).isEmpty(); - verify(consumerSpy).accept(FAIL_EXCEPTION); + verify(consumerSpy).accept(FAILURE); } } @Nested class and_the_error_is_not_an_instance_of_the_provided_type { @Test void never_calls_the_handler_function() { final var consumerSpy = Spy.consumer(e -> { }); - final var handler = Maybe.from(throwingOp) + final var handler = Maybe + .from(throwingOp) .catchError(AccessDeniedException.class, consumerSpy); - assertThat(handler.error()).contains(FAIL_EXCEPTION); + assertThat(handler.error()).contains(FAILURE); verify(consumerSpy, never()).accept(any()); } @@ -159,31 +181,49 @@ } @Nested class and_the_error_type_is_not_provided { - @Test void calls_the_handler_function() { - final var consumerSpy = Spy.consumer(e -> { }); - final var handler = Maybe.from(throwingOp) - .catchError(consumerSpy); + @Nested class and_the_error_matches_the_type_of_the_arg { + @Test void calls_the_handler_function() { + final var consumerSpy = Spy.consumer(e -> { }); + final var handler = Maybe.from(throwingOp) + .catchError(consumerSpy); - assertThat(handler.error()).isEmpty(); + assertThat(handler.error()).isEmpty(); - verify(consumerSpy).accept(FAIL_EXCEPTION); + verify(consumerSpy).accept(FAILURE); + } + } + + @Nested class and_the_error_does_not_match_the_type_of_the_arg { + @Test void calls_the_handler_function() { + final var consumerSpy = Spy.consumer(e -> { }); + final var handler = Maybe + .from(throwingOp) + .effect(() -> { }) + .catchError(consumerSpy); + + assertThat(handler.error()).isEmpty(); + + verify(consumerSpy).accept(FAILURE); + } } } } @Nested class when_the_error_is_not_present { @Test void never_calls_the_handler_function() { - final var consumerSpy = Spy.consumer(e -> { }); + final var runtimeSpy = Spy.consumer(e -> { }); + final var throwableSpy = Spy.consumer(e -> { }); final var handlers = List.of( - Maybe.from(noOp).catchError(RuntimeException.class, consumerSpy), - Maybe.from(noOp).catchError(consumerSpy) + Maybe.from(noOp).catchError(RuntimeException.class, runtimeSpy), + Maybe.from(noOp).catchError(throwableSpy) ); assertThat(handlers).isNotEmpty().allSatisfy(handler -> { assertThat(handler.error()).isEmpty(); }); - verify(consumerSpy, never()).accept(any()); + verify(runtimeSpy, never()).accept(any()); + verify(throwableSpy, never()).accept(any()); } } } @@ -193,7 +233,7 @@ @Test void calls_the_effect_callback_and_returns_a_new_handler() throws FileSystemException { final var effectSpy = Spy.lambda(throwingOp); final var successSpy = Spy.lambda(throwingOp); - final var errorSpy = Spy.throwingConsumer(err -> throwingOp.run()); + final var errorSpy = Spy.throwingConsumer((Throwable err) -> throwingOp.run()); final var handler = Maybe.from(noOp); final var newHandlers = List.of( handler.effect(effectSpy), @@ -202,7 +242,7 @@ assertThat(newHandlers).isNotEmpty().allSatisfy(newHandler -> { assertThat(newHandler).isNotSameAs(handler); - assertThat(newHandler.error()).contains(FAIL_EXCEPTION); + assertThat(newHandler.error()).contains(FAILURE); }); verify(effectSpy).run(); @@ -213,28 +253,39 @@ @Nested class when_the_error_is_present { @Nested class and_the_error_callback_is_provided { - @Test void calls_only_the_error_callback_and_returns_a_new_handler() throws FileSystemException { - final var successSpy = Spy.throwingRunnable(() -> { }); - final var errorSpy = Spy.throwingConsumer(err -> throwingOp.run()); - final var handler = Maybe.from(throwingOp); - final var newHandler = handler.effect(successSpy, errorSpy); + @Nested class and_the_error_matches_the_type_of_the_arg { + @Test void calls_only_the_error_callback_and_returns_a_handler_with_the_error() throws FileSystemException { + final var successSpy = Spy.throwingRunnable(() -> { }); + final var errorSpy = Spy.throwingConsumer((Throwable err) -> throwingOp.run()); + final var handler = Maybe.from(throwingOp).effect(successSpy, errorSpy); - assertThat(newHandler).isNotSameAs(handler); - assertThat(newHandler.error()).contains(FAIL_EXCEPTION); + assertThat(handler.error()).contains(FAILURE); - verify(successSpy, never()).run(); - verify(errorSpy).accept(FAIL_EXCEPTION); + verify(successSpy, never()).run(); + verify(errorSpy).accept(FAILURE); + } + } + + @Nested class and_the_error_does_not_match_the_type_of_the_arg { + @Test void calls_only_the_error_callback_and_returns_a_handler_with_the_error() throws FileSystemException { + final var successSpy = Spy.throwingRunnable(() -> { }); + final var errorSpy = Spy.throwingConsumer((Throwable err) -> throwingOp.run()); + final var handler = Maybe.from(throwingOp).effect(() -> { }).effect(successSpy, errorSpy); + + assertThat(handler.error()).contains(FAILURE); + + verify(successSpy, never()).run(); + verify(errorSpy).accept(FAILURE); + } } } @Nested class and_the_error_callback_is_not_provided { - @Test void never_calls_the_effect_callback_and_returns_a_new_empty_handler() throws FileSystemException { + @Test void never_calls_the_effect_callback_and_returns_an_empty_handler() throws FileSystemException { final var effectSpy = Spy.lambda(throwingOp); - final var handler = Maybe.from(throwingOp); - final var newHandler = handler.effect(effectSpy); + final var handler = Maybe.from(throwingOp).effect(effectSpy); - assertThat(newHandler).isNotSameAs(handler); - assertThat(newHandler.error()).isEmpty(); + assertThat(handler.error()).contains(FAILURE); verify(effectSpy, never()).run(); } @@ -244,19 +295,32 @@ @Nested class orElse { @Nested class when_the_error_is_present { - @Test void calls_the_effect_callback() { - final var consumerSpy = Spy.consumer(e -> { }); - final var handler = Maybe.from(throwingOp); + @Nested class and_the_error_matches_the_type_of_the_arg { + @Test void calls_the_effect_callback() { + final var consumerSpy = Spy.consumer(e -> { }); + final var handler = Maybe.from(throwingOp); - handler.orElse(consumerSpy); + handler.orElse(consumerSpy); + + verify(consumerSpy).accept(FAILURE); + } + } + + @Nested class and_the_error_does_not_match_the_type_of_the_arg { + @Test void calls_the_effect_callback() { + final var consumerSpy = Spy.consumer(e -> { }); + final var handler = Maybe.from(throwingOp).effect(() -> { }); - verify(consumerSpy).accept(FAIL_EXCEPTION); + handler.orElse(consumerSpy); + + verify(consumerSpy).accept(FAILURE); + } } } @Nested class when_the_error_is_not_present { @Test void never_calls_the_effect_callback() { - final var consumerSpy = Spy.consumer(e -> { }); + final var consumerSpy = Spy.consumer(e -> { }); final var handler = Maybe.from(noOp); handler.orElse(consumerSpy); @@ -268,21 +332,44 @@ @Nested class orThrow { @Nested class when_the_error_is_present { - @Test void throws_the_error() { - final var anotherError = new RuntimeException("OTHER"); - final var functionSpy = Spy.function((FileSystemException err) -> anotherError); - final var handler = Maybe.from(throwingOp); + @Nested class and_the_mapper_is_not_provided { + @Test void throws_the_present_error() { + final var handler = Maybe.from(throwingOp); + + assertThatCode(handler::orThrow).isEqualTo(FAILURE); + } + } + + @Nested class and_the_mapper_is_provided { + @Nested class and_the_error_matches_the_type_of_the_arg { + @Test void throws_the_error() { + final var anotherError = new RuntimeException("OTHER"); + final var functionSpy = Spy.function((Throwable err) -> anotherError); + final var handler = Maybe.from(throwingOp); - assertThatCode(handler::orThrow).isEqualTo(FAIL_EXCEPTION); - assertThatCode(() -> handler.orThrow(functionSpy)).isEqualTo(anotherError); + assertThatCode(() -> handler.orThrow(functionSpy)).isEqualTo(anotherError); - verify(functionSpy).apply(FAIL_EXCEPTION); + verify(functionSpy).apply(FAILURE); + } + } + + @Nested class and_the_error_does_not_match_the_type_of_the_arg { + @Test void throws_the_error() { + final var anotherError = new RuntimeException("OTHER"); + final var functionSpy = Spy.function((Throwable err) -> anotherError); + final var handler = Maybe.from(throwingOp).effect(() -> { }); + + assertThatCode(() -> handler.orThrow(functionSpy)).isEqualTo(anotherError); + + verify(functionSpy).apply(FAILURE); + } + } } } @Nested class when_the_error_is_not_present { @Test void no_exception_is_thrown() { - final var functionSpy = Spy.function((RuntimeException err) -> FAIL_EXCEPTION); + final var functionSpy = Spy.function((Throwable err) -> FAILURE); final var handler = Maybe.from(noOp); assertThatCode(handler::orThrow).doesNotThrowAnyException(); diff --git a/src/test/java/io/github/joselion/maybe/SolveHandlerTest.java b/src/test/java/io/github/joselion/maybe/SolveHandlerTest.java index db814cf..0ea8a00 100644 --- a/src/test/java/io/github/joselion/maybe/SolveHandlerTest.java +++ b/src/test/java/io/github/joselion/maybe/SolveHandlerTest.java @@ -18,6 +18,7 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import io.github.joselion.maybe.helpers.Commons; import io.github.joselion.maybe.util.function.ThrowingFunction; import io.github.joselion.maybe.util.function.ThrowingSupplier; import io.github.joselion.testing.Spy; @@ -114,7 +115,8 @@ @Test void calls_the_effect_callback() { final var consumerSpy = Spy.consumer(error -> { }); - Maybe.from(throwingOp) + Maybe + .from(throwingOp) .doOnError(FileSystemException.class, consumerSpy); verify(consumerSpy).accept(FAILURE); @@ -125,7 +127,8 @@ @Test void never_calls_the_effect_callback() { final var consumerSpy = Spy.consumer(error -> { }); - Maybe.from(throwingOp) + Maybe + .from(throwingOp) .doOnError(RuntimeException.class, consumerSpy); verify(consumerSpy, never()).accept(any()); @@ -134,26 +137,45 @@ } @Nested class and_the_error_type_is_not_provided { - @Test void calls_the_effect_callback() { - final var consumerSpy = Spy.consumer(error -> { }); + @Nested class and_the_error_matches_the_type_of_the_arg { + @Test void calls_the_effect_callback() { + final var consumerSpy = Spy.consumer(error -> { }); + + Maybe + .from(throwingOp) + .doOnError(consumerSpy); + + verify(consumerSpy).accept(FAILURE); + } + } + + @Nested class and_the_error_does_not_match_the_type_of_the_arg { + @Test void calls_the_effect_callback() { + final var consumerSpy = Spy.consumer(error -> { }); - Maybe.from(throwingOp) - .doOnError(consumerSpy); + Maybe + .from(throwingOp) + .solve(Commons::cast) + .doOnError(consumerSpy); - verify(consumerSpy).accept(FAILURE); + verify(consumerSpy).accept(FAILURE); + } } } } @Nested class when_the_value_is_present { @Test void never_calls_the_effect_callback() { - final var cunsumerSpy = Spy.consumer(error -> { }); + final var runtimeSpy = Spy.consumer(error -> { }); + final var throwableSpy = Spy.consumer(error -> { }); - Maybe.from(okOp) - .doOnError(RuntimeException.class, cunsumerSpy) - .doOnError(cunsumerSpy); + Maybe + .from(okOp) + .doOnError(RuntimeException.class, runtimeSpy) + .doOnError(throwableSpy); - verify(cunsumerSpy, never()).accept(any()); + verify(runtimeSpy, never()).accept(any()); + verify(throwableSpy, never()).accept(any()); } } } @@ -187,25 +209,42 @@ } @Nested class and_the_error_type_is_not_provided { - @Test void calls_the_handler_function() { - final var handlerSpy = Spy.function((FileSystemException e) -> OK); - final var solver = Maybe.from(throwingOp) - .catchError(handlerSpy); + @Nested class and_the_error_matches_the_type_of_the_arg { + @Test void calls_the_handler_function() { + final var handlerSpy = Spy.function((Throwable e) -> OK); + final var solver = Maybe.from(throwingOp) + .catchError(handlerSpy); - assertThat(solver.success()).contains(OK); - assertThat(solver.error()).isEmpty(); + assertThat(solver.success()).contains(OK); + assertThat(solver.error()).isEmpty(); - verify(handlerSpy).apply(FAILURE); + verify(handlerSpy).apply(FAILURE); + } + } + + @Nested class and_the_error_does_not_match_the_type_of_the_arg { + @Test void calls_the_handler_function() { + final var handlerSpy = Spy.function((Throwable e) -> OK); + final var solver = Maybe.from(throwingOp) + .solve(Commons::cast) + .catchError(handlerSpy); + + assertThat(solver.success()).contains(OK); + assertThat(solver.error()).isEmpty(); + + verify(handlerSpy).apply(FAILURE); + } } } } @Nested class when_the_value_is_present { @Test void never_calls_the_handler_function() { - final var functionSpy = Spy.function((RuntimeException e) -> OK); + final var runtimeSpy = Spy.function((RuntimeException e) -> OK); + final var throwableSpy = Spy.function((Throwable e) -> OK); final var solvers = List.of( - Maybe.from(okOp).catchError(RuntimeException.class, functionSpy), - Maybe.from(okOp).catchError(functionSpy) + Maybe.from(okOp).catchError(RuntimeException.class, runtimeSpy), + Maybe.from(okOp).catchError(throwableSpy) ); assertThat(solvers).isNotEmpty().allSatisfy(solver -> { @@ -213,25 +252,14 @@ assertThat(solver.error()).isEmpty(); }); - verify(functionSpy, never()).apply(any()); + verify(runtimeSpy, never()).apply(any()); + verify(throwableSpy, never()).apply(any()); } } } @Nested class onErrorSolve { @Nested class when_the_error_is_present { - @Nested class and_the_error_type_is_not_provided { - @Test void calls_the_solver_callback_and_returns_a_handler() { - final var solverSpy = Spy.throwingFunction(FileSystemException::getMessage); - final var handler = Maybe.from(throwingOp).onErrorSolve(solverSpy); - - assertThat(handler.success()).contains("FAIL"); - assertThat(handler.error()).isEmpty(); - - verify(solverSpy).apply(FAILURE); - } - } - @Nested class and_the_error_type_is_provided { @Nested class and_the_error_is_an_instance_of_the_provided_type { @Test void calls_the_solver_and_returns_a_handler() { @@ -257,14 +285,41 @@ } } } + + @Nested class and_the_error_type_is_not_provided { + @Nested class and_the_error_matches_the_type_of_the_arg { + @Test void calls_the_solver_callback_and_returns_a_handler() { + final var solverSpy = Spy.throwingFunction(Throwable::getMessage); + final var handler = Maybe.from(throwingOp).onErrorSolve(solverSpy); + + assertThat(handler.success()).contains("FAIL"); + assertThat(handler.error()).isEmpty(); + + verify(solverSpy).apply(FAILURE); + } + } + + @Nested class and_the_error_does_not_match_the_type_of_the_arg { + @Test void calls_the_solver_callback_and_returns_a_handler() { + final var solverSpy = Spy.throwingFunction(Throwable::getMessage); + final var handler = Maybe.from(throwingOp).solve(Commons::cast).onErrorSolve(solverSpy); + + assertThat(handler.success()).contains("FAIL"); + assertThat(handler.error()).isEmpty(); + + verify(solverSpy).apply(FAILURE); + } + } + } } @Nested class when_the_value_is_present { @Test void never_calls_the_solver_and_returns_a_handler_with_the_value() { - final var solverSpy = Spy.throwingFunction(RuntimeException::getMessage); + final var runtimeSpy = Spy.throwingFunction(RuntimeException::getMessage); + final var throwableSpy = Spy.throwingFunction(Throwable::getMessage); final var handlers = List.of( - Maybe.from(okOp).onErrorSolve(solverSpy), - Maybe.from(okOp).onErrorSolve(RuntimeException.class, solverSpy) + Maybe.from(okOp).onErrorSolve(throwableSpy), + Maybe.from(okOp).onErrorSolve(RuntimeException.class, runtimeSpy) ); assertThat(handlers).isNotEmpty().allSatisfy(handler -> { @@ -272,7 +327,8 @@ assertThat(handler.error()).isEmpty(); }); - verify(solverSpy, never()).apply(any()); + verify(runtimeSpy, never()).apply(any()); + verify(throwableSpy, never()).apply(any()); } } } @@ -282,7 +338,7 @@ @Test void calls_the_solver_callback_and_returns_a_handler() { final var solverSpy = Spy.throwingFunction(String::length); final var successSpy = Spy.throwingFunction(String::length); - final var errorSpy = Spy.throwingFunction(e -> -1); + final var errorSpy = Spy.throwingFunction((Throwable e) -> -1); final var handlers = List.of( Maybe.from(okOp).solve(solverSpy), Maybe.from(okOp).solve(successSpy, errorSpy) @@ -303,7 +359,8 @@ @Nested class and_the_error_solver_is_not_provided { @Test void never_calls_the_solver_callback_and_returns_a_handler_with_the_error() { final var successSpy = Spy.throwingFunction(String::length); - final var handler = Maybe.from(throwingOp) + final var handler = Maybe + .from(throwingOp) .solve(successSpy); assertThat(handler.success()).isEmpty(); @@ -317,17 +374,37 @@ } @Nested class and_the_error_solver_is_provided { - @Test void call_only_the_error_callback_and_returns_a_new_effect_handler() { - final var successSpy = Spy.throwingFunction(String::length); - final var errorSpy = Spy.throwingFunction(e -> -1); - final var handler = Maybe.from(throwingOp) - .solve(successSpy, errorSpy); + @Nested class and_the_error_matches_the_type_of_the_arg { + @Test void call_only_the_error_callback_and_returns_a_new_effect_handler() { + final var successSpy = Spy.throwingFunction(String::length); + final var errorSpy = Spy.throwingFunction((Throwable e) -> -1); + final var handler = Maybe + .from(throwingOp) + .solve(successSpy, errorSpy); + + assertThat(handler.success()).contains(-1); + assertThat(handler.error()).isEmpty(); - assertThat(handler.success()).contains(-1); - assertThat(handler.error()).isEmpty(); + verify(successSpy, never()).apply(any()); + verify(errorSpy).apply(FAILURE); + } + } - verify(successSpy, never()).apply(any()); - verify(errorSpy).apply(FAILURE); + @Nested class and_the_error_does_not_match_the_type_of_the_arg { + @Test void call_only_the_error_callback_and_returns_a_new_effect_handler() { + final var successSpy = Spy.throwingFunction(String::length); + final var errorSpy = Spy.throwingFunction((Throwable e) -> -1); + final var handler = Maybe + .from(throwingOp) + .solve(Commons::cast) + .solve(successSpy, errorSpy); + + assertThat(handler.success()).contains(-1); + assertThat(handler.error()).isEmpty(); + + verify(successSpy, never()).apply(any()); + verify(errorSpy).apply(FAILURE); + } } } } @@ -336,9 +413,9 @@ @Nested class effect { @Nested class when_the_value_is_present { @Test void calls_the_solver_callback_and_returns_a_handler() throws FileSystemException { - final var effectSpy = Spy.throwingConsumer(v -> throwingOp.get()); - final var successSpy = Spy.throwingConsumer(v -> throwingOp.get()); - final var errorSpy = Spy.throwingConsumer(err -> { }); + final var effectSpy = Spy.throwingConsumer((String v) -> throwingOp.get()); + final var successSpy = Spy.throwingConsumer((String v) -> throwingOp.get()); + final var errorSpy = Spy.throwingConsumer(err -> { }); final var handler = Maybe.from(okOp); final var newHandlers = List.of( handler.effect(effectSpy), @@ -356,27 +433,12 @@ } @Nested class when_the_error_is_present { - @Nested class and_the_error_callback_is_provided { - @Test void calls_only_the_error_callback_and_returns_a_handler() throws FileSystemException { - final var successSpy = Spy.throwingConsumer(v -> { }); - final var errorSpy = Spy.throwingConsumer(err -> throwingOp.get()); - final var handler = Maybe.from(throwingOp); - final var newHandler = handler.effect(successSpy, errorSpy); - - assertThat(newHandler.error()).contains(FAILURE); - - verify(successSpy, never()).accept(any()); - verify(errorSpy).accept(FAILURE); - } - } - @Nested class and_the_error_callback_is_not_provided { @Test void never_calls_the_effect_callback_and_returns_a_handler_with_the_error() throws FileSystemException { - final var effectSpy = Spy.throwingConsumer(v -> throwingOp.get()); - final var handler = Maybe.from(throwingOp); - final var newHandler = handler.effect(effectSpy); + final var effectSpy = Spy.throwingConsumer((String v) -> throwingOp.get()); + final var handler = Maybe.from(throwingOp).effect(effectSpy); - assertThat(newHandler.error()) + assertThat(handler.error()) .get(THROWABLE) .isExactlyInstanceOf(FAILURE.getClass()) .isEqualTo(FAILURE); @@ -384,6 +446,34 @@ verify(effectSpy, never()).accept(any()); } } + + @Nested class and_the_error_callback_is_provided { + @Nested class and_the_error_matches_the_type_of_the_arg { + @Test void calls_only_the_error_callback_and_returns_a_handler() throws FileSystemException { + final var successSpy = Spy.throwingConsumer(v -> { }); + final var errorSpy = Spy.throwingConsumer((Throwable err) -> throwingOp.get()); + final var handler = Maybe.from(throwingOp).effect(successSpy, errorSpy); + + assertThat(handler.error()).contains(FAILURE); + + verify(successSpy, never()).accept(any()); + verify(errorSpy).accept(FAILURE); + } + } + + @Nested class and_the_error_does_not_match_the_type_of_the_arg { + @Test void calls_only_the_error_callback_and_returns_a_handler() throws FileSystemException { + final var successSpy = Spy.throwingConsumer(v -> { }); + final var errorSpy = Spy.throwingConsumer((Throwable err) -> throwingOp.get()); + final var handler = Maybe.from(throwingOp).solve(Commons::cast).effect(successSpy, errorSpy); + + assertThat(handler.error()).contains(FAILURE); + + verify(successSpy, never()).accept(any()); + verify(errorSpy).accept(FAILURE); + } + } + } } } @@ -474,17 +564,37 @@ final var handler = Maybe.from(okOp); assertThat(handler.orElse(OTHER)).isEqualTo(OK); - assertThat(handler.orElse(RuntimeException::getMessage)).isEqualTo(OK); + assertThat(handler.orElse(Throwable::getMessage)).isEqualTo(OK); } } @Nested class when_the_error_is_present { - @Test void returns_the_provided_value() { - final var handler = Maybe.from(throwingOp); + @Nested class and_the_overload_is_a_fallback { + @Test void returns_the_fallback_value() { + final var handler = Maybe.from(throwingOp); - assertThat(handler.orElse(OTHER)).isEqualTo(OTHER); - assertThat(handler.orElse(FileSystemException::getMessage)).isEqualTo(FAILURE.getMessage()); + assertThat(handler.orElse(OTHER)).isEqualTo(OTHER); + } + } + + @Nested class and_the_overload_is_a_mapper { + @Nested class and_the_error_matches_the_type_of_the_arg { + @Test void returns_the_mapped_value() { + final var handler = Maybe.from(throwingOp); + + assertThat(handler.orElse(Throwable::getMessage)).isEqualTo(FAILURE.getMessage()); + } + } + + @Nested class and_the_error_does_not_match_the_type_of_the_arg { + @Test void returns_the_mapped_value() { + final var handler = Maybe.from(throwingOp).solve(Commons::cast); + + assertThat(handler.orElse(Throwable::getMessage)).isEqualTo(FAILURE.getMessage()); + } + } } + } } @@ -533,7 +643,7 @@ @Nested class orThrow { @Nested class when_the_value_is_present { @Test void returns_the_value() throws FileSystemException { - final var functionSpy = Spy.function((RuntimeException error) -> FAILURE); + final var functionSpy = Spy.function((Throwable error) -> FAILURE); final var handler = Maybe.from(okOp); assertThat(handler.orThrow()).isEqualTo(OK); @@ -544,16 +654,40 @@ } @Nested class when_the_error_is_present { - @Test void throws_the_error() { - final var anotherError = new RuntimeException(OTHER); - final var functionSpy = Spy.function((FileSystemException error) -> anotherError); - final var handler = Maybe.from(throwingOp); + @Nested class and_a_mapper_function_is_not_provided { + @Test void throws_the_error_present_in_the_handler() { + final var handler = Maybe.from(throwingOp); - assertThatCode(handler::orThrow).isEqualTo(FAILURE); - assertThatCode(() -> handler.orThrow(functionSpy)).isEqualTo(anotherError); + assertThatCode(handler::orThrow).isEqualTo(FAILURE); + } + } + + @Nested class and_a_mapper_function_is_provided { + @Nested class and_the_error_matches_the_type_of_the_arg { + @Test void throws_the_error_produced_by_the_function() { + final var anotherError = new RuntimeException(OTHER); + final var functionSpy = Spy.function((Throwable error) -> anotherError); + final var handler = Maybe.from(throwingOp); + + assertThatCode(() -> handler.orThrow(functionSpy)).isEqualTo(anotherError); + + verify(functionSpy).apply(FAILURE); + } + } - verify(functionSpy).apply(FAILURE); + @Nested class and_the_error_does_not_match_the_type_of_the_arg { + @Test void throws_the_error_produced_by_the_function() { + final var anotherError = new RuntimeException(OTHER); + final var handler = Maybe.from(throwingOp).solve(Commons::cast); + + assertThat(handler.success()).isEmpty(); + assertThat(handler.error()).isPresent(); + + assertThatCode(() -> handler.orThrow(x -> anotherError)).isEqualTo(anotherError); + } + } } + } }