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

com.tkroman.micromarshal.deriveAkkaMarshalling.scala Maven / Gradle / Ivy

package com.tkroman.micromarshal

import scala.collection.immutable.Seq
import scala.meta._

class deriveAkkaMarshalling(pickler: String = "upickle.default") extends scala.annotation.StaticAnnotation {

  inline def apply(defn: Any): Any = meta {
    val unmarshallerTypeName  = "_root_.akka.http.scaladsl.unmarshalling.FromEntityUnmarshaller"
    val marshallerTypeName    = "_root_.akka.http.scaladsl.marshalling.ToEntityMarshaller"
    val marshallerMediaType   = {
      Term.Select(
        Term.Name("_root_.akka.http.scaladsl.model.MediaTypes"), Term.Name("`application/json`")
      )
    }
    val unmarshallerMethod = q"_root_.akka.http.scaladsl.unmarshalling.Unmarshaller.byteStringUnmarshaller"
    val marshallerMethod = q"_root_.akka.http.scaladsl.marshalling.Marshaller.stringMarshaller"

    val picklerName = this match {
      case q"new $_(${Lit(p: String)})" =>
        s"_root_.$p"
      case q"new $_()" =>
        // TODO read this from default arg?
        "_root_.upickle.default"
    }

    val usePickler = Term.Name(picklerName)
    val rwType = Type.Name(s"$usePickler.ReadWriter")
    val rType = Type.Name(s"$usePickler.Reader")
    val wType = Type.Name(s"$usePickler.Writer")

    def mkImplicitDef(name: Type.Name, typarams: Seq[Type.Param], companion: Option[Defn.Object]) = {
      val apType  = Type.Apply(name, typarams.map(n => Type.Name(n.name.value)))
      val umType  = Type.Apply(Type.Name(unmarshallerTypeName), Seq(apType))
      val mType   = Type.Apply(Type.Name(marshallerTypeName), Seq(apType))
      val typaramsWithRWCtxBounds = typarams.map(tp => tp.copy(cbounds = tp.cbounds :+ rwType))
      val typaramsWithRCtxBounds = typarams.map(tp => tp.copy(cbounds = tp.cbounds :+ rType))
      val typaramsWithWCtxBounds = typarams.map(tp => tp.copy(cbounds = tp.cbounds :+ wType))
      val rwTypeAp = Type.Apply(rwType, Seq(apType))
      val rwTypeStx = s"ReadWriter[${apType.syntax}]"

      val rw = MkImplicitRwDefn(companion, rwTypeStx) {
        q"""
        implicit def upickleRw[..$typaramsWithRWCtxBounds]: $rwTypeAp = {
          $usePickler.macroRW[$apType]
        }
        """
      }

      rw ++ Seq(
        // implicit marshaller def
        q"""
        implicit def akkaMarshaller[..$typaramsWithWCtxBounds]: $mType = {
          $marshallerMethod($marshallerMediaType)
             .compose[$apType](t => $usePickler.write(t))
        }
        """,
        // implicit unmarshaller def
        q"""
        implicit def akkaUnmarshaller[..$typaramsWithRCtxBounds]: $umType = {
          $unmarshallerMethod.mapWithCharset { (str, charset) =>
              $usePickler.read[$apType](str.decodeString(charset.nioCharset()))
            }
        }
        """
      )
    }

    def mkImplicitVal(typeName: Type.Name, companion: Option[Defn.Object]) = {
      val umApType  = Type.Apply(Type.Name(unmarshallerTypeName), Seq(typeName))
      val mApType   = Type.Apply(Type.Name(marshallerTypeName), Seq(typeName))
      val rwTypeAp  = Type.Apply(rwType, Seq(typeName))
      val rwTypeStx = s"ReadWriter[${typeName.syntax}]"

      val rw = MkImplicitRwDefn(companion, rwTypeStx) {
        q"""
        implicit val upickleRw: $rwTypeAp = {
          $usePickler.macroRW[$typeName]
        }
        """
      }

      rw ++ Seq(
        // implicit marshaller val
        q"""
        implicit val akkaMarshaller: $mApType = {
          $marshallerMethod($marshallerMediaType).compose[$typeName](t => $usePickler.write(t))
        }
        """,
        // implicit unmarshaller val
        q"""
        implicit val akkaUnmarshaller: $umApType = {
          $unmarshallerMethod.mapWithCharset { (str, charset) =>
            $usePickler.read[$typeName](str.decodeString(charset.nioCharset()))
          }
        }
        """
      )
    }

    def mkImplicit(name: Type.Name, typarams: Seq[Type.Param], companion: Option[Defn.Object]): Seq[Stat] = {
      if (typarams.isEmpty) {
        mkImplicitVal(name, companion)
      } else {
        mkImplicitDef(name, typarams, companion)
      }
    }

    val withGenerated = defn match {
      // companion object exists
      case ClassOrSealedTraitWithObject(cls, name, typarams, companion) =>
        val implUm = mkImplicit(name, typarams, Some(companion))
        val templateStats = implUm ++ companion.templ.stats.getOrElse(Nil)
        val newCompanion = companion.copy(templ = companion.templ.copy(stats = Some(templateStats)))
        Term.Block(Seq(cls, newCompanion))

      case ClassOrSealedTraitWithoutObject(cls, name, typarams) =>
        val implUm = mkImplicit(name, typarams, None)
        val companion   = q"object ${Term.Name(name.value)} { ..$implUm }"
        Term.Block(Seq(cls, companion))

      case _ =>
        println(defn.structure)
        abort("@unmarshal must annotate a class or a sealed trait.")
    }

    withGenerated
  }
}

private[micromarshal] object ClassOrSealedTraitWithObject {
  def unapply(arg: Term.Block): Option[(Defn, Type.Name, Seq[Type.Param], Defn.Object)] = {
    arg match {
      case Term.Block(Seq(t: Defn.Trait, comp: Defn.Object)) if t.mods.exists(_.syntax == "sealed") =>
        Some((t, t.name, t.tparams, comp))

      case Term.Block(Seq(c: Defn.Class, comp: Defn.Object)) =>
        Some((c, c.name, c.tparams, comp))

      case _ => None
    }
  }
}

private[micromarshal] object ClassOrSealedTraitWithoutObject {
  def unapply(arg: Defn): Option[(Defn, Type.Name, Seq[Type.Param])] = {
    arg match {
      case t: Defn.Trait if t.mods.exists(_.syntax == "sealed") =>
        Some((t, t.name, t.tparams))
      case c: Defn.Class =>
        Some((c, c.name, c.tparams))
      case _ => None
    }
  }
}

private[micromarshal] object MkImplicitRwDefn {
  // TODO semantic API would be really nice here
  def apply(companion: Option[Defn.Object], expectedRwTypeStx: String)(mkImplicit: => Stat): Seq[Stat] = {
    def isImplicitRwDefinition(mods: Seq[Mod], tpe: Type) = {
      mods.exists(_.syntax == "implicit") && expectedRwTypeStx == tpe.syntax
    }

    companion match {
      case None =>
        Seq(mkImplicit)
      case Some(Defn.Object(_, _, Template(_, _, _, Some(stats)))) =>
        val rwIsDefined = stats.exists {
          case Defn.Val(mods, _, Some(tpe), _) => isImplicitRwDefinition(mods, tpe)
          case Defn.Var(mods, _, Some(tpe), _) => isImplicitRwDefinition(mods, tpe)
          case Defn.Def(mods, _, _, _, Some(tpe), _) => isImplicitRwDefinition(mods, tpe)
          case _ => false
        }
        if (rwIsDefined) {
          Seq.empty
        } else {
          Seq(mkImplicit)
        }
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy