Reject multiple @⁠HttpExchange declarations in HTTP interface clients

This commit updates HttpServiceMethod so that multiple @⁠HttpExchange
declarations on the same element (class or method) are rejected.

Closes gh-32049
This commit is contained in:
Sam Brannen
2024-01-18 18:25:12 +01:00
parent 9c6e55939e
commit 6691ff2072
2 changed files with 118 additions and 5 deletions

View File

@@ -16,6 +16,11 @@
package org.springframework.web.service.invoker;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Optional;
@@ -37,8 +42,10 @@ import org.springframework.lang.Nullable;
import org.springframework.web.service.annotation.GetExchange;
import org.springframework.web.service.annotation.HttpExchange;
import org.springframework.web.service.annotation.PostExchange;
import org.springframework.web.service.annotation.PutExchange;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.springframework.http.MediaType.APPLICATION_CBOR_VALUE;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
@@ -52,6 +59,7 @@ import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
*
* @author Rossen Stoyanchev
* @author Olga Maciaszek-Sharma
* @author Sam Brannen
*/
class HttpServiceMethodTests {
@@ -204,6 +212,33 @@ class HttpServiceMethodTests {
assertThat(requestValues.getHeaders().getAccept()).containsExactly(MediaType.APPLICATION_JSON);
}
@Test // gh-32049
void multipleAnnotationsAtClassLevel() {
Class<?> serviceInterface = MultipleClassLevelAnnotationsService.class;
assertThatIllegalStateException()
.isThrownBy(() -> this.proxyFactory.createClient(serviceInterface))
.withMessageContainingAll(
"Multiple @HttpExchange annotations found on " + serviceInterface,
"@" + HttpExchange.class.getName(),
"@" + ExtraHttpExchange.class.getName()
);
}
@Test // gh-32049
void multipleAnnotationsAtMethodLevel() throws NoSuchMethodException {
Class<?> serviceInterface = MultipleMethodLevelAnnotationsService.class;
Method method = serviceInterface.getMethod("post");
assertThatIllegalStateException()
.isThrownBy(() -> this.proxyFactory.createClient(serviceInterface))
.withMessageContainingAll(
"Multiple @HttpExchange annotations found on method " + method,
"@" + PostExchange.class.getName(),
"@" + PutExchange.class.getName()
);
}
protected void verifyReactorClientInvocation(String methodName, @Nullable ParameterizedTypeReference<?> expectedBodyType) {
assertThat(this.reactorClient.getInvokedMethodName()).isEqualTo(methodName);
assertThat(this.reactorClient.getBodyType()).isEqualTo(expectedBodyType);
@@ -305,9 +340,34 @@ class HttpServiceMethodTests {
}
@SuppressWarnings("unused")
@HttpExchange(url = "${baseUrl}", contentType = APPLICATION_CBOR_VALUE, accept = APPLICATION_CBOR_VALUE)
private interface TypeAndMethodLevelAnnotatedService extends MethodLevelAnnotatedService {
}
@HttpExchange("/exchange")
@ExtraHttpExchange
private interface MultipleClassLevelAnnotationsService {
@PostExchange("/post")
void post();
}
private interface MultipleMethodLevelAnnotationsService {
@PostExchange("/post")
@PutExchange("/post")
void post();
}
@HttpExchange
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
private @interface ExtraHttpExchange {
}
}