sttp.tapir.internal.CodecValueClassMacro.scala Maven / Gradle / Ivy
package sttp.tapir.internal
import sttp.tapir.Codec
import sttp.tapir.CodecFormat.TextPlain
import scala.quoted.*
private[tapir] object CodecValueClassMacro {
inline def derivedValueClass[T <: AnyVal]: Codec[String, T, TextPlain] = ${ derivedValueClassImpl[T] }
private def derivedValueClassImpl[T: Type](using q: Quotes): Expr[Codec[String, T, TextPlain]] = {
import q.reflect.*
val tpe = TypeRepr.of[T]
val isValueClass = tpe.baseClasses.contains(Symbol.classSymbol("scala.AnyVal"))
if (!isValueClass) {
report.errorAndAbort(s"Can only derive codec for value class.")
}
val field = tpe.typeSymbol.declaredFields.head
val fieldTpe = tpe.memberType(field)
def decodeBody(term: Term): Expr[T] = Apply(Select.unique(New(Inferred(tpe)), ""), List(term)).asExprOf[T]
def decode[F: Type]: Expr[F => T] = '{ (f: F) => ${ decodeBody('{ f }.asTerm) } }
def encodeBody[F: Type](term: Term): Expr[F] = Select(term, field).asExprOf[F]
def encode[F: Type]: Expr[T => F] = '{ (t: T) => ${ encodeBody[F]('{ t }.asTerm) } }
fieldTpe.asType match
case '[f] =>
val baseCodec = Expr.summon[Codec[String, f, TextPlain]].getOrElse {
report.errorAndAbort(
s"Cannot summon codec for value class ${tpe.show} wrapping ${fieldTpe.show}."
)
}
val dec = decode[f]
val enc = encode[f]
'{ $baseCodec.asInstanceOf[Codec[String, f, TextPlain]].map($dec)($enc) }
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy