Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(effect): Add .thenGet(..) and .thenReturn(..) operations #234

Merged
merged 1 commit into from
Feb 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading