Error Handling
If an exception occurs while a request is handled, the server will render the
exception into a structured response (JSON, XML, … depending on what the client accepts) via
ErrorHandler.default(), which is an alias for ErrorHandler.structured(). To customize the
serialization used, pass a SerializationBuilder to structured(...).
HTML Error Pages
To get a HTML error response instead of JSON, you can add the HTML error renderer to your app as a concern:
val app = Layout.create()
.addService<MyService>("service")
.add(ErrorHandler.html())
class MyService {
@ResourceMethod
fun hello(): String = "Hello World"
}Custom Error Responses
If you would like to control the response generated for exceptions, you can implement a custom
ErrorMapper and add it to your app as needed. The following example will render any exception
to a text response:
import org.genhttp.api.content.Handler
import org.genhttp.api.content.ProviderException
import org.genhttp.api.protocol.ByteString
import org.genhttp.api.protocol.ContentType
import org.genhttp.api.protocol.Request
import org.genhttp.api.protocol.Response
import org.genhttp.api.protocol.ResponseStatus
import org.genhttp.modules.errorhandling.*
import org.genhttp.modules.io.*
import org.genhttp.modules.layouting.*
import org.genhttp.modules.webservices.*
val errorHandling = ErrorHandler.from(TextErrorMapper())
val api = Layout.create()
.addService<MyService>("service")
.add(errorHandling)
class TextErrorMapper : ErrorMapper<Exception> {
override suspend fun getNotFound(request: Request, handler: Handler, acceptedFormat: ByteString?): Response? {
// hint: return null here to render the default error page of the server
return getResponse(request, ResponseStatus.NotFound, "The requested content was not found")
}
override suspend fun map(request: Request, handler: Handler, error: Exception, acceptedFormat: ByteString?): Response? {
if (error is ProviderException) {
return getResponse(request, error.status, error.stackTraceToString())
}
return getResponse(request, ResponseStatus.InternalServerError, error.stackTraceToString())
}
private fun getResponse(request: Request, status: ResponseStatus, message: String): Response =
request.respond()
.status(status)
.content(message, ContentType.TextPlain)
.build()
}
class MyService {
@ResourceMethod
fun hello(): String = "Hello World"
}The type argument of the mapper (ErrorMapper<Exception>) decides which exceptions it handles,
others are passed on to the error handler further up in the chain. As generic types are erased
on the JVM, ErrorHandler.from(...) reads it from the declaration of the mapper, or takes it
explicitly (ErrorHandler.from(mapper, Exception::class)). The CancellationException of a
coroutine that has been cancelled is never passed to a mapper.
acceptedFormat is the format negotiated from the client’s Accept header (as also used by
serialization) - use it if your mapper wants
to honor content negotiation like ErrorHandler.structured() does; the example above ignores it
and always renders plain text.