Skip to content

Commit

Permalink
feat(effect): Add EffectHandler .solve(..) operation (#232)
Browse files Browse the repository at this point in the history
  • Loading branch information
JoseLion authored Feb 11, 2024
1 parent 36eae26 commit 0f31880
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 44 deletions.
4 changes: 2 additions & 2 deletions src/main/java/io/github/joselion/maybe/CloseableHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ Optional<E> error() {
*
* @param <S> the type of the value returned by the {@code solver}
* @param <X> the type of exception the {@code solver} may throw
* @param solver the checked function operation to solve
* @param solver the throwing function operation to solve
* @return a {@link SolveHandler} with either the value solved or the thrown
* exception to be handled
*/
Expand Down Expand Up @@ -126,7 +126,7 @@ public <S, X extends Throwable> SolveHandler<S, X> solve(
* resource nor the error is present.
*
* @param <X> the type of exception the {@code effect} may throw
* @param effect the checked consumer operation to execute
* @param effect the throwing consumer operation to execute
* @return an {@link EffectHandler} with either the thrown exception to be
* handled or empty
*/
Expand Down
43 changes: 34 additions & 9 deletions src/main/java/io/github/joselion/maybe/EffectHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@

import io.github.joselion.maybe.helpers.Commons;
import io.github.joselion.maybe.util.function.ThrowingConsumer;
import io.github.joselion.maybe.util.function.ThrowingFunction;
import io.github.joselion.maybe.util.function.ThrowingRunnable;
import io.github.joselion.maybe.util.function.ThrowingSupplier;

/**
* EffectHandler is an API to handle the posible error of a {@link Maybe}'s
Expand Down Expand Up @@ -67,7 +69,7 @@ Optional<E> error() {
/**
* Runs an effect if the operation succeeds.
*
* @param effect a runnable function
* @param effect the runnable to run on success
* @return the same handler to continue chainning operations
*/
public EffectHandler<E> doOnSuccess(final Runnable effect) {
Expand All @@ -85,7 +87,7 @@ public EffectHandler<E> doOnSuccess(final Runnable effect) {
*
* @param <X> the type of the error to match
* @param ofType a class instance of the error type to match
* @param effect a consumer function that recieves the caught error
* @param effect a consumer that recieves the caught error
* @return the same handler to continue chainning operations
*/
public <X extends Throwable> EffectHandler<E> doOnError(final Class<X> ofType, final Consumer<? super X> effect) {
Expand All @@ -101,7 +103,7 @@ public <X extends Throwable> EffectHandler<E> doOnError(final Class<X> ofType, f
* Run an effect if the error is present. The error is passed in the argument
* of the {@code effect} consumer.
*
* @param effect a consumer function that recieves the caught error
* @param effect a consumer that recieves the caught error
* @return the same handler to continue chainning operations
*/
public EffectHandler<E> doOnError(final Consumer<Throwable> effect) {
Expand All @@ -118,7 +120,7 @@ public EffectHandler<E> doOnError(final Consumer<Throwable> effect) {
*
* @param <X> the type of the error to catch
* @param ofType thetype of the error to catch
* @param handler a consumer function that receives the caught error
* @param handler a consumer that receives the caught error
* @return an empty handler if an error instance of the provided type was
* caught. The same handler instance otherwise
*/
Expand All @@ -139,7 +141,7 @@ public <X extends Throwable> EffectHandler<E> catchError(final Class<X> ofType,
* the operations returns an empty {@link EffectHandler}. Otherwise, the same
* instance is returned.
*
* @param handler a consumer function that recieves the caught error
* @param handler a consumer that recieves the caught error
* @return an empty handler if the error is present. The same handler
* instance otherwise
*/
Expand All @@ -157,8 +159,8 @@ public EffectHandler<E> catchError(final Consumer<Throwable> handler) {
* previous effect in two different callbacks.
*
* @param <X> the type of the error the new effect may throw
* @param onSuccess a runnable checked function to run in case of succeess
* @param onError a runnable checked function to run in case of error
* @param onSuccess a throwing runnable to run in case of succeess
* @param onError a throwing runnable to run in case of error
* @return a new {@link EffectHandler} representing the result of one of the
* invoked callback
*/
Expand All @@ -184,19 +186,42 @@ public <X extends Throwable> EffectHandler<X> effect(
* either empty or has a different error cause by the next effect.
*
* @param <X> the type of the error the new effect may throw
* @param effect a runnable checked function to run in case of succeess
* @param effect a throwing runnable to run in case of succeess
* @return a new {@link EffectHandler} that is either empty or with the
* thrown error
*/
public <X extends Throwable> EffectHandler<X> effect(final ThrowingRunnable<? extends X> effect) {
return this.effect(effect, err -> { });
}

/**
* Chain a solver covering both cases of success or error of the
* previous effect in two different callbacks.
*
* <p>The 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.
*
* @param <T> the type of the value to be solved
* @param <X> the type of exception the callbacks may throw
* @param onSuccess a throwing supplier that solves a value
* @param onError a throwing function that receives the error and solves a value
* @return a {@link SolveHandler} with either the solved value or the error
*/
public <T, X extends Throwable> SolveHandler<T, X> solve(
final ThrowingSupplier<T, X> onSuccess,
final ThrowingFunction<Throwable, T, X> onError
) {
return this.error
.map(Maybe.partial(onError))
.orElseGet(() -> Maybe.from(onSuccess));
}

/**
* Terminal operation to handle the error if present. The error is passed in
* the argument of the {@code effect} consumer.
*
* @param effect a consumer function that receives the caught error
* @param effect a consumer that receives the caught error
*/
public void orElse(final Consumer<Throwable> effect) {
this.error.ifPresent(effect);
Expand Down
32 changes: 16 additions & 16 deletions src/main/java/io/github/joselion/maybe/SolveHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ Optional<E> error() {
* Run an effect if the operation solved successfully. The solved value
* is passed in the argument of the {@code effect} consumer.
*
* @param effect a function that receives the solved value
* @param effect a consumer that receives the solved value
* @return the same handler to continue chainning operations
*/
public SolveHandler<T, E> doOnSuccess(final Consumer<? super T> effect) {
Expand All @@ -104,7 +104,7 @@ public SolveHandler<T, E> doOnSuccess(final Consumer<? super T> effect) {
*
* @param <X> the type of the error to match
* @param ofType a class instance of the error type to match
* @param effect a consumer function that receives the caught error
* @param effect a consumer that receives the caught error
* @return the same handler to continue chainning operations
*/
public <X extends Throwable> SolveHandler<T, E> doOnError(final Class<X> ofType, final Consumer<? super X> effect) {
Expand All @@ -121,7 +121,7 @@ public <X extends Throwable> SolveHandler<T, E> doOnError(final Class<X> ofType,
* Run an effect if the error is present. The error is passed in the argument
* of the {@code effect} consumer.
*
* @param effect a consumer function that receives the caught error
* @param effect a consumer that receives the caught error
* @return the same handler to continue chainning operations
*/
public SolveHandler<T, E> doOnError(final Consumer<Throwable> effect) {
Expand Down Expand Up @@ -233,10 +233,10 @@ public <C extends Throwable, X extends Throwable> SolveHandler<T, X> onErrorSolv
*
* @param <S> the type of value returned by the next operation
* @param <X> the type of exception the new solver may throw
* @param onSuccess a checked function that receives the current value
* and solves another
* @param onError a checked function that receives the error and
* solves another value
* @param onSuccess a throwing function that receives the current value
* and solves another
* @param onError a throwing function that receives the error and solves
* another value
* @return a new handler with either the solved value or the error
*/
public <S, X extends Throwable> SolveHandler<S, X> solve(
Expand All @@ -255,8 +255,8 @@ public <S, X extends Throwable> SolveHandler<S, X> solve(
*
* @param <S> the type of value returned by the next operation
* @param <X> the type of exception the new solver may throw
* @param solver a checked function that receives the current value and
* solves another
* @param solver a throwing function that receives the current value and
* solves another
* @return a new handler with either the solved value or an error
*/
public <S, X extends Throwable> SolveHandler<S, X> solve(
Expand All @@ -275,8 +275,8 @@ public <S, X extends Throwable> SolveHandler<S, X> solve(
* error cases in two different callbacks.
*
* @param <X> the type of the error the effect may throw
* @param onSuccess a consumer checked function to run in case of succeess
* @param onError a consumer checked function to run in case of error
* @param onSuccess a throwing consumer to run in case of succeess
* @param onError a throwing consumer to run in case of error
* @return an {@link EffectHandler} representing the result of one of the
* invoked callback
*/
Expand All @@ -296,7 +296,7 @@ public <X extends Throwable> EffectHandler<X> effect(
* downstream.
*
* @param <X> the type of the error the effect may throw
* @param effect a consume checked function to run in case of succeess
* @param effect a throwing consume to run in case of succeess
* @return a new {@link EffectHandler} representing the result of the success
* callback or containg the error
*/
Expand Down Expand Up @@ -389,14 +389,14 @@ public T orElse(final Function<Throwable, ? extends T> mapper) {
}

/**
* Returns the solved value if present. Otherwise, the result produced by
* the supplying function as another value.
* Returns the solved value if present. Otherwise, another value produced by
* the {@code supplier}.
*
* @apiNote Use this method instead of {@link #orElse(Object)} to do lazy
* evaluation of the produced value. That means that the "else"
* value won't be evaluated if the error is not present.
* @param supplier the supplying function that produces another value if the
* operation failed to solve
* @param supplier the suplier that produces another value if the operation
* failed to solve
* @return the solved value if present. Another value otherwise
*/
public T orElseGet(final Supplier<? extends T> supplier) {
Expand Down
76 changes: 61 additions & 15 deletions src/test/java/io/github/joselion/maybe/EffectHandlerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
throw FAILURE;
};

private final ThrowingRunnable<RuntimeException> noOp = () -> { };
private final ThrowingRunnable<RuntimeException> noop = () -> { };

@Nested class empty {
@Test void returns_an_empty_handler() {
Expand Down Expand Up @@ -62,7 +62,7 @@
@Test void calls_the_effect_callback() {
final var runnableSpy = Spy.runnable(() -> { });

Maybe.from(noOp).doOnSuccess(runnableSpy);
Maybe.from(noop).doOnSuccess(runnableSpy);

verify(runnableSpy).run();
}
Expand Down Expand Up @@ -126,7 +126,7 @@

Maybe
.from(throwingOp)
.effect(() -> { })
.effect(noop)
.doOnError(consumerSpy);

verify(consumerSpy).accept(FAILURE);
Expand All @@ -140,7 +140,7 @@
final var runtimeSpy = Spy.<RuntimeException>consumer(error -> { });
final var throwableSpy = Spy.<Throwable>consumer(error -> { });

Maybe.from(noOp)
Maybe.from(noop)
.doOnError(RuntimeException.class, runtimeSpy)
.doOnError(throwableSpy);

Expand Down Expand Up @@ -198,7 +198,7 @@
final var consumerSpy = Spy.<Throwable>consumer(e -> { });
final var handler = Maybe
.from(throwingOp)
.effect(() -> { })
.effect(noop)
.catchError(consumerSpy);

assertThat(handler.error()).isEmpty();
Expand All @@ -214,8 +214,8 @@
final var runtimeSpy = Spy.<RuntimeException>consumer(e -> { });
final var throwableSpy = Spy.<Throwable>consumer(e -> { });
final var handlers = List.of(
Maybe.from(noOp).catchError(RuntimeException.class, runtimeSpy),
Maybe.from(noOp).catchError(throwableSpy)
Maybe.from(noop).catchError(RuntimeException.class, runtimeSpy),
Maybe.from(noop).catchError(throwableSpy)
);

assertThat(handlers).isNotEmpty().allSatisfy(handler -> {
Expand All @@ -234,7 +234,7 @@
final var effectSpy = Spy.lambda(throwingOp);
final var successSpy = Spy.lambda(throwingOp);
final var errorSpy = Spy.throwingConsumer((Throwable err) -> throwingOp.run());
final var handler = Maybe.from(noOp);
final var handler = Maybe.from(noop);
final var newHandlers = List.of(
handler.effect(effectSpy),
handler.effect(successSpy, errorSpy)
Expand All @@ -255,7 +255,7 @@
@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_with_the_error() throws FileSystemException {
final var successSpy = Spy.throwingRunnable(() -> { });
final var successSpy = Spy.throwingRunnable(noop);
final var errorSpy = Spy.throwingConsumer((Throwable err) -> throwingOp.run());
final var handler = Maybe.from(throwingOp).effect(successSpy, errorSpy);

Expand All @@ -268,9 +268,9 @@

@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 successSpy = Spy.throwingRunnable(noop);
final var errorSpy = Spy.throwingConsumer((Throwable err) -> throwingOp.run());
final var handler = Maybe.from(throwingOp).effect(() -> { }).effect(successSpy, errorSpy);
final var handler = Maybe.from(throwingOp).effect(noop).effect(successSpy, errorSpy);

assertThat(handler.error()).contains(FAILURE);

Expand All @@ -293,6 +293,52 @@
}
}

@Nested class solve {
@Nested class when_the_error_is_not_present {
@Test void calls_only_the_success_callback_and_returns_a_SolveHandler() {
final var successSpy = Spy.throwingSupplier(() -> "OK");
final var errorSpy = Spy.throwingFunction((Throwable e) -> "FAIL");
final var handler = Maybe.from(noop).solve(successSpy, errorSpy);

assertThat(handler).isInstanceOf(SolveHandler.class);
assertThat(handler.success()).contains("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_a_handler_SolveHandler() {
final var successSpy = Spy.throwingSupplier(() -> "OK");
final var errorSpy = Spy.throwingFunction((Throwable e) -> "FAIL");
final var handler = Maybe.from(throwingOp).solve(successSpy, errorSpy);

assertThat(handler).isInstanceOf(SolveHandler.class);
assertThat(handler.success()).contains("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_a_handler_SolveHandler() {
final var successSpy = Spy.throwingSupplier(() -> "OK");
final var errorSpy = Spy.throwingFunction((Throwable e) -> "FAIL");
final var handler = Maybe.from(throwingOp).effect(noop).solve(successSpy, errorSpy);

assertThat(handler).isInstanceOf(SolveHandler.class);
assertThat(handler.success()).contains("FAIL");

verify(successSpy, never()).get();
verify(errorSpy).apply(FAILURE);
}
}
}
}

@Nested class orElse {
@Nested class when_the_error_is_present {
@Nested class and_the_error_matches_the_type_of_the_arg {
Expand All @@ -309,7 +355,7 @@
@Nested class and_the_error_does_not_match_the_type_of_the_arg {
@Test void calls_the_effect_callback() {
final var consumerSpy = Spy.<Throwable>consumer(e -> { });
final var handler = Maybe.from(throwingOp).effect(() -> { });
final var handler = Maybe.from(throwingOp).effect(noop);

handler.orElse(consumerSpy);

Expand All @@ -321,7 +367,7 @@
@Nested class when_the_error_is_not_present {
@Test void never_calls_the_effect_callback() {
final var consumerSpy = Spy.<Throwable>consumer(e -> { });
final var handler = Maybe.from(noOp);
final var handler = Maybe.from(noop);

handler.orElse(consumerSpy);

Expand Down Expand Up @@ -357,7 +403,7 @@
@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(() -> { });
final var handler = Maybe.from(throwingOp).effect(noop);

assertThatCode(() -> handler.orThrow(functionSpy)).isEqualTo(anotherError);

Expand All @@ -370,7 +416,7 @@
@Nested class when_the_error_is_not_present {
@Test void no_exception_is_thrown() {
final var functionSpy = Spy.function((Throwable err) -> FAILURE);
final var handler = Maybe.from(noOp);
final var handler = Maybe.from(noop);

assertThatCode(handler::orThrow).doesNotThrowAnyException();
assertThatCode(() -> handler.orThrow(functionSpy)).doesNotThrowAnyException();
Expand Down
Loading

0 comments on commit 0f31880

Please sign in to comment.