Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to add custom error responses in Http4s?

Tags:

scala

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.

like image 937
Krzysztof Atłasik Avatar asked Dec 29 '19 10:12

Krzysztof Atłasik


People also ask

Can I add custom error responses to my website?

You may wish to provide custom error responses which are either friendlier, or in some language other than English, or perhaps which are styled more in line with your site layout. Customized error responses can be defined for any HTTP status code designated as an error condition - that is, any 4xx or 5xx status.

Why is my HTTP request returning an error?

When we make an HTTP request to a resource, it is common that the request has to consider the option of returning an error. It is the typical case that we made a RESTful request to query for a record, but it does not exist.

What is the Apache HTTP server response to 4xx and 5XX errors?

Although the Apache HTTP Server provides generic error responses in the event of 4xx or 5xx HTTP status codes, these responses are rather stark, uninformative, and can be intimidating to site users.

How do I get a 404 error message from a RuntimeException?

A simple class that extends RuntimeException, and this annotated with the tag @ResponseStatus (HttpStatus.NOT_FOUND) will return a 404 code to the client. Now if we make a request for a code greater than three, we will receive this response: But, as we said, we want the error message is customized.


2 Answers

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))
like image 174
Rafal Piotrowski Avatar answered Oct 13 '22 19:10

Rafal Piotrowski


I suppose you have defined your routes in a similar fashion, then you can add a default case statement

 HttpRoutes.of[IO] {
   case GET -> Root / "api" =>
        Ok()

   case _ -> Root =>
        // Your default route could be done like this
        Ok(io.circe.parser.parse("""{"message": "Not Found"}"""))
}
like image 35
user134 Avatar answered Oct 13 '22 20:10

user134