tapir
tapir copied to clipboard
Scala 3 enums have limited customisation support
Following on from https://github.com/softwaremill/tapir/pull/1824#discussion_r913111720
An improvement request for scala 3 enums.
In scala 3 there is an extra way to do enums on top of the existing scala 2 way.
- if you use traditional scala 2 enums in scala 3 code you still use the old
scala.Enumeration
. - Scala 3 style enums compile to a sealed class that extends
scala.reflect.Enum
under the covers as per https://docs.scala-lang.org/scala3/reference/enums/enums.html If the enum has no fields the implementations are anonymous, however if the enum has fields then subclasses are created.
As of v1.0.1, using new style enums, as expected the built in derivation will generate a reference to a set of empty objects. However although this makes logical sense if the JSON output is not important, this is not really useful in most cases when working to an existing API spec while modelling things as scala 3 enums.
To see what I mean, follow the instructions in my minimal test case https://github.com/johnduffell/tapir-schema-issue
I can see that it's a customisation to want a different representation, but it's common for me to want to conform to a JSON spec while wanting to model it as a scala enum. So even if we keep the existing behaviour by default, it would be good to hear thoughts on how these use case could be smoother.
I have tried to be as clear as possible - thanks for your time and let me know if you need anything else!
Currently I think automatically deriving schemas for scala3 enums is not possible. This can be solved in two ways:
- when https://github.com/lampepfl/dotty/discussions/15895 is resolved in some positive way
- when schemas are directly derived using macros, instead of through magnolia (https://github.com/softwaremill/tapir/issues/2110)
So far I've updated the docs to add a dedicated section on this problem. As a work-around, explicitly specifying the schema for your enum should work:
enum ColorEnum {
case Green extends ColorEnum
case Pink extends ColorEnum
}
given Schema[ColorEnum] = Schema.derivedEnumeration(encode = Some(v => v))
Hi @adamw, thanks for your response. I have this problem with Scala 3 endpoints. I am using the following code:
val positionInfoEndpoint: PublicEndpoint[DeviceLocation, Unit, GeolocationServiceResult, Any] =
endpoint.post
.in("position-info")
.in(jsonBody[DeviceLocation])
.out(jsonBody[GeolocationServiceResult])
[...]
val docEndpoints: List[ZServerEndpoint[Any, Any]] =
SwaggerInterpreter().fromEndpoints[Task](
List(positionInfoEndpoint),
"zio-geolocation-tapir",
"1.0.0"
)
The Scala 3 enum is in the DeviceLocation case class. Where do I have to use the Schema you mentioned above?
@longliveenduro the schema is derived when calling jsonBody[...]
so I think if you add the implicit before that call (assuming you are using auto-derviation) things should work
Sth like:
given Schema[ColorEnum] = Schema.derivedEnumeration(encode = Some(v => v))
val positionInfoEndpoint: PublicEndpoint[DeviceLocation, Unit, GeolocationServiceResult, Any] =
endpoint.post
.in("position-info")
.in(jsonBody[DeviceLocation])
.out(jsonBody[GeolocationServiceResult])
Works like a charm. Thank you very much @adamw !
This might be useful? https://github.com/abdolence/circe-tagged-adt-codec-scala3/blob/master/lib/shared/src/main/scala/org/latestbit/circe/adt/codec/impl/JsonTaggedAdtEncoder.scala
Scala 3 enumerations should be now fully customisable, as documented here: https://tapir.softwaremill.com/en/latest/endpoint/enumerations.html