Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Tapir Custom Codec

I am stuck at a place, I am using scala, tapir and circe.

sealed abstract class S1Error extends Product with Serializable
object S1Error {
  final case class SError(error: SMError) extends S1Error
}
sealed abstract class SMError(message: String)
object SMError {
  final case class SVError(message: String) extends SMError(message)
}

For tapir errorOut I am using this

val schemaVersionError: EndpointOutput.StatusMapping[SError] = statusMappingValueMatcher(
      StatusCode.BadRequest,
      jsonBody[SError]
        .description("XXXX.")
    ) {
      case SMError(SVError(_)) => true
      case _                                  => false
    }

Now because of this structure, the API result I get is

{
    "error": {
        "SVError": {
            "message": "XXXXG"
        }
    }
}

where as ideally I would want a response as

"message": "XXXXG"

I can not change the error structure. Is there a way to wrap this error using a custom codec to get the result as required.

like image 509
Arjun Karnwal Avatar asked Mar 12 '26 20:03

Arjun Karnwal


1 Answers

Tapir codec is derived from Circe's decoder and encoder.

What you see is the default way of encoding case classes by circe.

Circe provides the possibility to encode case classes the way you described with deriveUnwrappedEncoder from circe-generic-extras. Unfortunately, it doesn't compile for SMError (probably derivation mechanism gets confused by your abstract class hierarchy).

What you can do is just creating encoder manually:

sealed abstract class S1Error extends Product with Serializable

object S1Error {
  final case class SError(error: SMError) extends S1Error

  implicit val encoder: Encoder[SError] = Encoder[SMError].contramap(_.error)
  // or you can use deriveUnwrappedEncoder from circe-generic-extras:
  // implicit val encoder: Encoder[SError] = deriveUnwrappedEncoder
}

//I also needed to make message a field in SMError
sealed abstract class SMError(val message: String)
object SMError {
  final case class SVError(override val message: String) extends SMError(message)

  implicit val encoder: Encoder[SMError] = Encoder.encodeJsonObject.contramap{s => JsonObject("message" -> s.message.asJson)}
}

Response now looks like:

{
    "message": "XXXXG"
}
like image 183
Krzysztof Atłasik Avatar answered Mar 14 '26 17:03

Krzysztof Atłasik



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!