Support formatting annotations on HTTP service interface

Closes gh-29095
This commit is contained in:
rstoyanchev 2022-09-21 16:18:39 +01:00
parent 157c28bcec
commit 5192d99fa4
2 changed files with 28 additions and 7 deletions

View File

@ -27,6 +27,7 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.core.MethodParameter; import org.springframework.core.MethodParameter;
import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
@ -41,6 +42,8 @@ import org.springframework.web.bind.annotation.ValueConstants;
*/ */
public abstract class AbstractNamedValueArgumentResolver implements HttpServiceArgumentResolver { public abstract class AbstractNamedValueArgumentResolver implements HttpServiceArgumentResolver {
private static final TypeDescriptor STRING_TARGET_TYPE = TypeDescriptor.valueOf(String.class);
protected final Log logger = LogFactory.getLog(getClass()); protected final Log logger = LogFactory.getLog(getClass());
@ -83,13 +86,13 @@ public abstract class AbstractNamedValueArgumentResolver implements HttpServiceA
for (Map.Entry<String, ?> entry : ((Map<String, ?>) argument).entrySet()) { for (Map.Entry<String, ?> entry : ((Map<String, ?>) argument).entrySet()) {
addSingleOrMultipleValues( addSingleOrMultipleValues(
entry.getKey(), entry.getValue(), false, null, info.label, info.multiValued, entry.getKey(), entry.getValue(), false, null, info.label, info.multiValued,
requestValues); null, requestValues);
} }
} }
else { else {
addSingleOrMultipleValues( addSingleOrMultipleValues(
info.name, argument, info.required, info.defaultValue, info.label, info.multiValued, info.name, argument, info.required, info.defaultValue, info.label, info.multiValued,
requestValues); parameter, requestValues);
} }
return true; return true;
@ -133,7 +136,8 @@ public abstract class AbstractNamedValueArgumentResolver implements HttpServiceA
private void addSingleOrMultipleValues( private void addSingleOrMultipleValues(
String name, @Nullable Object value, boolean required, @Nullable Object defaultValue, String name, @Nullable Object value, boolean required, @Nullable Object defaultValue,
String valueLabel, boolean supportsMultiValues, HttpRequestValues.Builder requestValues) { String valueLabel, boolean supportsMultiValues, @Nullable MethodParameter parameter,
HttpRequestValues.Builder requestValues) {
if (supportsMultiValues) { if (supportsMultiValues) {
value = (ObjectUtils.isArray(value) ? Arrays.asList((Object[]) value) : value); value = (ObjectUtils.isArray(value) ? Arrays.asList((Object[]) value) : value);
@ -142,7 +146,7 @@ public abstract class AbstractNamedValueArgumentResolver implements HttpServiceA
for (Object element : elements) { for (Object element : elements) {
if (element != null) { if (element != null) {
hasValues = true; hasValues = true;
addSingleValue(name, element, false, null, valueLabel, requestValues); addSingleValue(name, element, false, null, valueLabel, null, requestValues);
} }
} }
if (hasValues) { if (hasValues) {
@ -152,12 +156,12 @@ public abstract class AbstractNamedValueArgumentResolver implements HttpServiceA
} }
} }
addSingleValue(name, value, required, defaultValue, valueLabel, requestValues); addSingleValue(name, value, required, defaultValue, valueLabel, parameter, requestValues);
} }
private void addSingleValue( private void addSingleValue(
String name, @Nullable Object value, boolean required, @Nullable Object defaultValue, String valueLabel, String name, @Nullable Object value, boolean required, @Nullable Object defaultValue, String valueLabel,
HttpRequestValues.Builder requestValues) { @Nullable MethodParameter parameter, HttpRequestValues.Builder requestValues) {
if (value instanceof Optional<?> optionalValue) { if (value instanceof Optional<?> optionalValue) {
value = optionalValue.orElse(null); value = optionalValue.orElse(null);
@ -168,8 +172,14 @@ public abstract class AbstractNamedValueArgumentResolver implements HttpServiceA
} }
if (this.conversionService != null && !(value instanceof String)) { if (this.conversionService != null && !(value instanceof String)) {
parameter = (parameter != null ? parameter.nestedIfOptional() : null);
if (parameter != null && parameter.getNestedParameterType() != Object.class) {
value = this.conversionService.convert(value, new TypeDescriptor(parameter), STRING_TARGET_TYPE);
}
else {
value = this.conversionService.convert(value, String.class); value = this.conversionService.convert(value, String.class);
} }
}
if (value == null) { if (value == null) {
Assert.isTrue(!required, "Missing " + valueLabel + " value '" + name + "'"); Assert.isTrue(!required, "Missing " + valueLabel + " value '" + name + "'");

View File

@ -21,6 +21,7 @@ import java.lang.annotation.ElementType;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import java.time.LocalDate;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
@ -31,6 +32,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.core.MethodParameter; import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AliasFor; import org.springframework.core.annotation.AliasFor;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.support.DefaultFormattingConversionService; import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.LinkedMultiValueMap;
@ -73,6 +75,12 @@ class NamedValueArgumentResolverTests {
assertTestValue("value", "test"); assertTestValue("value", "test");
} }
@Test // gh-29095
void dateTestValue() {
this.service.executeDate(LocalDate.of(2022, 9, 16));
assertTestValue("value", "2022-09-16");
}
@Test @Test
void objectTestValue() { void objectTestValue() {
this.service.execute(Boolean.TRUE); this.service.execute(Boolean.TRUE);
@ -174,6 +182,9 @@ class NamedValueArgumentResolverTests {
@GetExchange @GetExchange
void executeString(@TestValue String value); void executeString(@TestValue String value);
@GetExchange
void executeDate(@TestValue @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate value);
@GetExchange @GetExchange
void execute(@TestValue Object value); void execute(@TestValue Object value);