Skip to content

Commit

Permalink
feat(effect): Add .thenGet(..) and .thenReturn(..) operations (#234)
Browse files Browse the repository at this point in the history
  • Loading branch information
JoseLion authored Feb 11, 2024
1 parent 8473fb6 commit c460d9e
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 1 deletion.
42 changes: 41 additions & 1 deletion src/main/java/io/github/joselion/maybe/EffectHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -200,7 +201,7 @@ public <X extends Throwable> EffectHandler<X> effect(final ThrowingRunnable<? ex
*
* <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.
* invoked. It depends on whether the previous effect(s) 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
Expand All @@ -217,6 +218,45 @@ public <T, X extends Throwable> SolveHandler<T, X> 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.
*
* <p>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 <T> 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> T thenGet(final Supplier<? extends T> onSuccess, final Function<Throwable, ? extends T> 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.
*
* <p>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 <T> 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> 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.
Expand Down
61 changes: 61 additions & 0 deletions src/test/java/io/github/joselion/maybe/EffectHandlerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit c460d9e

Please sign in to comment.