Add fold method to Try
This commit is contained in:
parent
c9700868a0
commit
965b9872cc
3 changed files with 199 additions and 1 deletions
2
pom.xml
2
pom.xml
|
@ -6,7 +6,7 @@
|
|||
|
||||
<groupId>ninja.the-fire-archmage</groupId>
|
||||
<artifactId>datastructure-utils</artifactId>
|
||||
<version>2.0.1</version>
|
||||
<version>2.1.0</version>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>23</maven.compiler.source>
|
||||
|
|
|
@ -407,6 +407,59 @@ public class Try<T> {
|
|||
return result.isEmpty() && error.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms this Try into a single value by applying one of two mapping functions.
|
||||
* If this Try is successful, applies the value mapper to the contained value.
|
||||
* If this Try is a failure, applies the error mapper to the contained exception.
|
||||
* This operation allows for unified handling of both success and failure cases.
|
||||
*
|
||||
* <p>The fold operation is particularly useful when you need to:
|
||||
* <ul>
|
||||
* <li>Convert both success and failure cases to the same type</li>
|
||||
* <li>Implement error recovery by providing default values</li>
|
||||
* <li>Transform exceptions into meaningful result values</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Example usage:
|
||||
* <pre>{@code
|
||||
* // Convert both success and failure to string
|
||||
* String result = Try.of(() -> Integer.parseInt("42"))
|
||||
* .fold(
|
||||
* value -> "Success: " + value,
|
||||
* error -> "Error: " + error.getMessage()
|
||||
* );
|
||||
*
|
||||
* // Provide default value on error
|
||||
* Integer value = Try.of(() -> Integer.parseInt("invalid"))
|
||||
* .fold(
|
||||
* success -> success,
|
||||
* error -> 0 // default value
|
||||
* );
|
||||
* }</pre>
|
||||
*
|
||||
* @param <U> the type of the result after applying either mapping function
|
||||
* @param valueMapper function to apply to the value if this Try is successful
|
||||
* @param errorMapper function to apply to the exception if this Try is a failure
|
||||
* @return the result of applying the appropriate mapper function
|
||||
* @throws IllegalArgumentException if either mapper function is null
|
||||
* @throws IllegalStateException if this Try is of Void type (cannot be folded)
|
||||
*/
|
||||
public <U> U fold(Function<T,U> valueMapper, Function<Throwable, U> errorMapper) {
|
||||
if(valueMapper == null || errorMapper == null) {
|
||||
throw new IllegalArgumentException("Neither mapper can be null");
|
||||
}
|
||||
if(isVoidType()) {
|
||||
throw new IllegalStateException("A fold operation cannot be performed when the try type is Void");
|
||||
}
|
||||
if(error.isPresent()) {
|
||||
return errorMapper.apply(error.get());
|
||||
} else if(result.isPresent()) {
|
||||
return result.map(valueMapper).get();
|
||||
} else {
|
||||
throw new IllegalStateException("There was an error while folding the Try. No error or result present, which shouldn't be possible");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a collection of Try instances into a single Try containing a List.
|
||||
* If all Try instances are successful, returns a successful Try with a List of all values.
|
||||
|
|
|
@ -629,6 +629,151 @@ class TryTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("Fold Tests")
|
||||
class FoldTests {
|
||||
|
||||
@Test
|
||||
@DisplayName("Should fold successful Try with value mapper")
|
||||
void shouldFoldSuccessfulTryWithValueMapper() {
|
||||
Try<Integer> tryResult = Try.success(42);
|
||||
|
||||
String result = tryResult.fold(
|
||||
value -> "Success: " + value,
|
||||
error -> "Error: " + error.getMessage()
|
||||
);
|
||||
|
||||
assertThat(result).isEqualTo("Success: 42");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should fold failed Try with error mapper")
|
||||
void shouldFoldFailedTryWithErrorMapper() {
|
||||
RuntimeException exception = new RuntimeException("Test error");
|
||||
Try<Integer> tryResult = Try.failure(exception);
|
||||
|
||||
String result = tryResult.fold(
|
||||
value -> "Success: " + value,
|
||||
error -> "Error: " + error.getMessage()
|
||||
);
|
||||
|
||||
assertThat(result).isEqualTo("Error: Test error");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should provide default value on error using fold")
|
||||
void shouldProvideDefaultValueOnErrorUsingFold() {
|
||||
Try<Integer> tryResult = Try.of(() -> Integer.parseInt("invalid"));
|
||||
|
||||
Integer result = tryResult.fold(
|
||||
success -> success,
|
||||
error -> 0
|
||||
);
|
||||
|
||||
assertThat(result).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should transform both cases to same type")
|
||||
void shouldTransformBothCasesToSameType() {
|
||||
Try<String> successTry = Try.success("hello");
|
||||
Try<String> failureTry = Try.failure(new IOException("IO error"));
|
||||
|
||||
Integer successResult = successTry.fold(
|
||||
String::length,
|
||||
error -> -1
|
||||
);
|
||||
|
||||
Integer failureResult = failureTry.fold(
|
||||
String::length,
|
||||
error -> -1
|
||||
);
|
||||
|
||||
assertThat(successResult).isEqualTo(5);
|
||||
assertThat(failureResult).isEqualTo(-1);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should throw exception when fold called with null value mapper")
|
||||
void shouldThrowExceptionWhenFoldCalledWithNullValueMapper() {
|
||||
Try<String> tryResult = Try.success("hello");
|
||||
|
||||
assertThatThrownBy(() -> tryResult.fold(null, error -> "error"))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("Neither mapper can be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should throw exception when fold called with null error mapper")
|
||||
void shouldThrowExceptionWhenFoldCalledWithNullErrorMapper() {
|
||||
Try<String> tryResult = Try.success("hello");
|
||||
|
||||
assertThatThrownBy(() -> tryResult.fold(value -> "success", null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("Neither mapper can be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should throw exception when fold called on Void type")
|
||||
void shouldThrowExceptionWhenFoldCalledOnVoidType() {
|
||||
Try<Void> tryResult = Try.success();
|
||||
|
||||
assertThatThrownBy(() -> tryResult.fold(
|
||||
value -> "success",
|
||||
error -> "error"
|
||||
))
|
||||
.isInstanceOf(IllegalStateException.class)
|
||||
.hasMessage("A fold operation cannot be performed when the try type is Void");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle complex transformations in fold")
|
||||
void shouldHandleComplexTransformationsInFold() {
|
||||
Try<List<Integer>> tryResult = Try.of(() -> List.of(1, 2, 3, 4, 5));
|
||||
|
||||
String result = tryResult.fold(
|
||||
list -> "Sum: " + list.stream().mapToInt(Integer::intValue).sum(),
|
||||
error -> "Could not calculate sum: " + error.getMessage()
|
||||
);
|
||||
|
||||
assertThat(result).isEqualTo("Sum: 15");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle exception types in error mapper")
|
||||
void shouldHandleExceptionTypesInErrorMapper() {
|
||||
Try<Integer> tryResult = Try.of(() -> Integer.parseInt("not a number"));
|
||||
|
||||
String result = tryResult.fold(
|
||||
value -> "Parsed: " + value,
|
||||
error -> {
|
||||
if (error instanceof NumberFormatException) {
|
||||
return "Invalid number format";
|
||||
} else {
|
||||
return "Unknown error: " + error.getMessage();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
assertThat(result).isEqualTo("Invalid number format");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should preserve original exception information in fold")
|
||||
void shouldPreserveOriginalExceptionInformationInFold() {
|
||||
RuntimeException originalException = new RuntimeException("Original message");
|
||||
Try<String> tryResult = Try.failure(originalException);
|
||||
|
||||
Throwable result = tryResult.fold(
|
||||
value -> new IllegalStateException("Should not happen"),
|
||||
error -> error
|
||||
);
|
||||
|
||||
assertThat(result).isSameAs(originalException);
|
||||
assertThat(result.getMessage()).isEqualTo("Original message");
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("Sequence Tests")
|
||||
class SequenceTests {
|
||||
|
|
Loading…
Add table
Reference in a new issue