Skip to content

Commit 1e843e4

Browse files
support plain text serialization and deserialization (#41)
1 parent 6954bcc commit 1e843e4

File tree

7 files changed

+126
-22
lines changed

7 files changed

+126
-22
lines changed

router-protobuf/src/main/kotlin/io/moia/router/proto/ProtoEnabledRequestHandler.kt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,16 @@ package io.moia.router.proto
22

33
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent
44
import com.google.common.net.MediaType
5-
import io.moia.router.JsonDeserializationHandler
6-
import io.moia.router.JsonSerializationHandler
75
import io.moia.router.RequestHandler
86
import io.moia.router.ResponseEntity
97

108
abstract class ProtoEnabledRequestHandler : RequestHandler() {
119

1210
override fun serializationHandlers() =
13-
listOf(ProtoSerializationHandler(), JsonSerializationHandler(objectMapper))
11+
listOf(ProtoSerializationHandler()) + super.serializationHandlers()
1412

1513
override fun deserializationHandlers() =
16-
listOf(ProtoDeserializationHandler(), JsonDeserializationHandler(objectMapper))
14+
listOf(ProtoDeserializationHandler()) + super.deserializationHandlers()
1715

1816
override fun <T> createResponse(
1917
contentType: MediaType,

router/src/main/kotlin/io/moia/router/DeserializationHandler.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,15 @@ class JsonDeserializationHandler(private val objectMapper: ObjectMapper) : Deser
6969
}
7070
}
7171
}
72+
73+
object PlainTextDeserializationHandler : DeserializationHandler {
74+
private val text = MediaType.parse("text/*")
75+
override fun supports(input: APIGatewayProxyRequestEvent): Boolean =
76+
if (input.contentType() == null)
77+
false
78+
else
79+
MediaType.parse(input.contentType()).isCompatibleWith(text)
80+
81+
override fun deserialize(input: APIGatewayProxyRequestEvent, target: KType?): Any? =
82+
input.body
83+
}

router/src/main/kotlin/io/moia/router/RequestHandler.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ abstract class RequestHandler : RequestHandler<APIGatewayProxyRequestEvent, APIG
105105
}
106106

107107
open fun serializationHandlers(): List<SerializationHandler> = listOf(
108-
JsonSerializationHandler(objectMapper)
108+
JsonSerializationHandler(objectMapper), PlainTextSerializationHandler()
109109
)
110110

111111
open fun deserializationHandlers(): List<DeserializationHandler> = listOf(
@@ -124,6 +124,7 @@ abstract class RequestHandler : RequestHandler<APIGatewayProxyRequestEvent, APIG
124124
requestType?.classifier as KClass<*> == Unit::class -> Unit
125125
input.body == null && requestType.isMarkedNullable -> null
126126
input.body == null -> throw ApiException("no request body present", "REQUEST_BODY_MISSING", 400)
127+
input.body is String && requestType.classifier as KClass<*> == String::class -> input.body
127128
else -> deserializationHandlerChain.deserialize(input, requestType)
128129
}
129130
}

router/src/main/kotlin/io/moia/router/SerializationHandler.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,12 @@ class JsonSerializationHandler(private val objectMapper: ObjectMapper) : Seriali
4747

4848
override fun serialize(acceptHeader: MediaType, body: Any): String =
4949
objectMapper.writeValueAsString(body)
50+
}
51+
52+
class PlainTextSerializationHandler(val supportedAcceptTypes: List<MediaType> = listOf(MediaType.parse("text/*"))) : SerializationHandler {
53+
override fun supports(acceptHeader: MediaType, body: Any): Boolean =
54+
supportedAcceptTypes.any { acceptHeader.isCompatibleWith(it) }
55+
56+
override fun serialize(acceptHeader: MediaType, body: Any): String =
57+
body.toString()
5058
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright 2019 MOIA GmbH
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and limitations under the License.
14+
*
15+
*/
16+
17+
package io.moia.router
18+
19+
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent
20+
import org.junit.jupiter.api.Assertions.assertEquals
21+
import org.junit.jupiter.api.Assertions.assertFalse
22+
import org.junit.jupiter.api.Assertions.assertTrue
23+
import org.junit.jupiter.api.Test
24+
25+
class PlainTextDeserializationHandlerTest {
26+
27+
@Test
28+
fun `should support text`() {
29+
assertTrue(PlainTextDeserializationHandler.supports(APIGatewayProxyRequestEvent()
30+
.withHeader("content-type", "text/plain")))
31+
assertTrue(PlainTextDeserializationHandler.supports(APIGatewayProxyRequestEvent()
32+
.withHeader("content-type", "text/csv")))
33+
}
34+
35+
@Test
36+
fun `should not support anything else than text`() {
37+
assertFalse(PlainTextDeserializationHandler.supports(APIGatewayProxyRequestEvent()
38+
.withHeader("content-type", "image/png")))
39+
assertFalse(PlainTextDeserializationHandler.supports(APIGatewayProxyRequestEvent()
40+
.withHeader("content-type", "application/json")))
41+
}
42+
@Test
43+
fun `should not support anything when content type is null`() {
44+
assertFalse(PlainTextDeserializationHandler.supports(APIGatewayProxyRequestEvent()))
45+
}
46+
47+
@Test
48+
fun `should return body`() {
49+
val request = APIGatewayProxyRequestEvent().withBody("some")
50+
val result = PlainTextDeserializationHandler.deserialize(request, null)
51+
52+
assertEquals(request.body, result)
53+
}
54+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package io.moia.router
2+
3+
import com.google.common.net.MediaType
4+
import org.junit.jupiter.api.Assertions.assertEquals
5+
import org.junit.jupiter.api.Assertions.assertFalse
6+
import org.junit.jupiter.api.Assertions.assertTrue
7+
import org.junit.jupiter.api.Test
8+
9+
class PlainTextSerializationHandlerTest {
10+
11+
@Test
12+
fun `should support text`() {
13+
assertTrue(PlainTextSerializationHandler().supports(MediaType.parse("text/plain"), "some"))
14+
assertTrue(PlainTextSerializationHandler(listOf(MediaType.parse("text/csv"))).supports(MediaType.parse("text/csv"), "some"))
15+
}
16+
17+
@Test
18+
fun `should not support anything else than text`() {
19+
assertFalse(PlainTextSerializationHandler().supports(MediaType.parse("application/json"), "some"))
20+
assertFalse(PlainTextSerializationHandler().supports(MediaType.parse("image/jpeg"), "some"))
21+
}
22+
23+
@Test
24+
fun `should serialize string`() {
25+
assertEquals("some", PlainTextSerializationHandler().serialize(MediaType.parse("text/plain"), "some"))
26+
}
27+
}

router/src/test/kotlin/io/moia/router/RequestHandlerTest.kt

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import assertk.assertions.isNullOrEmpty
2424
import assertk.assertions.isTrue
2525
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent
2626
import com.fasterxml.jackson.module.kotlin.readValue
27-
import com.google.common.net.MediaType
2827
import io.mockk.mockk
2928
import io.moia.router.Router.Companion.router
3029
import org.junit.jupiter.api.Assertions.assertEquals
@@ -616,6 +615,27 @@ class RequestHandlerTest {
616615
assert(response.statusCode).isEqualTo(200)
617616
}
618617

618+
@Test
619+
fun `should deserialize plain text`() {
620+
class SampleRouter : RequestHandler() {
621+
override val router = router {
622+
POST("/some", { r: Request<String> -> ResponseEntity.ok(r.body) })
623+
.producing("text/plain")
624+
.consuming("text/plain")
625+
}
626+
}
627+
val request = POST("/some")
628+
.withAcceptHeader("text/plain")
629+
.withContentTypeHeader("text/plain")
630+
.withBody("just text")
631+
632+
val response = SampleRouter().handleRequest(request, mockk())
633+
634+
assert(response.statusCode).isEqualTo(200)
635+
assert(response.getHeaderCaseInsensitive("content-type")).isEqualTo("text/plain")
636+
assert(response.body).isEqualTo("just text")
637+
}
638+
619639
class TestRequestHandlerAuthorization : RequestHandler() {
620640
override val router = router {
621641
GET("/some") { _: Request<Unit> ->
@@ -767,22 +787,6 @@ class RequestHandlerTest {
767787

768788
data class CustomObject(val text: String, val number: Int)
769789

770-
class PlainTextSerializationHandler : SerializationHandler {
771-
override fun supports(acceptHeader: MediaType, body: Any): Boolean {
772-
return acceptHeader.`is`(MediaType.parse("text/plain"))
773-
}
774-
775-
override fun serialize(acceptHeader: MediaType, body: Any): String {
776-
return body.toString()
777-
}
778-
}
779-
780-
override fun serializationHandlers() =
781-
listOf(JsonSerializationHandler(objectMapper), PlainTextSerializationHandler())
782-
783-
override fun deserializationHandlers() =
784-
listOf(JsonDeserializationHandler(objectMapper))
785-
786790
override val router = router {
787791
defaultConsuming = setOf("application/json", "text/plain")
788792
defaultProducing = setOf("application/json", "text/plain")

0 commit comments

Comments
 (0)