Mapping Errors based on Value

Hello,

In order to provide an accurate circe Encoder/Decoder, I include the HTTP status code as a field on my response:

object HttpError {
  implicit val enc: Encoder[HttpError] = e =>
    Json.obj(
      "error"  -> e.error.asJson,
      "cause"  -> e.cause.asJson,
      "status" -> e.status.code.asJson
    )

  implicit val dec: Decoder[HttpError] = cursor =>
    for {
      error <- cursor.downField("error").as[String]
      cause <- cursor.downField("cause").as[Option[String]]
      code  <- cursor.downField("status").as[Int]
      status = StatusCode(code)
    } yield status match {
      case StatusCode.InternalServerError => InternalServerError(error, cause)
      case StatusCode.Unauthorized        => AuthorizationError(error, cause)
      ...
      case _                              => GenericHttpError(error, cause, status)
    }
}

Instead of manually specifying each error & variant, like so:

  def errorResponseMapper: EndpointOutput.OneOf[HttpError, HttpError] =
    oneOf[HttpError](
      oneOfVariant(
        StatusCode.Unauthorized,
        jsonBody[AuthorizationError]
      ),
      oneOfVariant(
        StatusCode.InternalServerError,
        jsonBody[InternalServerError]
      ),
      oneOfDefaultVariant(
        jsonBody[HttpError]
      )
    )

It would be useful to me if I could have a mapper which just uses the status and jsonBody[HttpError]. Is there any way to provide a function for the mapping, e.g:

oneOfVariantValue[HttpError](error => statusCode(error.status).jsonBody(error))

Is this possible?

Yes, it is possible, probably sth like this should work:

val myOutput = statusCode.and(jsonBody[HttpError]).map { case (sc, b) => b }(b => (b.status, b))

However, note that you’ll get less precise documentation out of that - it will just contain information that for some fixed status code (400 by default), the error body will be HttpError - without the error-code-specific entries.

That makes sense. Thanks.