Reject null for non-optional arguments

Closes gh-33339
This commit is contained in:
Olga Maciaszek-Sharma 2024-08-05 15:30:10 +02:00 committed by rstoyanchev
parent 4ac4c1b868
commit 51de84e148
14 changed files with 378 additions and 81 deletions

View File

@ -978,6 +978,9 @@ method parameters:
`MultiValueMap<String, ?>` with multiple cookies, a `Collection<?>` of values, or an
individual value. Type conversion is supported for non-String values.
The parameters can't be null unless they are set as not required through the annotation,
annotated with `@Nullable` or they are `Optional`.
|===

View File

@ -1115,7 +1115,8 @@ method parameters:
| `@Payload`
| Set the input payload(s) for the request. This can be a concrete value, or any producer
of values that can be adapted to a Reactive Streams `Publisher` via
`ReactiveAdapterRegistry`
`ReactiveAdapterRegistry`. The payload can't be null unless it's set as not required
through the annotation, annotated with `@Nullable` or it is `Optional`.
| `Object`, if followed by `MimeType`
| The value for a metadata entry in the input payload. This can be any `Object` as long

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 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.
@ -29,6 +29,7 @@ import org.springframework.util.Assert;
* annotated arguments.
*
* @author Rossen Stoyanchev
* @author Olga Maciaszek-Sharma
* @since 6.0
*/
public class PayloadArgumentResolver implements RSocketServiceArgumentResolver {
@ -54,8 +55,14 @@ public class PayloadArgumentResolver implements RSocketServiceArgumentResolver {
return false;
}
if (argument != null) {
ReactiveAdapter reactiveAdapter = this.reactiveAdapterRegistry.getAdapter(parameter.getParameterType());
if (argument == null) {
boolean required = (annot == null || annot.required()) && !parameter.isOptional();
Assert.isTrue(!required, () -> "Missing payload");
return true;
}
ReactiveAdapter reactiveAdapter = this.reactiveAdapterRegistry
.getAdapter(parameter.getParameterType());
if (reactiveAdapter == null) {
requestValues.setPayloadValue(argument);
}
@ -70,9 +77,6 @@ public class PayloadArgumentResolver implements RSocketServiceArgumentResolver {
reactiveAdapter.toPublisher(argument),
ParameterizedTypeReference.forType(nestedParameter.getNestedGenericParameterType()));
}
}
return true;
}
}

View File

@ -25,6 +25,7 @@ import reactor.core.publisher.Mono;
import org.springframework.core.MethodParameter;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.lang.Nullable;
import org.springframework.messaging.handler.annotation.Payload;
import static org.assertj.core.api.Assertions.assertThat;
@ -34,7 +35,9 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
* Tests for {@link PayloadArgumentResolver}.
*
* @author Rossen Stoyanchev
* @author Olga Maciaszek-Sharma
*/
@SuppressWarnings("DataFlowIssue")
class PayloadArgumentResolverTests extends RSocketServiceArgumentResolverTestSupport {
@Override
@ -47,9 +50,7 @@ class PayloadArgumentResolverTests extends RSocketServiceArgumentResolverTestSup
String payload = "payloadValue";
boolean resolved = execute(payload, initMethodParameter(Service.class, "execute", 0));
assertThat(resolved).isTrue();
assertThat(getRequestValues().getPayloadValue()).isEqualTo(payload);
assertThat(getRequestValues().getPayload()).isNull();
assertPayload(resolved, payload);
}
@Test
@ -57,10 +58,7 @@ class PayloadArgumentResolverTests extends RSocketServiceArgumentResolverTestSup
Mono<String> payloadMono = Mono.just("payloadValue");
boolean resolved = execute(payloadMono, initMethodParameter(Service.class, "executeMono", 0));
assertThat(resolved).isTrue();
assertThat(getRequestValues().getPayloadValue()).isNull();
assertThat(getRequestValues().getPayload()).isSameAs(payloadMono);
assertThat(getRequestValues().getPayloadElementType()).isEqualTo(new ParameterizedTypeReference<String>() {});
assertPayloadMono(resolved, payloadMono);
}
@Test
@ -92,7 +90,7 @@ class PayloadArgumentResolverTests extends RSocketServiceArgumentResolverTestSup
}
@Test
void notRequestBody() {
void notPayload() {
MethodParameter parameter = initMethodParameter(Service.class, "executeNotAnnotated", 0);
boolean resolved = execute("value", parameter);
@ -100,23 +98,69 @@ class PayloadArgumentResolverTests extends RSocketServiceArgumentResolverTestSup
}
@Test
void ignoreNull() {
boolean resolved = execute(null, initMethodParameter(Service.class, "execute", 0));
void nullPayload() {
assertThatIllegalArgumentException()
.isThrownBy(() -> execute(null, initMethodParameter(Service.class, "execute", 0)))
.withMessage("Missing payload");
assertThatIllegalArgumentException()
.isThrownBy(() -> execute(null, initMethodParameter(Service.class, "executeMono", 0)))
.withMessage("Missing payload");
}
@Test
void nullPayloadWithNullable() {
boolean resolved = execute(null, initMethodParameter(Service.class, "executeNullable", 0));
assertNullValues(resolved);
boolean resolvedMono = execute(null, initMethodParameter(Service.class, "executeNullableMono", 0));
assertNullValues(resolvedMono);
}
@Test
void nullPayloadWithNotRequired() {
boolean resolved = execute(null, initMethodParameter(Service.class, "executeNotRequired", 0));
assertNullValues(resolved);
boolean resolvedMono = execute(null, initMethodParameter(Service.class, "executeNotRequiredMono", 0));
assertNullValues(resolvedMono);
}
private void assertPayload(boolean resolved, String payload) {
assertThat(resolved).isTrue();
assertThat(getRequestValues().getPayloadValue()).isEqualTo(payload);
assertThat(getRequestValues().getPayload()).isNull();
}
private void assertPayloadMono(boolean resolved, Mono<String> payloadMono) {
assertThat(resolved).isTrue();
assertThat(getRequestValues().getPayloadValue()).isNull();
assertThat(getRequestValues().getPayload()).isSameAs(payloadMono);
assertThat(getRequestValues().getPayloadElementType()).isEqualTo(new ParameterizedTypeReference<String>() { });
}
private void assertNullValues(boolean resolved) {
assertThat(resolved).isTrue();
assertThat(getRequestValues().getPayloadValue()).isNull();
assertThat(getRequestValues().getPayload()).isNull();
assertThat(getRequestValues().getPayloadElementType()).isNull();
}
@SuppressWarnings("unused")
@SuppressWarnings({"unused"})
private interface Service {
void execute(@Payload String body);
void executeNotRequired(@Payload(required = false) String body);
void executeNullable(@Nullable @Payload String body);
void executeMono(@Payload Mono<String> body);
void executeNullableMono(@Nullable @Payload Mono<String> body);
void executeNotRequiredMono(@Payload(required = false) Mono<String> body);
void executeSingle(@Payload Single<String> body);
void executeMonoVoid(@Payload Mono<Void> body);

View File

@ -39,6 +39,7 @@ import org.springframework.web.bind.annotation.ValueConstants;
* request header, path variable, cookie, and others.
*
* @author Rossen Stoyanchev
* @author Olga Maciaszek-Sharma
* @since 6.0
*/
public abstract class AbstractNamedValueArgumentResolver implements HttpServiceArgumentResolver {
@ -145,7 +146,7 @@ public abstract class AbstractNamedValueArgumentResolver implements HttpServiceA
.formatted(parameter.getNestedParameterType().getName()));
}
}
boolean required = (info.required && !parameter.getParameterType().equals(Optional.class));
boolean required = (info.required && !parameter.isOptional());
String defaultValue = (ValueConstants.DEFAULT_NONE.equals(info.defaultValue) ? null : info.defaultValue);
return info.update(name, required, defaultValue);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 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.
@ -16,6 +16,8 @@
package org.springframework.web.service.invoker;
import java.util.Optional;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@ -41,17 +43,26 @@ public class HttpMethodArgumentResolver implements HttpServiceArgumentResolver {
public boolean resolve(
@Nullable Object argument, MethodParameter parameter, HttpRequestValues.Builder requestValues) {
if (!parameter.getParameterType().equals(HttpMethod.class)) {
parameter = parameter.nestedIfOptional();
if (!parameter.getNestedParameterType().equals(HttpMethod.class)) {
return false;
}
Assert.notNull(argument, "HttpMethod is required");
if (argument instanceof Optional<?> optionalValue) {
argument = optionalValue.orElse(null);
}
if (argument == null) {
Assert.isTrue(parameter.isOptional(), "HttpMethod is required");
return true;
}
HttpMethod httpMethod = (HttpMethod) argument;
requestValues.setHttpMethod(httpMethod);
if (logger.isTraceEnabled()) {
logger.trace("Resolved HTTP method to: " + httpMethod.name());
}
return true;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 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.
@ -16,6 +16,8 @@
package org.springframework.web.service.invoker;
import java.util.Optional;
import org.springframework.core.MethodParameter;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.ReactiveAdapter;
@ -30,6 +32,7 @@ import org.springframework.web.bind.annotation.RequestBody;
* annotated arguments.
*
* @author Rossen Stoyanchev
* @author Olga Maciaszek-Sharma
* @since 6.0
*/
public class RequestBodyArgumentResolver implements HttpServiceArgumentResolver {
@ -68,9 +71,18 @@ public class RequestBodyArgumentResolver implements HttpServiceArgumentResolver
return false;
}
if (argument != null) {
if (argument instanceof Optional<?> optionalValue) {
argument = optionalValue.orElse(null);
}
if (argument == null) {
Assert.isTrue(!annot.required() || parameter.isOptional(), "RequestBody is required");
return true;
}
if (this.reactiveAdapterRegistry != null) {
ReactiveAdapter adapter = this.reactiveAdapterRegistry.getAdapter(parameter.getParameterType());
ReactiveAdapter adapter = this.reactiveAdapterRegistry
.getAdapter(parameter.getParameterType());
if (adapter != null) {
MethodParameter nestedParameter = parameter.nested();
@ -90,11 +102,8 @@ public class RequestBodyArgumentResolver implements HttpServiceArgumentResolver
return true;
}
}
// Not a reactive type
requestValues.setBodyValue(argument);
}
return true;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 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.
@ -17,9 +17,11 @@
package org.springframework.web.service.invoker;
import java.net.URL;
import java.util.Optional;
import org.springframework.core.MethodParameter;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.web.util.UriBuilderFactory;
import org.springframework.web.util.UriTemplate;
@ -42,14 +44,22 @@ public class UriBuilderFactoryArgumentResolver implements HttpServiceArgumentRes
public boolean resolve(
@Nullable Object argument, MethodParameter parameter, HttpRequestValues.Builder requestValues) {
if (!parameter.getParameterType().equals(UriBuilderFactory.class)) {
parameter = parameter.nestedIfOptional();
if (!parameter.getNestedParameterType().equals(UriBuilderFactory.class)) {
return false;
}
if (argument != null) {
requestValues.setUriBuilderFactory((UriBuilderFactory) argument);
if (argument instanceof Optional<?> optionalValue) {
argument = optionalValue.orElse(null);
}
if (argument == null) {
Assert.isTrue(parameter.isOptional(), "UriBuilderFactory is required");
return true;
}
requestValues.setUriBuilderFactory((UriBuilderFactory) argument);
return true;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 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.
@ -17,15 +17,18 @@
package org.springframework.web.service.invoker;
import java.net.URI;
import java.util.Optional;
import org.springframework.core.MethodParameter;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* {@link HttpServiceArgumentResolver} that resolves the URL for the request
* from a {@link URI} argument.
*
* @author Rossen Stoyanchev
* @author Olga Maciaszek-Sharma
* @since 6.0
*/
public class UrlArgumentResolver implements HttpServiceArgumentResolver {
@ -34,14 +37,22 @@ public class UrlArgumentResolver implements HttpServiceArgumentResolver {
public boolean resolve(
@Nullable Object argument, MethodParameter parameter, HttpRequestValues.Builder requestValues) {
if (!parameter.getParameterType().equals(URI.class)) {
parameter = parameter.nestedIfOptional();
if (!parameter.getNestedParameterType().equals(URI.class)) {
return false;
}
if (argument != null) {
requestValues.setUri((URI) argument);
if (argument instanceof Optional<?> optionalValue) {
argument = optionalValue.orElse(null);
}
if (argument == null) {
Assert.isTrue(parameter.isOptional(), "URI is required");
return true;
}
requestValues.setUri((URI) argument);
return true;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 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.
@ -16,6 +16,8 @@
package org.springframework.web.service.invoker;
import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpMethod;
@ -68,12 +70,38 @@ class HttpMethodArgumentResolverTests {
assertThatIllegalArgumentException().isThrownBy(() -> this.service.execute(null));
}
@Test
void nullHttpMethodWithNullable() {
this.service.executeNullableHttpMethod(null);
assertThat(getActualMethod()).isEqualTo(HttpMethod.GET);
}
@Test
void nullHttpMethodWithOptional() {
this.service.executeOptionalHttpMethod(null);
assertThat(getActualMethod()).isEqualTo(HttpMethod.GET);
}
@Test
void emptyOptionalHttpMethod() {
this.service.executeOptionalHttpMethod(Optional.empty());
assertThat(getActualMethod()).isEqualTo(HttpMethod.GET);
}
@Test
void optionalHttpMethod() {
this.service.executeOptionalHttpMethod(Optional.of(HttpMethod.POST));
assertThat(getActualMethod()).isEqualTo(HttpMethod.POST);
}
@Nullable
private HttpMethod getActualMethod() {
return this.client.getRequestValues().getHttpMethod();
}
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
private interface Service {
@HttpExchange
@ -85,6 +113,12 @@ class HttpMethodArgumentResolverTests {
@GetExchange
void executeNotHttpMethod(String test);
@GetExchange
void executeNullableHttpMethod(@Nullable HttpMethod method);
@GetExchange
void executeOptionalHttpMethod(Optional<HttpMethod> method);
}
}

View File

@ -48,6 +48,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
* {@link TestValue @TestValue} annotation and {@link TestNamedValueArgumentResolver}.
*
* @author Rossen Stoyanchev
* @author Olga Maciaszek-Sharma
*/
class NamedValueArgumentResolverTests {
@ -134,7 +135,7 @@ class NamedValueArgumentResolverTests {
}
@Test
void optionalEmpthyWithDefaultValue() {
void optionalEmptyWithDefaultValue() {
this.service.executeOptionalWithDefaultValue(Optional.empty());
assertTestValue("value", "default");
}
@ -157,6 +158,12 @@ class NamedValueArgumentResolverTests {
assertTestValue("value", "test");
}
@Test
void nullTestValueWithNullable() {
this.service.executeNullable(null);
assertTestValue("value");
}
private void assertTestValue(String key, String... values) {
List<String> actualValues = this.argumentResolver.getTestValues().get(key);
if (ObjectUtils.isEmpty(values)) {
@ -207,6 +214,9 @@ class NamedValueArgumentResolverTests {
@GetExchange
void executeMapWithOptionalValue(@TestValue Map<String, Optional<String>> values);
@GetExchange
void executeNullable(@Nullable @TestValue String value);
}

View File

@ -16,6 +16,8 @@
package org.springframework.web.service.invoker;
import java.util.Optional;
import io.reactivex.rxjava3.core.Completable;
import io.reactivex.rxjava3.core.Single;
import org.junit.jupiter.api.Test;
@ -35,7 +37,9 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
* Tests for {@link RequestBodyArgumentResolver}.
*
* @author Rossen Stoyanchev
* @author Olga Maciaszek-Sharma
*/
@SuppressWarnings({"DataFlowIssue", "OptionalAssignedToNull"})
class RequestBodyArgumentResolverTests {
private final TestReactorExchangeAdapter client = new TestReactorExchangeAdapter();
@ -102,18 +106,68 @@ class RequestBodyArgumentResolverTests {
}
@Test
void ignoreNull() {
this.service.execute(null);
void nullRequestBody() {
assertThatIllegalArgumentException()
.isThrownBy(() -> this.service.execute(null))
.withMessage("RequestBody is required");
assertThatIllegalArgumentException()
.isThrownBy(() -> this.service.executeMono(null))
.withMessage("RequestBody is required");
}
@Test
void nullRequestBodyWithNullable() {
this.service.executeNullable(null);
assertThat(getBodyValue()).isNull();
assertThat(getPublisherBody()).isNull();
this.service.executeMono(null);
this.service.executeNullableMono(null);
assertThat(getBodyValue()).isNull();
assertThat(getPublisherBody()).isNull();
}
@Test
void nullRequestBodyWithNotRequired() {
this.service.executeNotRequired(null);
assertThat(getBodyValue()).isNull();
assertThat(getPublisherBody()).isNull();
this.service.executeNotRequiredMono(null);
assertThat(getBodyValue()).isNull();
assertThat(getPublisherBody()).isNull();
}
@Test
void nullRequestBodyWithOptional() {
this.service.executeOptional(null);
assertThat(getBodyValue()).isNull();
assertThat(getPublisherBody()).isNull();
}
@Test
void emptyOptionalRequestBody() {
this.service.executeOptional(Optional.empty());
assertThat(getBodyValue()).isNull();
assertThat(getPublisherBody()).isNull();
}
@Test
void optionalStringBody() {
String body = "bodyValue";
this.service.executeOptional(Optional.of(body));
assertThat(getBodyValue()).isEqualTo(body);
assertThat(getPublisherBody()).isNull();
}
@Nullable
private Object getBodyValue() {
return getReactiveRequestValues().getBodyValue();
@ -134,14 +188,30 @@ class RequestBodyArgumentResolverTests {
}
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
private interface Service {
@GetExchange
void execute(@RequestBody String body);
@GetExchange
void executeNullable(@Nullable @RequestBody String body);
@GetExchange
void executeNotRequired(@RequestBody(required = false) String body);
@GetExchange
void executeOptional(@RequestBody Optional<String> body);
@GetExchange
void executeMono(@RequestBody Mono<String> body);
@GetExchange
void executeNullableMono(@Nullable @RequestBody Mono<String> body);
@GetExchange
void executeNotRequiredMono(@RequestBody(required = false) Mono<String> body);
@GetExchange
void executeSingle(@RequestBody Single<String> body);

View File

@ -16,6 +16,8 @@
package org.springframework.web.service.invoker;
import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.springframework.lang.Nullable;
@ -24,12 +26,14 @@ import org.springframework.web.util.DefaultUriBuilderFactory;
import org.springframework.web.util.UriBuilderFactory;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link UriBuilderFactoryArgumentResolver}.
*
* @author Olga Maciaszek-Sharma
*/
@SuppressWarnings({"DataFlowIssue", "OptionalAssignedToNull"})
class UriBuilderFactoryArgumentResolverTests {
private final TestExchangeAdapter client = new TestExchangeAdapter();
@ -49,24 +53,65 @@ class UriBuilderFactoryArgumentResolverTests {
}
@Test
void ignoreNullUriBuilderFactory(){
this.service.execute(null);
void nullUriBuilderFactory() {
assertThatIllegalArgumentException()
.isThrownBy(() -> this.service.execute(null))
.withMessage("UriBuilderFactory is required");
}
@Test
void nullUriBuilderFactoryWithNullable(){
this.service.executeNullable(null);
assertThat(getRequestValues().getUriBuilderFactory()).isEqualTo(null);
assertThat(getRequestValues().getUriTemplate()).isEqualTo("/path");
assertThat(getRequestValues().getUri()).isNull();
}
@Test
void nullUriBuilderFactoryWithOptional(){
this.service.executeOptional(null);
assertThat(getRequestValues().getUriBuilderFactory()).isEqualTo(null);
assertThat(getRequestValues().getUriTemplate()).isEqualTo("/path");
assertThat(getRequestValues().getUri()).isNull();
}
@Test
void emptyOptionalUriBuilderFactory(){
this.service.executeOptional(Optional.empty());
assertThat(getRequestValues().getUriBuilderFactory()).isEqualTo(null);
assertThat(getRequestValues().getUriTemplate()).isEqualTo("/path");
assertThat(getRequestValues().getUri()).isNull();
}
@Test
void optionalUriBuilderFactory(){
UriBuilderFactory factory = new DefaultUriBuilderFactory("https://example.com");
this.service.executeOptional(Optional.of(factory));
assertThat(getRequestValues().getUriBuilderFactory()).isEqualTo(factory);
assertThat(getRequestValues().getUriTemplate()).isEqualTo("/path");
assertThat(getRequestValues().getUri()).isNull();
}
private HttpRequestValues getRequestValues() {
return this.client.getRequestValues();
}
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
private interface Service {
@GetExchange("/path")
void execute(@Nullable UriBuilderFactory uri);
void execute(UriBuilderFactory uri);
@GetExchange("/path")
void executeNullable(@Nullable UriBuilderFactory uri);
@GetExchange("/path")
void executeOptional(Optional<UriBuilderFactory> uri);
}
}

View File

@ -17,6 +17,7 @@
package org.springframework.web.service.invoker;
import java.net.URI;
import java.util.Optional;
import org.junit.jupiter.api.Test;
@ -24,13 +25,16 @@ import org.springframework.lang.Nullable;
import org.springframework.web.service.annotation.GetExchange;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
/**
* Tests for {@link UrlArgumentResolver}.
*
* @author Rossen Stoyanchev
* @author Olga Maciaszek-Sharma
*/
@SuppressWarnings({"DataFlowIssue", "OptionalAssignedToNull"})
class UrlArgumentResolverTests {
private final TestExchangeAdapter client = new TestExchangeAdapter();
@ -59,22 +63,62 @@ class UrlArgumentResolverTests {
}
@Test
void ignoreNull() {
this.service.execute(null);
void nullUrl() {
assertThatIllegalArgumentException()
.isThrownBy(() -> this.service.execute(null))
.withMessage("URI is required");
}
@Test
void nullUrlWithNullable() {
this.service.executeNullable(null);
assertThat(getRequestValues().getUri()).isNull();
assertThat(getRequestValues().getUriTemplate()).isEqualTo("/path");
}
@Test
void nullUrlWithOptional() {
this.service.executeOptional(null);
assertThat(getRequestValues().getUri()).isNull();
assertThat(getRequestValues().getUriTemplate()).isEqualTo("/path");
}
@Test
void emptyOptionalUrl() {
this.service.executeOptional(Optional.empty());
assertThat(getRequestValues().getUri()).isNull();
assertThat(getRequestValues().getUriTemplate()).isEqualTo("/path");
}
@Test
void optionalUrl() {
URI dynamicUrl = URI.create("dynamic-path");
this.service.executeOptional(Optional.of(dynamicUrl));
assertThat(getRequestValues().getUri()).isEqualTo(dynamicUrl);
assertThat(getRequestValues().getUriTemplate()).isEqualTo("/path");
}
private HttpRequestValues getRequestValues() {
return this.client.getRequestValues();
}
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
private interface Service {
@GetExchange("/path")
void execute(@Nullable URI uri);
void execute(URI uri);
@GetExchange("/path")
void executeNullable(@Nullable URI uri);
@GetExchange("/path")
void executeOptional(Optional<URI> uri);
@GetExchange
void executeNotUri(String other);