Resolve property-dependent parameter names for exception messages

Prior to this commit when a required parameter defined as a property or
expression placeholder was missing, the exception thrown would refer to
the placeholder instead of the resolved name.

This change covers messaging handlers and web controllers, both blocking
and reactive. It also fixes the error message when handling null values
for non-required parameters, as well as in cases that need conversion.

See gh-32323
Closes gh-32462
This commit is contained in:
Andrea Mauro
2024-03-09 10:45:01 +01:00
committed by Simon Baslé
parent 5698191ba0
commit 458c30cb63
9 changed files with 234 additions and 19 deletions

View File

@@ -97,9 +97,9 @@ public abstract class AbstractNamedValueMethodArgumentResolver implements SyncHa
arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
}
else if (namedValueInfo.required && !nestedParameter.isOptional()) {
handleMissingValue(namedValueInfo.name, nestedParameter, message);
handleMissingValue(resolvedName.toString(), nestedParameter, message);
}
arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
arg = handleNullValue(resolvedName.toString(), arg, nestedParameter.getNestedParameterType());
}
else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
@@ -113,7 +113,7 @@ public abstract class AbstractNamedValueMethodArgumentResolver implements SyncHa
arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
}
else if (namedValueInfo.required && !nestedParameter.isOptional()) {
handleMissingValue(namedValueInfo.name, nestedParameter, message);
handleMissingValue(resolvedName.toString(), nestedParameter, message);
}
}
}

View File

@@ -105,9 +105,9 @@ public abstract class AbstractNamedValueMethodArgumentResolver implements Handle
arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
}
else if (namedValueInfo.required && !nestedParameter.isOptional()) {
handleMissingValue(namedValueInfo.name, nestedParameter, message);
handleMissingValue(resolvedName.toString(), nestedParameter, message);
}
arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
arg = handleNullValue(resolvedName.toString(), arg, nestedParameter.getNestedParameterType());
}
else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
@@ -121,7 +121,7 @@ public abstract class AbstractNamedValueMethodArgumentResolver implements Handle
arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
}
else if (namedValueInfo.required && !nestedParameter.isOptional()) {
handleMissingValue(namedValueInfo.name, nestedParameter, message);
handleMissingValue(resolvedName.toString(), nestedParameter, message);
}
}
}

View File

@@ -36,6 +36,7 @@ import org.springframework.messaging.support.NativeMessageHeaderAccessor;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.springframework.messaging.handler.annotation.MessagingPredicates.header;
import static org.springframework.messaging.handler.annotation.MessagingPredicates.headerPlain;
@@ -151,6 +152,37 @@ class HeaderMethodArgumentResolverTests {
assertThat(result).isEqualTo(Optional.empty());
}
@Test
void missingParameterFromSystemPropertyThroughPlaceholder() {
String expected = "sysbar";
System.setProperty("systemProperty", expected);
Message<byte[]> message = MessageBuilder.withPayload(new byte[0]).build();
MethodParameter param = this.resolvable.annot(header("#{systemProperties.systemProperty}")).arg();
assertThatThrownBy(() ->
resolveArgument(param, message))
.isInstanceOf(MessageHandlingException.class)
.hasMessageContaining(expected);
System.clearProperty("systemProperty");
}
@Test
void notNullablePrimitiveParameterFromSystemPropertyThroughPlaceholder() {
String expected = "sysbar";
System.setProperty("systemProperty", expected);
Message<byte[]> message = MessageBuilder.withPayload(new byte[0]).build();
MethodParameter param = this.resolvable.annot(header("${systemProperty}").required(false)).arg();
assertThatThrownBy(() ->
resolver.resolveArgument(param, message))
.isInstanceOf(IllegalStateException.class)
.hasMessageContaining(expected);
System.clearProperty("systemProperty");
}
@SuppressWarnings({"unchecked", "ConstantConditions"})
private <T> T resolveArgument(MethodParameter param, Message<?> message) {
return (T) this.resolver.resolveArgument(param, message).block(Duration.ofSeconds(5));
@@ -165,7 +197,8 @@ class HeaderMethodArgumentResolverTests {
@Header(name = "#{systemProperties.systemProperty}") String param4,
String param5,
@Header("foo") Optional<String> param6,
@Header("nativeHeaders.param1") String nativeHeaderParam1) {
@Header("nativeHeaders.param1") String nativeHeaderParam1,
@Header(name = "${systemProperty}", required = false) int primitivePlaceholderParam) {
}

View File

@@ -35,6 +35,7 @@ import org.springframework.messaging.support.NativeMessageHeaderAccessor;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.springframework.messaging.handler.annotation.MessagingPredicates.header;
import static org.springframework.messaging.handler.annotation.MessagingPredicates.headerPlain;
@@ -137,6 +138,36 @@ class HeaderMethodArgumentResolverTests {
}
}
@Test
void missingParameterFromSystemPropertyThroughPlaceholder() {
String expected = "sysbar";
System.setProperty("systemProperty", expected);
Message<byte[]> message = MessageBuilder.withPayload(new byte[0]).build();
MethodParameter param = this.resolvable.annot(header("#{systemProperties.systemProperty}")).arg();
assertThatThrownBy(() ->
resolver.resolveArgument(param, message))
.isInstanceOf(MessageHandlingException.class)
.hasMessageContaining(expected);
System.clearProperty("systemProperty");
}
@Test
void notNullablePrimitiveParameterFromSystemPropertyThroughPlaceholder() {
String expected = "sysbar";
System.setProperty("systemProperty", expected);
Message<byte[]> message = MessageBuilder.withPayload(new byte[0]).build();
MethodParameter param = this.resolvable.annot(header("${systemProperty}").required(false)).arg();
assertThatThrownBy(() ->
resolver.resolveArgument(param, message))
.isInstanceOf(IllegalStateException.class)
.hasMessageContaining(expected);
System.clearProperty("systemProperty");
}
@Test
void resolveOptionalHeaderWithValue() throws Exception {
Message<String> message = MessageBuilder.withPayload("foo").setHeader("foo", "bar").build();
@@ -162,7 +193,8 @@ class HeaderMethodArgumentResolverTests {
@Header(name = "#{systemProperties.systemProperty}") String param4,
String param5,
@Header("foo") Optional<String> param6,
@Header("nativeHeaders.param1") String nativeHeaderParam1) {
@Header("nativeHeaders.param1") String nativeHeaderParam1,
@Header(name = "${systemProperty}", required = false) int primitivePlaceholderParam) {
}