Add ifPresentOrElse() API to Resolved
- Deprecate Resolved#get - Add javadocs - Improve unit test coverage See #380 Add support for AUTO_CONSUME schema type See #380
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2023-2023 the original author or authors.
|
||||
* Copyright 2023-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -27,6 +27,7 @@ import org.springframework.lang.Nullable;
|
||||
*
|
||||
* @param <T> the resolved type
|
||||
* @author Christophe Bornet
|
||||
* @author Chris Bono
|
||||
*/
|
||||
public final class Resolved<T> {
|
||||
|
||||
@@ -41,28 +42,96 @@ public final class Resolved<T> {
|
||||
this.exception = exception;
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create a {@code Resolved} when resolution succeeds.
|
||||
* @param value the non-{@code null} resolved value
|
||||
* @param <T> the type of the value
|
||||
* @return a {@code Resolved} containing the resolved value
|
||||
*/
|
||||
public static <T> Resolved<T> of(T value) {
|
||||
return new Resolved<>(value, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create a {@code Resolved} when resolution fails.
|
||||
* @param reason the non-{@code null} reason the resolution failed
|
||||
* @param <T> the type of the value
|
||||
* @return a {@code Resolved} containing an {@link IllegalArgumentException} with the
|
||||
* reason for the failure
|
||||
*/
|
||||
public static <T> Resolved<T> failed(String reason) {
|
||||
return new Resolved<>(null, new IllegalArgumentException(reason));
|
||||
}
|
||||
|
||||
public static <T> Resolved<T> failed(RuntimeException e) {
|
||||
return new Resolved<>(null, e);
|
||||
/**
|
||||
* Factory method to create a {@code Resolved} when resolution fails.
|
||||
* @param reason the non-{@code null} reason the resolution failed
|
||||
* @param <T> the type of the value
|
||||
* @return a {@code Resolved} containing the reason for the failure
|
||||
*/
|
||||
public static <T> Resolved<T> failed(RuntimeException reason) {
|
||||
return new Resolved<>(null, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the optional resolved value.
|
||||
* @return an optional with the resolved value or empty if failed to resolve
|
||||
* @deprecated Use {@link #value()} instead
|
||||
*/
|
||||
@Deprecated(since = "1.1.0", forRemoval = true)
|
||||
public Optional<T> get() {
|
||||
return value();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the resolved value.
|
||||
* @return an optional with the resolved value or empty if failed to resolve
|
||||
*/
|
||||
public Optional<T> value() {
|
||||
return Optional.ofNullable(this.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the exception that may have occurred during resolution.
|
||||
* @return an optional with the resolution exception or empty if no error occurred
|
||||
*/
|
||||
public Optional<RuntimeException> exception() {
|
||||
return Optional.ofNullable(this.exception);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the given action with the resolved value if a value was resolved and no
|
||||
* exception occurred.
|
||||
* @param action the action to be performed
|
||||
*/
|
||||
public void ifResolved(Consumer<? super T> action) {
|
||||
if (this.value != null) {
|
||||
if (this.value != null && this.exception == null) {
|
||||
action.accept(this.value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the given action with the resolved value if a non-{@code null} value was
|
||||
* resolved and no exception occurred. Otherwise, if an exception occurred then the
|
||||
* provided error action is performed with the exception.
|
||||
* @param action the action to be performed
|
||||
* @param errorAction the error action to be performed
|
||||
*/
|
||||
public void ifResolvedOrElse(Consumer<? super T> action, Consumer<RuntimeException> errorAction) {
|
||||
if (this.value != null && this.exception == null) {
|
||||
action.accept(this.value);
|
||||
}
|
||||
else if (this.exception != null) {
|
||||
errorAction.accept(this.exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the resolved value if a value was resolved and no exception occurred,
|
||||
* otherwise throws the resolution exception back to the caller.
|
||||
* @return the resolved value if a value was resolved and no exception occurred
|
||||
* @throws RuntimeException if an exception occurred during resolution
|
||||
*/
|
||||
public T orElseThrow() {
|
||||
if (this.value == null && this.exception != null) {
|
||||
throw this.exception;
|
||||
@@ -70,6 +139,15 @@ public final class Resolved<T> {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the resolved value if a value was resolved and no exception occurred,
|
||||
* otherwise wraps the resolution exception with the provided error message and throws
|
||||
* back to the caller.
|
||||
* @param wrappingErrorMessage additional context to add to the wrapped exception
|
||||
* @return the resolved value if a value was resolved and no exception occurred
|
||||
* @throws RuntimeException wrapping the resolution exception if an exception occurred
|
||||
* during resolution
|
||||
*/
|
||||
public T orElseThrow(Supplier<String> wrappingErrorMessage) {
|
||||
if (this.value == null && this.exception != null) {
|
||||
throw new RuntimeException(wrappingErrorMessage.get(), this.exception);
|
||||
|
||||
@@ -58,7 +58,7 @@ class DefaultTopicResolverTests {
|
||||
@MethodSource("resolveNoMessageInfoProvider")
|
||||
void resolveNoMessageInfo(String testName, @Nullable String userTopic, @Nullable String defaultTopic,
|
||||
@Nullable String expectedTopic) {
|
||||
assertThat(resolver.resolveTopic(userTopic, () -> defaultTopic).get().orElse(null)).isEqualTo(expectedTopic);
|
||||
assertThat(resolver.resolveTopic(userTopic, () -> defaultTopic).value().orElse(null)).isEqualTo(expectedTopic);
|
||||
}
|
||||
|
||||
static Stream<Arguments> resolveNoMessageInfoProvider() {
|
||||
@@ -76,7 +76,7 @@ class DefaultTopicResolverTests {
|
||||
@MethodSource("resolveByMessageInstanceProvider")
|
||||
<T> void resolveByMessageInstance(String testName, @Nullable String userTopic, T message,
|
||||
@Nullable String defaultTopic, @Nullable String expectedTopic) {
|
||||
assertThat(resolver.resolveTopic(userTopic, message, () -> defaultTopic).get().orElse(null))
|
||||
assertThat(resolver.resolveTopic(userTopic, message, () -> defaultTopic).value().orElse(null))
|
||||
.isEqualTo(expectedTopic);
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ class DefaultTopicResolverTests {
|
||||
@MethodSource("resolveByMessageTypeProvider")
|
||||
void resolveByMessageType(String testName, @Nullable String userTopic, Class<?> messageType,
|
||||
@Nullable String defaultTopic, @Nullable String expectedTopic) {
|
||||
assertThat(resolver.resolveTopic(userTopic, messageType, () -> defaultTopic).get().orElse(null))
|
||||
assertThat(resolver.resolveTopic(userTopic, messageType, () -> defaultTopic).value().orElse(null))
|
||||
.isEqualTo(expectedTopic);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2023-2023 the original author or authors.
|
||||
* Copyright 2023-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -20,8 +20,15 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
import static org.assertj.core.api.Assertions.assertThatRuntimeException;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link Resolved}.
|
||||
@@ -30,33 +37,119 @@ import org.junit.jupiter.api.Test;
|
||||
*/
|
||||
class ResolvedTests {
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
@Test
|
||||
void success() {
|
||||
assertThat(Resolved.of("good").get()).hasValue("good");
|
||||
assertThat(Resolved.of("good").orElseThrow()).isEqualTo("good");
|
||||
void deprecatedGetDelegatesToNewValueMethod() {
|
||||
Resolved<String> resolved = Mockito.spy(Resolved.of("hello"));
|
||||
resolved.get();
|
||||
verify(resolved).value();
|
||||
}
|
||||
|
||||
@Test
|
||||
void failedWithSimpleReason() {
|
||||
var resolved = Resolved.failed("oops");
|
||||
assertThatIllegalArgumentException().isThrownBy(resolved::orElseThrow).withMessage("oops");
|
||||
assertThat(resolved.get()).isEmpty();
|
||||
@SuppressWarnings("unchecked")
|
||||
static Consumer<String> mockValueAction() {
|
||||
return (Consumer<String>) mock(Consumer.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void failedWithReason() {
|
||||
var resolved = Resolved.failed(new IllegalStateException("5150"));
|
||||
assertThatIllegalStateException().isThrownBy(resolved::orElseThrow).withMessage("5150");
|
||||
assertThat(resolved.get()).isEmpty();
|
||||
@SuppressWarnings("unchecked")
|
||||
static Consumer<RuntimeException> mockErrorAction() {
|
||||
return (Consumer<RuntimeException>) mock(Consumer.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void failedWithAdditionalMessage() {
|
||||
var resolved = Resolved.failed(new IllegalStateException("5150"));
|
||||
assertThatRuntimeException().isThrownBy(() -> resolved.orElseThrow(() -> "extra message"))
|
||||
.withMessage("extra message")
|
||||
.withCause(new IllegalStateException("5150"));
|
||||
assertThat(resolved.get()).isEmpty();
|
||||
@Nested
|
||||
class WhenResolutionSucceeds {
|
||||
|
||||
@Test
|
||||
void valueDoesReturnValue() {
|
||||
var resolved = Resolved.of("smile");
|
||||
assertThat(resolved.value()).hasValue("smile");
|
||||
}
|
||||
|
||||
@Test
|
||||
void orElseThrowDoesReturnValue() {
|
||||
var resolved = Resolved.of("smile");
|
||||
assertThat(resolved.orElseThrow()).isEqualTo("smile");
|
||||
}
|
||||
|
||||
@Test
|
||||
void exceptionDoesReturnEmpty() {
|
||||
var resolved = Resolved.of("smile");
|
||||
assertThat(resolved.exception()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void ifResolvedDoesCallValueAction() {
|
||||
var resolved = Resolved.of("smile");
|
||||
var valueAction = mockValueAction();
|
||||
resolved.ifResolved(valueAction);
|
||||
verify(valueAction).accept("smile");
|
||||
}
|
||||
|
||||
@Test
|
||||
void ifResolvedOrElseDoesCallValueActionAndIgnoresErrorAction() {
|
||||
var resolved = Resolved.of("smile");
|
||||
var valueAction = mockValueAction();
|
||||
var errorAction = mockErrorAction();
|
||||
resolved.ifResolvedOrElse(valueAction, errorAction);
|
||||
verify(valueAction).accept("smile");
|
||||
verifyNoInteractions(errorAction);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
class WhenResolutionFails {
|
||||
|
||||
@Test
|
||||
void valueDoesReturnEmpty() {
|
||||
var resolved = Resolved.failed("5150");
|
||||
assertThat(resolved.value()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void orElseThrowDoesThrowSimpleReason() {
|
||||
var resolved = Resolved.failed("5150");
|
||||
assertThatIllegalArgumentException().isThrownBy(resolved::orElseThrow).withMessage("5150");
|
||||
}
|
||||
|
||||
@Test
|
||||
void orElseThrowDoesThrowReason() {
|
||||
var resolved = Resolved.failed(new IllegalStateException("5150"));
|
||||
assertThatIllegalStateException().isThrownBy(resolved::orElseThrow).withMessage("5150");
|
||||
}
|
||||
|
||||
@Test
|
||||
void orElseThrowDoesThrowReasonWithExtraMessage() {
|
||||
var resolved = Resolved.failed(new IllegalStateException("5150"));
|
||||
assertThatRuntimeException().isThrownBy(() -> resolved.orElseThrow(() -> "extra message"))
|
||||
.withMessage("extra message")
|
||||
.withCause(new IllegalStateException("5150"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void exceptionDoesReturnReason() {
|
||||
var resolved = Resolved.failed("5150");
|
||||
assertThat(resolved.exception()).hasValueSatisfying(
|
||||
(ex) -> assertThat(ex).isInstanceOf(IllegalArgumentException.class).hasMessage("5150"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void ifResolvedDoesNotCallValueAction() {
|
||||
var resolved = Resolved.<String>failed("5150");
|
||||
var valueAction = mockValueAction();
|
||||
resolved.ifResolved(valueAction);
|
||||
verifyNoInteractions(valueAction);
|
||||
}
|
||||
|
||||
@Test
|
||||
void ifResolvedOrElseDoesIgnoreValueActionAndCallsErrorAction() {
|
||||
var resolved = Resolved.<String>failed("5150");
|
||||
var valueAction = mockValueAction();
|
||||
var errorAction = mockErrorAction();
|
||||
resolved.ifResolvedOrElse(valueAction, errorAction);
|
||||
verifyNoInteractions(valueAction);
|
||||
verify(errorAction).accept(resolved.exception().get());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user