softwaremill / tapir

Rapid development of self-documenting APIs
https://tapir.softwaremill.com
Apache License 2.0
1.35k stars 411 forks source link

Snake case and discriminator config ignored #3904

Open axaluss opened 3 months ago

axaluss commented 3 months ago

Tapir version: 1.10.12

Scala version: 2.13

I created a simple http post endpoint that receives a json request and produces a json response. It uses schema derivation to map the request and response to case classes. i added implicit config to use discrinator and snake case members

‘’’

implicit val customConfiguration: Configuration = Configuration.default.withDiscriminator(“type_discriminator”).withSnakeCaseMemberNames ‘’’

What is the problem?

The discriminator and snake case work in openapi spec generation but they are completely ignored for zio http server interpretation https://tapir.softwaremill.com/en/latest/server/ziohttp.html

How to reproduce? Apply the implicit config to a tapir endpoint with schema/json derivation and run it in zio server interpreter.

Maybe you can provide code to reproduce the problem?

Additional information

adamw commented 3 months ago

Which JSON library are you using to perform the deserialisation?

You need to configure both tapir & the JSON codec separately, see: https://tapir.softwaremill.com/en/latest/endpoint/json.html

axaluss commented 3 months ago

i am using circe. I think I figured it out.

I had to make sure the Configurations are of the right package.

object MyTapirJsonCirce  extends TapirJsonCirce {
  implicit lazy val customTapirSchemaConfig: sttp.tapir.generic.Configuration =
    sttp.tapir.generic.Configuration.default
      .withDiscriminator("type_discriminator")
      .withSnakeCaseMemberNames

  implicit lazy val customCirceJsonConfig: io.circe.generic.extras.Configuration =
    io.circe.generic.extras.Configuration.default
      .withDiscriminator("type_discriminator")
      .withSnakeCaseMemberNames
}

Then I also had to figure out the right import incantation such that the implicits align in just the right way to work... not sure which of these are actually needed.

...
import io.circe._
import io.circe.syntax._
import io.circe.generic.extras._
import io.circe.generic.extras.auto._
import sttp.tapir.Codec.JsonCodec
import sttp.tapir.Schema
import sttp.tapir.generic.auto._
import MyTapirJsonCirce._

val theEndpoint: Endpoint[Unit, Request, ErrorResponse, Response, Any] =
      endpoint
        .in("product")
        .in("events")
        .post
        .in(EndpointInput.derived[Request])
        .out(EndpointOutput.derived[Response])

case class Request(
        @jsonbody
        event:                            Event,
        @query accountId:                 String)
    sealed trait Event
    case class CreateUpdate(
        adminGraphqlApiId: String)
        extends Event
axaluss commented 3 months ago

It may be beneficial to have a documented nontrivial example of this use case of snake case + discriminator

adamw commented 3 months ago

There's an example here on how to use discriminators: https://github.com/softwaremill/tapir/blob/7f6d4218ff72fcd259eced12113fb38666c4270b/examples/src/main/scala/sttp/tapir/examples/custom_types/sealedTraitWithDiscriminator.scala#L23

I'm currently working on having these examples better visible and discoverable :)

adamw commented 3 months ago

As for the improts, you need the import MyTapirJsonCirce._ as it brings into scope the configuraton (which is used during the EndpointInput.derived[Request] call), and the import io.circe.generic.extras.auto._ + import sttp.tapir.generic.auto._ to automatically derive the codec/schema. I don't think the others are needed.