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

fix(core): Handle possible nulls in factory methods #222

Merged
merged 1 commit into from
Dec 4, 2023
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
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