Update contribution
Closes gh-33220
This commit is contained in:
@@ -129,7 +129,7 @@ public abstract class AbstractNamedValueArgumentResolver implements HttpServiceA
|
||||
*/
|
||||
@Nullable
|
||||
protected NamedValueInfo createNamedValueInfo(
|
||||
MethodParameter parameter, HttpRequestValues.Metadata requestValues) {
|
||||
MethodParameter parameter, HttpRequestValues.Metadata metadata) {
|
||||
|
||||
return createNamedValueInfo(parameter);
|
||||
}
|
||||
@@ -246,6 +246,8 @@ public abstract class AbstractNamedValueArgumentResolver implements HttpServiceA
|
||||
* @param required whether it is marked as required
|
||||
* @param defaultValue fallback value, possibly {@link ValueConstants#DEFAULT_NONE}
|
||||
* @param label how it should appear in error messages, e.g. "path variable", "request header"
|
||||
* @param multiValued whether this argument resolver supports sending multiple values;
|
||||
* if not, then multiple values are formatted as a String value
|
||||
*/
|
||||
public NamedValueInfo(
|
||||
String name, boolean required, @Nullable String defaultValue, String label, boolean multiValued) {
|
||||
|
||||
@@ -55,41 +55,72 @@ import org.springframework.web.bind.annotation.RequestParam;
|
||||
*/
|
||||
public class RequestParamArgumentResolver extends AbstractNamedValueArgumentResolver {
|
||||
|
||||
private boolean formatAsSingleValue = true;
|
||||
private boolean favorSingleValue;
|
||||
|
||||
|
||||
public RequestParamArgumentResolver(ConversionService conversionService) {
|
||||
super(conversionService);
|
||||
}
|
||||
|
||||
public RequestParamArgumentResolver(ConversionService conversionService, boolean formatAsSingleValue) {
|
||||
super(conversionService);
|
||||
this.formatAsSingleValue = formatAsSingleValue;
|
||||
|
||||
/**
|
||||
* Whether to format multiple values (e.g. collection, array) as a single
|
||||
* String value through the configured {@link ConversionService} unless the
|
||||
* content type is form data, or it is a multipart request.
|
||||
* <p>By default, this is {@code false} in which case formatting is not applied,
|
||||
* and a separate parameter with the same name is created for each value.
|
||||
* @since 6.2
|
||||
*/
|
||||
public void setFavorSingleValue(boolean favorSingleValue) {
|
||||
this.favorSingleValue = favorSingleValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the setting for {@link #setFavorSingleValue favorSingleValue}.
|
||||
* @since 6.2
|
||||
*/
|
||||
public boolean isFavorSingleValue() {
|
||||
return this.favorSingleValue;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter, HttpRequestValues.Metadata requestValues) {
|
||||
MediaType contentType = requestValues.getContentType();
|
||||
if (contentType != null && isMultiValueFormContentType(contentType)) {
|
||||
this.formatAsSingleValue = true;
|
||||
}
|
||||
|
||||
return createNamedValueInfo(parameter);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
|
||||
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter, HttpRequestValues.Metadata metadata) {
|
||||
RequestParam annot = parameter.getParameterAnnotation(RequestParam.class);
|
||||
if (annot == null) {
|
||||
return null;
|
||||
}
|
||||
return new NamedValueInfo(
|
||||
annot.name(), annot.required(), annot.defaultValue(), "request parameter",
|
||||
supportsMultipleValues(parameter, metadata));
|
||||
}
|
||||
|
||||
return (annot == null ? null :
|
||||
new NamedValueInfo(annot.name(), annot.required(), annot.defaultValue(),
|
||||
"request parameter", this.formatAsSingleValue));
|
||||
@Override
|
||||
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
|
||||
// Shouldn't be called since we override createNamedValueInfo with HttpRequestValues.Metadata
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the resolver should send multi-value request parameters
|
||||
* as individual values. If not, they are formatted to a single String value.
|
||||
* The default implementation uses {@link #isFavorSingleValue()} to decide
|
||||
* unless the content type is form data, or it is a multipart request.
|
||||
* @since 6.2
|
||||
*/
|
||||
protected boolean supportsMultipleValues(MethodParameter parameter, HttpRequestValues.Metadata metadata) {
|
||||
return (!isFavorSingleValue() || isFormOrMultipartContent(metadata));
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the content type is form data, or it is a multipart request.
|
||||
* @since 6.2
|
||||
*/
|
||||
protected boolean isFormOrMultipartContent(HttpRequestValues.Metadata metadata) {
|
||||
MediaType mediaType = metadata.getContentType();
|
||||
return (mediaType != null && (mediaType.getType().equals("multipart") ||
|
||||
mediaType.isCompatibleWith(MediaType.APPLICATION_FORM_URLENCODED)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -99,17 +130,4 @@ public class RequestParamArgumentResolver extends AbstractNamedValueArgumentReso
|
||||
requestValues.addRequestParameter(name, (String) value);
|
||||
}
|
||||
|
||||
protected boolean isFormatAsSingleValue() {
|
||||
return this.formatAsSingleValue;
|
||||
}
|
||||
|
||||
protected void setFormatAsSingleValue(boolean formatAsSingleValue) {
|
||||
this.formatAsSingleValue = formatAsSingleValue;
|
||||
}
|
||||
|
||||
protected boolean isMultiValueFormContentType(MediaType contentType) {
|
||||
return contentType.equals(MediaType.APPLICATION_FORM_URLENCODED)
|
||||
|| contentType.getType().equals("multipart");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,17 +16,18 @@
|
||||
|
||||
package org.springframework.web.service.invoker;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.service.annotation.GetExchange;
|
||||
import org.springframework.web.service.annotation.PostExchange;
|
||||
import org.springframework.web.util.UriComponents;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@@ -62,24 +63,18 @@ class RequestParamArgumentResolverTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void requestParamWithDisabledFormattingCollectionValue() {
|
||||
ConversionService conversionService = new DefaultConversionService();
|
||||
boolean formatAsSingleValue = false;
|
||||
Service service = builder.customArgumentResolver(
|
||||
new RequestParamArgumentResolver(conversionService, formatAsSingleValue))
|
||||
.build()
|
||||
.createClient(Service.class);
|
||||
List<String> collectionParams = List.of("1", "2", "3");
|
||||
service.getForm("value 1", collectionParams);
|
||||
RequestParamArgumentResolver resolver = new RequestParamArgumentResolver(new DefaultConversionService());
|
||||
resolver.setFavorSingleValue(true);
|
||||
|
||||
Object uriVariables = this.client.getRequestValues().getUriVariables();
|
||||
assertThat(uriVariables).isNotInstanceOf(MultiValueMap.class).isInstanceOf(HashMap.class);
|
||||
assertThat((HashMap<String, String>) uriVariables).hasSize(4)
|
||||
.containsEntry("queryParam0", "param1")
|
||||
.containsEntry("queryParam0[0]", "value 1")
|
||||
.containsEntry("queryParam1", "param2")
|
||||
.containsEntry("queryParam1[0]", String.join(",", collectionParams));
|
||||
Service service = builder.customArgumentResolver(resolver).build().createClient(Service.class);
|
||||
service.getWithParams("value 1", List.of("1", "2", "3"));
|
||||
|
||||
HttpRequestValues values = this.client.getRequestValues();
|
||||
String uriTemplate = values.getUriTemplate();
|
||||
Map<String, String> uriVariables = values.getUriVariables();
|
||||
UriComponents uri = UriComponentsBuilder.fromUriString(uriTemplate).buildAndExpand(uriVariables).encode();
|
||||
assertThat(uri.getQuery()).isEqualTo("param1=value%201¶m2=1,2,3");
|
||||
}
|
||||
|
||||
private interface Service {
|
||||
@@ -88,7 +83,7 @@ class RequestParamArgumentResolverTests {
|
||||
void postForm(@RequestParam String param1, @RequestParam String param2);
|
||||
|
||||
@GetExchange
|
||||
void getForm(@RequestParam String param1, @RequestParam List<String> param2);
|
||||
void getWithParams(@RequestParam String param1, @RequestParam List<String> param2);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user