Skip to content

Commit

Permalink
fix(core): Handle possible nulls in factory methods (#222)
Browse files Browse the repository at this point in the history
  • Loading branch information
JoseLion authored Dec 4, 2023
1 parent 9ee0fd9 commit c01e34d
Show file tree
Hide file tree
Showing 8 changed files with 201 additions and 51 deletions.
34 changes: 21 additions & 13 deletions src/main/java/io/github/joselion/maybe/CloseableHandler.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.github.joselion.maybe;

import static java.util.Objects.isNull;

import java.util.Optional;

import io.github.joselion.maybe.helpers.Commons;
Expand All @@ -22,12 +24,8 @@ public class CloseableHandler<T extends AutoCloseable, E extends Throwable> {

private final Either<E, T> value;

private CloseableHandler(final T resource) {
this.value = Either.ofRight(resource);
}

private CloseableHandler(final E error) {
this.value = Either.ofLeft(error);
private CloseableHandler(final Either<E, T> value) {
this.value = value;
}

/**
Expand All @@ -39,7 +37,12 @@ private CloseableHandler(final E error) {
* @return a new instance of CloseableHandler with the given resource
*/
static <T extends AutoCloseable, E extends Throwable> CloseableHandler<T, E> from(final T resource) {
return new CloseableHandler<>(resource);
final var nullException = new NullPointerException("The \"Maybe<T>\" resource solved to null");
final var either = isNull(resource) // NOSONAR
? Either.<E, T>ofLeft(Commons.cast(nullException))
: Either.<E, T>ofRight(resource);

return new CloseableHandler<>(either);
}

/**
Expand All @@ -51,7 +54,12 @@ static <T extends AutoCloseable, E extends Throwable> CloseableHandler<T, E> fro
* @return a new instance of the failed CloseableHandler with the error
*/
static <T extends AutoCloseable, E extends Throwable> CloseableHandler<T, E> failure(final E error) {
return new CloseableHandler<>(error);
final var nullException = new NullPointerException("The \"Maybe<T>\" error was null");
final var either = isNull(error) // NOSONAR
? Either.<E, T>ofLeft(Commons.cast(nullException))
: Either.<E, T>ofLeft(error);

return new CloseableHandler<>(either);
}

/**
Expand Down Expand Up @@ -95,13 +103,13 @@ public <S, X extends Throwable> SolveHandler<S, X> solve(
return this.value
.mapLeft(Commons::<X>cast)
.unwrap(
SolveHandler::ofError,
SolveHandler::failure,
resource -> {
try (var res = resource) {
return SolveHandler.ofSuccess(solver.apply(res));
return SolveHandler.from(solver.apply(res));
} catch (final Throwable e) { //NOSONAR
final var error = Commons.<X>cast(e);
return SolveHandler.ofError(error);
return SolveHandler.failure(error);
}
}
);
Expand Down Expand Up @@ -154,14 +162,14 @@ public <X extends Throwable> EffectHandler<X> effect(
return this.value
.mapLeft(Commons::<X>cast)
.unwrap(
EffectHandler::ofError,
EffectHandler::failure,
resource -> {
try (var res = resource) {
effect.accept(res);
return EffectHandler.empty();
} catch (final Throwable e) { // NOSONAR
final var error = Commons.<X>cast(e);
return EffectHandler.ofError(error);
return EffectHandler.failure(error);
}
}
);
Expand Down
11 changes: 9 additions & 2 deletions src/main/java/io/github/joselion/maybe/EffectHandler.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package io.github.joselion.maybe;

import static java.util.Objects.isNull;

import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;

import org.eclipse.jdt.annotation.Nullable;

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

Expand Down Expand Up @@ -45,8 +48,12 @@ static <E extends Throwable> EffectHandler<E> empty() {
* @param error the error to instanciate the EffectHandler
* @return a EffectHandler instance with an error value
*/
static <E extends Throwable> EffectHandler<E> ofError(final E error) {
return new EffectHandler<>(error);
static <E extends Throwable> EffectHandler<E> failure(final E error) {
return new EffectHandler<>(
isNull(error) // NOSONAR
? Commons.cast(new NullPointerException("The \"Maybe<T>\" error was null"))
: error
);
}

/**
Expand Down
10 changes: 5 additions & 5 deletions src/main/java/io/github/joselion/maybe/Maybe.java
Original file line number Diff line number Diff line change
Expand Up @@ -157,10 +157,10 @@ public static <T, E extends Throwable> SolveHandler<T, E> from(
final ThrowingSupplier<? extends T, ? extends E> solver
) {
try {
return SolveHandler.ofSuccess(solver.get());
return SolveHandler.from(solver.get());
} catch (Throwable e) { // NOSONAR
final var error = Commons.<E>cast(e);
return SolveHandler.ofError(error);
return SolveHandler.failure(error);
}
}

Expand Down Expand Up @@ -199,7 +199,7 @@ public static <E extends Throwable> EffectHandler<E> from(final ThrowingRunnable
return EffectHandler.empty();
} catch (Throwable e) { // NOSONAR
final var error = Commons.<E>cast(e);
return EffectHandler.ofError(error);
return EffectHandler.failure(error);
}
}

Expand Down Expand Up @@ -444,7 +444,7 @@ public <U, E extends Throwable> SolveHandler<U, E> solve(
.orElseThrow();
} catch (final NoSuchElementException e) {
final var error = Commons.<E>cast(e);
return SolveHandler.ofError(error);
return SolveHandler.failure(error);
}
}

Expand Down Expand Up @@ -484,7 +484,7 @@ public <E extends Throwable> EffectHandler<E> effect(final ThrowingConsumer<? su
.orElseThrow();
} catch (final NoSuchElementException e) {
final var error = Commons.<E>cast(e);
return EffectHandler.ofError(error);
return EffectHandler.failure(error);
}
}

Expand Down
38 changes: 25 additions & 13 deletions src/main/java/io/github/joselion/maybe/SolveHandler.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.github.joselion.maybe;

import static java.util.Objects.isNull;

import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
Expand Down Expand Up @@ -39,8 +41,13 @@ private SolveHandler(final Either<E, T> value) {
* @param success the success value to instantiate the SolveHandler
* @return a SolveHandler instance with a success value
*/
static <T, E extends Throwable> SolveHandler<T, E> ofSuccess(final T success) {
return new SolveHandler<>(Either.ofRight(success));
static <T, E extends Throwable> SolveHandler<T, E> from(final T success) {
final var nullException = new NullPointerException("The \"Maybe<T>\" value solved to null");
final var either = isNull(success) // NOSONAR
? Either.<E, T>ofLeft(Commons.cast(nullException))
: Either.<E, T>ofRight(success);

return new SolveHandler<>(either);
}

/**
Expand All @@ -51,8 +58,13 @@ static <T, E extends Throwable> SolveHandler<T, E> ofSuccess(final T success) {
* @param error the error to instantiate the SolveHandler
* @return a SolveHandler instance with an error value
*/
static <T, E extends Throwable> SolveHandler<T, E> ofError(final E error) {
return new SolveHandler<>(Either.ofLeft(error));
static <T, E extends Throwable> SolveHandler<T, E> failure(final E error) {
final var nullException = new NullPointerException("The \"Maybe<T>\" error was null");
final var either = isNull(error) // NOSONAR
? Either.<E, T>ofLeft(Commons.cast(nullException))
: Either.<E, T>ofLeft(error);

return new SolveHandler<>(either);
}

/**
Expand Down Expand Up @@ -140,7 +152,7 @@ public <X extends Throwable> SolveHandler<T, E> catchError(
.filter(ofType::isInstance)
.map(ofType::cast)
.map(handler)
.map(SolveHandler::<T, E>ofSuccess)
.map(SolveHandler::<T, E>from)
.orElse(this);
}

Expand All @@ -156,7 +168,7 @@ public <X extends Throwable> SolveHandler<T, E> catchError(
public SolveHandler<T, E> catchError(final Function<? super E, ? extends T> handler) {
return this.value
.mapLeft(handler)
.mapLeft(SolveHandler::<T, E>ofSuccess)
.mapLeft(SolveHandler::<T, E>from)
.leftOrElse(this);
}

Expand Down Expand Up @@ -229,7 +241,7 @@ public <S, X extends Throwable> SolveHandler<S, X> solve(
return this.value
.mapLeft(Commons::<X>cast)
.unwrap(
SolveHandler::ofError,
SolveHandler::failure,
Maybe.partial(solver)
);
}
Expand Down Expand Up @@ -305,7 +317,7 @@ public <X extends Throwable> EffectHandler<X> effect(final ThrowingConsumer<? su
return this.value
.mapLeft(Commons::<X>cast)
.unwrap(
EffectHandler::ofError,
EffectHandler::failure,
Maybe.partial(effect)
);
}
Expand Down Expand Up @@ -341,8 +353,8 @@ public <U> SolveHandler<U, E> map(final Function<? super T, ? extends U> mapper)
return this.value
.mapRight(mapper)
.unwrap(
SolveHandler::ofError,
SolveHandler::ofSuccess
SolveHandler::failure,
SolveHandler::from
);
}

Expand All @@ -358,12 +370,12 @@ public <U> SolveHandler<U, E> map(final Function<? super T, ? extends U> mapper)
*/
public <U> SolveHandler<U, ClassCastException> cast(final Class<U> type) {
return this.value.unwrap(
error -> ofError(new ClassCastException(error.getMessage())),
error -> failure(new ClassCastException(error.getMessage())),
success -> {
try {
return ofSuccess(type.cast(success));
return from(type.cast(success));
} catch (ClassCastException error) {
return ofError(error);
return failure(error);
}
}
);
Expand Down
10 changes: 5 additions & 5 deletions src/main/java17/io/github/joselion/maybe/Maybe.java
Original file line number Diff line number Diff line change
Expand Up @@ -157,10 +157,10 @@ public static <T, E extends Throwable> SolveHandler<T, E> from(
final ThrowingSupplier<? extends T, ? extends E> solver
) {
try {
return SolveHandler.ofSuccess(solver.get());
return SolveHandler.from(solver.get());
} catch (Throwable e) { // NOSONAR
final var error = Commons.<E>cast(e);
return SolveHandler.ofError(error);
return SolveHandler.failure(error);
}
}

Expand Down Expand Up @@ -199,7 +199,7 @@ public static <E extends Throwable> EffectHandler<E> from(final ThrowingRunnable
return EffectHandler.empty();
} catch (Throwable e) { // NOSONAR
final var error = Commons.<E>cast(e);
return EffectHandler.ofError(error);
return EffectHandler.failure(error);
}
}

Expand Down Expand Up @@ -444,7 +444,7 @@ public <U, E extends Throwable> SolveHandler<U, E> solve(
.orElseThrow();
} catch (final NoSuchElementException e) {
final var error = Commons.<E>cast(e);
return SolveHandler.ofError(error);
return SolveHandler.failure(error);
}
}

Expand Down Expand Up @@ -484,7 +484,7 @@ public <E extends Throwable> EffectHandler<E> effect(final ThrowingConsumer<? su
.orElseThrow();
} catch (final NoSuchElementException e) {
final var error = Commons.<E>cast(e);
return EffectHandler.ofError(error);
return EffectHandler.failure(error);
}
}

Expand Down
47 changes: 47 additions & 0 deletions src/test/java/io/github/joselion/maybe/CloseableHandlerTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,53 @@
throw FAIL_EXCEPTION;
};

@Nested class from {
@Nested class when_the_resource_is_not_null {
@Test void returns_a_handler_with_the_value() {
final var fis = getFIS();
final var handler = CloseableHandler.from(fis);

assertThat(handler.resource()).containsSame(fis);
assertThat(handler.error()).isEmpty();
}
}

@Nested class when_the_resource_is_null {
@Test void returns_a_handler_with_a_NullPointerException_error() {
final var handler = CloseableHandler.from(null);

assertThat(handler.resource()).isEmpty();
assertThat(handler.error())
.get(THROWABLE)
.isExactlyInstanceOf(NullPointerException.class)
.hasMessage("The \"Maybe<T>\" resource solved to null");
}
}
}

@Nested class failure {
@Nested class when_the_error_is_not_null {
@Test void returns_a_handler_with_the_error() {
final var handler = CloseableHandler.failure(FAIL_EXCEPTION);

assertThat(handler.resource()).isEmpty();
assertThat(handler.error()).containsSame(FAIL_EXCEPTION);
}
}

@Nested class when_the_error_is_null {
@Test void returns_a_handler_with_a_NullPointerException_error() {
final var handler = CloseableHandler.failure(null);

assertThat(handler.resource()).isEmpty();
assertThat(handler.error())
.get(THROWABLE)
.isExactlyInstanceOf(NullPointerException.class)
.hasMessage("The \"Maybe<T>\" error was null");
}
}
}

@Nested class solve {
@Nested class when_the_resource_is_present {
@Nested class when_the_operation_succeeds {
Expand Down
30 changes: 30 additions & 0 deletions src/test/java/io/github/joselion/maybe/EffectHandlerTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
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.spy;
Expand Down Expand Up @@ -31,6 +32,35 @@

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

@Nested class empty {
@Test void returns_an_empty_handler() {
final var handler = EffectHandler.empty();

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

@Nested class failure {
@Nested class when_the_error_is_not_null {
@Test void returns_a_handler_with_the_error() {
final var handler = EffectHandler.failure(FAIL_EXCEPTION);

assertThat(handler.error()).containsSame(FAIL_EXCEPTION);
}
}

@Nested class when_the_error_is_null {
@Test void returns_a_handler_with_a_NullPointerException_error() {
final var handler = EffectHandler.failure(null);

assertThat(handler.error())
.get(THROWABLE)
.isExactlyInstanceOf(NullPointerException.class)
.hasMessage("The \"Maybe<T>\" error was null");
}
}
}

@Nested class doOnSuccess {
@Nested class when_the_value_is_present {
@Test void calls_the_effect_callback() {
Expand Down
Loading

0 comments on commit c01e34d

Please sign in to comment.