Skip to content

Commit

Permalink
feat(core): Add .cast(..) operations overloads (#236)
Browse files Browse the repository at this point in the history
  • Loading branch information
JoseLion authored Feb 13, 2024
1 parent 14f057d commit 9d51c1a
Show file tree
Hide file tree
Showing 5 changed files with 198 additions and 57 deletions.
35 changes: 27 additions & 8 deletions src/main/java/io/github/joselion/maybe/Maybe.java
Original file line number Diff line number Diff line change
Expand Up @@ -317,18 +317,37 @@ public <E extends Throwable> EffectHandler<E> effect(final ThrowingConsumer<? su
}

/**
* If the value is present, cast the value to another type. In case of an
* exception during the cast, a Maybe with {@link #empty()} is returned.
* If the value is present, casts the value to the provided {@code type}
* class. If the value is not assignable to {@code type}, returns a handler
* with a {@link ClassCastException} error.
*
* @param <U> the type that the value will be cast to
* @param <U> the type of the cast value
* @param type the class instance of the type to cast
* @return a new {@code Maybe} with the cast value if it can be cast,
* {@link #empty()} otherwise
* @return a handler with either the cast value or a ClassCastException error
*/
public <U> SolveHandler<U, ClassCastException> cast(final Class<U> type) {
return Maybe
.of(this.value)
.solve(type::cast);
return this.solve(type::cast);
}

/**
* If the value is present, casts the value to the provided {@code type}
* class. If the value is not assignable to {@code type}, maps the error with
* the provided {@code onError} function, which receives the produced
* {@link ClassCastException} on its argument.
*
* @param <U> the type of the cast value
* @param <X> the type of the mapped exception
* @param type the class instance of the type to cast
* @param onError a function to map the error in case of failure
* @return a handler with either the cast value or the mapped error
*/
public <U, X extends Throwable> SolveHandler<U, X> cast(
final Class<U> type,
final Function<Throwable, ? extends X> onError
) {
return this
.solve(type::cast)
.mapError(onError);
}

/**
Expand Down
33 changes: 27 additions & 6 deletions src/main/java/io/github/joselion/maybe/SolveHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -402,19 +402,40 @@ public <U> SolveHandler<U, E> flatMap(final Function<? super T, Maybe<? extends
}

/**
* If the value is present, cast the value to anoter type. If the cast fails
* or if the error is present, it returns a new handler which contains a
* {@link ClassCastException} error.
* If the value is present, casts the value to the provided {@code type}
* class. If the error is present or the value not assignable to {@code type},
* returns a handler with a {@link ClassCastException} error.
*
* @param <U> the type the value will be cast to
* @param <U> the type of the cast value
* @param type the class instance of the type to cast
* @return a new handler with either the cast value or a ClassCastException
* error
* @return a handler with either the cast value or a ClassCastException error
*/
public <U> SolveHandler<U, ClassCastException> cast(final Class<U> type) {
return this.solve(type::cast);
}

/**
* If the value is present, casts the value to the provided {@code type}
* class. If the value is not assignable to {@code type}, maps the error with
* the provided {@code onError} function, which receives the produced
* {@link ClassCastException} on its argument. If the error is present,
* returns a handler with the same error.
*
* @param <U> the type of the cast value
* @param <X> the type of the mapped exception
* @param type the class instance of the type to cast
* @param onError a function to map the error in case of failure
* @return a handler with either the cast value or the mapped error
*/
public <U, X extends Throwable> SolveHandler<U, X> cast(
final Class<U> type,
final Function<ClassCastException, ? extends X> onError
) {
return this
.solve(type::cast)
.mapError(ClassCastException.class, onError);
}

/**
* Returns the solved value if present. Another value otherwise.
*
Expand Down
35 changes: 27 additions & 8 deletions src/main/java17/io/github/joselion/maybe/Maybe.java
Original file line number Diff line number Diff line change
Expand Up @@ -318,18 +318,37 @@ public <E extends Throwable> EffectHandler<E> effect(final ThrowingConsumer<? su
}

/**
* If the value is present, cast the value to another type. In case of an
* exception during the cast, a Maybe with {@link #empty()} is returned.
* If the value is present, casts the value to the provided {@code type}
* class. If the value is not assignable to {@code type}, returns a handler
* with a {@link ClassCastException} error.
*
* @param <U> the type that the value will be cast to
* @param <U> the type of the cast value
* @param type the class instance of the type to cast
* @return a new {@code Maybe} with the cast value if it can be cast,
* {@link #empty()} otherwise
* @return a handler with either the cast value or a ClassCastException error
*/
public <U> SolveHandler<U, ClassCastException> cast(final Class<U> type) {
return Maybe
.of(this.value)
.solve(type::cast);
return this.solve(type::cast);
}

/**
* If the value is present, casts the value to the provided {@code type}
* class. If the value is not assignable to {@code type}, maps the error with
* the provided {@code onError} function, which receives the produced
* {@link ClassCastException} on its argument.
*
* @param <U> the type of the cast value
* @param <X> the type of the mapped exception
* @param type the class instance of the type to cast
* @param onError a function to map the error in case of failure
* @return a handler with either the cast value or the mapped error
*/
public <U, X extends Throwable> SolveHandler<U, X> cast(
final Class<U> type,
final Function<Throwable, ? extends X> onError
) {
return this
.solve(type::cast)
.mapError(onError);
}

/**
Expand Down
89 changes: 70 additions & 19 deletions src/test/java/io/github/joselion/maybe/MaybeTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Optional;

Expand All @@ -29,22 +30,22 @@

private static final String OK = "OK";

private static final IOException FAIL_EXCEPTION = new IOException("FAIL");
private static final IOException FAILURE = new IOException("FAIL");

private final ThrowingFunction<String, String, IOException> failFunction = val -> {
throw FAIL_EXCEPTION;
throw FAILURE;
};

private final ThrowingSupplier<String, IOException> failSupplier = () -> {
throw FAIL_EXCEPTION;
throw FAILURE;
};

private final ThrowingConsumer<String, IOException> failConsumer = it -> {
throw FAIL_EXCEPTION;
throw FAILURE;
};

private final ThrowingRunnable<IOException> failRunnable = () -> {
throw FAIL_EXCEPTION;
throw FAILURE;
};

@Nested class of {
Expand Down Expand Up @@ -113,7 +114,7 @@
final var handler = Maybe.from(supplierSpy);

assertThat(handler.success()).isEmpty();
assertThat(handler.error()).contains(FAIL_EXCEPTION);
assertThat(handler.error()).contains(FAILURE);

verify(supplierSpy).get();
}
Expand All @@ -137,7 +138,7 @@
final var runnableSpy = Spy.lambda(failRunnable);
final var handler = Maybe.from(runnableSpy);

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

verify(runnableSpy).run();
}
Expand All @@ -158,7 +159,7 @@
assertThat(Maybe.partial(failureSpy).apply(OK))
.isInstanceOf(SolveHandler.class)
.extracting(SolveHandler::error, optional(IOException.class))
.contains(FAIL_EXCEPTION);
.contains(FAILURE);

verify(successSpy).apply(OK);
verify(failureSpy).apply(OK);
Expand All @@ -178,7 +179,7 @@
assertThat(Maybe.partial(failureSpy).apply(OK))
.isInstanceOf(EffectHandler.class)
.extracting(EffectHandler::error, optional(IOException.class))
.contains(FAIL_EXCEPTION);
.contains(FAILURE);

verify(successSpy).accept(OK);
verify(failureSpy).accept(OK);
Expand Down Expand Up @@ -314,7 +315,7 @@
.solve(functionSpy);

assertThat(handler.success()).isEmpty();
assertThat(handler.error()).contains(FAIL_EXCEPTION);
assertThat(handler.error()).contains(FAILURE);

verify(functionSpy).apply(OK);
}
Expand Down Expand Up @@ -367,27 +368,77 @@
final var handler = Maybe.of(OK)
.effect(consumerSpy);

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

verify(consumerSpy).accept(OK);
}
}
}

@Nested class cast {
@Nested class when_the_value_is_castable_to_the_passed_type {
@Test void returns_a_solve_handler_with_the_cast_value() {
final var maybe = Maybe.<Number>of(3);
@Nested class when_the_value_is_present {
@Nested class and_the_value_is_an_instance_of_the_type {
@Test void returns_a_solve_handler_with_the_cast_value() {
final var mapperSpy = Spy.function((Throwable e) -> FAILURE);
final var handler = Maybe.of((Number) 3);
final var overloads = List.of(
handler.cast(Integer.class),
handler.cast(Integer.class, mapperSpy)
);

assertThat(overloads).isNotEmpty().allSatisfy(overload -> {
assertThat(overload.success()).contains(3);
assertThat(overload.error()).isEmpty();
});

verify(mapperSpy, never()).apply(any());
}
}

assertThat(maybe.cast(Integer.class).success()).contains(3);
@Nested class and_the_value_is_not_an_instance_of_the_type {
@Nested class and_the_error_mapper_is_not_provided {
@Test void returns_a_solve_handler_with_a_ClassCastException() {
final var handler = Maybe.of("3").cast(Integer.class);

assertThat(handler.success()).isEmpty();
assertThat(handler.error()).containsInstanceOf(ClassCastException.class);
}
}

@Nested class and_the_error_mapper_is_provided {
@Test void returns_a_solve_handler_with_the_mapped_error() {
final var mapperSpy = Spy.function((Throwable e) -> FAILURE);
final var handler = Maybe.of("3").cast(Integer.class, mapperSpy);

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

verify(mapperSpy).apply(any(ClassCastException.class));
}
}
}
}

@Nested class when_the_value_is_not_castable_to_the_passed_type {
@Test void returns_a_solve_handler_with_a_ClassCastException() {
final var maybe = Maybe.of("3");
@Nested class when_the_value_is_not_present {
@Nested class and_the_error_mapper_is_not_provided {
@Test void returns_a_solve_handler_with_a_NoSuchElementException() {
final var handler = Maybe.empty().cast(String.class);

assertThat(handler.success()).isEmpty();
assertThat(handler.error()).containsInstanceOf(NoSuchElementException.class);
}
}

assertThat(maybe.cast(Integer.class).error()).containsInstanceOf(ClassCastException.class);
@Nested class and_the_error_mapper_is_provided {
@Test void returns_a_solve_handler_with_the_mapped_error() {
final var mapperSpy = Spy.function((Throwable e) -> FAILURE);
final var handler = Maybe.empty().cast(String.class, mapperSpy);

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

verify(mapperSpy).apply(any(NoSuchElementException.class));
}
}
}
}
Expand Down
63 changes: 47 additions & 16 deletions src/test/java/io/github/joselion/maybe/SolveHandlerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -602,36 +602,67 @@

@Nested class cast {
@Nested class when_the_value_is_present {
@Nested class and_the_object_can_be_cast {
@Nested class and_the_value_is_an_instance_of_the_type {
@Test void returns_a_handler_with_the_cast_value() {
final var anyValue = (Object) "Hello";
final var handler = SolveHandler.from(anyValue)
.cast(String.class);
final var mapperSpy = Spy.function((ClassCastException e) -> new RuntimeException(e));
final var handler = SolveHandler.from(anyValue);
final var overloads = List.of(
handler.cast(String.class),
handler.cast(String.class, mapperSpy)
);

assertThat(handler.success()).contains("Hello");
assertThat(handler.error()).isEmpty();
assertThat(overloads).isNotEmpty().allSatisfy(overload -> {
assertThat(overload.success()).contains("Hello");
assertThat(overload.error()).isEmpty();
});

verify(mapperSpy, never()).apply(any());
}
}

@Nested class and_the_object_can_not_be_cast {
@Test void returns_a_handler_with_the_cast_exception() {
final var handler = SolveHandler.from(3).cast(String.class);
@Nested class and_the_value_is_not_an_instance_of_the_type {
@Nested class and_the_error_mapper_is_not_provided {
@Test void returns_a_handler_with_a_ClassCastException() {
final var handler = SolveHandler.from(3).cast(String.class);

assertThat(handler.success()).isEmpty();
assertThat(handler.error())
.get(THROWABLE)
.isExactlyInstanceOf(ClassCastException.class)
.hasMessage("Cannot cast java.lang.Integer to java.lang.String");
assertThat(handler.success()).isEmpty();
assertThat(handler.error())
.get(THROWABLE)
.isExactlyInstanceOf(ClassCastException.class)
.hasMessage("Cannot cast java.lang.Integer to java.lang.String");
}
}

@Nested class and_the_error_mapper_is_provided {
@Test void returns_a_handler_with_the_mapped_error() {
final var mapperSpy = Spy.function((ClassCastException e) -> FAILURE);
final var handler = SolveHandler.from(3).cast(String.class, mapperSpy);

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

verify(mapperSpy).apply(any(ClassCastException.class));
}
}
}
}

@Nested class when_the_error_is_present {
@Test void returns_a_handler_with_the_error() {
final var handler = SolveHandler.failure(FAILURE).cast(String.class);
final var mapperSpy = Spy.function((ClassCastException e) -> new RuntimeException(e));
final var handler = SolveHandler.failure(FAILURE);
final var overloads = List.of(
handler.cast(String.class),
handler.cast(String.class, mapperSpy)
);

assertThat(handler.success()).isEmpty();
assertThat(handler.error()).get().isSameAs(FAILURE);
assertThat(overloads).isNotEmpty().allSatisfy(overload -> {
assertThat(overload.success()).isEmpty();
assertThat(overload.error()).get().isSameAs(FAILURE);
});

verify(mapperSpy, never()).apply(any());
}
}
}
Expand Down

0 comments on commit 9d51c1a

Please sign in to comment.