Add HttpMethod and PathVariable argument resolvers

See gh-28386
This commit is contained in:
Olga Maciaszek-Sharma 2022-04-21 16:06:38 +00:00 committed by rstoyanchev
parent c418768f05
commit c2a008fc22
8 changed files with 979 additions and 112 deletions

View File

@ -0,0 +1,51 @@
/*
* 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 org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpMethod;
import org.springframework.lang.Nullable;
/**
* An implementation of {@link HttpServiceMethodArgumentResolver} that resolves
* request HTTP method based on argument type. Arguments of type
* {@link HttpMethod} will be used to determine the method.
*
* @author Olga Maciaszek-Sharma
* @since 6.0
*/
public class HttpMethodArgumentResolver implements HttpServiceMethodArgumentResolver {
private static final Log LOG = LogFactory.getLog(HttpMethodArgumentResolver.class);
@Override
public void resolve(@Nullable Object argument, MethodParameter parameter,
HttpRequestDefinition requestDefinition) {
if (argument == null) {
return;
}
if (argument instanceof HttpMethod httpMethod) {
if (LOG.isTraceEnabled()) {
LOG.trace("Resolved HTTP method to: " + httpMethod.name());
}
requestDefinition.setHttpMethod(httpMethod);
}
}
}

View File

@ -28,7 +28,9 @@ import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.MethodParameter;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.ReactiveAdapter;
import org.springframework.core.ReactiveAdapterRegistry;
@ -104,6 +106,8 @@ final class HttpServiceMethod {
Assert.isTrue(arguments.length == this.parameters.length, "Method argument mismatch");
for (int i = 0; i < this.parameters.length; i++) {
Object argumentValue = arguments[i];
ParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();
this.parameters[i].initParameterNameDiscovery(nameDiscoverer);
for (HttpServiceMethodArgumentResolver resolver : this.argumentResolvers) {
resolver.resolve(argumentValue, this.parameters[i], requestDefinition);
}

View File

@ -16,7 +16,6 @@
package org.springframework.web.service.invoker;
import java.lang.reflect.Method;
import java.time.Duration;
import java.util.HashMap;

View File

@ -0,0 +1,152 @@
/*
* 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.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.ReactiveAdapter;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PathVariable;
/**
* An implementation of {@link HttpServiceMethodArgumentResolver} 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}.
*
* @author Olga Maciaszek-Sharma
* @since 6.0
*/
public class PathVariableArgumentResolver implements HttpServiceMethodArgumentResolver {
private static final Log LOG = LogFactory.getLog(PathVariableArgumentResolver.class);
private static final TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(String.class);
@Nullable
private final ConversionService conversionService;
public PathVariableArgumentResolver(@Nullable ConversionService conversionService) {
this.conversionService = conversionService;
}
@Override
public void resolve(@Nullable Object argument, MethodParameter parameter,
HttpRequestDefinition requestDefinition) {
PathVariable annotation = parameter.getParameterAnnotation(PathVariable.class);
if (annotation == null) {
return;
}
String resolvedAnnotationName = StringUtils.hasText(annotation.value())
? annotation.value() : annotation.name();
boolean required = annotation.required();
Object resolvedArgument = resolveFromOptional(argument);
if (resolvedArgument instanceof Map<?, ?> valueMap) {
if (StringUtils.hasText(resolvedAnnotationName)) {
Object value = valueMap.get(resolvedAnnotationName);
Object resolvedValue = resolveFromOptional(value);
addUriParameter(requestDefinition, resolvedAnnotationName, resolvedValue, required);
return;
}
valueMap.entrySet()
.forEach(entry -> addUriParameter(requestDefinition, entry, required));
return;
}
String name = StringUtils.hasText(resolvedAnnotationName)
? resolvedAnnotationName : parameter.getParameterName();
addUriParameter(requestDefinition, name, resolvedArgument, required);
}
private void addUriParameter(HttpRequestDefinition requestDefinition, @Nullable String name,
@Nullable Object value, boolean required) {
if (name == null) {
throw new IllegalStateException("Path variable name cannot be null");
}
String stringValue = getStringValue(value, required);
if (LOG.isTraceEnabled()) {
LOG.trace("Path variable " + name + " resolved to " + stringValue);
}
requestDefinition.getUriVariables().put(name, stringValue);
}
@Nullable
private String getStringValue(@Nullable Object value, boolean required) {
validateForNull(value, required);
validateForReactiveWrapper(value);
return value != null
? convertToString(TypeDescriptor.valueOf(value.getClass()), value) : null;
}
private void addUriParameter(HttpRequestDefinition requestDefinition,
Map.Entry<?, ?> entry, boolean required) {
Object resolvedName = resolveFromOptional(entry.getKey());
String stringName = getStringValue(resolvedName, true);
Object resolvedValue = resolveFromOptional(entry.getValue());
addUriParameter(requestDefinition, stringName, resolvedValue, required);
}
private void validateForNull(@Nullable Object argument, boolean required) {
if (argument == null) {
if (required) {
throw new IllegalStateException("Required variable cannot be null");
}
}
}
private void validateForReactiveWrapper(@Nullable Object object) {
if (object != null) {
Class<?> type = object.getClass();
ReactiveAdapterRegistry adapterRegistry = ReactiveAdapterRegistry.getSharedInstance();
ReactiveAdapter adapter = adapterRegistry.getAdapter(type);
if (adapter != null) {
throw new IllegalStateException(getClass().getSimpleName() +
" does not support reactive type wrapper: " + type);
}
}
}
@Nullable
private Object resolveFromOptional(@Nullable Object argument) {
if (argument instanceof Optional) {
return ((Optional<?>) argument).orElse(null);
}
return argument;
}
@Nullable
private String convertToString(TypeDescriptor typeDescriptor, @Nullable Object value) {
if (value == null) {
return null;
}
if (value instanceof String) {
return (String) value;
}
if (this.conversionService != null) {
return (String) this.conversionService.convert(value, typeDescriptor, STRING_TYPE_DESCRIPTOR);
}
return String.valueOf(value);
}
}

View File

@ -0,0 +1,93 @@
/*
* 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.Collections;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.http.HttpMethod;
import org.springframework.lang.Nullable;
import org.springframework.web.service.annotation.GetRequest;
import org.springframework.web.service.annotation.HttpRequest;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link HttpMethodArgumentResolver}.
*
* @author Olga Maciaszek-Sharma
*/
class HttpMethodArgumentResolverTests extends HttpServiceMethodTestSupport {
private final Service service = createService(Service.class,
Collections.singletonList(new HttpMethodArgumentResolver()));
@Test
void shouldResolveRequestMethodFromArgument() {
Mono<Void> execution = this.service.execute(HttpMethod.GET);
StepVerifier.create(execution).verifyComplete();
assertThat(getRequestDefinition().getHttpMethod()).isEqualTo(HttpMethod.GET);
}
@Test
void shouldIgnoreArgumentsNotMatchingType() {
Mono<Void> execution = this.service.execute("test");
StepVerifier.create(execution).verifyComplete();
assertThat(getRequestDefinition().getHttpMethod()).isNull();
}
@Test
void shouldOverrideMethodAnnotationWithMethodArgument() {
Mono<Void> execution = this.service.executeGet(HttpMethod.POST);
StepVerifier.create(execution).verifyComplete();
assertThat(getRequestDefinition().getHttpMethod()).isEqualTo(HttpMethod.POST);
}
@Test
void shouldIgnoreNullValue() {
Mono<Void> execution = this.service.executeForNull(null);
StepVerifier.create(execution).verifyComplete();
assertThat(getRequestDefinition().getHttpMethod()).isNull();
}
private interface Service {
@HttpRequest
Mono<Void> execute(HttpMethod method);
@GetRequest
Mono<Void> executeGet(HttpMethod method);
@HttpRequest
Mono<Void> execute(String test);
@HttpRequest
Mono<Void> execute(HttpMethod firstMethod, HttpMethod secondMethod);
@HttpRequest
Mono<Void> executeForNull(@Nullable HttpMethod method);
}
}

View File

@ -0,0 +1,155 @@
/*
* 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.time.Duration;
import java.util.Collections;
import java.util.List;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.lang.Nullable;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Contains utility methods for {@link HttpServiceMethod} tests.
*
* @author Rossen Stoyanchev
* @author Olga Maciaszek-Sharma
*/
public class HttpServiceMethodTestSupport {
private final TestHttpClientAdapter clientAdapter;
protected HttpServiceMethodTestSupport() {
clientAdapter = new TestHttpClientAdapter();
}
protected <S> S createService(Class<S> serviceType) {
return createService(serviceType, Collections.emptyList());
}
protected <S> S createService(Class<S> serviceType,
List<HttpServiceMethodArgumentResolver> argumentResolvers) {
HttpServiceProxyFactory factory = new HttpServiceProxyFactory(
argumentResolvers, this.clientAdapter, ReactiveAdapterRegistry.getSharedInstance(),
Duration.ofSeconds(5));
return factory.createService(serviceType);
}
protected HttpRequestDefinition getRequestDefinition() {
return this.clientAdapter.getRequestDefinition();
}
protected TestHttpClientAdapter getClientAdapter() {
return this.clientAdapter;
}
protected static class TestHttpClientAdapter implements HttpClientAdapter {
@Nullable
private String methodName;
@Nullable
private HttpRequestDefinition requestDefinition;
@Nullable
private ParameterizedTypeReference<?> bodyType;
String getMethodName() {
assertThat(this.methodName).isNotNull();
return this.methodName;
}
HttpRequestDefinition getRequestDefinition() {
assertThat(this.requestDefinition).isNotNull();
return this.requestDefinition;
}
@Nullable
public ParameterizedTypeReference<?> getBodyType() {
return this.bodyType;
}
@Override
public Mono<Void> requestToVoid(HttpRequestDefinition def) {
saveInput("requestToVoid", def, null);
return Mono.empty();
}
@Override
public Mono<HttpHeaders> requestToHeaders(HttpRequestDefinition def) {
saveInput("requestToHeaders", def, null);
return Mono.just(new HttpHeaders());
}
@Override
public <T> Mono<T> requestToBody(HttpRequestDefinition def,
ParameterizedTypeReference<T> bodyType) {
saveInput("requestToBody", def, bodyType);
return (Mono<T>) Mono.just(getMethodName());
}
@Override
public <T> Flux<T> requestToBodyFlux(HttpRequestDefinition def,
ParameterizedTypeReference<T> bodyType) {
saveInput("requestToBodyFlux", def, bodyType);
return (Flux<T>) Flux.just("request", "To", "Body", "Flux");
}
@Override
public Mono<ResponseEntity<Void>> requestToBodilessEntity(HttpRequestDefinition def) {
saveInput("requestToBodilessEntity", def, null);
return Mono.just(ResponseEntity.ok().build());
}
@Override
public <T> Mono<ResponseEntity<T>> requestToEntity(HttpRequestDefinition def,
ParameterizedTypeReference<T> bodyType) {
saveInput("requestToEntity", def, bodyType);
return Mono.just((ResponseEntity<T>) ResponseEntity.ok("requestToEntity"));
}
@Override
public <T> Mono<ResponseEntity<Flux<T>>> requestToEntityFlux(HttpRequestDefinition def,
ParameterizedTypeReference<T> bodyType) {
saveInput("requestToEntityFlux", def, bodyType);
return Mono.just(ResponseEntity.ok((Flux<T>) Flux.just("request", "To", "Entity", "Flux")));
}
private <T> void saveInput(
String methodName, HttpRequestDefinition definition,
@Nullable ParameterizedTypeReference<T> bodyType) {
this.methodName = methodName;
this.requestDefinition = definition;
this.bodyType = bodyType;
}
}
}

View File

@ -17,9 +17,6 @@
package org.springframework.web.service.invoker;
import java.time.Duration;
import java.util.Collections;
import io.reactivex.rxjava3.core.Completable;
import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.core.Single;
@ -29,7 +26,6 @@ import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
@ -55,19 +51,14 @@ import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
*
* @author Rossen Stoyanchev
*/
public class HttpServiceMethodTests {
public class HttpServiceMethodTests extends HttpServiceMethodTestSupport {
private static final ParameterizedTypeReference<String> BODY_TYPE = new ParameterizedTypeReference<>() {};
private final TestHttpClientAdapter clientAdapter = new TestHttpClientAdapter();
@Test
void reactorService() {
ReactorService service = createService(ReactorService.class);
Mono<Void> voidMono = service.execute();
StepVerifier.create(voidMono).verifyComplete();
verifyClientInvocation("requestToVoid", null);
@ -99,9 +90,7 @@ public class HttpServiceMethodTests {
@Test
void rxJavaService() {
RxJavaService service = createService(RxJavaService.class);
Completable completable = service.execute();
assertThat(completable).isNotNull();
@ -152,7 +141,7 @@ public class HttpServiceMethodTests {
service.performGet();
HttpRequestDefinition request = this.clientAdapter.getRequestDefinition();
HttpRequestDefinition request = getRequestDefinition();
assertThat(request.getHttpMethod()).isEqualTo(HttpMethod.GET);
assertThat(request.getUriTemplate()).isNull();
assertThat(request.getHeaders().getContentType()).isNull();
@ -160,7 +149,7 @@ public class HttpServiceMethodTests {
service.performPost();
request = this.clientAdapter.getRequestDefinition();
request = getRequestDefinition();
assertThat(request.getHttpMethod()).isEqualTo(HttpMethod.POST);
assertThat(request.getUriTemplate()).isEqualTo("/url");
assertThat(request.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_JSON);
@ -174,7 +163,7 @@ public class HttpServiceMethodTests {
service.performGet();
HttpRequestDefinition request = this.clientAdapter.getRequestDefinition();
HttpRequestDefinition request = getRequestDefinition();
assertThat(request.getHttpMethod()).isEqualTo(HttpMethod.GET);
assertThat(request.getUriTemplate()).isEqualTo("/base");
assertThat(request.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_CBOR);
@ -182,25 +171,17 @@ public class HttpServiceMethodTests {
service.performPost();
request = this.clientAdapter.getRequestDefinition();
request = getRequestDefinition();
assertThat(request.getHttpMethod()).isEqualTo(HttpMethod.POST);
assertThat(request.getUriTemplate()).isEqualTo("/base/url");
assertThat(request.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_JSON);
assertThat(request.getHeaders().getAccept()).containsExactly(MediaType.APPLICATION_JSON);
}
private <S> S createService(Class<S> serviceType) {
HttpServiceProxyFactory factory = new HttpServiceProxyFactory(
Collections.emptyList(), this.clientAdapter, ReactiveAdapterRegistry.getSharedInstance(),
Duration.ofSeconds(5));
return factory.createService(serviceType);
}
private void verifyClientInvocation(String methodName, @Nullable ParameterizedTypeReference<?> expectedBodyType) {
assertThat((this.clientAdapter.getMethodName())).isEqualTo(methodName);
assertThat(this.clientAdapter.getBodyType()).isEqualTo(expectedBodyType);
TestHttpClientAdapter clientAdapter = getClientAdapter();
assertThat((clientAdapter.getMethodName())).isEqualTo(methodName);
assertThat(clientAdapter.getBodyType()).isEqualTo(expectedBodyType);
}
@ -292,87 +273,4 @@ public class HttpServiceMethodTests {
@HttpRequest(url = "/base", contentType = APPLICATION_CBOR_VALUE, accept = APPLICATION_CBOR_VALUE)
private interface TypeAndMethodAnnotatedService extends MethodAnnotatedService {
}
@SuppressWarnings("unchecked")
private static class TestHttpClientAdapter implements HttpClientAdapter {
@Nullable
private String methodName;
@Nullable
private HttpRequestDefinition requestDefinition;
@Nullable
private ParameterizedTypeReference<?> bodyType;
public String getMethodName() {
assertThat(this.methodName).isNotNull();
return this.methodName;
}
public HttpRequestDefinition getRequestDefinition() {
assertThat(this.requestDefinition).isNotNull();
return this.requestDefinition;
}
@Nullable
public ParameterizedTypeReference<?> getBodyType() {
return this.bodyType;
}
@Override
public Mono<Void> requestToVoid(HttpRequestDefinition def) {
saveInput("requestToVoid", def, null);
return Mono.empty();
}
@Override
public Mono<HttpHeaders> requestToHeaders(HttpRequestDefinition def) {
saveInput("requestToHeaders", def, null);
return Mono.just(new HttpHeaders());
}
@Override
public <T> Mono<T> requestToBody(HttpRequestDefinition def, ParameterizedTypeReference<T> bodyType) {
saveInput("requestToBody", def, bodyType);
return (Mono<T>) Mono.just(getMethodName());
}
@Override
public <T> Flux<T> requestToBodyFlux(HttpRequestDefinition def, ParameterizedTypeReference<T> bodyType) {
saveInput("requestToBodyFlux", def, bodyType);
return (Flux<T>) Flux.just("request", "To", "Body", "Flux");
}
@Override
public Mono<ResponseEntity<Void>> requestToBodilessEntity(HttpRequestDefinition def) {
saveInput("requestToBodilessEntity", def, null);
return Mono.just(ResponseEntity.ok().build());
}
@Override
public <T> Mono<ResponseEntity<T>> requestToEntity(HttpRequestDefinition def, ParameterizedTypeReference<T> bodyType) {
saveInput("requestToEntity", def, bodyType);
return Mono.just((ResponseEntity<T>) ResponseEntity.ok("requestToEntity"));
}
@Override
public <T> Mono<ResponseEntity<Flux<T>>> requestToEntityFlux(HttpRequestDefinition def, ParameterizedTypeReference<T> bodyType) {
saveInput("requestToEntityFlux", def, bodyType);
return Mono.just(ResponseEntity.ok((Flux<T>) Flux.just("request", "To", "Entity", "Flux")));
}
private <T> void saveInput(
String methodName, HttpRequestDefinition definition, @Nullable ParameterizedTypeReference<T> bodyType) {
this.methodName = methodName;
this.requestDefinition = definition;
this.bodyType = bodyType;
}
}
}

View File

@ -0,0 +1,515 @@
/*
* 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.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import io.reactivex.rxjava3.core.Observable;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.service.annotation.HttpRequest;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
/**
* Tests for {@link PathVariableArgumentResolver}.
*
* @author Olga Maciaszek-Sharma
*/
class PathVariableArgumentResolverTests extends HttpServiceMethodTestSupport {
private final Service service = createService(Service.class,
Collections.singletonList(new PathVariableArgumentResolver(null)));
@Test
void shouldResolvePathVariableWithNameFromParameter() {
Mono<Void> execution = this.service.execute("test");
StepVerifier.create(execution).verifyComplete();
assertThat(getRequestDefinition().getUriVariables().get("id")).isEqualTo("test");
}
@Test
void shouldResolvePathVariableWithNameFromAnnotationName() {
Mono<Void> execution = this.service.executeNamed("test");
StepVerifier.create(execution).verifyComplete();
assertThat(getRequestDefinition().getUriVariables().get("id")).isEqualTo("test");
}
@Test
void shouldResolvePathVariableNameFromValue() {
Mono<Void> execution = this.service.executeNamedWithValue("test");
StepVerifier.create(execution).verifyComplete();
assertThat(getRequestDefinition().getUriVariables().get("id")).isEqualTo("test");
}
@Test
void shouldOverrideNameIfValuePresentInAnnotation() {
Mono<Void> execution = this.service.executeValueNamed("test");
StepVerifier.create(execution).verifyComplete();
assertThat(getRequestDefinition().getUriVariables().get("id")).isEqualTo("test");
}
@Test
void shouldResolvePathVariableWithNameFromObject() {
Mono<Void> execution = this.service.execute(new TestObject("test"));
StepVerifier.create(execution).verifyComplete();
assertThat(getRequestDefinition().getUriVariables().get("id")).isEqualTo("test");
}
@Test
void shouldResolvePathVariableWithConversionService() {
Service service = createService(Service.class,
Collections.singletonList(new PathVariableArgumentResolver(
new DefaultConversionService())));
Mono<Void> execution = service.execute(Boolean.TRUE);
StepVerifier.create(execution).verifyComplete();
assertThat(getRequestDefinition().getUriVariables().get("id")).isEqualTo("true");
}
@Test
void shouldResolvePathVariableFromOptionalArgumentWithConversionService() {
Service service = createService(Service.class,
Collections.singletonList(new PathVariableArgumentResolver(
new DefaultConversionService())));
Mono<Void> execution = service.executeOptional(Optional.of(Boolean.TRUE));
StepVerifier.create(execution).verifyComplete();
assertThat(getRequestDefinition().getUriVariables().get("id")).isEqualTo("true");
}
@Test
void shouldResolvePathVariableFromOptionalArgument() {
Mono<Void> execution = this.service.execute(Optional.of("test"));
StepVerifier.create(execution).verifyComplete();
assertThat(getRequestDefinition().getUriVariables().get("id")).isEqualTo("test");
}
@Test
void shouldThrowExceptionForNullWithConversionService() {
assertThatIllegalStateException().isThrownBy(() -> {
Service service = createService(Service.class,
Collections.singletonList(new PathVariableArgumentResolver(
new DefaultConversionService())));
Mono<Void> execution = service.executeNamedWithValue(null);
StepVerifier.create(execution).verifyComplete();
});
}
@Test
void shouldThrowExceptionForNull() {
assertThatIllegalStateException().isThrownBy(() -> {
Mono<Void> execution = this.service.executeNamedWithValue(null);
StepVerifier.create(execution).verifyComplete();
});
}
@Test
void shouldThrowExceptionForEmptyOptional() {
assertThatIllegalStateException().isThrownBy(() -> {
Service service = createService(Service.class, Collections.singletonList(new PathVariableArgumentResolver(new DefaultConversionService())));
Mono<Void> execution = service.execute(Optional.empty());
StepVerifier.create(execution).verifyComplete();
});
}
@Test
void shouldThrowExceptionForEmptyOptionalWithoutConversionService() {
assertThatIllegalStateException().isThrownBy(() -> {
Mono<Void> execution = this.service.execute(Optional.empty());
StepVerifier.create(execution).verifyComplete();
});
}
@Test
void shouldNotThrowExceptionForNullWithConversionServiceWhenNotRequired() {
Service service = createService(Service.class, Collections.singletonList(new PathVariableArgumentResolver(new DefaultConversionService())));
Mono<Void> execution = service.executeNotRequired(null);
StepVerifier.create(execution).verifyComplete();
assertThat(getRequestDefinition().getUriVariables().get("id")).isNull();
}
@Test
void shouldNotThrowExceptionForNullWhenNotRequired() {
Mono<Void> execution = this.service.executeNotRequired(null);
StepVerifier.create(execution).verifyComplete();
assertThat(getRequestDefinition().getUriVariables().get("id")).isEqualTo(null);
}
@Test
void shouldNotThrowExceptionForEmptyOptionalWhenNotRequired() {
Service service = createService(Service.class, Collections.singletonList(new PathVariableArgumentResolver(new DefaultConversionService())));
Mono<Void> execution = service.executeOptionalNotRequired(Optional.empty());
StepVerifier.create(execution).verifyComplete();
assertThat(getRequestDefinition().getUriVariables().get("id")).isEqualTo(null);
}
@Test
void shouldNotThrowExceptionForEmptyOptionalWithoutConversionServiceWhenNotRequired() {
Mono<Void> execution = this.service.executeOptionalNotRequired(Optional.empty());
StepVerifier.create(execution).verifyComplete();
assertThat(getRequestDefinition().getUriVariables().get("id")).isEqualTo(null);
}
@Test
void shouldThrowExceptionForReactorWrapper() {
assertThatIllegalStateException().isThrownBy(() -> {
Mono<Void> execution = this.service.executeMono(Mono.just("test"));
StepVerifier.create(execution).verifyComplete();
});
}
@Test
void shouldThrowExceptionForRXWrapper() {
assertThatIllegalStateException().isThrownBy(() -> {
Mono<Void> execution = this.service.executeObservable(Observable.just("test"));
StepVerifier.create(execution).verifyComplete();
});
}
@Test
void shouldThrowExceptionForOptionalReactorWrapper() {
assertThatIllegalStateException().isThrownBy(() -> {
Mono<Void> execution = this.service.executeOptionalMono(Optional.of(Mono.just("test")));
StepVerifier.create(execution).verifyComplete();
});
}
@Test
void shouldThrowExceptionForOptionalRXWrapper() {
assertThatIllegalStateException().isThrownBy(() -> {
Mono<Void> execution = this.service.executeOptionalObservable(Optional.of(Observable.just("test")));
StepVerifier.create(execution).verifyComplete();
});
}
@Test
void shouldResolvePathVariableFromNamedMap() {
Mono<Void> execution = this.service.executeNamedMap(Map.of("id", "test"));
StepVerifier.create(execution).verifyComplete();
assertThat(getRequestDefinition().getUriVariables().get("id")).isEqualTo("test");
}
@Test
void shouldResolvePathVariableFromMapWithAnnotationValue() {
Mono<Void> execution = this.service.executeNamedValueMap(Map.of("id", "test"));
StepVerifier.create(execution).verifyComplete();
assertThat(getRequestDefinition().getUriVariables().get("id")).isEqualTo("test");
}
@Test
void shouldThrowExceptionForReactorWrapperInNamedMap() {
assertThatIllegalStateException().isThrownBy(() -> {
Mono<Void> execution = this.service.executeNamedReactorMap(Map.of("id", Flux.just("test")));
StepVerifier.create(execution).verifyComplete();
});
}
@Test
void shouldResolveOptionalPathVariableFromNamedMap() {
Mono<Void> execution = this.service.executeOptionalValueNamedMap(Map.of("id", Optional.of("test")));
StepVerifier.create(execution).verifyComplete();
assertThat(getRequestDefinition().getUriVariables().get("id")).isEqualTo("test");
}
@Test
void shouldResolveNamedPathVariableFromNamedMapWithConversionService() {
Service service = createService(Service.class, Collections.singletonList(new PathVariableArgumentResolver(new DefaultConversionService())));
Mono<Void> execution = service.executeNamedBooleanMap(Map.of("id", Boolean.TRUE));
StepVerifier.create(execution).verifyComplete();
assertThat(getRequestDefinition().getUriVariables().get("id")).isEqualTo("true");
}
@Test
void shouldThrowExceptionForNullNamedMap() {
assertThatIllegalStateException().isThrownBy(() -> {
Mono<Void> execution = this.service.executeNamedValueMap(null);
StepVerifier.create(execution).verifyComplete();
});
}
@Test
void shouldThrowExceptionForNullNamedMapValue() {
assertThatIllegalStateException().isThrownBy(() -> {
Mono<Void> execution = this.service.executeNamedValueMap(new HashMap<>());
StepVerifier.create(execution).verifyComplete();
});
}
@Test
void shouldThrowExceptionForEmptyOptionalNamedMapValue() {
assertThatIllegalStateException().isThrownBy(() -> {
Service service = createService(Service.class, Collections.singletonList(new PathVariableArgumentResolver(new DefaultConversionService())));
Mono<Void> execution = service.executeOptionalValueNamedMap(Map.of("id", Optional.empty()));
StepVerifier.create(execution).verifyComplete();
});
}
@Test
void shouldNotThrowExceptionForNullNamedMapValueWhenNotRequired() {
Mono<Void> execution = this.service.executeNamedValueMapNotRequired(null);
StepVerifier.create(execution).verifyComplete();
assertThat(getRequestDefinition().getUriVariables().get("id")).isEqualTo(null);
}
@Test
void shouldNotThrowExceptionForEmptyOptionalNamedMapValueWhenNotRequired() {
Service service = createService(Service.class, Collections.singletonList(new PathVariableArgumentResolver(new DefaultConversionService())));
Mono<Void> execution = service.executeOptionalValueMapNotRequired(Map.of("id", Optional.empty()));
StepVerifier.create(execution).verifyComplete();
assertThat(getRequestDefinition().getUriVariables().get("id")).isEqualTo(null);
}
@Test
void shouldResolvePathVariablesFromMap() {
Mono<Void> execution = this.service.executeValueMap(Map.of("id", "test"));
StepVerifier.create(execution).verifyComplete();
assertThat(getRequestDefinition().getUriVariables().get("id")).isEqualTo("test");
}
@Test
void shouldThrowExceptionForReactorWrapperValueInMap() {
assertThatIllegalStateException().isThrownBy(() -> {
Mono<Void> execution = this.service.executeReactorValueMap(Map.of("id", Flux.just("test")));
StepVerifier.create(execution).verifyComplete();
});
}
@Test
void shouldThrowExceptionForReactorWrapperKeyInMap() {
assertThatIllegalStateException().isThrownBy(() -> {
Mono<Void> execution = this.service.executeReactorKeyMap(Map.of(Flux.just("id"), "test"));
StepVerifier.create(execution).verifyComplete();
});
}
@Test
void shouldResolvePathVariableFromOptionalMapValue() {
Mono<Void> execution = this.service.executeOptionalValueMap(Map.of("id", Optional.of("test")));
StepVerifier.create(execution).verifyComplete();
assertThat(getRequestDefinition().getUriVariables().get("id")).isEqualTo("test");
}
@Test
void shouldResolvePathVariableFromOptionalMapKey() {
Mono<Void> execution = this.service.executeOptionalKeyMap(Map.of(Optional.of("id"), "test"));
StepVerifier.create(execution).verifyComplete();
assertThat(getRequestDefinition().getUriVariables().get("id")).isEqualTo("test");
}
@Test
void shouldResolvePathVariableFromMapWithConversionService() {
Service service = createService(Service.class, Collections.singletonList(new PathVariableArgumentResolver(new DefaultConversionService())));
Mono<Void> execution = service.executeBooleanMap(Map.of(Boolean.TRUE, Boolean.TRUE));
StepVerifier.create(execution).verifyComplete();
assertThat(getRequestDefinition().getUriVariables()
.get("true")).isEqualTo("true");
}
@Test
void shouldThrowExceptionForNullMapValue() {
assertThatIllegalStateException().isThrownBy(() -> {
Mono<Void> execution = this.service.executeValueMap(null);
StepVerifier.create(execution).verifyComplete();
});
}
@Test
void shouldThrowExceptionForEmptyOptionalMapValue() {
assertThatIllegalStateException().isThrownBy(() -> {
Service service = createService(Service.class, Collections.singletonList(new PathVariableArgumentResolver(new DefaultConversionService())));
Mono<Void> execution = service.executeOptionalValueMap(Map.of("id", Optional.empty()));
StepVerifier.create(execution).verifyComplete();
});
}
@Test
void shouldThrowExceptionForEmptyOptionalMapKey() {
assertThatIllegalStateException().isThrownBy(() -> {
Service service = createService(Service.class, Collections.singletonList(new PathVariableArgumentResolver(new DefaultConversionService())));
Mono<Void> execution = service.executeOptionalKeyMap(Map.of(Optional.empty(), "test"));
StepVerifier.create(execution).verifyComplete();
});
}
@Test
void shouldNotThrowExceptionForNullMapValueWhenNotRequired() {
Mono<Void> execution = this.service.executeValueMapNotRequired(null);
StepVerifier.create(execution).verifyComplete();
assertThat(getRequestDefinition().getUriVariables().get("id")).isEqualTo(null);
}
@Test
void shouldNotThrowExceptionForEmptyOptionalMapValueWhenNotRequired() {
Service service = createService(Service.class, Collections.singletonList(new PathVariableArgumentResolver(new DefaultConversionService())));
Mono<Void> execution = service.executeOptionalValueMapNotRequired(Map.of("id", Optional.empty()));
StepVerifier.create(execution).verifyComplete();
assertThat(getRequestDefinition().getUriVariables().get("id")).isEqualTo(null);
}
private interface Service {
@HttpRequest
Mono<Void> execute(@PathVariable String id);
@HttpRequest
Mono<Void> executeNotRequired(@Nullable @PathVariable(required = false) String id);
@HttpRequest
Mono<Void> executeOptional(@PathVariable Optional<Boolean> id);
@HttpRequest
Mono<Void> executeOptionalNotRequired(@PathVariable(required = false) Optional<String> id);
@HttpRequest
Mono<Void> executeNamedWithValue(@Nullable @PathVariable(name = "test", value = "id") String employeeId);
@HttpRequest
Mono<Void> executeNamed(@PathVariable(name = "id") String employeeId);
@HttpRequest
Mono<Void> executeValueNamed(@PathVariable("id") String employeeId);
@HttpRequest
Mono<Void> execute(@PathVariable Object id);
@HttpRequest
Mono<Void> execute(@PathVariable Boolean id);
@HttpRequest
Mono<Void> executeMono(@PathVariable Mono<String> id);
@HttpRequest
Mono<Void> executeObservable(@PathVariable Observable<String> id);
@HttpRequest
Mono<Void> executeOptionalMono(@PathVariable Optional<Mono<String>> id);
@HttpRequest
Mono<Void> executeOptionalObservable(@PathVariable Optional<Observable<String>> id);
@HttpRequest
Mono<Void> executeNamedMap(@PathVariable(name = "id") Map<String, String> map);
@HttpRequest
Mono<Void> executeNamedValueMap(@Nullable @PathVariable("id") Map<String, String> map);
@HttpRequest
Mono<Void> executeNamedBooleanMap(@PathVariable("id") Map<String, Boolean> map);
@HttpRequest
Mono<Void> executeNamedReactorMap(@PathVariable(name = "id") Map<String, Flux<String>> map);
@HttpRequest
Mono<Void> executeOptionalValueNamedMap(@PathVariable("id") Map<String, Optional<String>> map);
@HttpRequest
Mono<Void> executeNamedValueMapNotRequired(@Nullable @PathVariable(name = "id", required = false) Map<String, String> map);
@HttpRequest
Mono<Void> executeOptionalValueMapNotRequired(@PathVariable(name = "id", required = false) Map<String, Optional<String>> map);
@HttpRequest
Mono<Void> executeValueMap(@Nullable @PathVariable Map<String, String> map);
@HttpRequest
Mono<Void> executeReactorValueMap(@PathVariable Map<String, Flux<String>> map);
@HttpRequest
Mono<Void> executeReactorKeyMap(@PathVariable Map<Flux<String>, String> map);
@HttpRequest
Mono<Void> executeOptionalValueMap(@PathVariable Map<String, Optional<String>> map);
@HttpRequest
Mono<Void> executeOptionalKeyMap(@PathVariable Map<Optional<String>, String> map);
@HttpRequest
Mono<Void> executeBooleanMap(@PathVariable Map<Boolean, Boolean> map);
@HttpRequest
Mono<Void> executeValueMapNotRequired(@Nullable @PathVariable(required = false) Map<String, String> map);
}
static class TestObject {
TestObject(String value) {
this.value = value;
}
String value;
@Override
public String toString() {
return value;
}
}
}