Search code examples
scalahttp4s

How to add custom error responses in Http4s?


Whenever I hit unknown route in my http4s application it returns 404 error page with Content-Type: text/plain and body:

Not found

How can I force it to always return body as JSON with Content-Type: application/json?

{"message": "Not found"}

I figured out that when I assembly httpApp I can map over it and "adjust" responses:

val httpApp = Router.publicRoutes[F].orNotFound.map(ErrorTranslator.handle)

where ErrorTranslator just detects responses with status code of client error and Content-Type which is not application/json and then just wraps body into JSON:

object ErrorTranslator {

  val ContentType = "Content-Type"
  val ApplicationJson = "application/json"

  private def translate[F[_]: ConcurrentEffect: Sync](r: Response[F]): Response[F] =
    r.headers.get(CaseInsensitiveString(ContentType)).map(_.value) match {
      case Some(ApplicationJson) => r
      case _                     => r.withEntity(r.bodyAsText.map(ErrorView(_))) //wrap reponse body into enity
    }

  def handle[F[_]: ConcurrentEffect: Sync]: PartialFunction[Response[F], Response[F]] = {
    case Status.ClientError(r) => translate(r)
    case r                     => r
  }

}

It works, but I wonder if there is maybe some less convoluted solution?

It would be also great if a solution could "translate" other errors, like 400 Bad request into JSON, similarily to presented code.


Solution

  • You can also make it with value and mapF function:

    val jsonNotFound: Response[F] =
      Response(
        Status.NotFound,
        body = Stream("""{"error": "Not found"}""").through(utf8Encode),
        headers = Headers(`Content-Type`(MediaType.application.json) :: Nil)
      )
    val routes: HttpRoutes[F] = routes.someRoutes().mapF(_.getOrElse(jsonNotFound))