From c72287c873992dd136f2ba2ca426b4303849c969 Mon Sep 17 00:00:00 2001 From: Niels Simonides <5821618+nsmnds@users.noreply.github.com> Date: Sat, 23 Nov 2024 10:23:50 +0100 Subject: [PATCH] Fixed argument resolver & finer grained serialization (#298) Co-authored-by: nsimonides --- .../configuration/WirespecConfiguration.kt | 32 ++++++++++++++++--- .../web/WirespecMethodArgumentResolver.kt | 12 +++---- .../configuration/WirespecConfiguration.kt | 25 ++++++++++++--- .../web/WirespecMethodArgumentResolver.kt | 12 +++---- .../integration/spring/shared/Util.kt | 24 ++++++++++++++ 5 files changed, 81 insertions(+), 24 deletions(-) create mode 100644 src/integration/spring/src/jvmMain/kotlin/community/flock/wirespec/integration/spring/shared/Util.kt diff --git a/src/integration/spring/src/jvmMain/kotlin/community/flock/wirespec/integration/spring/java/configuration/WirespecConfiguration.kt b/src/integration/spring/src/jvmMain/kotlin/community/flock/wirespec/integration/spring/java/configuration/WirespecConfiguration.kt index 44fc5788..ba0205d0 100644 --- a/src/integration/spring/src/jvmMain/kotlin/community/flock/wirespec/integration/spring/java/configuration/WirespecConfiguration.kt +++ b/src/integration/spring/src/jvmMain/kotlin/community/flock/wirespec/integration/spring/java/configuration/WirespecConfiguration.kt @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper import community.flock.wirespec.integration.jackson.java.WirespecModuleJava import community.flock.wirespec.integration.spring.java.web.WirespecResponseBodyAdvice import community.flock.wirespec.java.Wirespec +import java.lang.reflect.ParameterizedType import java.lang.reflect.Type import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration @@ -17,12 +18,35 @@ open class WirespecConfiguration { open fun wirespecSerialization(objectMapper: ObjectMapper) = object : Wirespec.Serialization { private val wirespecObjectMapper = objectMapper.copy().registerModule(WirespecModuleJava()) + private val stringListDelimiter = "," - override fun serialize(body: T, type: Type?): String = wirespecObjectMapper.writeValueAsString(body) + override fun serialize(body: T, type: Type?): String = when { + body is String -> body + isStringIterable(type) && body is Iterable<*> -> body.joinToString(stringListDelimiter) + else -> wirespecObjectMapper.writeValueAsString(body) + } override fun deserialize(raw: String?, valueType: Type?): T? = raw?.let { - val type = wirespecObjectMapper.constructType(valueType) - wirespecObjectMapper.readValue(it, type) + when { + isStringIterable(valueType) -> { + @Suppress("UNCHECKED_CAST") + raw.split(stringListDelimiter) as T + } + else -> { + val type = wirespecObjectMapper.constructType(valueType) + wirespecObjectMapper.readValue(it, type) + } + } + } + + private fun isStringIterable(type: Type?): Boolean { + if (type !is ParameterizedType) return false + + val rawType = type.rawType as Class<*> + if (!Iterable::class.java.isAssignableFrom(rawType)) return false + + val typeArgument = type.actualTypeArguments.firstOrNull() + return typeArgument == String::class.java } } -} +} \ No newline at end of file diff --git a/src/integration/spring/src/jvmMain/kotlin/community/flock/wirespec/integration/spring/java/web/WirespecMethodArgumentResolver.kt b/src/integration/spring/src/jvmMain/kotlin/community/flock/wirespec/integration/spring/java/web/WirespecMethodArgumentResolver.kt index 559b343a..b83d9f47 100644 --- a/src/integration/spring/src/jvmMain/kotlin/community/flock/wirespec/integration/spring/java/web/WirespecMethodArgumentResolver.kt +++ b/src/integration/spring/src/jvmMain/kotlin/community/flock/wirespec/integration/spring/java/web/WirespecMethodArgumentResolver.kt @@ -1,5 +1,7 @@ package community.flock.wirespec.integration.spring.java.web +import community.flock.wirespec.integration.spring.shared.extractPath +import community.flock.wirespec.integration.spring.shared.extractQueries import community.flock.wirespec.java.Wirespec import jakarta.servlet.http.HttpServletRequest import java.util.stream.Collectors @@ -34,14 +36,8 @@ class WirespecMethodArgumentResolver( fun HttpServletRequest.toRawRequest(): Wirespec.RawRequest = Wirespec.RawRequest( method, - pathInfo.split("/"), - queryString - ?.split("&") - ?.associate { - val (key, value) = it.split("=") - key to value - } - .orEmpty(), + extractPath(), + extractQueries(), headerNames.toList().associateWith(::getHeader), reader.lines().collect(Collectors.joining(System.lineSeparator())) ) diff --git a/src/integration/spring/src/jvmMain/kotlin/community/flock/wirespec/integration/spring/kotlin/configuration/WirespecConfiguration.kt b/src/integration/spring/src/jvmMain/kotlin/community/flock/wirespec/integration/spring/kotlin/configuration/WirespecConfiguration.kt index 5410db06..1faf0938 100644 --- a/src/integration/spring/src/jvmMain/kotlin/community/flock/wirespec/integration/spring/kotlin/configuration/WirespecConfiguration.kt +++ b/src/integration/spring/src/jvmMain/kotlin/community/flock/wirespec/integration/spring/kotlin/configuration/WirespecConfiguration.kt @@ -7,6 +7,7 @@ import community.flock.wirespec.kotlin.Wirespec import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.context.annotation.Import +import kotlin.reflect.KClass import kotlin.reflect.KType import kotlin.reflect.javaType @@ -20,10 +21,26 @@ open class WirespecConfiguration { private val wirespecObjectMapper = objectMapper.copy().registerModule(WirespecModuleKotlin()) - override fun serialize(t: T, kType: KType): String = wirespecObjectMapper.writeValueAsString(t) + private val stringListDelimiter = "," - override fun deserialize(raw: String, kType: KType): T = wirespecObjectMapper - .constructType(kType.javaType) - .let { wirespecObjectMapper.readValue(raw, it) } + override fun serialize(t: T, kType: KType): String = + when { + t is String -> t + isStringIterable(kType) && t is Iterable<*> -> t.joinToString(stringListDelimiter) + else -> wirespecObjectMapper.writeValueAsString(t) + } + + override fun deserialize(raw: String, kType: KType): T = + if (isStringIterable(kType)) { + raw.split(stringListDelimiter) as T + } else { + wirespecObjectMapper + .constructType(kType.javaType) + .let { wirespecObjectMapper.readValue(raw, it) } + } } + + fun isStringIterable(kType: KType) = + (kType.classifier as? KClass<*>)?.java?.let { Iterable::class.java.isAssignableFrom(it) } == true && + kType.arguments.singleOrNull()?.type?.classifier == String::class } diff --git a/src/integration/spring/src/jvmMain/kotlin/community/flock/wirespec/integration/spring/kotlin/web/WirespecMethodArgumentResolver.kt b/src/integration/spring/src/jvmMain/kotlin/community/flock/wirespec/integration/spring/kotlin/web/WirespecMethodArgumentResolver.kt index d55cd1b7..2ec1f38d 100644 --- a/src/integration/spring/src/jvmMain/kotlin/community/flock/wirespec/integration/spring/kotlin/web/WirespecMethodArgumentResolver.kt +++ b/src/integration/spring/src/jvmMain/kotlin/community/flock/wirespec/integration/spring/kotlin/web/WirespecMethodArgumentResolver.kt @@ -1,5 +1,7 @@ package community.flock.wirespec.integration.spring.kotlin.web +import community.flock.wirespec.integration.spring.shared.extractPath +import community.flock.wirespec.integration.spring.shared.extractQueries import community.flock.wirespec.kotlin.Wirespec import jakarta.servlet.http.HttpServletRequest import java.util.stream.Collectors @@ -36,14 +38,8 @@ class WirespecMethodArgumentResolver( fun HttpServletRequest.toRawRequest(): Wirespec.RawRequest = Wirespec.RawRequest( method = method, - path = pathInfo?.split("/")?.drop(1) ?: emptyList(), - queries = queryString - ?.split("&") - ?.associate { - val (key, value) = it.split("=") - key to value - } - .orEmpty(), + path = extractPath(), + queries = extractQueries(), headers = headerNames.toList().associateWith(::getHeader), body = reader.lines().collect(Collectors.joining(System.lineSeparator())) ) diff --git a/src/integration/spring/src/jvmMain/kotlin/community/flock/wirespec/integration/spring/shared/Util.kt b/src/integration/spring/src/jvmMain/kotlin/community/flock/wirespec/integration/spring/shared/Util.kt new file mode 100644 index 00000000..ff824ef3 --- /dev/null +++ b/src/integration/spring/src/jvmMain/kotlin/community/flock/wirespec/integration/spring/shared/Util.kt @@ -0,0 +1,24 @@ +package community.flock.wirespec.integration.spring.shared + +import jakarta.servlet.http.HttpServletRequest + +/** + * Depending on how the controller is mapped: + * + * @RequestMapping("/pet/{id}") + * // or + * @GetMapping("/pet/{id}") + * + * The path ends up in either the pathInfo or servletPath + */ +fun HttpServletRequest.extractPath() = (if (pathInfo != null) pathInfo else servletPath) + .split("/") + .filter { it.isNotEmpty() } + +fun HttpServletRequest.extractQueries() = queryString + ?.split("&") + ?.associate { + val (key, value) = it.split("=") + key to value + } + .orEmpty() \ No newline at end of file