tapir icon indicating copy to clipboard operation
tapir copied to clipboard

Derived schema name for `List[T]` is `scala.collection.immutable.:: [BUG]

Open hindog opened this issue 2 years ago • 6 comments

Tapir version: 1.7.3

Scala version: 2.12.15

When deriving a List[T] where T is defined inside of a companion object, the generated schema for List[T] is scala.collection.immutable.:: { head, tl }

How to reproduce?

import sttp.tapir.Schema
import sttp.tapir.generic.auto._

object TestSchema extends App {

  case class Container(values: List[Container.Value])

  object Container {
    case class Value(key: String, value: String)
  }

  case class Root(containers: List[Container])
  val s = implicitly[Schema[Root]]

  println(s.show)
}

This prints:

schema is SProduct(List(SProductField(FieldName(containers,containers),Schema(SArray(Schema(SProduct(List(SProductField(FieldName(values,values),Schema(SCoproduct(List(Schema(SProduct(List(SProductField(FieldName(head,head),Schema(SProduct(List(SProductField(FieldName(key,key),Schema(SString(),None,false,None,None,None,None,false,false,All(List()),AttributeMap(Map()))), SProductField(FieldName(value,value),Schema(SString(),None,false,None,None,None,None,false,false,All(List()),AttributeMap(Map()))))),Some(SName(whoco.sourcing.services.chat.TestSchema.Container.Value,List())),false,None,None,None,None,false,false,All(List()),AttributeMap(Map()))), SProductField(FieldName(tl,tl),Schema(SRef(SName(scala.collection.immutable.List,List(Value))),None,false,None,None,None,None,false,false,All(List()),AttributeMap(Map()))))),Some(SName(scala.collection.immutable.::,List(Value))),false,None,None,None,None,false,false,All(List()),AttributeMap(Map())), Schema(SProduct(List()),Some(SName(scala.collection.immutable.Nil,List())),false,None,None,None,None,false,false,All(List()),AttributeMap(Map()))),None),Some(SName(scala.collection.immutable.List,List(Value))),false,None,None,None,None,false,false,All(List()),AttributeMap(Map()))))),Some(SName(whoco.sourcing.services.chat.TestSchema.Container,List())),false,None,None,None,None,false,false,All(List()),AttributeMap(Map()))),None,true,None,None,None,None,false,false,All(List()),AttributeMap(Map())))))

Note the references to the implementation details of Scala List.

hindog avatar Sep 19 '23 23:09 hindog

Which tapir and Scala version are you using? I'm getting

schema is SProduct(List(SProductField(FieldName(containers,containers),Schema(SArray(Schema(SProduct(List(SProductField(FieldName(values,values),Schema(SArray(Schema(SProduct(List(SProductField(FieldName(key,key),Schema(SString(),None,false,None,None,None,None,false,false,All(List()),AttributeMap(Map()))), SProductField(FieldName(value,value),Schema(SString(),None,false,None,None,None,None,false,false,All(List()),AttributeMap(Map()))))),Some(SName(sttp.tapir.examples3.Test.Container.Value,List())),false,None,None,None,None,false,false,All(List()),AttributeMap(Map()))),None,true,None,None,None,None,false,false,All(List()),AttributeMap(Map()))))),Some(SName(sttp.tapir.examples3.Test.Container,List())),false,None,None,None,None,false,false,All(List()),AttributeMap(Map()))),None,true,None,None,None,None,false,false,All(List()),AttributeMap(Map())))))

which seems correct (Scala 3.3.0, latest tapir)

adamw avatar Sep 20 '23 00:09 adamw

Hmm, guessing it might be related to Scala version then. Here's what I'm using:

Tapir version: 1.7.3

Scala version: 2.12.15

I can also try with 2.13 when I'm back online tomorrow.

hindog avatar Sep 20 '23 01:09 hindog

Indeed this seems to be broken on 2.12 only. 2.13 works fine. Must be something with implicit resolution, but what exactly, no idea ... is the "inside of companion object" an important variable here? (that is, does it work fine if the classes are defined at the top level)

adamw avatar Sep 20 '23 16:09 adamw

Yes, if we make Value a top-level class then we don't see the issue (that has been our work-around so far).

Another potential clue I can provide is that if I change case class Root(containers: List[Container]) to not take a List[Container], but just a single value, then the issue doesn't happen anymore, eg this works ok:

object TestSchema extends App {

  case class Container(values: List[Container.Value])

  object Container {
    case class Value(key: String, value: String)
  }

  case class Root(container: Container)
  val s = implicitly[Schema[Root]]

  println(s.show)
}

hindog avatar Sep 21 '23 00:09 hindog

Ok, thanks for the report, but since that's a rather old Scala version, I doubt that we'll get to investigating this any time soon. So if there's a work-around that's great, another option would be update to 2.13 / 3 :)

adamw avatar Sep 21 '23 00:09 adamw

Sounds good, and appreciated. Yeah getting on 2.13 has been one of our goals so maybe we should prioritize that :)

hindog avatar Sep 21 '23 00:09 hindog