Skip to content

Commit

Permalink
if handler returns null, just proceed with next one, eventually calli…
Browse files Browse the repository at this point in the history
…ng unhandled()
  • Loading branch information
angryziber committed Aug 17, 2023
1 parent 5eb1eaf commit 18365aa
Show file tree
Hide file tree
Showing 2 changed files with 23 additions and 12 deletions.
27 changes: 17 additions & 10 deletions server/src/klite/ErrorHandler.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,41 +14,48 @@ fun interface ThrowableHandler<in E: Throwable> {

open class ErrorHandler {
private val logger = logger(ErrorHandler::class.qualifiedName!!)
private val handlers = mutableMapOf<KClass<out Throwable>, ThrowableHandler<Throwable>>(
NoSuchElementException::class to ThrowableHandler { e, _ -> logger.error(e); ErrorResponse(NotFound, e.message?.takeIf { "is empty" !in it }) }
)
private val handlers = mutableMapOf<KClass<out Throwable>, ThrowableHandler<Throwable>>()
private val statusCodes = mutableMapOf<KClass<out Throwable>, StatusCode>(
IllegalArgumentException::class to BadRequest,
IllegalStateException::class to BadRequest,
BusinessException::class to UnprocessableEntity
)

init {
on<NoSuchElementException> { e, _ -> logger.error(e); ErrorResponse(NotFound, e.message?.takeIf { "is empty" !in it }) }
on<NullPointerException> { e, _ ->
if (e.message?.startsWith("Parameter specified as non-null is null") == true)
ErrorResponse(BadRequest, e.message!!.substring(e.message!!.indexOf(", parameter ") + 12) + " is required")
else null
}
}

@Suppress("UNCHECKED_CAST")
fun <T: Throwable> on(e: KClass<out T>, handler: ThrowableHandler<T>) { handlers[e] = handler as ThrowableHandler<Throwable> }
inline fun <reified T: Throwable> on(handler: ThrowableHandler<T>) = on(T::class, handler)

fun on(e: KClass<out Throwable>, statusCode: StatusCode) { statusCodes[e] = statusCode }

fun handle(exchange: HttpExchange, e: Throwable) {
exchange.failure = e
if (!exchange.isResponseStarted) toResponse(exchange, e)?.let {
if (!exchange.isResponseStarted) toResponse(exchange, e).let {
if (it.statusCode.bodyAllowed) exchange.render(it.statusCode, it) else exchange.send(it.statusCode)
} else if (e.message == null || e.message!!.let { "Broken pipe" !in it && "Connection reset" !in it })
logger.error("Error after headers sent", e)
}

open fun toResponse(exchange: HttpExchange, e: Throwable): ErrorResponse? {
open fun toResponse(exchange: HttpExchange, e: Throwable): ErrorResponse {
if (e is RedirectException) exchange.header("Location", e.location)
if (e is StatusCodeException) return ErrorResponse(e.statusCode, e.message)
if (e is NullPointerException && e.message?.startsWith("Parameter specified as non-null is null") == true)
return ErrorResponse(BadRequest, e.message!!.substring(e.message!!.indexOf(", parameter ") + 12) + " is required")

// TODO: look for subclasses
handlers[e::class]?.let { handler ->
handler.handle(e, exchange)?.let { return it }
}
statusCodes[e::class]?.let {
logger.error(e)
return ErrorResponse(it, e.message)
}
handlers[e::class]?.let { handler ->
return handler.handle(e, exchange)
}
return unhandled(e)
}

Expand Down
8 changes: 6 additions & 2 deletions server/test/klite/ErrorHandlerTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,12 @@ class ErrorHandlerTest {
}

@Test fun unhandled() {
expect(errorHandler.toResponse(exchange, Exception("Kaboom")))
.toEqual(ErrorResponse(InternalServerError, "Kaboom"))
expect(errorHandler.toResponse(exchange, Exception("Kaboom"))).toEqual(ErrorResponse(InternalServerError, "Kaboom"))
}

@Test fun `handler returning null proceeds further`() {
errorHandler.on<NullPointerException> { _, _ -> null }
expect(errorHandler.toResponse(exchange, NullPointerException())).toEqual(ErrorResponse(InternalServerError, null))
}

@Test fun `handle broken pipe - client closed connection`() {
Expand Down

0 comments on commit 18365aa

Please sign in to comment.