Fix HttpEntity support with Kotlin Serialization
This commit adds HttpEntity type unwrapping logic to KotlinRequestBodyAdvice and KotlinResponseBodyAdvice. Closes gh-35281
This commit is contained in:
parent
0d2a0d7b9e
commit
3dc2aa79a4
|
@ -28,6 +28,7 @@ import kotlin.reflect.jvm.ReflectJvmMapping;
|
||||||
import org.jspecify.annotations.Nullable;
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import org.springframework.core.MethodParameter;
|
import org.springframework.core.MethodParameter;
|
||||||
|
import org.springframework.http.HttpEntity;
|
||||||
import org.springframework.http.converter.AbstractKotlinSerializationHttpMessageConverter;
|
import org.springframework.http.converter.AbstractKotlinSerializationHttpMessageConverter;
|
||||||
import org.springframework.http.converter.HttpMessageConverter;
|
import org.springframework.http.converter.HttpMessageConverter;
|
||||||
import org.springframework.http.converter.SmartHttpMessageConverter;
|
import org.springframework.http.converter.SmartHttpMessageConverter;
|
||||||
|
@ -61,6 +62,10 @@ public class KotlinRequestBodyAdvice extends RequestBodyAdviceAdapter {
|
||||||
for (KParameter p : Objects.requireNonNull(function).getParameters()) {
|
for (KParameter p : Objects.requireNonNull(function).getParameters()) {
|
||||||
if (KParameter.Kind.VALUE.equals(p.getKind())) {
|
if (KParameter.Kind.VALUE.equals(p.getKind())) {
|
||||||
if (index == i++) {
|
if (index == i++) {
|
||||||
|
if (HttpEntity.class.isAssignableFrom(parameter.getParameterType())) {
|
||||||
|
return Collections.singletonMap(KType.class.getName(),
|
||||||
|
Objects.requireNonNull(p.getType().getArguments().get(0).getType()));
|
||||||
|
}
|
||||||
return Collections.singletonMap(KType.class.getName(), p.getType());
|
return Collections.singletonMap(KType.class.getName(), p.getType());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ import kotlin.reflect.jvm.ReflectJvmMapping;
|
||||||
import org.jspecify.annotations.Nullable;
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import org.springframework.core.MethodParameter;
|
import org.springframework.core.MethodParameter;
|
||||||
|
import org.springframework.http.HttpEntity;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.converter.AbstractKotlinSerializationHttpMessageConverter;
|
import org.springframework.http.converter.AbstractKotlinSerializationHttpMessageConverter;
|
||||||
import org.springframework.http.converter.HttpMessageConverter;
|
import org.springframework.http.converter.HttpMessageConverter;
|
||||||
|
@ -61,6 +62,9 @@ public class KotlinResponseBodyAdvice implements ResponseBodyAdvice<Object> {
|
||||||
|
|
||||||
KFunction<?> function = ReflectJvmMapping.getKotlinFunction(Objects.requireNonNull(returnType.getMethod()));
|
KFunction<?> function = ReflectJvmMapping.getKotlinFunction(Objects.requireNonNull(returnType.getMethod()));
|
||||||
KType type = Objects.requireNonNull(function).getReturnType();
|
KType type = Objects.requireNonNull(function).getReturnType();
|
||||||
|
if (HttpEntity.class.isAssignableFrom(returnType.getParameterType())) {
|
||||||
|
return Collections.singletonMap(KType.class.getName(), Objects.requireNonNull(type.getArguments().get(0).getType()));
|
||||||
|
}
|
||||||
return Collections.singletonMap(KType.class.getName(), type);
|
return Collections.singletonMap(KType.class.getName(), type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,8 @@ import kotlinx.serialization.Serializable
|
||||||
import org.assertj.core.api.Assertions
|
import org.assertj.core.api.Assertions
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import org.springframework.core.MethodParameter
|
import org.springframework.core.MethodParameter
|
||||||
|
import org.springframework.http.RequestEntity
|
||||||
|
import org.springframework.http.ResponseEntity
|
||||||
import org.springframework.http.converter.StringHttpMessageConverter
|
import org.springframework.http.converter.StringHttpMessageConverter
|
||||||
import org.springframework.http.converter.json.KotlinSerializationJsonHttpMessageConverter
|
import org.springframework.http.converter.json.KotlinSerializationJsonHttpMessageConverter
|
||||||
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean
|
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean
|
||||||
|
@ -68,6 +70,22 @@ class RequestResponseBodyMethodProcessorKotlinTests {
|
||||||
.contains("\"value\":\"foo\"")
|
.contains("\"value\":\"foo\"")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun writeEntityWithKotlinSerializationJsonMessageConverter() {
|
||||||
|
val method = SampleController::writeMessageEntity::javaMethod.get()!!
|
||||||
|
val handlerMethod = HandlerMethod(SampleController(), method)
|
||||||
|
val methodReturnType = handlerMethod.returnType
|
||||||
|
|
||||||
|
val converters = listOf(KotlinSerializationJsonHttpMessageConverter())
|
||||||
|
val processor = RequestResponseBodyMethodProcessor(converters, null, listOf(KotlinResponseBodyAdvice()))
|
||||||
|
|
||||||
|
val returnValue: Any? = SampleController().writeMessageEntity().body
|
||||||
|
processor.handleReturnValue(returnValue, methodReturnType, this.container, this.request)
|
||||||
|
|
||||||
|
Assertions.assertThat(this.servletResponse.contentAsString)
|
||||||
|
.contains("\"value\":\"foo\"")
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun writeGenericTypeWithKotlinSerializationJsonMessageConverter() {
|
fun writeGenericTypeWithKotlinSerializationJsonMessageConverter() {
|
||||||
val method = SampleController::writeMessages::javaMethod.get()!!
|
val method = SampleController::writeMessages::javaMethod.get()!!
|
||||||
|
@ -118,6 +136,24 @@ class RequestResponseBodyMethodProcessorKotlinTests {
|
||||||
Assertions.assertThat(result).isEqualTo(Message("foo"))
|
Assertions.assertThat(result).isEqualTo(Message("foo"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
fun readEntityWithKotlinSerializationJsonMessageConverter() {
|
||||||
|
val content = "{\"value\" : \"foo\"}"
|
||||||
|
this.servletRequest.setContent(content.toByteArray(StandardCharsets.UTF_8))
|
||||||
|
this.servletRequest.setContentType("application/json")
|
||||||
|
|
||||||
|
val converters = listOf(StringHttpMessageConverter(), KotlinSerializationJsonHttpMessageConverter())
|
||||||
|
val processor = RequestResponseBodyMethodProcessor(converters, null, listOf(KotlinRequestBodyAdvice()))
|
||||||
|
|
||||||
|
val method = SampleController::readMessageEntity::javaMethod.get()!!
|
||||||
|
val methodParameter = MethodParameter(method, 0)
|
||||||
|
|
||||||
|
val result = processor.resolveArgument(methodParameter, container, request, factory) as Message
|
||||||
|
|
||||||
|
Assertions.assertThat(result).isEqualTo(Message("foo"))
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
@Test
|
@Test
|
||||||
fun readGenericTypeWithKotlinSerializationJsonMessageConverter() {
|
fun readGenericTypeWithKotlinSerializationJsonMessageConverter() {
|
||||||
|
@ -161,6 +197,10 @@ class RequestResponseBodyMethodProcessorKotlinTests {
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
fun writeMessage() = Message("foo")
|
fun writeMessage() = Message("foo")
|
||||||
|
|
||||||
|
@RequestMapping
|
||||||
|
@ResponseBody
|
||||||
|
fun writeMessageEntity() = ResponseEntity.ok(Message("foo"))
|
||||||
|
|
||||||
@RequestMapping
|
@RequestMapping
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
fun writeMessages() = listOf(Message("foo"), Message("bar"))
|
fun writeMessages() = listOf(Message("foo"), Message("bar"))
|
||||||
|
@ -169,6 +209,10 @@ class RequestResponseBodyMethodProcessorKotlinTests {
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
fun readMessage(message: Message) = message.value
|
fun readMessage(message: Message) = message.value
|
||||||
|
|
||||||
|
@RequestMapping
|
||||||
|
@ResponseBody
|
||||||
|
fun readMessageEntity(entity: RequestEntity<Message>) = entity.body!!.value
|
||||||
|
|
||||||
@RequestMapping
|
@RequestMapping
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
fun readMessages(messages: List<Message>) = messages.map { it.value }.reduce { acc, string -> "$acc $string" }
|
fun readMessages(messages: List<Message>) = messages.map { it.value }.reduce { acc, string -> "$acc $string" }
|
||||||
|
|
Loading…
Reference in New Issue