Skip to content

Commit 9cd0b43

Browse files
ruibritoptmduesterhoeft
authored andcommitted
imprv: jackson exceptions: JsonParseException and InvalidDefinitionEx… (#17)
* imprv: jackson exceptions: JsonParseException and InvalidDefinitionException * imprv: increase coverage * imprv: increase coverage * Use with to improve readability
1 parent 828add8 commit 9cd0b43

File tree

2 files changed

+88
-5
lines changed

2 files changed

+88
-5
lines changed

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

+28-4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import com.amazonaws.services.lambda.runtime.Context
44
import com.amazonaws.services.lambda.runtime.RequestHandler
55
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent
66
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent
7+
import com.fasterxml.jackson.core.JsonParseException
8+
import com.fasterxml.jackson.databind.exc.InvalidDefinitionException
79
import com.fasterxml.jackson.databind.exc.InvalidFormatException
810
import com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException
911
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
@@ -122,6 +124,8 @@ abstract class RequestHandler : RequestHandler<APIGatewayProxyRequestEvent, APIG
122124
*/
123125
open fun createUnprocessableEntityErrorBody(errors: List<UnprocessableEntityError>): Any = errors
124126

127+
private fun createUnprocessableEntityErrorBody(error: UnprocessableEntityError): Any = createUnprocessableEntityErrorBody(listOf(error))
128+
125129
open fun createApiExceptionErrorResponse(contentType: MediaType, input: APIGatewayProxyRequestEvent, ex: ApiException): APIGatewayProxyResponseEvent =
126130
createErrorBody(ex.toApiError()).let {
127131
APIGatewayProxyResponseEvent()
@@ -138,19 +142,39 @@ abstract class RequestHandler : RequestHandler<APIGatewayProxyRequestEvent, APIG
138142

139143
open fun createUnexpectedErrorResponse(contentType: MediaType, input: APIGatewayProxyRequestEvent, ex: Exception): APIGatewayProxyResponseEvent =
140144
when (ex) {
145+
is JsonParseException -> createResponse(contentType, input,
146+
ResponseEntity(422, createUnprocessableEntityErrorBody(
147+
UnprocessableEntityError(
148+
message = "INVALID_ENTITY",
149+
code = "ENTITY",
150+
path = "",
151+
details = mapOf(
152+
"payload" to ex.requestPayloadAsString.orEmpty(),
153+
"message" to ex.message.orEmpty()
154+
)))))
155+
is InvalidDefinitionException -> createResponse(contentType, input,
156+
ResponseEntity(422, createUnprocessableEntityErrorBody(
157+
UnprocessableEntityError(
158+
message = "INVALID_FIELD_FORMAT",
159+
code = "FIELD",
160+
path = ex.path.last().fieldName.orEmpty(),
161+
details = mapOf(
162+
"cause" to ex.cause?.message.orEmpty(),
163+
"message" to ex.message.orEmpty()
164+
)))))
141165
is InvalidFormatException ->
142166
createResponse(contentType, input,
143-
ResponseEntity(422, createUnprocessableEntityErrorBody(listOf(
167+
ResponseEntity(422, createUnprocessableEntityErrorBody(
144168
UnprocessableEntityError(
145169
message = "INVALID_FIELD_FORMAT",
146170
code = "FIELD",
147-
path = ex.path.last().fieldName.orEmpty())))))
171+
path = ex.path.last().fieldName.orEmpty()))))
148172
is MissingKotlinParameterException ->
149173
createResponse(contentType, input,
150-
ResponseEntity(422, createUnprocessableEntityErrorBody(listOf(UnprocessableEntityError(
174+
ResponseEntity(422, createUnprocessableEntityErrorBody(UnprocessableEntityError(
151175
message = "MISSING_REQUIRED_FIELDS",
152176
code = "FIELD",
153-
path = ex.parameter.name.orEmpty())))))
177+
path = ex.parameter.name.orEmpty()))))
154178
else -> createResponse(contentType, input,
155179
ResponseEntity(500, createErrorBody(ApiError(ex.message.orEmpty(), "INTERNAL_SERVER_ERROR"))))
156180
}

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

+60-1
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,18 @@ import assertk.assertions.isNotNull
77
import assertk.assertions.isNullOrEmpty
88
import assertk.assertions.isTrue
99
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent
10+
import com.fasterxml.jackson.module.kotlin.readValue
1011
import com.google.common.net.MediaType
1112
import io.mockk.mockk
1213
import io.moia.router.Router.Companion.router
1314
import org.junit.jupiter.api.Assertions.assertEquals
1415
import org.junit.jupiter.api.Test
16+
import java.time.LocalDate
1517

1618
class RequestHandlerTest {
1719

1820
private val testRequestHandler = TestRequestHandler()
21+
private val mapper = testRequestHandler.objectMapper
1922

2023
@Test
2124
fun `should match request`() {
@@ -197,6 +200,62 @@ class RequestHandlerTest {
197200
.withBody("""{"greeting": "hello","age": "a"}"""), mockk()
198201
)
199202
assert(response.statusCode).isEqualTo(422)
203+
val body = mapper.readValue<List<UnprocessableEntityError>>(response.body)
204+
assert(body.size).isEqualTo(1)
205+
with(body.first()) {
206+
assert(code).isEqualTo("FIELD")
207+
assert(message).isEqualTo("INVALID_FIELD_FORMAT")
208+
assert(path).isEqualTo("age")
209+
assert(details.isNotEmpty()).isEqualTo(false)
210+
}
211+
}
212+
213+
@Test
214+
fun `should handle deserialization error, when field can not be parsed to class`() {
215+
216+
val response = testRequestHandler.handleRequest(
217+
POST("/some")
218+
.withHeaders(
219+
mapOf(
220+
"Accept" to "application/json",
221+
"Content-Type" to "application/json"
222+
)
223+
)
224+
.withBody("""{"greeting": "hello","age": 1, "bday": "2000-01-AA"}"""), mockk()
225+
)
226+
assert(response.statusCode).isEqualTo(422)
227+
val body = mapper.readValue<List<UnprocessableEntityError>>(response.body)
228+
assert(body.size).isEqualTo(1)
229+
with(body.first()) {
230+
assert(code).isEqualTo("FIELD")
231+
assert(message).isEqualTo("INVALID_FIELD_FORMAT")
232+
assert(path).isEqualTo("bday")
233+
assert(details.isNotEmpty()).isEqualTo(true)
234+
}
235+
}
236+
237+
@Test
238+
fun `should handle deserialization error, when json can not be parsed`() {
239+
240+
val response = testRequestHandler.handleRequest(
241+
POST("/some")
242+
.withHeaders(
243+
mapOf(
244+
"Accept" to "application/json",
245+
"Content-Type" to "application/json"
246+
)
247+
)
248+
.withBody("""{"greeting": "hello", bday: "2000-01-01"}"""), mockk()
249+
)
250+
assert(response.statusCode).isEqualTo(422)
251+
val body = mapper.readValue<List<UnprocessableEntityError>>(response.body)
252+
assert(body.size).isEqualTo(1)
253+
with(body.first()) {
254+
assert(code).isEqualTo("ENTITY")
255+
assert(message).isEqualTo("INVALID_ENTITY")
256+
assert(path).isEqualTo("")
257+
assert(details.isNotEmpty()).isEqualTo(true)
258+
}
200259
}
201260

202261
@Test
@@ -458,7 +517,7 @@ class RequestHandlerTest {
458517
class TestRequestHandler : RequestHandler() {
459518

460519
data class TestResponse(val greeting: String)
461-
data class TestRequest(val greeting: String, val age: Int = 0)
520+
data class TestRequest(val greeting: String, val age: Int = 0, val bday: LocalDate = LocalDate.now())
462521

463522
override val router = router {
464523
GET("/some") { _: Request<Unit> ->

0 commit comments

Comments
 (0)