Extract base class AbstractNamedValueArgumentResolver
Closes gh-28395
This commit is contained in:
parent
d91b840d0e
commit
f8ac5985bd
|
@ -0,0 +1,235 @@
|
|||
/*
|
||||
* Copyright 2002-2022 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.service.invoker;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.web.bind.annotation.ValueConstants;
|
||||
|
||||
|
||||
/**
|
||||
* Base class for arguments that resolve to a named request value such as a
|
||||
* request header, path variable, cookie, and others.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 6.0
|
||||
*/
|
||||
public abstract class AbstractNamedValueArgumentResolver implements HttpServiceArgumentResolver {
|
||||
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private final ConversionService conversionService;
|
||||
|
||||
private final Map<MethodParameter, NamedValueInfo> namedValueInfoCache = new ConcurrentHashMap<>(256);
|
||||
|
||||
|
||||
/**
|
||||
* Create an instance.
|
||||
* @param conversionService the {@link ConversionService} to use to format
|
||||
* Object to String values.
|
||||
*/
|
||||
protected AbstractNamedValueArgumentResolver(ConversionService conversionService) {
|
||||
Assert.notNull(conversionService, "ConversionService is required");
|
||||
this.conversionService = conversionService;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the configured {@link ConversionService}.
|
||||
*/
|
||||
public ConversionService getConversionService() {
|
||||
return this.conversionService;
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public boolean resolve(
|
||||
@Nullable Object argument, MethodParameter parameter, HttpRequestValues.Builder requestValues) {
|
||||
|
||||
NamedValueInfo info = getNamedValueInfo(parameter);
|
||||
if (info == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Map.class.isAssignableFrom(parameter.getParameterType())) {
|
||||
Assert.isInstanceOf(Map.class, argument);
|
||||
for (Map.Entry<String, ?> entry : ((Map<String, ?>) argument).entrySet()) {
|
||||
addSingleOrMultipleValues(
|
||||
entry.getKey(), entry.getValue(), false, null, info.label, info.multiValued,
|
||||
requestValues);
|
||||
}
|
||||
}
|
||||
else {
|
||||
addSingleOrMultipleValues(
|
||||
info.name, argument, info.required, info.defaultValue, info.label, info.multiValued,
|
||||
requestValues);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private NamedValueInfo getNamedValueInfo(MethodParameter parameter) {
|
||||
NamedValueInfo info = this.namedValueInfoCache.get(parameter);
|
||||
if (info == null) {
|
||||
info = createNamedValueInfo(parameter);
|
||||
if (info == null) {
|
||||
return null;
|
||||
}
|
||||
info = updateNamedValueInfo(parameter, info);
|
||||
this.namedValueInfoCache.put(parameter, info);
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return information about the request value, or {@code null} if the
|
||||
* parameter does not represent a request value of interest.
|
||||
*/
|
||||
@Nullable
|
||||
protected abstract NamedValueInfo createNamedValueInfo(MethodParameter parameter);
|
||||
|
||||
private NamedValueInfo updateNamedValueInfo(MethodParameter parameter, NamedValueInfo info) {
|
||||
String name = info.name;
|
||||
if (info.name.isEmpty()) {
|
||||
name = parameter.getParameterName();
|
||||
if (name == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"Name for argument of type [" + parameter.getParameterType().getName() + "] " +
|
||||
"not specified, and parameter name information not found in class file either.");
|
||||
}
|
||||
}
|
||||
boolean required = (info.required && !parameter.getParameterType().equals(Optional.class));
|
||||
String defaultValue = (ValueConstants.DEFAULT_NONE.equals(info.defaultValue) ? null : info.defaultValue);
|
||||
return info.update(name, required, defaultValue);
|
||||
}
|
||||
|
||||
private void addSingleOrMultipleValues(
|
||||
String name, @Nullable Object value, boolean required, @Nullable Object defaultValue,
|
||||
String valueLabel, boolean supportsMultiValues, HttpRequestValues.Builder requestValues) {
|
||||
|
||||
if (supportsMultiValues) {
|
||||
value = (ObjectUtils.isArray(value) ? Arrays.asList((Object[]) value) : value);
|
||||
if (value instanceof Collection<?> elements) {
|
||||
boolean hasValues = false;
|
||||
for (Object element : elements) {
|
||||
if (element != null) {
|
||||
hasValues = true;
|
||||
addSingleValue(name, element, false, null, valueLabel, requestValues);
|
||||
}
|
||||
}
|
||||
if (hasValues) {
|
||||
return;
|
||||
}
|
||||
value = null;
|
||||
}
|
||||
}
|
||||
|
||||
addSingleValue(name, value, required, defaultValue, valueLabel, requestValues);
|
||||
}
|
||||
|
||||
private void addSingleValue(
|
||||
String name, @Nullable Object value, boolean required, @Nullable Object defaultValue, String valueLabel,
|
||||
HttpRequestValues.Builder requestValues) {
|
||||
|
||||
if (value instanceof Optional<?> optionalValue) {
|
||||
value = optionalValue.orElse(null);
|
||||
}
|
||||
|
||||
if (value == null && defaultValue != null) {
|
||||
value = defaultValue;
|
||||
}
|
||||
|
||||
if (!(value instanceof String)) {
|
||||
value = getConversionService().convert(value, String.class);
|
||||
}
|
||||
|
||||
if (value == null) {
|
||||
Assert.isTrue(!required, "Missing " + valueLabel + " value '" + name + "'");
|
||||
return;
|
||||
}
|
||||
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Resolved " + valueLabel + " value '" + name + ":" + value + "'");
|
||||
}
|
||||
|
||||
addRequestValue(name, (String) value, requestValues);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the given, single request value. This may be called multiples times
|
||||
* if the request value is multivalued.
|
||||
* @param name the request value name
|
||||
* @param value the value
|
||||
* @param requestValues builder to add the request value to
|
||||
*/
|
||||
protected abstract void addRequestValue(String name, String value, HttpRequestValues.Builder requestValues);
|
||||
|
||||
|
||||
/**
|
||||
* Info about a request value, typically extracted from a method parameter annotation.
|
||||
*/
|
||||
protected static class NamedValueInfo {
|
||||
|
||||
private final String name;
|
||||
|
||||
private final boolean required;
|
||||
|
||||
@Nullable
|
||||
private final String defaultValue;
|
||||
|
||||
private final String label;
|
||||
|
||||
private final boolean multiValued;
|
||||
|
||||
/**
|
||||
* Create an instance.
|
||||
* @param name the name to use, possibly empty if not specified
|
||||
* @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"
|
||||
*/
|
||||
public NamedValueInfo(
|
||||
String name, boolean required, @Nullable String defaultValue, String label, boolean multiValued) {
|
||||
|
||||
this.name = name;
|
||||
this.required = required;
|
||||
this.defaultValue = defaultValue;
|
||||
this.label = label;
|
||||
this.multiValued = multiValued;
|
||||
}
|
||||
|
||||
public NamedValueInfo update(String name, boolean required, @Nullable String defaultValue) {
|
||||
return new NamedValueInfo(name, required, defaultValue, this.label, this.multiValued);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -34,6 +34,7 @@ import org.springframework.core.ParameterizedTypeReference;
|
|||
import org.springframework.core.ReactiveAdapter;
|
||||
import org.springframework.core.ReactiveAdapterRegistry;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.core.annotation.SynthesizingMethodParameter;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
|
@ -86,7 +87,7 @@ final class HttpServiceMethod {
|
|||
DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();
|
||||
MethodParameter[] parameters = new MethodParameter[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
parameters[i] = new MethodParameter(method, i);
|
||||
parameters[i] = new SynthesizingMethodParameter(method, i);
|
||||
parameters[i].initParameterNameDiscovery(nameDiscoverer);
|
||||
}
|
||||
return parameters;
|
||||
|
|
|
@ -16,94 +16,48 @@
|
|||
|
||||
package org.springframework.web.service.invoker;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
|
||||
|
||||
/**
|
||||
* An implementation of {@link HttpServiceArgumentResolver} that resolves
|
||||
* request path variables based on method arguments annotated
|
||||
* with {@link PathVariable}. {@code null} values are allowed only
|
||||
* if {@link PathVariable#required()} is {@code true}.
|
||||
* {@link HttpServiceArgumentResolver} for {@link PathVariable @PathVariable}
|
||||
* annotated arguments.
|
||||
*
|
||||
* <p>The argument may be a single variable value or a {@code Map} with multiple
|
||||
* variable and values. Each value may be a String or an Object to be converted
|
||||
* to a String through the configured {@link ConversionService}.
|
||||
*
|
||||
* <p>If the value is required but {@code null}, {@link IllegalArgumentException}
|
||||
* is raised. The value is not required if:
|
||||
* <ul>
|
||||
* <li>{@link PathVariable#required()} is set to {@code false}
|
||||
* <li>The argument is declared as {@link java.util.Optional}
|
||||
* </ul>
|
||||
*
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 6.0
|
||||
*/
|
||||
public class PathVariableArgumentResolver implements HttpServiceArgumentResolver {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(PathVariableArgumentResolver.class);
|
||||
|
||||
|
||||
private final ConversionService conversionService;
|
||||
public class PathVariableArgumentResolver extends AbstractNamedValueArgumentResolver {
|
||||
|
||||
|
||||
public PathVariableArgumentResolver(ConversionService conversionService) {
|
||||
Assert.notNull(conversionService, "ConversionService is required");
|
||||
this.conversionService = conversionService;
|
||||
super(conversionService);
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public boolean resolve(
|
||||
@Nullable Object argument, MethodParameter parameter, HttpRequestValues.Builder requestValues) {
|
||||
|
||||
PathVariable annotation = parameter.getParameterAnnotation(PathVariable.class);
|
||||
if (annotation == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Class<?> parameterType = parameter.getParameterType();
|
||||
boolean required = (annotation.required() && !Optional.class.isAssignableFrom(parameterType));
|
||||
|
||||
if (Map.class.isAssignableFrom(parameterType)) {
|
||||
if (argument != null) {
|
||||
Assert.isInstanceOf(Map.class, argument);
|
||||
((Map<String, ?>) argument).forEach((key, value) ->
|
||||
addUriParameter(key, value, required, requestValues));
|
||||
}
|
||||
}
|
||||
else {
|
||||
String name = StringUtils.hasText(annotation.value()) ? annotation.value() : annotation.name();
|
||||
name = StringUtils.hasText(name) ? name : parameter.getParameterName();
|
||||
Assert.notNull(name, "Failed to determine path variable name for parameter: " + parameter);
|
||||
addUriParameter(name, argument, required, requestValues);
|
||||
}
|
||||
|
||||
return true;
|
||||
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
|
||||
PathVariable annot = parameter.getParameterAnnotation(PathVariable.class);
|
||||
return (annot == null ? null :
|
||||
new NamedValueInfo(annot.name(), annot.required(), null, "path variable", false));
|
||||
}
|
||||
|
||||
private void addUriParameter(
|
||||
String name, @Nullable Object value, boolean required, HttpRequestValues.Builder requestValues) {
|
||||
|
||||
if (value instanceof Optional) {
|
||||
value = ((Optional<?>) value).orElse(null);
|
||||
}
|
||||
|
||||
if (!(value instanceof String)) {
|
||||
value = this.conversionService.convert(value, String.class);
|
||||
}
|
||||
|
||||
if (value == null) {
|
||||
Assert.isTrue(!required, "Missing required path variable '" + name + "'");
|
||||
return;
|
||||
}
|
||||
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Resolved path variable '" + name + "' to " + value);
|
||||
}
|
||||
|
||||
requestValues.setUriVariable(name, (String) value);
|
||||
@Override
|
||||
protected void addRequestValue(String name, String value, HttpRequestValues.Builder requestValues) {
|
||||
requestValues.setUriVariable(name, value);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,22 +16,9 @@
|
|||
|
||||
package org.springframework.web.service.invoker;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.ValueConstants;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -49,100 +36,36 @@ import org.springframework.web.bind.annotation.ValueConstants;
|
|||
* <p>Individual header values may be Strings or Objects to be converted to
|
||||
* String values through the configured {@link ConversionService}.
|
||||
*
|
||||
* <p>If the value is required but {@code null}, {@link IllegalArgumentException}
|
||||
* is raised. The value is not required if:
|
||||
* <ul>
|
||||
* <li>{@link RequestHeader#required()} is set to {@code false}
|
||||
* <li>{@link RequestHeader#defaultValue()} provides a fallback value
|
||||
* <li>The argument is declared as {@link java.util.Optional}
|
||||
* </ul>
|
||||
*
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 6.0
|
||||
*/
|
||||
public class RequestHeaderArgumentResolver implements HttpServiceArgumentResolver {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(RequestHeaderArgumentResolver.class);
|
||||
|
||||
private final ConversionService conversionService;
|
||||
public class RequestHeaderArgumentResolver extends AbstractNamedValueArgumentResolver {
|
||||
|
||||
|
||||
public RequestHeaderArgumentResolver(ConversionService conversionService) {
|
||||
Assert.notNull(conversionService, "ConversionService is required");
|
||||
this.conversionService = conversionService;
|
||||
super(conversionService);
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public boolean resolve(
|
||||
@Nullable Object argument, MethodParameter parameter, HttpRequestValues.Builder requestValues) {
|
||||
|
||||
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
|
||||
RequestHeader annot = parameter.getParameterAnnotation(RequestHeader.class);
|
||||
if (annot == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Class<?> parameterType = parameter.getParameterType();
|
||||
boolean required = (annot.required() && !Optional.class.isAssignableFrom(parameterType));
|
||||
Object defaultValue = (ValueConstants.DEFAULT_NONE.equals(annot.defaultValue()) ? null : annot.defaultValue());
|
||||
|
||||
if (Map.class.isAssignableFrom(parameterType)) {
|
||||
if (argument != null) {
|
||||
Assert.isInstanceOf(Map.class, argument);
|
||||
((Map<String, ?>) argument).forEach((key, value) ->
|
||||
addHeader(key, value, false, defaultValue, requestValues));
|
||||
}
|
||||
}
|
||||
else {
|
||||
String name = StringUtils.hasText(annot.value()) ? annot.value() : annot.name();
|
||||
name = StringUtils.hasText(name) ? name : parameter.getParameterName();
|
||||
Assert.notNull(name, "Failed to determine request header name for parameter: " + parameter);
|
||||
addHeader(name, argument, required, defaultValue, requestValues);
|
||||
}
|
||||
|
||||
return true;
|
||||
return (annot == null ? null :
|
||||
new NamedValueInfo(annot.name(), annot.required(), annot.defaultValue(), "request header", true));
|
||||
}
|
||||
|
||||
private void addHeader(
|
||||
String name, @Nullable Object value, boolean required, @Nullable Object defaultValue,
|
||||
HttpRequestValues.Builder requestValues) {
|
||||
|
||||
value = (ObjectUtils.isArray(value) ? Arrays.asList((Object[]) value) : value);
|
||||
if (value instanceof Collection<?> elements) {
|
||||
boolean hasValue = false;
|
||||
for (Object element : elements) {
|
||||
if (element != null) {
|
||||
hasValue = true;
|
||||
addHeaderValue(name, element, false, requestValues);
|
||||
}
|
||||
}
|
||||
if (hasValue) {
|
||||
return;
|
||||
}
|
||||
value = null;
|
||||
}
|
||||
|
||||
if (value instanceof Optional<?> optionalValue) {
|
||||
value = optionalValue.orElse(null);
|
||||
}
|
||||
|
||||
if (value == null && defaultValue != null) {
|
||||
value = defaultValue;
|
||||
}
|
||||
|
||||
addHeaderValue(name, value, required, requestValues);
|
||||
}
|
||||
|
||||
private void addHeaderValue(
|
||||
String name, @Nullable Object value, boolean required, HttpRequestValues.Builder requestValues) {
|
||||
|
||||
if (!(value instanceof String)) {
|
||||
value = this.conversionService.convert(value, String.class);
|
||||
}
|
||||
|
||||
if (value == null) {
|
||||
Assert.isTrue(!required, "Missing required header '" + name + "'");
|
||||
return;
|
||||
}
|
||||
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Resolved header '" + name + ":" + value + "'");
|
||||
}
|
||||
|
||||
requestValues.addHeader(name, (String) value);
|
||||
@Override
|
||||
protected void addRequestValue(String name, String value, HttpRequestValues.Builder requestValues) {
|
||||
requestValues.addHeader(name, value);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -42,106 +42,86 @@ class PathVariableArgumentResolverTests {
|
|||
|
||||
|
||||
@Test
|
||||
void shouldResolvePathVariableWithNameFromParameter() {
|
||||
void stringVariable() {
|
||||
this.service.execute("test");
|
||||
assertPathVariable("id", "test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldResolvePathVariableWithNameFromAnnotationName() {
|
||||
this.service.executeNamed("test");
|
||||
assertPathVariable("id", "test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldResolvePathVariableNameFromValue() {
|
||||
this.service.executeNamedWithValue("test");
|
||||
assertPathVariable("id", "test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldOverrideNameIfValuePresentInAnnotation() {
|
||||
this.service.executeValueNamed("test");
|
||||
assertPathVariable("id", "test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldResolvePathVariableWithConversion() {
|
||||
void objectVariable() {
|
||||
this.service.execute(Boolean.TRUE);
|
||||
assertPathVariable("id", "true");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldResolvePathVariableFromOptionalArgumentWithConversion() {
|
||||
this.service.executeOptional(Optional.of(Boolean.TRUE));
|
||||
assertPathVariable("id", "true");
|
||||
void namedVariable() {
|
||||
this.service.executeNamed("test");
|
||||
assertPathVariable("id", "test");
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@Test
|
||||
void nullVariableRequired() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.service.execute(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldResolvePathVariableFromOptionalArgument() {
|
||||
void nullVariableNotRequired() {
|
||||
this.service.executeNotRequired(null);
|
||||
assertPathVariable("id", null);
|
||||
}
|
||||
|
||||
@Test
|
||||
void optionalStringVariable() {
|
||||
this.service.execute(Optional.of("test"));
|
||||
assertPathVariable("id", "test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowExceptionForNull() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.service.executeNamedWithValue(null));
|
||||
void optionalObjectVariable() {
|
||||
this.service.executeOptional(Optional.of(Boolean.TRUE));
|
||||
assertPathVariable("id", "true");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowExceptionForEmptyOptional() {
|
||||
void optionalEmpty() {
|
||||
this.service.executeOptional(Optional.empty());
|
||||
assertPathVariable("id", null);
|
||||
}
|
||||
|
||||
@Test
|
||||
void optionalEmptyOnObjectArgument() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.service.execute(Optional.empty()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldIgnoreNullWithConversionServiceWhenNotRequired() {
|
||||
this.service.executeNotRequired(null);
|
||||
assertThat(getActualUriVariables().get("id")).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldIgnoreNullWhenNotRequired() {
|
||||
this.service.executeNotRequired(null);
|
||||
assertPathVariable("id", null);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldIgnoreEmptyOptionalWhenNotRequired() {
|
||||
this.service.executeOptionalNotRequired(Optional.empty());
|
||||
assertPathVariable("id", null);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldResolvePathVariablesFromMap() {
|
||||
this.service.executeValueMap(Map.of("id", "test"));
|
||||
void mapOfVariables() {
|
||||
this.service.executeMap(Map.of("id", "test"));
|
||||
assertPathVariable("id", "test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldResolvePathVariableFromOptionalMapValue() {
|
||||
this.service.executeOptionalValueMap(Map.of("id", Optional.of("test")));
|
||||
void mapOfVariablesIsNull() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.service.executeMap(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void mapOfVariablesHasOptionalValue() {
|
||||
this.service.executeMapWithOptionalValue(Map.of("id", Optional.of("test")));
|
||||
assertPathVariable("id", "test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldIgnoreNullMapValue() {
|
||||
this.service.executeValueMap(null);
|
||||
assertThat(getActualUriVariables()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowExceptionForEmptyOptionalMapValue() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.service.executeOptionalValueMap(Map.of("id", Optional.empty())));
|
||||
void mapOfVariablesHasOptionalEmpty() {
|
||||
this.service.executeMapWithOptionalValue(Map.of("id", Optional.empty()));
|
||||
assertPathVariable("id", null);
|
||||
}
|
||||
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
private void assertPathVariable(String name, @Nullable String expectedValue) {
|
||||
assertThat(getActualUriVariables().get(name)).isEqualTo(expectedValue);
|
||||
}
|
||||
|
||||
private Map<String, String> getActualUriVariables() {
|
||||
return this.clientAdapter.getRequestValues().getUriVariables();
|
||||
assertThat(this.clientAdapter.getRequestValues().getUriVariables().get(name))
|
||||
.isEqualTo(expectedValue);
|
||||
}
|
||||
|
||||
|
||||
|
@ -151,6 +131,12 @@ class PathVariableArgumentResolverTests {
|
|||
@GetExchange
|
||||
void execute(@PathVariable String id);
|
||||
|
||||
@GetExchange
|
||||
void execute(@PathVariable Object id);
|
||||
|
||||
@GetExchange
|
||||
void executeNamed(@PathVariable(name = "id") String employeeId);
|
||||
|
||||
@GetExchange
|
||||
void executeNotRequired(@Nullable @PathVariable(required = false) String id);
|
||||
|
||||
|
@ -158,28 +144,10 @@ class PathVariableArgumentResolverTests {
|
|||
void executeOptional(@PathVariable Optional<Boolean> id);
|
||||
|
||||
@GetExchange
|
||||
void executeOptionalNotRequired(@PathVariable(required = false) Optional<String> id);
|
||||
void executeMap(@Nullable @PathVariable Map<String, String> map);
|
||||
|
||||
@GetExchange
|
||||
void executeNamedWithValue(@Nullable @PathVariable(name = "test", value = "id") String employeeId);
|
||||
|
||||
@GetExchange
|
||||
void executeNamed(@PathVariable(name = "id") String employeeId);
|
||||
|
||||
@GetExchange
|
||||
void executeValueNamed(@PathVariable("id") String employeeId);
|
||||
|
||||
@GetExchange
|
||||
void execute(@PathVariable Object id);
|
||||
|
||||
@GetExchange
|
||||
void execute(@PathVariable Boolean id);
|
||||
|
||||
@GetExchange
|
||||
void executeValueMap(@Nullable @PathVariable Map<String, String> map);
|
||||
|
||||
@GetExchange
|
||||
void executeOptionalValueMap(@PathVariable Map<String, Optional<String>> map);
|
||||
void executeMapWithOptionalValue(@PathVariable Map<String, Optional<String>> map);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -23,8 +23,8 @@ import java.util.Optional;
|
|||
import org.apache.groovy.util.Maps;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.service.annotation.GetExchange;
|
||||
|
||||
|
@ -56,43 +56,25 @@ class RequestHeaderArgumentResolverTests {
|
|||
assertRequestHeaders("id", "true");
|
||||
}
|
||||
|
||||
@Test
|
||||
void namedHeader() {
|
||||
this.service.executeNamed("test");
|
||||
assertRequestHeaders("id", "test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void listHeader() {
|
||||
this.service.execute(List.of("test1", Boolean.TRUE, "test3"));
|
||||
this.service.executeList(List.of("test1", Boolean.TRUE, "test3"));
|
||||
assertRequestHeaders("multiValueHeader", "test1", "true", "test3");
|
||||
}
|
||||
|
||||
@Test
|
||||
void arrayHeader() {
|
||||
this.service.execute("test1", Boolean.FALSE, "test3");
|
||||
this.service.executeArray("test1", Boolean.FALSE, "test3");
|
||||
assertRequestHeaders("multiValueHeader", "test1", "false", "test3");
|
||||
}
|
||||
|
||||
@Test
|
||||
void mapHeader() {
|
||||
this.service.executeMap(Maps.of("header1", "true", "header2", "false"));
|
||||
assertRequestHeaders("header1", "true");
|
||||
assertRequestHeaders("header2", "false");
|
||||
}
|
||||
|
||||
@Test
|
||||
void mapHeaderNull() {
|
||||
this.service.executeMap(null);
|
||||
assertThat(getActualHeaders()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void mapWithOptional() {
|
||||
this.service.executeOptionalMapValue(Map.of("id", Optional.of("test")));
|
||||
void namedHeader() {
|
||||
this.service.executeNamed("test");
|
||||
assertRequestHeaders("id", "test");
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@Test
|
||||
void nullHeaderRequired() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.service.executeString(null));
|
||||
|
@ -101,18 +83,23 @@ class RequestHeaderArgumentResolverTests {
|
|||
@Test
|
||||
void nullHeaderNotRequired() {
|
||||
this.service.executeNotRequired(null);
|
||||
assertThat(getActualHeaders().get("id")).isNull();
|
||||
assertRequestHeaders("id");
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullHeaderWithDefaultValue() {
|
||||
this.service.executeWithDefaultValue(null);
|
||||
assertRequestHeaders("id", "default");
|
||||
}
|
||||
|
||||
@Test
|
||||
void optional() {
|
||||
void optionalStringHeader() {
|
||||
this.service.executeOptional(Optional.of("test"));
|
||||
assertRequestHeaders("id", "test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void optionalWithConversion() {
|
||||
void optionalObjectHeader() {
|
||||
this.service.executeOptional(Optional.of(Boolean.TRUE));
|
||||
assertRequestHeaders("id", "true");
|
||||
}
|
||||
|
@ -120,27 +107,41 @@ class RequestHeaderArgumentResolverTests {
|
|||
@Test
|
||||
void optionalEmpty() {
|
||||
this.service.executeOptional(Optional.empty());
|
||||
assertThat(getActualHeaders().get("id")).isNull();
|
||||
assertRequestHeaders("id");
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultValueWithNull() {
|
||||
this.service.executeWithDefaultValue(null);
|
||||
assertRequestHeaders("id", "default");
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultValueWithOptional() {
|
||||
void optionalEmpthyWithDefaultValue() {
|
||||
this.service.executeOptionalWithDefaultValue(Optional.empty());
|
||||
assertRequestHeaders("id", "default");
|
||||
}
|
||||
|
||||
private void assertRequestHeaders(String key, String... values) {
|
||||
assertThat(getActualHeaders().get(key)).containsOnly(values);
|
||||
@Test
|
||||
void mapOfHeaders() {
|
||||
this.service.executeMap(Maps.of("header1", "true", "header2", "false"));
|
||||
assertRequestHeaders("header1", "true");
|
||||
assertRequestHeaders("header2", "false");
|
||||
}
|
||||
|
||||
private HttpHeaders getActualHeaders() {
|
||||
return this.clientAdapter.getRequestValues().getHeaders();
|
||||
@Test
|
||||
void mapOfHeadersIsNull() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.service.executeMap(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void mapOfHeadersHasOptionalValue() {
|
||||
this.service.executeMapWithOptionalValue(Map.of("id", Optional.of("test")));
|
||||
assertRequestHeaders("id", "test");
|
||||
}
|
||||
|
||||
private void assertRequestHeaders(String key, String... values) {
|
||||
List<String> actualValues = this.clientAdapter.getRequestValues().getHeaders().get(key);
|
||||
if (ObjectUtils.isEmpty(values)) {
|
||||
assertThat(actualValues).isNull();
|
||||
}
|
||||
else {
|
||||
assertThat(actualValues).containsOnly(values);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -148,38 +149,38 @@ class RequestHeaderArgumentResolverTests {
|
|||
private interface Service {
|
||||
|
||||
@GetExchange
|
||||
void executeString(@Nullable @RequestHeader String id);
|
||||
void executeString(@RequestHeader String id);
|
||||
|
||||
@GetExchange
|
||||
void execute(@RequestHeader Object id);
|
||||
|
||||
@GetExchange
|
||||
void executeList(@RequestHeader List<Object> multiValueHeader);
|
||||
|
||||
@GetExchange
|
||||
void executeArray(@RequestHeader Object... multiValueHeader);
|
||||
|
||||
@GetExchange
|
||||
void executeNamed(@RequestHeader(name = "id") String employeeId);
|
||||
|
||||
@GetExchange
|
||||
void execute(@RequestHeader List<Object> multiValueHeader);
|
||||
|
||||
@GetExchange
|
||||
void execute(@RequestHeader Object... multiValueHeader);
|
||||
|
||||
@GetExchange
|
||||
void executeMap(@Nullable @RequestHeader Map<String, String> id);
|
||||
|
||||
@GetExchange
|
||||
void executeOptionalMapValue(@RequestHeader Map<String, Optional<String>> headers);
|
||||
|
||||
@GetExchange
|
||||
void executeNotRequired(@Nullable @RequestHeader(required = false) String id);
|
||||
|
||||
@GetExchange
|
||||
void executeOptional(@RequestHeader Optional<Object> id);
|
||||
void executeWithDefaultValue(@Nullable @RequestHeader(defaultValue = "default") String id);
|
||||
|
||||
@GetExchange
|
||||
void executeWithDefaultValue(@Nullable @RequestHeader(defaultValue = "default") String id);
|
||||
void executeOptional(@RequestHeader Optional<Object> id);
|
||||
|
||||
@GetExchange
|
||||
void executeOptionalWithDefaultValue(@RequestHeader(defaultValue = "default") Optional<Object> id);
|
||||
|
||||
@GetExchange
|
||||
void executeMap(@Nullable @RequestHeader Map<String, String> id);
|
||||
|
||||
@GetExchange
|
||||
void executeMapWithOptionalValue(@RequestHeader Map<String, Optional<String>> headers);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue