softwaremill / tapir

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

[BUG] Ambigous implicit values with multi-level enumeratum's enums #1333

Open DenisNovac opened 3 years ago

DenisNovac commented 3 years ago

Tapir version: 0.18.0-M17

Scala version: 2.13.6

Describe the bug

When you have inner enum inside other enum and both of them extends TapirCodecEnumeratum trait - compile will fail with error:

  • ambiguous implicit values: both method schemaForEnumEntry in trait TapirCodecEnumeratum of type [E <: enumeratum.EnumEntry](implicit enum: enumeratum.Enum[E]): sttp.tapir.Schema[E] and method schemaForEnumEntry in trait TapirCodecEnumeratum of type [E <: enumeratum.EnumEntry](implicit enum: enumeratum.Enum[E]): sttp.tapir.Schema[E] match expected type sttp.tapir.Schema.Typeclass[Outer.Inner] implicit val schema: Schema[ClassWithInner] = Schema.derived[ClassWithInner]

  • magnolia: could not find Schema.Typeclass for type Outer.Inner in parameter 'i' of product type ClassWithInner implicit val schema: Schema[ClassWithInner] = Schema.derived[ClassWithInner]

How to reproduce?

Example of code:


import enumeratum._
import io.circe.Codec
import io.circe.generic.semiauto.deriveCodec
import sttp.tapir.Schema
import sttp.tapir.codec.enumeratum.TapirCodecEnumeratum

case class ClassWithOuter(i: Outer)       // compiles fine
case class ClassWithInner(i: Outer.Inner) // won't compile

object ClassWithInner {
  implicit val codec: Codec[ClassWithInner]   = deriveCodec[ClassWithInner]
  implicit val schema: Schema[ClassWithInner] = Schema.derived[ClassWithInner]
}

sealed trait Outer extends EnumEntry

object Outer extends Enum[Outer] with CirceEnum[Outer] with TapirCodecEnumeratum {

  case object TestOnj extends Outer
  sealed trait Inner  extends Outer

  object Inner extends Enum[Inner] with CirceEnum[Inner] with TapirCodecEnumeratum {
    case object TestInner extends Inner
    override def values: IndexedSeq[Inner] = findValues
  }

  // uncomment this to fix compile
  //implicit val innerSchema: Schema[Inner] = Inner.schemaForEnumEntry[Inner]

  override def values: IndexedSeq[Outer] = findValues
}

By the way, circe does not have this issue. I guess, it is because CirceEnum have the type.

Additional information

Project code: https://github.com/DenisNovac/tapir-enumeratum

adamw commented 3 years ago

Right, I suspect CirceEnum brings in the codecs only for a single type, while TapirCodecEnumeratum brings in codecs for any type. So it should be completely sufficient to extend that trait only by the outer enum, or just import sttp.tapir.codec.enumeratum._ which should be equivalent.

I think the intended usage of CirceEnum and TapirCodecEnumeratum is different - creating a single object containing all of the codec derivation rules that are used in an app, see https://tapir.softwaremill.com/en/latest/mytapir.html

DenisNovac commented 3 years ago

trait only by the outer enum

I tried it and it gives the same error. But it seems to work when i place it only here:

object ClassWithInner extends TapirCodecEnumeratum {
  implicit val codec: Codec[ClassWithInner]   = deriveCodec[ClassWithInner]
  implicit val schema: Schema[ClassWithInner] = Schema.derived[ClassWithInner]
}

So it seems that you need to place this trait in every place you make schema for some type which contains enum elements instead of just creating the schema for this enum.

Imports are working but IDEA says they are unused a removes it. It happens with many such imports so we don't really use them in projects.

adamw commented 3 years ago

Hm yeah you're right, no idea why the version with ... extends doesn't work when it's on the outside enum.

As for intellij, I suppose you could report a bug, as these imports are used and incorrectly marked as unused.