From 33be891e29d2813dff4695890ab96d15d09915ee Mon Sep 17 00:00:00 2001 From: kciesielski Date: Thu, 28 Sep 2023 09:22:23 +0200 Subject: [PATCH] Use 'enumeration' to clearly name true enums --- .../sttp/tapir/internal/EnumMacros.scala | 44 ------------------- .../sttp/tapir/macros/ValidatorMacros.scala | 2 +- doc/contributing.md | 17 +++++-- .../sttp/tapir/json/pickler/Pickler.scala | 6 +-- .../sttp/tapir/json/pickler/Writers.scala | 4 +- .../sttp/tapir/json/pickler/macros.scala | 3 +- 6 files changed, 20 insertions(+), 56 deletions(-) delete mode 100644 core/src/main/scala-3/sttp/tapir/internal/EnumMacros.scala diff --git a/core/src/main/scala-3/sttp/tapir/internal/EnumMacros.scala b/core/src/main/scala-3/sttp/tapir/internal/EnumMacros.scala deleted file mode 100644 index 7f9f07a2e5..0000000000 --- a/core/src/main/scala-3/sttp/tapir/internal/EnumMacros.scala +++ /dev/null @@ -1,44 +0,0 @@ -package sttp.tapir.internal - -import scala.quoted.* - -private[tapir] object EnumMacros: - - transparent inline def isScalaEnum[T]: Boolean = inline compiletime.erasedValue[T] match - case _: Null => false - case _: Nothing => false - case _: reflect.Enum => allChildrenObjectsOrEnumCases[T] - case _ => false - - /** Checks whether type T has child types, and all children of type T are objects or enum cases or sealed parents of such. Useful for - * determining whether an enum is indeed an enum, or will be desugared to a sealed hierarchy, in which case it's not really an - * enumeration in context of schemas and JSON codecs. - */ - inline def allChildrenObjectsOrEnumCases[T]: Boolean = ${ allChildrenObjectsOrEnumCasesImpl[T] } - - def allChildrenObjectsOrEnumCasesImpl[T: Type](using q: Quotes): Expr[Boolean] = - Expr(enumerationTypeChildren[T](failOnError = false).nonEmpty) - - /** Recursively scans a symbol and builds a list of all children and their children, as long as all of them are objects or enum cases or - * sealed parents of such. Useful for determining whether an enum is indeed an enum, or will be desugared to a sealed hierarchy, in which - * case it's not really an enumeration (in context of schemas and JSON codecs). - */ - def enumerationTypeChildren[T: Type](failOnError: Boolean)(using q: Quotes): List[q.reflect.Symbol] = - import quotes.reflect.* - - val tpe = TypeRepr.of[T] - val symbol = tpe.typeSymbol - - def flatChildren(s: Symbol): List[Symbol] = s.children.toList.flatMap { c => - if (c.isClassDef) { - if (!(c.flags is Flags.Sealed)) - if (failOnError) - report.errorAndAbort("All children must be objects or enum cases, or sealed parent of such.") - else - Nil - else - flatChildren(c) - } else List(c) - } - - flatChildren(symbol) diff --git a/core/src/main/scala-3/sttp/tapir/macros/ValidatorMacros.scala b/core/src/main/scala-3/sttp/tapir/macros/ValidatorMacros.scala index 538a0ee712..5db2cb0ad9 100644 --- a/core/src/main/scala-3/sttp/tapir/macros/ValidatorMacros.scala +++ b/core/src/main/scala-3/sttp/tapir/macros/ValidatorMacros.scala @@ -1,6 +1,6 @@ package sttp.tapir.macros -import sttp.tapir.internal.EnumMacros.* +import sttp.tapir.internal.EnumerationMacros.* import sttp.tapir.Validator import sttp.tapir.Schema diff --git a/doc/contributing.md b/doc/contributing.md index 01909f7047..2a69325f32 100644 --- a/doc/contributing.md +++ b/doc/contributing.md @@ -2,13 +2,22 @@ All suggestions welcome :)! -If you'd like to contribute, see the list of [issues](https://github.com/softwaremill/tapir/issues) and pick one! +If you'd like to contribute, see the list of [issues](https://github.com/softwaremill/tapir/issues) and pick one! Or report your own. If you have an idea you'd like to discuss, that's always a good option. -If you are having doubts on the *why* or *how* something works, don't hesitate to ask a question on -[discourse](https://softwaremill.community/c/tapir) or via github. This probably means that the documentation, scaladocs or +If you are having doubts on the _why_ or _how_ something works, don't hesitate to ask a question on +[discourse](https://softwaremill.community/c/tapir) or via github. This probably means that the documentation, scaladocs or code is unclear and can be improved for the benefit of all. +## Conventions + +### Enumerations + +Scala 3 introduces `enum`, which can be used to represent sealed hierarchies with simpler syntax, or actual "true" enumerations, +that is parameterless enums or sealed traits with only case objects as children. Tapir needs to treat the latter differently, +in order to allow using OpenAPI `enum` elements and derive JSON codecs which represent them as simple values (without discriminator). +Let's use the name `enumeration` in Tapir codebase to represent these "true" enumerations and avoid ambiguity. + ## Acknowledgments Tuple-concatenating code is copied from [akka-http](https://github.com/akka/akka-http/blob/master/akka-http/src/main/scala/akka/http/scaladsl/server/util/TupleOps.scala) @@ -17,4 +26,4 @@ Parts of generic derivation configuration is copied from [circe](https://github. Implementation of mirror for union and intersection types are originally implemented by [Iltotore](https://github.com/Iltotore) in [this gist](https://gist.github.com/Iltotore/eece20188d383f7aee16a0b89eeb887f) -Tapir logo & stickers have been drawn by [impurepics](https://twitter.com/impurepics). \ No newline at end of file +Tapir logo & stickers have been drawn by [impurepics](https://twitter.com/impurepics). diff --git a/json/pickler/src/main/scala/sttp/tapir/json/pickler/Pickler.scala b/json/pickler/src/main/scala/sttp/tapir/json/pickler/Pickler.scala index f32935d40b..d56a16ffbb 100644 --- a/json/pickler/src/main/scala/sttp/tapir/json/pickler/Pickler.scala +++ b/json/pickler/src/main/scala/sttp/tapir/json/pickler/Pickler.scala @@ -1,6 +1,6 @@ package sttp.tapir.json.pickler -import sttp.tapir.internal.EnumMacros.* +import sttp.tapir.internal.EnumerationMacros.* import sttp.tapir.Codec.JsonCodec import sttp.tapir.DecodeResult.Error.JsonDecodeException import sttp.tapir.DecodeResult.{Error, Value} @@ -69,7 +69,7 @@ object Pickler: s"Unexpected product type (case class) ${implicitly[ClassTag[T]].runtimeClass.getSimpleName()}, this method should only be used with sum types (like sealed hierarchy)" ) case _: Mirror.SumOf[T] => - inline if (isScalaEnum[T]) + inline if (isEnumeration[T]) error("oneOfUsingField cannot be used with enums. Try Pickler.derivedEnumeration instead.") else { given schemaV: Schema[V] = discriminatorPickler.schema @@ -278,7 +278,7 @@ object Pickler: case p: Mirror.ProductOf[T] => picklerProduct(p, childPicklers) case _: Mirror.SumOf[T] => val schema: Schema[T] = - inline if (isScalaEnum[T]) + inline if (isEnumeration[T]) Schema.derivedEnumeration[T].defaultStringBased else Schema.derived[T] diff --git a/json/pickler/src/main/scala/sttp/tapir/json/pickler/Writers.scala b/json/pickler/src/main/scala/sttp/tapir/json/pickler/Writers.scala index adb675ca23..f000bfbb51 100644 --- a/json/pickler/src/main/scala/sttp/tapir/json/pickler/Writers.scala +++ b/json/pickler/src/main/scala/sttp/tapir/json/pickler/Writers.scala @@ -3,7 +3,7 @@ package sttp.tapir.json.pickler import _root_.upickle.core.Annotator.Checker import _root_.upickle.core.{ObjVisitor, Visitor, _} import _root_.upickle.implicits.{WritersVersionSpecific, macros => upickleMacros} -import sttp.tapir.internal.EnumMacros.* +import sttp.tapir.internal.EnumerationMacros.* import sttp.tapir.Schema import sttp.tapir.SchemaType.SProduct import sttp.tapir.generic.Configuration @@ -65,7 +65,7 @@ private[pickler] trait Writers extends WritersVersionSpecific with UpickleHelper ) } - inline if upickleMacros.isMemberOfSealedHierarchy[T] && !isScalaEnum[T] then + inline if upickleMacros.isMemberOfSealedHierarchy[T] && !isEnumeration[T] then annotate[T]( writer, upickleMacros.tagName[T], diff --git a/json/pickler/src/main/scala/sttp/tapir/json/pickler/macros.scala b/json/pickler/src/main/scala/sttp/tapir/json/pickler/macros.scala index 94680453ad..5f13419dc8 100644 --- a/json/pickler/src/main/scala/sttp/tapir/json/pickler/macros.scala +++ b/json/pickler/src/main/scala/sttp/tapir/json/pickler/macros.scala @@ -2,7 +2,7 @@ package sttp.tapir.json.pickler import _root_.upickle.implicits.* import _root_.upickle.implicits.{macros => uMacros} -import sttp.tapir.internal.EnumMacros.* +import sttp.tapir.internal.EnumerationMacros.* import sttp.tapir.SchemaType import sttp.tapir.SchemaType.SProduct @@ -92,4 +92,3 @@ private[pickler] object macros: Expr.block(statements, '{}) } -