-
Notifications
You must be signed in to change notification settings - Fork 426
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: implement Scala 3 Constant and Union support for string-based l…
…iterals as enums (#3846) Co-authored-by: adamw <adam@warski.org>
- Loading branch information
1 parent
4904700
commit 74aa169
Showing
9 changed files
with
179 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
60 changes: 60 additions & 0 deletions
60
core/src/main/scala-3/sttp/tapir/macros/union_derivation.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
package sttp.tapir.macros | ||
|
||
import scala.compiletime.* | ||
import scala.deriving.* | ||
import scala.quoted.* | ||
|
||
@scala.annotation.implicitNotFound("${A} is not a union of ${T}") | ||
private[tapir] sealed trait IsUnionOf[T, A] | ||
|
||
private[tapir] object IsUnionOf: | ||
|
||
private val singleton: IsUnionOf[Any, Any] = new IsUnionOf[Any, Any] {} | ||
|
||
transparent inline given derived[T, A]: IsUnionOf[T, A] = ${ deriveImpl[T, A] } | ||
|
||
private def deriveImpl[T, A](using quotes: Quotes, t: Type[T], a: Type[A]): Expr[IsUnionOf[T, A]] = | ||
import quotes.reflect.* | ||
val tpe: TypeRepr = TypeRepr.of[A] | ||
val bound: TypeRepr = TypeRepr.of[T] | ||
|
||
def validateTypes(tpe: TypeRepr): Unit = | ||
tpe.dealias match | ||
case o: OrType => | ||
validateTypes(o.left) | ||
validateTypes(o.right) | ||
case o => | ||
if o <:< bound then () | ||
else report.errorAndAbort(s"${o.show} is not a subtype of ${bound.show}") | ||
|
||
tpe.dealias match | ||
case o: OrType => | ||
validateTypes(o) | ||
('{ IsUnionOf.singleton.asInstanceOf[IsUnionOf[T, A]] }).asExprOf[IsUnionOf[T, A]] | ||
case o => | ||
if o <:< bound then ('{ IsUnionOf.singleton.asInstanceOf[IsUnionOf[T, A]] }).asExprOf[IsUnionOf[T, A]] | ||
else report.errorAndAbort(s"${tpe.show} is not a Union") | ||
|
||
private[tapir] object UnionDerivation: | ||
transparent inline def constValueUnionTuple[T, A](using IsUnionOf[T, A]): Tuple = ${ constValueUnionTupleImpl[T, A] } | ||
|
||
private def constValueUnionTupleImpl[T: Type, A: Type](using Quotes): Expr[Tuple] = | ||
Expr.ofTupleFromSeq(constTypes[T, A]) | ||
|
||
private def constTypes[T: Type, A: Type](using Quotes): List[Expr[Any]] = | ||
import quotes.reflect.* | ||
val tpe: TypeRepr = TypeRepr.of[A] | ||
val bound: TypeRepr = TypeRepr.of[T] | ||
|
||
def transformTypes(tpe: TypeRepr): List[TypeRepr] = | ||
tpe.dealias match | ||
case o: OrType => | ||
transformTypes(o.left) ::: transformTypes(o.right) | ||
case o: Constant if o <:< bound && o.isSingleton => | ||
o :: Nil | ||
case o => | ||
report.errorAndAbort(s"${o.show} is not a subtype of ${bound.show}") | ||
|
||
transformTypes(tpe).distinct.map(_.asType match | ||
case '[t] => '{ constValue[t] } | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package sttp.tapir | ||
|
||
import org.scalatest.{Assertion, Inside} | ||
import org.scalatest.flatspec.AnyFlatSpec | ||
import org.scalatest.matchers.should.Matchers | ||
import org.scalatestplus.scalacheck.Checkers | ||
import sttp.tapir.CodecFormat.TextPlain | ||
import sttp.tapir.DecodeResult.Value | ||
|
||
import sttp.tapir.DecodeResult.InvalidValue | ||
|
||
class CodecScala3Test extends AnyFlatSpec with Matchers with Checkers with Inside { | ||
|
||
it should "derive a codec for a string-based union type" in { | ||
// given | ||
val codec = summon[Codec[String, "Apple" | "Banana", TextPlain]] | ||
|
||
// then | ||
codec.encode("Apple") shouldBe "Apple" | ||
codec.encode("Banana") shouldBe "Banana" | ||
codec.decode("Apple") shouldBe Value("Apple") | ||
codec.decode("Banana") shouldBe Value("Banana") | ||
codec.decode("Orange") should matchPattern { case DecodeResult.InvalidValue(List(ValidationError(_, "Orange", _, _))) => } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters