File tree Expand file tree Collapse file tree 11 files changed +173
-10
lines changed
main/kotlin/io/moia/router/proto
test/kotlin/io/moia/router/proto
main/kotlin/io/moia/router
test/kotlin/io/moia/router Expand file tree Collapse file tree 11 files changed +173
-10
lines changed Original file line number Diff line number Diff line change 11package io.moia.router.proto
22
33import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent
4- import io.moia.router.DeserializationHandler
5- import io.moia.router.contentType
64import com.google.common.net.MediaType
75import com.google.protobuf.Parser
6+ import io.moia.router.DeserializationHandler
7+ import io.moia.router.contentType
8+ import isCompatibleWith
89import java.util.Base64
910import kotlin.reflect.KClass
1011import kotlin.reflect.KType
1112import kotlin.reflect.full.staticFunctions
1213
1314class ProtoDeserializationHandler : DeserializationHandler {
1415 private val proto = MediaType .parse(" application/x-protobuf" )
16+ private val protoStructuredSuffixWildcard = MediaType .parse(" application/*+x-protobuf" )
1517
1618 override fun supports (input : APIGatewayProxyRequestEvent ): Boolean =
17- input.contentType() != null && MediaType .parse(input.contentType()).`is `(proto)
19+ if (input.contentType() == null )
20+ false
21+ else {
22+ MediaType .parse(input.contentType()).let { proto.isCompatibleWith(it) || protoStructuredSuffixWildcard.isCompatibleWith(it) }
23+ }
1824
1925 override fun deserialize (input : APIGatewayProxyRequestEvent , target : KType ? ): Any {
2026 val bytes = Base64 .getDecoder().decode(input.body)
Original file line number Diff line number Diff line change @@ -3,18 +3,20 @@ package io.moia.router.proto
33import com.google.common.net.MediaType
44import com.google.protobuf.GeneratedMessageV3
55import io.moia.router.SerializationHandler
6+ import isCompatibleWith
67import java.util.Base64
78
89class ProtoSerializationHandler : SerializationHandler {
910
1011 private val json = MediaType .parse(" application/json" )
12+ private val jsonStructuredSuffixWildcard = MediaType .parse(" application/*+json" )
1113
1214 override fun supports (acceptHeader : MediaType , body : Any ): Boolean =
1315 body is GeneratedMessageV3
1416
1517 override fun serialize (acceptHeader : MediaType , body : Any ): String {
1618 val message = body as GeneratedMessageV3
17- return if (acceptHeader.` is `(json )) {
19+ return if (json.isCompatibleWith( acceptHeader) || jsonStructuredSuffixWildcard.isCompatibleWith(acceptHeader )) {
1820 ProtoBufUtils .toJsonWithoutWrappers(message)
1921 } else {
2022 Base64 .getEncoder().encodeToString(message.toByteArray())
Original file line number Diff line number Diff line change @@ -21,5 +21,6 @@ internal class ProtoDeserializationHandlerTest {
2121 @Test
2222 fun `Deserializer should support if the content type of the input is protobuf` () {
2323 assertTrue(ProtoDeserializationHandler ().supports(APIGatewayProxyRequestEvent ().withHeader(" content-type" , " application/x-protobuf" )))
24+ assertTrue(ProtoDeserializationHandler ().supports(APIGatewayProxyRequestEvent ().withHeader(" content-type" , " application/vnd.moia.v1+x-protobuf" )))
2425 }
2526}
Original file line number Diff line number Diff line change @@ -35,6 +35,20 @@ class RequestHandlerTest {
3535 assert (response.body).isEqualTo(""" {"hello":"Hello","request":""}""" )
3636 }
3737
38+ @Test
39+ fun `should match request to proto handler with version accept header and return json` () {
40+
41+ val response = testRequestHandler.handleRequest(
42+ APIGatewayProxyRequestEvent ()
43+ .withPath(" /some-proto" )
44+ .withHttpMethod(" GET" )
45+ .withHeaders(mapOf (" Accept" to " application/vnd.moia.v1+json" )), mockk()
46+ )
47+
48+ assert (response.statusCode).isEqualTo(200 )
49+ assert (response.body).isEqualTo(""" {"hello":"v1","request":""}""" )
50+ }
51+
3852 @Test
3953 fun `should match request to proto handler and return proto` () {
4054
@@ -109,6 +123,9 @@ class RequestHandlerTest {
109123
110124 defaultContentType = " application/x-protobuf"
111125
126+ GET (" /some-proto" ) { _: Request <Unit > -> ResponseEntity .ok(Sample .newBuilder().setHello(" v1" ).build()) }
127+ .producing(" application/vnd.moia.v1+x-protobuf" , " application/vnd.moia.v1+json" )
128+
112129 GET (" /some-proto" ) { _: Request <Unit > -> ResponseEntity .ok(Sample .newBuilder().setHello(" Hello" ).build()) }
113130 .producing(" application/x-protobuf" , " application/json" )
114131 POST (" /some-proto" ) { r: Request <Sample > -> ResponseEntity .ok(r.body) }
Original file line number Diff line number Diff line change @@ -4,6 +4,7 @@ import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent
44import com.fasterxml.jackson.databind.ObjectMapper
55import com.fasterxml.jackson.databind.type.TypeFactory
66import com.google.common.net.MediaType
7+ import isCompatibleWith
78import kotlin.reflect.KClass
89import kotlin.reflect.KType
910import kotlin.reflect.full.isSubclassOf
@@ -28,9 +29,14 @@ class DeserializationHandlerChain(private val handlers: List<DeserializationHand
2829class JsonDeserializationHandler (private val objectMapper : ObjectMapper ) : DeserializationHandler {
2930
3031 private val json = MediaType .parse(" application/json" )
32+ private val jsonStructuredSuffixWildcard = MediaType .parse(" application/*+json" )
3133
3234 override fun supports (input : APIGatewayProxyRequestEvent ) =
33- input.contentType() != null && MediaType .parse(input.contentType()!! ).`is `(json)
35+ if (input.contentType() == null )
36+ false
37+ else {
38+ MediaType .parse(input.contentType()).let { json.isCompatibleWith(it) || jsonStructuredSuffixWildcard.isCompatibleWith(it) }
39+ }
3440
3541 override fun deserialize (input : APIGatewayProxyRequestEvent , target : KType ? ): Any? {
3642 val targetClass = target?.classifier as KClass <* >
Original file line number Diff line number Diff line change 1+ import com.google.common.net.MediaType
2+
3+ fun MediaType.isCompatibleWith (other : MediaType ): Boolean =
4+ if (this .`is `(other))
5+ true
6+ else {
7+ type() == other.type() &&
8+ (subtype().contains(" +" ) && other.subtype().contains(" +" )) &&
9+ this .subtype().substringBeforeLast(" +" ) == " *" &&
10+ this .subtype().substringAfterLast(" +" ) == other.subtype().substringAfterLast(" +" )
11+ }
Original file line number Diff line number Diff line change @@ -2,6 +2,7 @@ package io.moia.router
22
33import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent
44import com.google.common.net.MediaType
5+ import isCompatibleWith
56
67data class RequestPredicate (
78 val method : String ,
@@ -49,7 +50,7 @@ data class RequestPredicate(
4950 fun matchedAcceptType (acceptedMediaTypes : List <MediaType >) =
5051 produces
5152 .map { MediaType .parse(it) }
52- .firstOrNull { acceptedMediaTypes.any { acceptedType -> it.` is ` (acceptedType) } }
53+ .firstOrNull { acceptedMediaTypes.any { acceptedType -> it.isCompatibleWith (acceptedType) } }
5354
5455 private fun acceptMatches (acceptedMediaTypes : List <MediaType >) =
5556 matchedAcceptType(acceptedMediaTypes) != null
@@ -58,7 +59,7 @@ data class RequestPredicate(
5859 when {
5960 consumes.isEmpty() -> true
6061 contentType == null -> false
61- else -> consumes.any { MediaType .parse(contentType).` is ` (MediaType .parse(it)) }
62+ else -> consumes.any { MediaType .parse(contentType).isCompatibleWith (MediaType .parse(it)) }
6263 }
6364}
6465
Original file line number Diff line number Diff line change @@ -2,6 +2,7 @@ package io.moia.router
22
33import com.fasterxml.jackson.databind.ObjectMapper
44import com.google.common.net.MediaType
5+ import isCompatibleWith
56
67interface SerializationHandler {
78
@@ -23,8 +24,10 @@ class SerializationHandlerChain(private val handlers: List<SerializationHandler>
2324class JsonSerializationHandler (private val objectMapper : ObjectMapper ) : SerializationHandler {
2425
2526 private val json = MediaType .parse(" application/json" )
27+ private val jsonStructuredSuffixWildcard = MediaType .parse(" application/*+json" )
2628
27- override fun supports (acceptHeader : MediaType , body : Any ): Boolean = acceptHeader.`is `(json)
29+ override fun supports (acceptHeader : MediaType , body : Any ): Boolean =
30+ json.isCompatibleWith(acceptHeader) || jsonStructuredSuffixWildcard.isCompatibleWith(acceptHeader)
2831
2932 override fun serialize (acceptHeader : MediaType , body : Any ): String =
3033 objectMapper.writeValueAsString(body)
Original file line number Diff line number Diff line change 1+ package io.moia.router
2+
3+ import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent
4+ import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
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 JsonDeserializationHandlerTest {
10+
11+ val deserializationHandler = JsonDeserializationHandler (jacksonObjectMapper())
12+
13+ @Test
14+ fun `should support json` () {
15+ assertTrue(deserializationHandler.supports(APIGatewayProxyRequestEvent ()
16+ .withHeader(" content-type" , " application/json" )))
17+ assertTrue(deserializationHandler.supports(APIGatewayProxyRequestEvent ()
18+ .withHeader(" content-type" , " application/vnd.moia.v1+json" )))
19+ }
20+
21+ @Test
22+ fun `should not support anything else than json` () {
23+ assertFalse(deserializationHandler.supports(APIGatewayProxyRequestEvent ()
24+ .withHeader(" content-type" , " image/png" )))
25+ assertFalse(deserializationHandler.supports(APIGatewayProxyRequestEvent ()
26+ .withHeader(" content-type" , " text/plain" )))
27+ }
28+ }
Original file line number Diff line number Diff line change 1+ package io.moia.router
2+
3+ import com.google.common.net.MediaType
4+ import isCompatibleWith
5+ import org.assertj.core.api.BDDAssertions.then
6+ import org.junit.jupiter.api.Test
7+
8+ class MediaTypeTest {
9+
10+ @Test
11+ fun `should match` () {
12+ then(MediaType .parse(" application/json" ).isCompatibleWith(MediaType .parse(" application/json" ))).isTrue()
13+ }
14+
15+ @Test
16+ fun `should match subtype wildcard` () {
17+ then(MediaType .parse(" application/json" ).isCompatibleWith(MediaType .parse(" application/*" ))).isTrue()
18+ }
19+
20+ @Test
21+ fun `should not match subtype wildcard in different tpye` () {
22+ then(MediaType .parse(" application/json" ).isCompatibleWith(MediaType .parse(" image/*" ))).isFalse()
23+ }
24+
25+ @Test
26+ fun `should match wildcard` () {
27+ then(MediaType .parse(" application/json" ).isCompatibleWith(MediaType .parse(" */*" ))).isTrue()
28+ }
29+
30+ @Test
31+ fun `should match wildcard structured syntax suffix` () {
32+ then(MediaType .parse(" application/*+json" ).isCompatibleWith(MediaType .parse(" application/vnd.moia+json" ))).isTrue()
33+ then(MediaType .parse(" application/*+json" ).isCompatibleWith(MediaType .parse(" application/vnd.moia.v1+json" ))).isTrue()
34+ }
35+
36+ @Test
37+ fun `should not match wildcard structured syntax suffix on non suffix type` () {
38+ then(MediaType .parse(" application/*+json" ).isCompatibleWith(MediaType .parse(" application/json" ))).isFalse()
39+ }
40+
41+ @Test
42+ fun `should not match wildcard structured syntax suffix on differnt suffix` () {
43+ then(MediaType .parse(" application/*+json" ).isCompatibleWith(MediaType .parse(" application/*+x-protobuf" ))).isFalse()
44+ }
45+ }
You can’t perform that action at this time.
0 commit comments