Skip to content

Commit 3dc2aa7

Browse files
committed
Fix HttpEntity support with Kotlin Serialization
This commit adds HttpEntity type unwrapping logic to KotlinRequestBodyAdvice and KotlinResponseBodyAdvice. Closes gh-35281
1 parent 0d2a0d7 commit 3dc2aa7

File tree

3 files changed

+53
-0
lines changed

3 files changed

+53
-0
lines changed

spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/KotlinRequestBodyAdvice.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.jspecify.annotations.Nullable;
2929

3030
import org.springframework.core.MethodParameter;
31+
import org.springframework.http.HttpEntity;
3132
import org.springframework.http.converter.AbstractKotlinSerializationHttpMessageConverter;
3233
import org.springframework.http.converter.HttpMessageConverter;
3334
import org.springframework.http.converter.SmartHttpMessageConverter;
@@ -61,6 +62,10 @@ public boolean supports(MethodParameter methodParameter, Type targetType,
6162
for (KParameter p : Objects.requireNonNull(function).getParameters()) {
6263
if (KParameter.Kind.VALUE.equals(p.getKind())) {
6364
if (index == i++) {
65+
if (HttpEntity.class.isAssignableFrom(parameter.getParameterType())) {
66+
return Collections.singletonMap(KType.class.getName(),
67+
Objects.requireNonNull(p.getType().getArguments().get(0).getType()));
68+
}
6469
return Collections.singletonMap(KType.class.getName(), p.getType());
6570
}
6671
}

spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/KotlinResponseBodyAdvice.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.jspecify.annotations.Nullable;
2727

2828
import org.springframework.core.MethodParameter;
29+
import org.springframework.http.HttpEntity;
2930
import org.springframework.http.MediaType;
3031
import org.springframework.http.converter.AbstractKotlinSerializationHttpMessageConverter;
3132
import org.springframework.http.converter.HttpMessageConverter;
@@ -61,6 +62,9 @@ public boolean supports(MethodParameter returnType, Class<? extends HttpMessageC
6162

6263
KFunction<?> function = ReflectJvmMapping.getKotlinFunction(Objects.requireNonNull(returnType.getMethod()));
6364
KType type = Objects.requireNonNull(function).getReturnType();
65+
if (HttpEntity.class.isAssignableFrom(returnType.getParameterType())) {
66+
return Collections.singletonMap(KType.class.getName(), Objects.requireNonNull(type.getArguments().get(0).getType()));
67+
}
6468
return Collections.singletonMap(KType.class.getName(), type);
6569
}
6670

spring-webmvc/src/test/kotlin/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorKotlinTests.kt

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import kotlinx.serialization.Serializable
2020
import org.assertj.core.api.Assertions
2121
import org.junit.jupiter.api.Test
2222
import org.springframework.core.MethodParameter
23+
import org.springframework.http.RequestEntity
24+
import org.springframework.http.ResponseEntity
2325
import org.springframework.http.converter.StringHttpMessageConverter
2426
import org.springframework.http.converter.json.KotlinSerializationJsonHttpMessageConverter
2527
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean
@@ -68,6 +70,22 @@ class RequestResponseBodyMethodProcessorKotlinTests {
6870
.contains("\"value\":\"foo\"")
6971
}
7072

73+
@Test
74+
fun writeEntityWithKotlinSerializationJsonMessageConverter() {
75+
val method = SampleController::writeMessageEntity::javaMethod.get()!!
76+
val handlerMethod = HandlerMethod(SampleController(), method)
77+
val methodReturnType = handlerMethod.returnType
78+
79+
val converters = listOf(KotlinSerializationJsonHttpMessageConverter())
80+
val processor = RequestResponseBodyMethodProcessor(converters, null, listOf(KotlinResponseBodyAdvice()))
81+
82+
val returnValue: Any? = SampleController().writeMessageEntity().body
83+
processor.handleReturnValue(returnValue, methodReturnType, this.container, this.request)
84+
85+
Assertions.assertThat(this.servletResponse.contentAsString)
86+
.contains("\"value\":\"foo\"")
87+
}
88+
7189
@Test
7290
fun writeGenericTypeWithKotlinSerializationJsonMessageConverter() {
7391
val method = SampleController::writeMessages::javaMethod.get()!!
@@ -118,6 +136,24 @@ class RequestResponseBodyMethodProcessorKotlinTests {
118136
Assertions.assertThat(result).isEqualTo(Message("foo"))
119137
}
120138

139+
@Test
140+
@Suppress("UNCHECKED_CAST")
141+
fun readEntityWithKotlinSerializationJsonMessageConverter() {
142+
val content = "{\"value\" : \"foo\"}"
143+
this.servletRequest.setContent(content.toByteArray(StandardCharsets.UTF_8))
144+
this.servletRequest.setContentType("application/json")
145+
146+
val converters = listOf(StringHttpMessageConverter(), KotlinSerializationJsonHttpMessageConverter())
147+
val processor = RequestResponseBodyMethodProcessor(converters, null, listOf(KotlinRequestBodyAdvice()))
148+
149+
val method = SampleController::readMessageEntity::javaMethod.get()!!
150+
val methodParameter = MethodParameter(method, 0)
151+
152+
val result = processor.resolveArgument(methodParameter, container, request, factory) as Message
153+
154+
Assertions.assertThat(result).isEqualTo(Message("foo"))
155+
}
156+
121157
@Suppress("UNCHECKED_CAST")
122158
@Test
123159
fun readGenericTypeWithKotlinSerializationJsonMessageConverter() {
@@ -161,6 +197,10 @@ class RequestResponseBodyMethodProcessorKotlinTests {
161197
@ResponseBody
162198
fun writeMessage() = Message("foo")
163199

200+
@RequestMapping
201+
@ResponseBody
202+
fun writeMessageEntity() = ResponseEntity.ok(Message("foo"))
203+
164204
@RequestMapping
165205
@ResponseBody
166206
fun writeMessages() = listOf(Message("foo"), Message("bar"))
@@ -169,6 +209,10 @@ class RequestResponseBodyMethodProcessorKotlinTests {
169209
@ResponseBody
170210
fun readMessage(message: Message) = message.value
171211

212+
@RequestMapping
213+
@ResponseBody
214+
fun readMessageEntity(entity: RequestEntity<Message>) = entity.body!!.value
215+
172216
@RequestMapping
173217
@ResponseBody
174218
fun readMessages(messages: List<Message>) = messages.map { it.value }.reduce { acc, string -> "$acc $string" }

0 commit comments

Comments
 (0)