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(solver): Add onErrorSolve(..) operations #230

Merged
merged 1 commit into from
Feb 7, 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
51 changes: 51 additions & 0 deletions src/main/java/io/github/joselion/maybe/SolveHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,57 @@ public SolveHandler<T, E> catchError(final Function<? super E, ? extends T> hand
.leftOrElse(this);
}

/**
* If the value was not previously solved, chains another solver function.
*
* <p>This is helpful to try to solve values in different ways, like when
* nesting a try-catch in another catch block, but more functional.
*
* <p>The caught error {@code E} is passed in the argument of the
* {@code solver} function.
*
* @param <X> the type of the error the new solver may throw
* @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 <X extends Throwable> SolveHandler<T, X> onErrorSolve(
final ThrowingFunction<? super E, ? extends T, ? extends X> solver
) {
return this.value
.unwrap(
Maybe.partial(solver),
SolveHandler::from
);
}

/**
* If the value was not previously solved and the type matches the error
* thrown, chains another solver function.
*
* <p>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 <X> the type of the error the new solver may throw
* @param <C> the type of the error to catch
* @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 <X extends Throwable, C extends Throwable> SolveHandler<T, X> onErrorSolve(
final Class<? extends C> ofType,
final ThrowingFunction<? super E, ? extends T, ? extends X> solver
) {
return this.value
.unwrap(
x -> ofType.isInstance(x)
? Maybe.of(x).solve(solver)
: SolveHandler.failure(Commons.cast(x)),
SolveHandler::from
);
}

/**
* Chain another solver covering both cases of success or error of the
* previous solver in two different callbacks.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@
@Nested class when_the_error_is_present {
@Test void returns_a_handler_with_the_propagated_error() throws Throwable {
final var error = new IOException("Something went wrong...");
final var solverSpy = Spy.<ThrowingFunction<AutoCloseable, ?, ?>>lambda(fis -> "");
final var solverSpy = Spy.throwingFunction(fis -> "");
final var handler = CloseableHandler.failure(error)
.solve(solverSpy);

Expand Down Expand Up @@ -176,7 +176,7 @@
@Nested class when_the_error_is_present {
@Test void returns_a_handler_with_the_propagated_error() throws Throwable {
final var error = new IOException("Something went wrong...");
final var effectSpy = Spy.<ThrowingConsumer<AutoCloseable, ?>>lambda(res -> { });
final var effectSpy = Spy.throwingConsumer(res -> { });
final var handler = CloseableHandler.failure(error)
.effect(effectSpy);

Expand Down
32 changes: 13 additions & 19 deletions src/test/java/io/github/joselion/maybe/EffectHandlerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import static org.assertj.core.api.InstanceOfAssertFactories.THROWABLE;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import java.nio.file.AccessDeniedException;
Expand All @@ -15,7 +14,6 @@
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import io.github.joselion.maybe.util.function.ThrowingConsumer;
import io.github.joselion.maybe.util.function.ThrowingRunnable;
import io.github.joselion.testing.Spy;
import io.github.joselion.testing.UnitTest;
Expand Down Expand Up @@ -66,7 +64,7 @@

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

verify(runnableSpy, times(1)).run();
verify(runnableSpy).run();
}
}

Expand All @@ -91,7 +89,7 @@
Maybe.from(throwingOp)
.doOnError(FileSystemException.class, consumerSpy);

verify(consumerSpy, times(1)).accept(FAIL_EXCEPTION);
verify(consumerSpy).accept(FAIL_EXCEPTION);
}
}

Expand All @@ -114,7 +112,7 @@
Maybe.from(throwingOp)
.doOnError(consumerSpy);

verify(consumerSpy, times(1)).accept(FAIL_EXCEPTION);
verify(consumerSpy).accept(FAIL_EXCEPTION);
}
}
}
Expand Down Expand Up @@ -143,7 +141,7 @@

assertThat(handler.error()).isEmpty();

verify(consumerSpy, times(1)).accept(FAIL_EXCEPTION);
verify(consumerSpy).accept(FAIL_EXCEPTION);
}
}

Expand All @@ -168,7 +166,7 @@

assertThat(handler.error()).isEmpty();

verify(consumerSpy, times(1)).accept(FAIL_EXCEPTION);
verify(consumerSpy).accept(FAIL_EXCEPTION);
}
}
}
Expand All @@ -195,9 +193,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<RuntimeException, FileSystemException>>lambda(
err -> throwingOp.run()
);
final var errorSpy = Spy.throwingConsumer(err -> throwingOp.run());
final var handler = Maybe.from(noOp);
final var newHandlers = List.of(
handler.effect(effectSpy),
Expand All @@ -209,27 +205,25 @@
assertThat(newHandler.error()).contains(FAIL_EXCEPTION);
});

verify(effectSpy, times(1)).run();
verify(successSpy, times(1)).run();
verify(effectSpy).run();
verify(successSpy).run();
verify(errorSpy, never()).accept(any());
}
}

@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<FileSystemException>>lambda(() -> { });
final var errorSpy = Spy.<ThrowingConsumer<FileSystemException, FileSystemException>>lambda(
err -> throwingOp.run()
);
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);

assertThat(newHandler).isNotSameAs(handler);
assertThat(newHandler.error()).contains(FAIL_EXCEPTION);

verify(successSpy, never()).run();
verify(errorSpy, times(1)).accept(FAIL_EXCEPTION);
verify(errorSpy).accept(FAIL_EXCEPTION);
}
}

Expand All @@ -256,7 +250,7 @@

handler.orElse(consumerSpy);

verify(consumerSpy, times(1)).accept(FAIL_EXCEPTION);
verify(consumerSpy).accept(FAIL_EXCEPTION);
}
}

Expand All @@ -282,7 +276,7 @@
assertThatCode(handler::orThrow).isEqualTo(FAIL_EXCEPTION);
assertThatCode(() -> handler.orThrow(functionSpy)).isEqualTo(anotherError);

verify(functionSpy, times(1)).apply(FAIL_EXCEPTION);
verify(functionSpy).apply(FAIL_EXCEPTION);
}
}

Expand Down
45 changes: 22 additions & 23 deletions src/test/java/io/github/joselion/maybe/MaybeTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import static org.assertj.core.api.InstanceOfAssertFactories.optional;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import java.io.FileInputStream;
Expand Down Expand Up @@ -98,13 +97,13 @@
@Nested class when_a_value_is_provided {
@Nested class and_the_operation_succeeds {
@Test void returns_a_handler_with_the_value() throws IOException {
final var supplierSpy = Spy.<ThrowingSupplier<String, IOException>>lambda(() -> OK);
final var supplierSpy = Spy.throwingSupplier(() -> OK);
final var handler = Maybe.from(supplierSpy);

assertThat(handler.success()).contains(OK);
assertThat(handler.error()).isEmpty();

verify(supplierSpy, times(1)).get();
verify(supplierSpy).get();
}
}

Expand All @@ -116,20 +115,20 @@
assertThat(handler.success()).isEmpty();
assertThat(handler.error()).contains(FAIL_EXCEPTION);

verify(supplierSpy, times(1)).get();
verify(supplierSpy).get();
}
}
}

@Nested class when_an_effect_is_passed {
@Nested class and_the_operation_succeeds {
@Test void returns_an_empty_handler() {
final var runnableSpy = Spy.<ThrowingRunnable<RuntimeException>>lambda(() -> { });
final var runnableSpy = Spy.throwingRunnable(() -> { });
final var handler = Maybe.from(runnableSpy);

assertThat(handler.error()).isEmpty();

verify(runnableSpy, times(1)).run();
verify(runnableSpy).run();
}
}

Expand All @@ -140,7 +139,7 @@

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

verify(runnableSpy, times(1)).run();
verify(runnableSpy).run();
}
}
}
Expand All @@ -149,7 +148,7 @@
@Nested class partial {
@Nested class when_a_function_is_provided {
@Test void returns_a_function_that_takes_a_value_and_returns_a_solve_handler() throws IOException {
final var successSpy = Spy.<ThrowingFunction<String, Integer, RuntimeException>>lambda(String::length);
final var successSpy = Spy.throwingFunction(String::length);
final var failureSpy = Spy.lambda(failFunction);

assertThat(Maybe.partial(successSpy).apply(OK))
Expand All @@ -161,14 +160,14 @@
.extracting(SolveHandler::error, optional(IOException.class))
.contains(FAIL_EXCEPTION);

verify(successSpy, times(1)).apply(OK);
verify(failureSpy, times(1)).apply(OK);
verify(successSpy).apply(OK);
verify(failureSpy).apply(OK);
}
}

@Nested class when_a_consumer_is_provided {
@Test void returns_a_function_that_takes_a_value_and_returns_an_effect_handler() throws IOException {
final var successSpy = Spy.<ThrowingConsumer<String, RuntimeException>>lambda(v -> { });
final var successSpy = Spy.throwingConsumer(v -> { });
final var failureSpy = Spy.lambda(failConsumer);

assertThat(Maybe.partial(successSpy).apply(OK))
Expand All @@ -181,8 +180,8 @@
.extracting(EffectHandler::error, optional(IOException.class))
.contains(FAIL_EXCEPTION);

verify(successSpy, times(1)).accept(OK);
verify(failureSpy, times(1)).accept(OK);
verify(successSpy).accept(OK);
verify(failureSpy).accept(OK);
}
}
}
Expand Down Expand Up @@ -270,13 +269,13 @@
@Nested class solve {
@Nested class when_the_value_is_present {
@Test void the_callback_is_called_with_the_value() {
final var functionSpy = Spy.<ThrowingFunction<Integer, String, RuntimeException>>lambda(v -> OK);
final var functionSpy = Spy.throwingFunction(v -> OK);
final var handler = Maybe.of(1).solve(functionSpy);

assertThat(handler.success()).contains(OK);
assertThat(handler.error()).isEmpty();

verify(functionSpy, times(1)).apply(1);
verify(functionSpy).apply(1);
}
}

Expand Down Expand Up @@ -304,7 +303,7 @@
assertThat(handler.success()).contains(OK);
assertThat(handler.error()).isEmpty();

verify(functionSpy, times(1)).apply(OK);
verify(functionSpy).apply(OK);
}
}

Expand All @@ -317,27 +316,27 @@
assertThat(handler.success()).isEmpty();
assertThat(handler.error()).contains(FAIL_EXCEPTION);

verify(functionSpy, times(1)).apply(OK);
verify(functionSpy).apply(OK);
}
}
}

@Nested class effect {
@Nested class when_the_value_is_present {
@Test void the_callback_is_called_with_the_value() {
final var consumerSpy = Spy.<ThrowingConsumer<String, RuntimeException>>lambda(v -> { });
final var consumerSpy = Spy.throwingConsumer(v -> { });
final var handler = Maybe.of(OK)
.effect(consumerSpy);

assertThat(handler.error()).isEmpty();

verify(consumerSpy, times(1)).accept(OK);
verify(consumerSpy).accept(OK);
}
}

@Nested class when_the_value_is_not_present {
@Test void the_callback_is_never_called_and_returns_a_handler_with_an_error() {
final var consumerSpy = Spy.<ThrowingConsumer<Object, RuntimeException>>lambda(v -> { });
final var consumerSpy = Spy.throwingConsumer(v -> { });
final var handler = Maybe.empty()
.effect(consumerSpy);

Expand All @@ -352,13 +351,13 @@

@Nested class when_the_new_operation_succeeds {
@Test void returns_an_empty_handler() {
final var consumerSpy = Spy.<ThrowingConsumer<String, RuntimeException>>lambda(v -> { });
final var consumerSpy = Spy.throwingConsumer(v -> { });
final var handler = Maybe.of(OK)
.effect(consumerSpy);

assertThat(handler.error()).isEmpty();

verify(consumerSpy, times(1)).accept(OK);
verify(consumerSpy).accept(OK);
}
}

Expand All @@ -370,7 +369,7 @@

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

verify(consumerSpy, times(1)).accept(OK);
verify(consumerSpy).accept(OK);
}
}
}
Expand Down
Loading
Loading