Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
How to instantiate new Result objects
@Test
void testFailure() {
// When
Result<?, String> result = Results.failure("The operation failed");
// Then
assertTrue(result::hasFailure);
assertFalse(result::hasSuccess);
}@Test
void testOfNullable() {
// Given
String string1 = "The operation succeeded";
String string2 = null;
// When
Result<String, Integer> result1 = Results.ofNullable(string1, 404);
Result<String, Integer> result2 = Results.ofNullable(string2, 404);
// Then
assertTrue(result1::hasSuccess);
assertTrue(result2::hasFailure);
}@Test
void testOfOptional() {
// Given
Optional<BigDecimal> optional1 = Optional.of(BigDecimal.ONE);
Optional<BigDecimal> optional2 = Optional.empty();
// When
Result<BigDecimal, Integer> result1 = Results.ofOptional(optional1, -1);
Result<BigDecimal, Integer> result2 = Results.ofOptional(optional2, -1);
// Then
assertTrue(result1::hasSuccess);
assertTrue(result2::hasFailure);
}String task1() {
return "OK";
}
String task2() throws Exception {
throw new Exception("Whoops!");
}
@Test
void testOfCallable() {
// When
Result<String, Exception> result1 = Results.ofCallable(this::task1);
Result<String, Exception> result2 = Results.ofCallable(this::task2);
// Then
assertTrue(result1::hasSuccess);
assertTrue(result2::hasFailure);
}



Check out some REST APIs that consume and produce Result objects
How to find out if the operation succeded or failed
@Test
void testHasSuccess() {
// Given
Result<?, ?> result1 = success(1024);
Result<?, ?> result2 = failure(1024);
// When
boolean result1HasSuccess = result1.hasSuccess();
boolean result2HasSuccess = result2.hasSuccess();
// Then
assertTrue(result1HasSuccess);
assertFalse(result2HasSuccess);
}How to declare dependencies without having to worry about version numbers
@Test
void testHasFailure() {
// Given
Result<?, ?> result1 = success(512);
Result<?, ?> result2 = failure(512);
// When
boolean result1HasFailure = result1.hasFailure();
boolean result2HasFailure = result2.hasFailure();
// Then
assertFalse(result1HasFailure);
assertTrue(result2HasFailure);
}@Test
void testFilter() {
// Given
Result<Integer, String> result = success(1);
// When
Result<Integer, String> filtered = result.filter(x -> x % 2 == 0, x -> "It's odd");
// Then
assertTrue(filtered.hasFailure());
}@Test
void testRecover() {
// Given
Result<Integer, String> result = failure("OK");
// When
Result<Integer, String> filtered = result.recover("OK"::equals, String::length);
// Then
assertTrue(filtered.hasSuccess());
}@Test
void testIfSuccess() {
// Given
List<Object> list = new ArrayList<>();
Result<Integer, String> result = success(100);
// When
result.ifSuccess(list::add);
// Then
assertEquals(100, list.getFirst());
}@Test
void testIfFailure() {
// Given
List<Object> list = new ArrayList<>();
Result<Integer, String> result = failure("ERROR");
// When
result.ifFailure(list::add);
// Then
assertEquals("ERROR", list.getFirst());
}@Test
void testIfSuccessOrElse() {
// Given
List<Object> list1 = new ArrayList<>();
List<Object> list2 = new ArrayList<>();
Result<Long, String> result1 = success(100L);
Result<Long, String> result2 = failure("ERROR");
// When
result1.ifSuccessOrElse(list1::add, list1::add);
result2.ifSuccessOrElse(list2::add, list2::add);
// Then
assertEquals(100L, list1.getFirst());
assertEquals("ERROR", list2.getFirst());
}import static com.leakyabstractions.result.assertj.ResultAssertions.assertThat;
@Test
void testAssertThat() {
// Given
final int zero = 0;
// When
final Result<Integer, String> result = success(zero);
// Then
assertThat(zero).isZero();
assertThat(result).hasSuccess(zero);
}import static com.leakyabstractions.result.assertj.ResultAssert.assertThatResult;
import static org.assertj.core.api.Assertions.assertThat;
@Test
void testAssertThatResult() {
// Given
final int zero = 0;
// When
final Result<Integer, String> result = success(zero);
// Then
assertThat(zero).isZero();
assertThatResult(result).hasSuccess(zero);
}<!-- Import the BOM -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.leakyabstractions</groupId>
<artifactId>result-bom</artifactId>
<version>1.0.0.0</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
<!-- Define dependencies without version numbers -->
<dependencies>
<dependency>
<groupId>com.leakyabstractions</groupId>
<artifactId>result</artifactId>
</dependency>
<dependency>
<groupId>com.leakyabstractions</groupId>
<artifactId>result-assertj</artifactId>
<scope>test</scope>
</dependency>
</dependencies>dependencies {
// Import the BOM
implementation platform("com.leakyabstractions:result-bom:1.0.0.0")
// Define dependencies without version numbers
implementation("com.leakyabstractions:result")
testImplementation("com.leakyabstractions:result-assertj")
}@Test
void testGetSuccess() {
// Given
Result<?, ?> result1 = success("SUCCESS");
Result<?, ?> result2 = failure("FAILURE");
// Then
Optional<?> success1 = result1.getSuccess();
Optional<?> success2 = result2.getSuccess();
// Then
assertEquals("SUCCESS", success1.get());
assertTrue(success2::isEmpty);
}@Test
void testGetFailure() {
// Given
Result<?, ?> result1 = success("SUCCESS");
Result<?, ?> result2 = failure("FAILURE");
// Then
Optional<?> failure1 = result1.getFailure();
Optional<?> failure2 = result2.getFailure();
// Then
assertTrue(failure1::isEmpty);
assertEquals("FAILURE", failure2.get());
}@Test
void testGetOrElse() {
// Given
Result<String, String> result1 = success("IDEAL");
Result<String, String> result2 = failure("ERROR");
String alternative = "OTHER";
// When
String value1 = result1.orElse(alternative);
String value2 = result2.orElse(alternative);
// Then
assertEquals("IDEAL", value1);
assertEquals("OTHER", value2);
}@Test
void testGetOrElseMap() {
// Given
Result<String, Integer> result1 = success("OK");
Result<String, Integer> result2 = failure(1024);
Result<String, Integer> result3 = failure(-256);
Function<Integer, String> mapper = x -> x > 0 ? "HI" : "LO";
// When
String value1 = result1.orElseMap(mapper);
String value2 = result2.orElseMap(mapper);
String value3 = result3.orElseMap(mapper);
// Then
assertEquals("OK", value1);
assertEquals("HI", value2);
assertEquals("LO", value3);
}@Test
void testStreamSuccess() {
// Given
Result<?, ?> result1 = success("Yes");
Result<?, ?> result2 = failure("No");
// When
Stream<?> stream1 = result1.streamSuccess();
Stream<?> stream2 = result2.streamSuccess();
// Then
assertEquals("Yes", stream1.findFirst().orElse(null));
assertNull(stream2.findFirst().orElse(null));
}
@Test
void testStreamFailure() {
// Given
Result<?, ?> result1 = success("Yes");
Result<?, ?> result2 = failure("No");
// When
Stream<?> stream1 = result1.streamFailure();
Stream<?> stream2 = result2.streamFailure();
// Then
assertNull(stream1.findFirst().orElse(null));
assertEquals("No", stream2.findFirst().orElse(null));
}dependencies {
// ...
implementation platform('com.leakyabstractions:result-bom:1.0.0.0')
implementation 'com.leakyabstractions:result'
implementation 'com.leakyabstractions:result-jackson'
}@Configuration
public class JacksonConfig {
@Bean
public Module registerResultModule() {
return new ResultModule();
}
}public class ApiResponse<S> {
@JsonProperty String version;
@JsonProperty Instant generatedOn;
@JsonProperty Result<S, ApiError> result;
}@RestController
public class PetController {
// ...
@GetMapping("/pet")
ApiResponse<Collection<Pet>> list(@RequestHeader("X-Type") RepositoryType type) {
log.info("List all pets in {} pet store", type);
return response(locate(type)
.flatMapSuccess(PetRepository::listPets)
.ifSuccess(x -> log.info("Listed {} pet(s) in {}", x.size(), type))
.ifFailure(this::logError));
}
}./gradlew bootRuncurl -s -H 'x-type: local' http://localhost:8080/pet/0{
"version": "1.0",
"result": {
"success":{
"id": 0,
"name": "Rocky",
"status": "AVAILABLE"
}
}
}dependencies {
// ...
implementation(platform("com.leakyabstractions:result-bom:1.0.0.0"))
implementation("com.leakyabstractions:result")
implementation("com.leakyabstractions:result-micronaut-serde")
}@Serdeable
public class ApiResponse<S> {
@JsonProperty String version;
@JsonProperty Instant generatedOn;
@JsonProperty Result<S, ApiError> result;
}@Controller
public class PetController {
// ...
@Get("/pet")
ApiResponse<Collection<Pet>> list(@Header("X-Type") RepositoryType type) {
log.info("List all pets in {} pet store", type);
return response(locate(type)
.flatMapSuccess(PetRepository::listPets)
.ifSuccess(x -> log.info("Listed {} pet(s) in {}", x.size(), type))
.ifFailure(this::logError));
}
}./gradlew runcurl -s -H 'x-type: local' http://localhost:8080/pet/0{
"version": "1.0",
"result": {
"success":{
"id": 0,
"name": "Rocky",
"status": "AVAILABLE"
}
}
}How to serialize Result objects with Jackson 2.x and 3.x
/** Represents an API response */
public class ApiResponse {
@JsonProperty
String version;
@JsonProperty
Result<String, String> result;
// Constructors, getters and setters omitted
}ApiResponse response = new ApiResponse();
response.setVersion("v1");
response.setResult(success("Perfect"));ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(response);Java 8 optional type `java.util.Optional<java.lang.String>`
not supported by default:
add Module "com.fasterxml.jackson.datatype:jackson-datatype-jdk8"
to enable handlingString json = "{\"version\":\"v2\",\"result\":{\"success\":\"OK\"}}";
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.readValue(json, ApiResponse.class);Cannot construct instance of `com.leakyabstractions.result.api.Result`
(no Creators, like default constructor, exist):
abstract types either need to be mapped to concrete types,
have custom deserializer, or contain additional type informationObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new ResultModule());objectMapper.findAndRegisterModules();JsonMapper.Builder builder = JsonMapper.builder();
builder.addModule(new ResultModule());
ObjectMapper objectMapper = builder.build();builder.findAndAddModules();@Test
void serializeSuccessfulResult() throws Exception {
// Given
ApiResponse response = new ApiResponse("v3", success("All good"));
// When
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new ResultModule());
String json = objectMapper.writeValueAsString(response);
// Then
assertTrue(json.contains("v3"));
assertTrue(json.contains("All good"));
}{
"version": "v3",
"result": {
"failure": null,
"success": "All good"
}
}@Test
void serializeFailedResult() throws Exception {
// Given
ApiResponse response = new ApiResponse("v4", failure("Oops"));
// When
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.findAndRegisterModules();
String json = objectMapper.writeValueAsString(response);
// Then
assertTrue(json.contains("v4"));
assertTrue(json.contains("Oops"));
} // End{
"version": "v4",
"result": {
"failure": "Oops",
"success": null
}
}@Test
void deserializeSuccessfulResult() {
// Given
String json = "{\"version\":\"v5\",\"result\":{\"success\":\"Yay\"}}";
// When
ObjectMapper objectMapper = new ObjectMapper().findAndRegisterModules();
ApiResponse response = objectMapper.readValue(json, ApiResponse.class);
// Then
assertEquals("v5", response.getVersion());
assertEquals("Yay", response.getResult().orElse(null));
}@Test
void deserializeFailedResult() {
// Given
String json = "{\"version\":\"v6\",\"result\":{\"failure\":\"Nay\"}}";
// When
ObjectMapper objectMapper = new ObjectMapper().findAndRegisterModules();
ApiResponse response = objectMapper.readValue(json, ApiResponse.class);
// Then
assertEquals("v6", response.getVersion());
assertEquals("Nay", response.getResult().getFailure().orElse(null));
}Boosting results with enhanced capabilities
<dependencies>
<dependency>
<groupId>com.leakyabstractions</groupId>
<artifactId>result</artifactId>
<version>1.0.0.0</version>
</dependency>
</dependencies>dependencies {
implementation("com.leakyabstractions:result:1.0.0.0")
}How to solve simple use-case scenarios

/** Represents an API operation */
@Serdeable
public record ApiOperation(String name, Result<String, String> result) {
}@Controller("/operations")
public class ApiController {
@Get("/last")
ApiOperation lastOperation() {
return new ApiOperation("setup", Results.success("Perfect"));
}
}curl 'http://localhost:8080/operations/last'No serializable introspection present for type Success.
Consider adding Serdeable. Serializable annotate to type Success.
Alternatively if you are not in control of the project's source code,
you can use @SerdeImport(Success.class) to enable serialization of this type.@Test
void testSerializationProblem(ObjectMapper objectMapper) {
// Given
ApiOperation op = new ApiOperation("setup", success("Perfect"));
// Then
SerdeException error = assertThrows(SerdeException.class,
() -> objectMapper.writeValueAsString(op));
assertTrue(error.getMessage().startsWith(
"No serializable introspection present for type Success."));
}{
"name": "setup",
"result": {
"failure": null,
"success": "Perfect"
}
}@Controller("/operations")
public class ApiController {
@Post("/notify")
Map<String, String> notify(@Body ApiOperation op) {
return op.result()
.mapSuccess(s -> Map.of("message", op.name() + " succeeded: " + s))
.orElseMap(f -> Map.of("error", op.name() + " failed: " + f));
}
}No bean introspection available for type
[interface com.leakyabstractions.result.api.Result].
Ensure the class is annotated with
io.micronaut.core.annotation.Introspected@Test
void testDeserializationProblem(ObjectMapper objectMapper) {
// Given
String json = """
{"name":"renew","result":{"success":"OK"}}""";
// Then
IntrospectionException error = assertThrows(IntrospectionException.class,
() -> objectMapper.readValue(json, ApiOperation.class));
String errorMessage = error.getMessage(); // Extract error message
// Verify error message
assertTrue(errorMessage.startsWith("No bean introspection available " +
"for type [interface com.leakyabstractions.result.api.Result]."));
} // End@Test
void serializeSuccessfulResult(ObjectMapper objectMapper)
throws IOException {
// Given
ApiOperation op = new ApiOperation("clean", success("All good"));
// When
String json = objectMapper.writeValueAsString(op);
// Then
assertEquals("""
{"name":"clean","result":{"success":"All good"}}""", json);
}{
"name": "clean",
"result": {
"failure": null,
"success": "All good"
}
}@Test
void serializeFailedResult(ObjectMapper objectMapper)
throws IOException {
// Given
ApiOperation op = new ApiOperation("build", failure("Oops"));
// When
String json = objectMapper.writeValueAsString(op);
// Then
assertEquals("""
{"name":"build","result":{"failure":"Oops"}}""", json);
}{
"name": "build",
"result": {
"failure": "Oops",
"success": null
}
}@Test
void deserializeSuccessfulResult(ObjectMapper objectMapper)
throws IOException {
// Given
String json = """
{"name":"check","result":{"success":"Yay"}}""";
// When
ApiOperation response = objectMapper.readValue(json, ApiOperation.class);
// Then
assertEquals("check", response.name());
assertEquals("Yay", response.result().orElse(null));
}@Test
void deserializeFailedResult(ObjectMapper objectMapper)
throws IOException {
// Given
String json = """
{"name":"start","result":{"failure":"Nay"}}""";
// When
ApiOperation response = objectMapper.readValue(json, ApiOperation.class);
// Then
assertEquals("start", response.name());
assertEquals("Nay", response.result().getFailure().orElse(null));
}Result<User, Exception> result = Results.ofCallable(() -> db.getUser(id));result.ifFailure(error -> logger.error("Couldn't get user: {}", id, error));String name = result.mapSuccess(User::name).orElse("Anonymous");<dependencies>
<dependency>
<groupId>com.leakyabstractions</groupId>
<artifactId>result</artifactId>
<version>1.0.0.0</version>
</dependency>
</dependencies>dependencies {
implementation("com.leakyabstractions:result:1.0.0.0")
}public String usingExceptions(int number) throws SimpleException {
if (number < 0) {
throw new SimpleException(number);
}
return "ok";
}public Result<String, SimpleFailure> usingResults(int number) {
if (number < 0) {
return Results.failure(new SimpleFailure(number));
}
return Results.success("ok");
}public String usingExceptions(int number) throws ComplexException {
try {
return simple.usingExceptions(number).toUpperCase();
} catch (SimpleException e) {
throw new ComplexException(e);
}
}public Result<String, ComplexFailure> usingResults(int number) {
return simple.usingResults(number)
.map(String::toUpperCase, ComplexFailure::new);
}Level up and lessons learned
Feel free to tweak and share — no strings attached
Supplier<Result<Integer, String>> supplier = () -> success(123);
Result<Integer, String> lazy = LazyResults.ofSupplier(supplier);/* Represents the operation we may omit */
Result<Long, Exception> expensiveCalculation(AtomicLong timesExecuted) {
long counter = timesExecuted.incrementAndGet();
return success(counter);
}@Test
void shouldSkipExpensiveCalculation() {
AtomicLong timesExecuted = new AtomicLong();
// Given
Result<Long, Exception> lazy = LazyResults
.ofSupplier(() -> expensiveCalculation(timesExecuted));
// When
Result<String, Exception> transformed = lazy.mapSuccess(Object::toString);
// Then
assertNotNull(transformed);
assertEquals(0L, timesExecuted.get());
}@Test
void shouldExecuteExpensiveCalculation() {
AtomicLong timesExecuted = new AtomicLong();
// Given
Result<Long, Exception> lazy = LazyResults
.ofSupplier(() -> expensiveCalculation(timesExecuted));
// When
Result<String, Exception> transformed = lazy.mapSuccess(Object::toString);
boolean success = transformed.hasSuccess();
// Then
assertTrue(success);
assertEquals(1L, timesExecuted.get());
}@Test
void shouldHandleSuccessEagerly() {
AtomicLong timesExecuted = new AtomicLong();
AtomicLong consumerExecuted = new AtomicLong();
Consumer<Long> consumer = x -> consumerExecuted.incrementAndGet();
// Given
Result<Long, Exception> lazy = LazyResults
.ofSupplier(() -> expensiveCalculation(timesExecuted));
// When
lazy.ifSuccess(consumer);
// Then
assertEquals(1L, timesExecuted.get());
assertEquals(1L, consumerExecuted.get());
}@Test
void shouldHandleFailureEagerly() {
AtomicLong timesExecuted = new AtomicLong();
AtomicLong consumerExecuted = new AtomicLong();
Consumer<Exception> consumer = x -> consumerExecuted.incrementAndGet();
// Given
Result<Long, Exception> lazy = LazyResults
.ofSupplier(() -> expensiveCalculation(timesExecuted));
// When
lazy.ifFailure(consumer);
// Then
assertEquals(1L, timesExecuted.get());
assertEquals(0L, consumerExecuted.get());
}@Test
void shouldHandleSuccessLazily() {
AtomicLong timesExecuted = new AtomicLong();
AtomicLong consumerExecuted = new AtomicLong();
Consumer<Long> consumer = LazyConsumer
.of(x -> consumerExecuted.incrementAndGet());
// Given
Result<Long, Exception> lazy = LazyResults
.ofSupplier(() -> expensiveCalculation(timesExecuted));
// When
lazy.ifSuccess(consumer);
// Then
assertEquals(0L, timesExecuted.get());
assertEquals(0L, consumerExecuted.get());
}How to take Result objects to the next level


@Test
void testMapSuccess() {
// Given
Result<String, ?> result = success("HELLO");
// When
Result<Integer, ?> mapped = result.mapSuccess(String::length);
// Then
assertEquals(5, mapped.orElse(null));
}@Test
void testMapFailure() {
// Given
Result<?, BigDecimal> result = failure(ONE);
// When
Result<?, Boolean> mapped = result.mapFailure(TWO::equals);
// Then
assertFalse(mapped.getFailure().orElse(null));
}@Test
void testMap() {
// Given
Result<String, BigDecimal> result1 = success("HELLO");
Result<String, BigDecimal> result2 = failure(ONE);
// When
Result<Integer, Boolean> mapped1 = result1.map(String::length, TWO::equals);
Result<Integer, Boolean> mapped2 = result2.map(String::length, TWO::equals);
// Then
assertEquals(5, mapped1.orElse(null));
assertFalse(mapped2.getFailure().orElse(null));
}enum PetError {NOT_FOUND, NO_CONFIG}
record Pet(long id, String name) {
static final Pet DEFAULT = new Pet(0, "Default pet");
static final Pet ROCKY = new Pet(1, "Rocky");
static final Pet GARFIELD = new Pet(2, "Garfield");
}
record PetStore(Pet... pets) {
PetStore() {
this(Pet.ROCKY, Pet.GARFIELD);
}
Result<Pet, PetError> find(long id) {
Optional<Pet> found = stream(pets).filter(pet -> pet.id() == id).findAny();
return Results.ofOptional(found, NOT_FOUND);
}
Result<Pet, PetError> getDefaultPet(PetError error) {
return error == NO_CONFIG ? success(Pet.DEFAULT) : failure(error);
}
Result<Long, PetError> getDefaultPetId(PetError error) {
return getDefaultPet(error).mapSuccess(Pet::id);
}
}@Test
void testFlatMapSuccess() {
// Given
PetStore store = new PetStore();
Result<Long, PetError> result = success(100L);
// When
Result<Pet, PetError> mapped = result.flatMapSuccess(store::find);
// Then
assertEquals(NOT_FOUND, mapped.getFailure().orElse(null));
}@Test
void testFlatMapFailure() {
// Given
PetStore store = new PetStore();
Result<Long, PetError> result = failure(NO_CONFIG);
// When
Result<Long, PetError> mapped = result.flatMapFailure(store::getDefaultPetId);
// Then
assertEquals(Pet.DEFAULT.id(), mapped.orElse(null));
}@Test
void testFlatMap() {
// Given
PetStore store = new PetStore();
Result<Long, PetError> result1 = success(100L);
Result<Long, PetError> result2 = failure(NO_CONFIG);
// When
Result<Pet, PetError> mapped1 = result1.flatMap(store::find, store::getDefaultPet);
Result<Pet, PetError> mapped2 = result2.flatMap(store::find, store::getDefaultPet);
// Then
assertEquals(NOT_FOUND, mapped1.getFailure().orElse(null));
assertEquals(Pet.DEFAULT, mapped2.orElse(null));
}