All Downloads are FREE. Search and download functionalities are using the official Maven repository.

sttp.tapir.internal.MapToMacro.scala Maven / Gradle / Ivy

The newest version!
package sttp.tapir.internal

import scala.reflect.macros.blackbox

private[tapir] object MapToMacro {
  def generateMapTo[THIS_TYPE[_], T: c.WeakTypeTag, CASE_CLASS: c.WeakTypeTag](c: blackbox.Context): c.Expr[THIS_TYPE[CASE_CLASS]] =
    c.Expr[THIS_TYPE[CASE_CLASS]](generateDelegateMap[T, CASE_CLASS](c)("map"))

  def generateMapSecurityInTo[RESULT, T: c.WeakTypeTag, CASE_CLASS: c.WeakTypeTag](c: blackbox.Context): c.Expr[RESULT] =
    c.Expr[RESULT](generateDelegateMap[T, CASE_CLASS](c)("mapSecurityIn"))

  def generateMapInTo[RESULT, T: c.WeakTypeTag, CASE_CLASS: c.WeakTypeTag](c: blackbox.Context): c.Expr[RESULT] =
    c.Expr[RESULT](generateDelegateMap[T, CASE_CLASS](c)("mapIn"))

  def generateMapErrorOutTo[RESULT, T: c.WeakTypeTag, CASE_CLASS: c.WeakTypeTag](c: blackbox.Context): c.Expr[RESULT] =
    c.Expr[RESULT](generateDelegateMap[T, CASE_CLASS](c)("mapErrorOut"))

  def generateMapOutTo[RESULT, T: c.WeakTypeTag, CASE_CLASS: c.WeakTypeTag](c: blackbox.Context): c.Expr[RESULT] =
    c.Expr[RESULT](generateDelegateMap[T, CASE_CLASS](c)("mapOut"))

  private def generateDelegateMap[T: c.WeakTypeTag, CASE_CLASS: c.WeakTypeTag](
      c: blackbox.Context
  )(delegateTo: String): c.Tree = {
    import c.universe._
    val to = MapToMacro.tupleToCaseClass[T, CASE_CLASS](c)
    val from = MapToMacro.tupleFromCaseClass[T, CASE_CLASS](c)

    q"${c.prefix}.${TermName(delegateTo)}($to)($from)"
  }

  private def tupleToCaseClass[TUPLE: c.WeakTypeTag, CASE_CLASS: c.WeakTypeTag](c: blackbox.Context): c.Tree = {
    import c.universe._

    val caseClassUtil = new CaseClassUtil[c.type, CASE_CLASS](c, "mapTo mapping")
    val tupleType = weakTypeOf[TUPLE]
    val tupleTypeArgs = tupleType.dealias.typeArgs
    if (caseClassUtil.fields.size == 0) {
      q"(t: ${tupleType.dealias}) => ${caseClassUtil.className}()"
    } else if (caseClassUtil.fields.size == 1) {
      verifySingleFieldCaseClass(c)(caseClassUtil, tupleType)
      // Compilation failure if `CaseClass` gets passed as `[Wrapper.CaseClass]` caused by invalid `className`
      // retrieval below, workaround available (see: https://github.com/softwaremill/tapir/issues/2540)
      q"(t: ${tupleType.dealias}) => ${caseClassUtil.className}(t)"
    } else {
      verifyCaseClassMatchesTuple(c)(caseClassUtil, tupleType, tupleTypeArgs)
      val ctorArgs = (1 to tupleTypeArgs.length).map(idx => q"t.${TermName(s"_$idx")}")
      // Compilation failure if `CaseClass` gets passed as `[Wrapper.CaseClass]` caused by invalid `className`
      // retrieval below, workaround available (see: https://github.com/softwaremill/tapir/issues/2540)
      q"(t: ${tupleType.dealias}) => ${caseClassUtil.className}(..$ctorArgs)"
    }
  }

  private def tupleFromCaseClass[TUPLE: c.WeakTypeTag, CASE_CLASS: c.WeakTypeTag](c: blackbox.Context): c.Tree = {
    import c.universe._

    val caseClassUtil = new CaseClassUtil[c.type, CASE_CLASS](c, "mapTo mapping")
    val tupleType = weakTypeOf[TUPLE]
    if (caseClassUtil.fields.size == 1) {
      verifySingleFieldCaseClass(c)(caseClassUtil, tupleType)
    } else {
      verifyCaseClassMatchesTuple(c)(caseClassUtil, tupleType, tupleType.dealias.typeArgs)
    }

    val tupleArgs = caseClassUtil.fields.map(field => q"t.${TermName(s"${field.name}")}")
    val classType = caseClassUtil.classSymbol.asType
    q"(t: $classType) => (..$tupleArgs)"
  }

  private def verifySingleFieldCaseClass[CASE_CLASS](
      c: blackbox.Context
  )(caseClassUtil: CaseClassUtil[c.type, CASE_CLASS], tupleType: c.Type): Unit = {
    val field = caseClassUtil.fields.head
    if (!(field.info.resultType =:= tupleType)) {
      c.abort(
        c.enclosingPosition,
        s"The type doesn't match the type of the case class field: $tupleType, $field"
      )
    }
  }

  private def verifyCaseClassMatchesTuple[CASE_CLASS](c: blackbox.Context)(
      caseClassUtil: CaseClassUtil[c.type, CASE_CLASS],
      tupleType: c.Type,
      tupleTypeArgs: List[c.Type]
  ): Unit = {
    val tupleSymbol = tupleType.typeSymbol
    if (!tupleSymbol.fullName.startsWith("scala.Tuple") && caseClassUtil.fields.nonEmpty) {
      c.abort(c.enclosingPosition, s"Expected source type to be a tuple, but got: ${tupleType.dealias}")
    }

    if (caseClassUtil.fields.size != tupleTypeArgs.size) {
      if (caseClassUtil.fields.size > 22) {
        c.abort(
          c.enclosingPosition,
          s"Cannot map to ${caseClassUtil.t}: arities of up to 22 are supported. If you need more inputs/outputs, map them to classes with less fields, and then combine these classes."
        )
      } else {
        c.abort(
          c.enclosingPosition,
          s"The arity of the source type (${tupleTypeArgs.size}) doesn't match the arity of the target type (${caseClassUtil.fields.size}): ${tupleType.dealias}, ${caseClassUtil.t}"
        )
      }
    }

    caseClassUtil.fields.zip(tupleTypeArgs).foreach { case (caseClassField, tupleArg) =>
      if (!(caseClassField.info.resultType =:= tupleArg)) {
        c.abort(
          c.enclosingPosition,
          s"The type of the tuple field doesn't match the type of the case class field ($caseClassField): $tupleArg, ${caseClassField.info.resultType}"
        )
      }
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy