Http4s decoder how to customize error message for invalid fields

1k views Asked by At

I have following code like:

 case req @ POST -> Root =>
        req
          .decode[UserCreateRequest] { decodedRequest =>

my stack is http4s + zio.

Ive added custom decoder for this case class where I have a line:

email <- Either.cond(StringValidator.isValidEmail(emailStr), Email(emailStr), DecodingFailure("email", c.history))

Posting invalid json, with invalid email returns me:

HTTP/1.1 422 Unprocessable Entity Content-Type: text/plain; charset=UTF-8 Date: Tue, 19 Jan 2021 16:46:27 GMT Content-Length: 29

The request body was invalid.

Response code: 422 (Unprocessable Entity); Time: 681ms; Content length: 29 bytes

which I would like to customize. In http4s code I see InvalidMessageBodyFailure. But I can not find in docs any info how to customize this response.

Any one maybe tried this already ?

thanks

edit:

sample UserCreateRequest:

final case class UserCreateRequest(
    email: Email
  )

final case class Email(value: String) extends AnyVal

json request:

{
 "email": "myemail[at]gmail.com"
}

this can be achieved using such code:

(for {
          decodedJson <- req.asJson.mapError { decodingError =>
            HttpDecodingError(cause = decodingError.getMessage)
          }
          decodedRequest <- Task.fromEither(decodedJson.as[UserCreateRequest]).mapError { decodingError =>
            HttpDecodingError(cause = decodingError.getMessage)
          }
          response <- UserService
            .createNewUser(
              decodedRequest.email
            )
            .bimap(
              error => HttpGenericError(msg = error.msg, cause = error.cause.toString),
              u => UserResponse(u.email.value)
            )
        } yield response).foldM((error: HttpError) => BadRequest(error), u => Ok(u))

but I wonder if it can be simplified, by some http4s core features, which are done already but not documented :)

1

There are 1 answers

12
Yuval Itzchakov On

You can directly return the Status from your API. i.e. you could construct an UnprocessableEntity instance and use the withXXX methods to alter the response.

Assuming some structure:

final case class UserCreateRequest(isValid: Boolean)

You can do:

case req @ POST -> Root / "foo" =>
  for {
    req <- req.decodeJson[UserCreateRequest]
    resp <- if (req.isValid) Ok()
            else UnprocessableEntity().map(_.withEntity(???).withAttribute(???))
  } yield resp