softwaremill / tapir

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

AsyncApi schema - use of `.withDiscriminator` for `Schema` renders illegal async api spec #3754

Open kamilkloch opened 6 months ago

kamilkloch commented 6 months ago
/** Use of discriminator, resulting yaml does not validate. Problematic part:
  * ```
  * discriminator:
  *   propertyName: fruit
  *   mapping:
  *     Apple: '#/components/schemas/Apple'
  *     Potato: '#/components/schemas/Potato'
  * ```
  *
  * It should explicitly provide discriminator values using `const` override.
  * ```
  * Fruit:
  *   title: Fruit
  *   oneOf:
  *   - $ref: '#/components/schemas/Apple'
  *   - $ref: '#/components/schemas/Potato'
  *   discriminator: fruit
  * Apple:
  *   title: Apple
  *   type: object
  *   required:
  *   - color
  *   - fruit
  *   properties:
  *     color:
  *       type: string
  *     fruit:
  *       type: string
  *       const: Apple
  * ```
  *
  * Also note `sttp.tapir.Schema` lacks `const: Option[T]` property which would be needed to represent such schemas.
  */
object AsyncApiExample2 {

  sttp.tapir.Schema

  sealed trait Fruit

  object Fruit {
    case class Apple(color: String) extends Fruit

    case class Potato(weight: Double) extends Fruit

    private implicit val circeConfig: Configuration = Configuration.default.withDiscriminator("fruit")
    implicit val fruitCodec: io.circe.Codec[Fruit] = deriveConfiguredCodec

    private implicit val tapirConfig = sttp.tapir.generic.Configuration.default.withDiscriminator("fruit")
    implicit val fruitSchema: sttp.tapir.Schema[Fruit] = sttp.tapir.Schema.derived
  }

  val ws = endpoint.get
    .in("ws")
    .out(
      webSocketBody[Fruit, CodecFormat.Json, Fruit, CodecFormat.Json](Fs2Streams[IO])
        .responsesExample(Fruit.Apple("red"))
        .responsesExample(Fruit.Potato(1.0))
    )

  // print raw jsonschema for Fruit
  val jsonSchema = TapirSchemaToJsonSchema(Fruit.fruitSchema, markOptionsAsNullable = true, metaSchema = MetaSchemaDraft04)
  import sttp.apispec.circe._
  println(jsonSchema.asJson.deepDropNullValues)

  // print async api
  val asyncApiYaml = AsyncAPIInterpreter()
    .toAsyncAPI(ws, "web socket", "1.0")
    .toYaml

  def main(args: Array[String]): Unit = println(asyncApiYaml)
}

Repo with code: https://github.com/kamilkloch/tapir-async-api/blob/master/src/main/scala/AsyncApiExample2.scala

kamilkloch commented 6 months ago

Duplicate of #3275