Skip to content

Commit

Permalink
feat(solver): Add onErrorSolve(..) operations
Browse files Browse the repository at this point in the history
  • Loading branch information
JoseLion committed Feb 7, 2024
1 parent 58b0392 commit 406284f
Show file tree
Hide file tree
Showing 6 changed files with 234 additions and 77 deletions.
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

0 comments on commit 406284f

Please sign in to comment.