@@ -12,6 +12,7 @@ import org.slf4j.LoggerFactory
12
12
import kotlin.reflect.KClass
13
13
import kotlin.reflect.jvm.reflect
14
14
15
+ @Suppress(" UnstableApiUsage" )
15
16
abstract class RequestHandler : RequestHandler <APIGatewayProxyRequestEvent , APIGatewayProxyResponseEvent > {
16
17
17
18
open val objectMapper = jacksonObjectMapper()
@@ -29,41 +30,39 @@ abstract class RequestHandler : RequestHandler<APIGatewayProxyRequestEvent, APIG
29
30
val matchResult = routerFunction.requestPredicate.match(input)
30
31
log.debug(" match result for route '$routerFunction ' is '$matchResult '" )
31
32
if (matchResult.match) {
33
+ val matchedAcceptType = routerFunction.requestPredicate.matchedAcceptType(input.acceptedMediaTypes())
34
+ ? : MediaType .parse(router.defaultContentType)
32
35
if (! permissionHandlerSupplier()(input).hasAnyRequiredPermission(routerFunction.requestPredicate.requiredPermissions))
33
- return createApiExceptionErrorResponse(input, ApiException (" unauthorized" , " UNAUTHORIZED" , 401 ))
36
+ return createApiExceptionErrorResponse(matchedAcceptType, input, ApiException (" unauthorized" , " UNAUTHORIZED" , 401 ))
34
37
35
38
val handler: HandlerFunction <Any , Any > = routerFunction.handler
36
39
return try {
37
40
val requestBody = deserializeRequest(handler, input)
38
41
val request =
39
42
Request (input, requestBody, routerFunction.requestPredicate.pathPattern)
40
43
val response = router.filter.then(handler as HandlerFunction <* , * >).invoke(request)
41
- createResponse(routerFunction.requestPredicate. matchedAcceptType(input.acceptHeader()) , input, response)
44
+ createResponse(matchedAcceptType, input, response)
42
45
} catch (e: Exception ) {
43
46
when (e) {
44
- is ApiException -> createApiExceptionErrorResponse(input, e)
47
+ is ApiException -> createApiExceptionErrorResponse(matchedAcceptType, input, e)
45
48
.also { log.info(" Caught api error while handling ${input.httpMethod} ${input.path} - $e " ) }
46
- else -> createUnexpectedErrorResponse(input, e)
49
+ else -> createUnexpectedErrorResponse(matchedAcceptType, input, e)
47
50
.also { log.error(" Caught exception handling ${input.httpMethod} ${input.path} - $e " , e) }
48
51
}
49
52
}
50
53
}
51
54
52
55
matchResult
53
56
}
54
- return handleNonDirectMatch(matchResults, input)
57
+ return handleNonDirectMatch(MediaType .parse(router.defaultContentType), matchResults, input)
55
58
}
56
59
57
60
open fun serializationHandlers (): List <SerializationHandler > = listOf (
58
- JsonSerializationHandler (
59
- objectMapper
60
- )
61
+ JsonSerializationHandler (objectMapper)
61
62
)
62
63
63
64
open fun deserializationHandlers (): List <DeserializationHandler > = listOf (
64
- JsonDeserializationHandler (
65
- objectMapper
66
- )
65
+ JsonDeserializationHandler (objectMapper)
67
66
)
68
67
69
68
open fun permissionHandlerSupplier (): (r: APIGatewayProxyRequestEvent ) -> PermissionHandler =
@@ -80,89 +79,87 @@ abstract class RequestHandler : RequestHandler<APIGatewayProxyRequestEvent, APIG
80
79
}
81
80
}
82
81
83
- private fun handleNonDirectMatch (matchResults : List <RequestMatchResult >, input : APIGatewayProxyRequestEvent ): APIGatewayProxyResponseEvent {
82
+ private fun handleNonDirectMatch (defaultContentType : MediaType , matchResults : List <RequestMatchResult >, input : APIGatewayProxyRequestEvent ): APIGatewayProxyResponseEvent {
84
83
// no direct match
85
- if (matchResults.any { it.matchPath && it.matchMethod && ! it.matchContentType }) {
86
- return createApiExceptionErrorResponse(
87
- input, ApiException (
88
- httpResponseStatus = 415 ,
89
- message = " Unsupported Media Type" ,
90
- code = " UNSUPPORTED_MEDIA_TYPE"
84
+ val apiException =
85
+ when {
86
+ matchResults.any { it.matchPath && it.matchMethod && ! it.matchContentType } ->
87
+ ApiException (
88
+ httpResponseStatus = 415 ,
89
+ message = " Unsupported Media Type" ,
90
+ code = " UNSUPPORTED_MEDIA_TYPE"
91
+ )
92
+ matchResults.any { it.matchPath && it.matchMethod && ! it.matchAcceptType } ->
93
+ ApiException (
94
+ httpResponseStatus = 406 ,
95
+ message = " Not Acceptable" ,
96
+ code = " NOT_ACCEPTABLE"
97
+ )
98
+ matchResults.any { it.matchPath && ! it.matchMethod } ->
99
+ ApiException (
100
+ httpResponseStatus = 405 ,
101
+ message = " Method Not Allowed" ,
102
+ code = " METHOD_NOT_ALLOWED"
91
103
)
92
- )
93
- }
94
- if (matchResults.any { it.matchPath && it.matchMethod && ! it.matchAcceptType }) {
95
- return createApiExceptionErrorResponse(
96
- input, ApiException (
97
- httpResponseStatus = 406 ,
98
- message = " Not Acceptable" ,
99
- code = " NOT_ACCEPTABLE"
104
+ else -> ApiException (
105
+ httpResponseStatus = 404 ,
106
+ message = " Not found" ,
107
+ code = " NOT_FOUND"
100
108
)
101
- )
102
- }
103
- if (matchResults.any { it.matchPath && ! it.matchMethod }) {
104
- return createApiExceptionErrorResponse(
105
- input, ApiException (
106
- httpResponseStatus = 405 ,
107
- message = " Method Not Allowed" ,
108
- code = " METHOD_NOT_ALLOWED"
109
+ }
110
+ val contentType = input.acceptedMediaTypes().firstOrNull() ? : defaultContentType
111
+ return createApiExceptionErrorResponse(contentType, input, apiException)
112
+ }
113
+
114
+ /* *
115
+ * Customize the format of an api error
116
+ */
117
+ open fun createErrorBody (error : ApiError ): Any = error
118
+
119
+ /* *
120
+ * Customize the format of an unprocessable entity error
121
+ */
122
+ open fun createUnprocessableEntityErrorBody (error : UnprocessableEntityError ): Any =
123
+ error
124
+
125
+ open fun createApiExceptionErrorResponse (contentType : MediaType , input : APIGatewayProxyRequestEvent , ex : ApiException ): APIGatewayProxyResponseEvent =
126
+ createErrorBody(ex.toApiError()).let {
127
+ APIGatewayProxyResponseEvent ()
128
+ .withBody(
129
+ // in case of 406 we might find no serializer so fall back to the default
130
+ if (serializationHandlerChain.supports(contentType, it))
131
+ serializationHandlerChain.serialize(contentType, it)
132
+ else
133
+ serializationHandlerChain.serialize(MediaType .parse(router.defaultContentType), it)
109
134
)
110
- )
135
+ .withStatusCode(ex.httpResponseStatus)
136
+ .withHeaders(mapOf (" Content-Type" to contentType.toString()))
111
137
}
112
- return createApiExceptionErrorResponse(
113
- input, ApiException (
114
- httpResponseStatus = 404 ,
115
- message = " Not found" ,
116
- code = " NOT_FOUND"
117
- )
118
- )
119
- }
120
138
121
- open fun createApiExceptionErrorResponse (input : APIGatewayProxyRequestEvent , ex : ApiException ): APIGatewayProxyResponseEvent =
122
- APIGatewayProxyResponseEvent ()
123
- .withBody(objectMapper.writeValueAsString(mapOf (
124
- " message" to ex.message,
125
- " code" to ex.code,
126
- " details" to ex.details
127
- )))
128
- .withStatusCode(ex.httpResponseStatus)
129
- .withHeaders(mapOf (" Content-Type" to " application/json" ))
130
-
131
- open fun createUnexpectedErrorResponse (input : APIGatewayProxyRequestEvent , ex : Exception ): APIGatewayProxyResponseEvent =
139
+ open fun createUnexpectedErrorResponse (contentType : MediaType , input : APIGatewayProxyRequestEvent , ex : Exception ): APIGatewayProxyResponseEvent =
132
140
when (ex) {
133
141
is MissingKotlinParameterException ->
134
- APIGatewayProxyResponseEvent ()
135
- .withBody(objectMapper.writeValueAsString(
136
- listOf (mapOf (
137
- " path" to ex.parameter.name.orEmpty(),
138
- " message" to " Missing required field" ,
139
- " code" to " MISSING_REQUIRED_FIELDS"
140
- ))))
141
- .withStatusCode(422 )
142
- .withHeaders(mapOf (" Content-Type" to " application/json" ))
143
- else ->
144
- APIGatewayProxyResponseEvent ()
145
- .withBody(objectMapper.writeValueAsString(mapOf (
146
- " message" to ex.message,
147
- " code" to " INTERNAL_SERVER_ERROR"
148
- )))
149
- .withStatusCode(500 )
150
- .withHeaders(mapOf (" Content-Type" to " application/json" ))
142
+ createResponse(contentType, input,
143
+ ResponseEntity (422 , createUnprocessableEntityErrorBody(UnprocessableEntityError (
144
+ message = " Missing required field" ,
145
+ code = " MISSING_REQUIRED_FIELDS" ,
146
+ path = ex.parameter.name.orEmpty()))))
147
+ else -> createResponse(contentType, input,
148
+ ResponseEntity (500 , createErrorBody(ApiError (ex.message.orEmpty(), " INTERNAL_SERVER_ERROR" ))))
151
149
}
152
150
153
151
open fun <T > createResponse (contentType : MediaType ? , input : APIGatewayProxyRequestEvent , response : ResponseEntity <T >): APIGatewayProxyResponseEvent {
154
- // TODO add default accept type
155
152
return when {
156
153
// no-content response
157
- response.body == null && response.statusCode == 204 -> APIGatewayProxyResponseEvent ()
158
- .withStatusCode(204 )
154
+ response.body == null -> APIGatewayProxyResponseEvent ()
155
+ .withStatusCode(response.statusCode )
159
156
.withHeaders(response.headers)
160
- serializationHandlerChain.supports(contentType!! , response) ->
157
+ serializationHandlerChain.supports(contentType!! , response.body ) ->
161
158
APIGatewayProxyResponseEvent ()
162
159
.withStatusCode(response.statusCode)
163
- .withBody(serializationHandlerChain.serialize(contentType, response))
160
+ .withBody(serializationHandlerChain.serialize(contentType, response.body ))
164
161
.withHeaders(response.headers + (" Content-Type" to contentType.toString()))
165
- else -> throw IllegalArgumentException (" unsupported response $response " )
162
+ else -> throw IllegalArgumentException (" unsupported response ${ response.body. let { (it as Any ):: class .java }} and $contentType " )
166
163
}
167
164
}
168
165
0 commit comments