我想使用 scalameta 注释宏在 Scala 中自动生成 REST API 模型。具体来说,给定:
@Resource case class User(
@get id : Int,
@get @post @patch name : String,
@get @post email : String,
registeredOn : Long
)
我想生成:
object User {
case class Get(id: Int, name: String, email: String)
case class Post(name: String, email: String)
case class Patch(name: Option[String])
}
trait UserRepo {
def getAll: Seq[User.Get]
def get(id: Int): User.Get
def create(request: User.Post): User.Get
def replace(id: Int, request: User.Put): User.Get
def update(id: Int, request: User.Patch): User.Get
def delete(id: Int): User.Get
}
我在这里工作:https ://github.com/pathikrit/metarest
具体来说,我正在这样做:
import scala.collection.immutable.Seq
import scala.collection.mutable
import scala.annotation.{StaticAnnotation, compileTimeOnly}
import scala.meta._
class get extends StaticAnnotation
class put extends StaticAnnotation
class post extends StaticAnnotation
class patch extends StaticAnnotation
@compileTimeOnly("@metarest.Resource not expanded")
class Resource extends StaticAnnotation {
inline def apply(defn: Any): Any = meta {
val (cls: Defn.Class, companion: Defn.Object) = defn match {
case Term.Block(Seq(cls: Defn.Class, companion: Defn.Object)) => (cls, companion)
case cls: Defn.Class => (cls, q"object ${Term.Name(cls.name.value)} {}")
case _ => abort("@metarest.Resource must annotate a class")
}
val paramsWithAnnotation = for {
Term.Param(mods, name, decltype, default) <- cls.ctor.paramss.flatten
seenMods = mutable.Set.empty[String]
modifier <- mods if seenMods.add(modifier.toString)
(tpe, defArg) <- modifier match {
case mod"@get" | mod"@put" | mod"@post" => Some(decltype -> default)
case mod"@patch" =>
val optDeclType = decltype.collect({case tpe: Type => targ"Option[$tpe]"})
val defaultArg = default match {
case Some(term) => q"Some($term)"
case None => q"None"
}
Some(optDeclType -> Some(defaultArg))
case _ => None
}
} yield modifier -> Term.Param(Nil, name, tpe, defArg)
val models = paramsWithAnnotation
.groupBy(_._1.toString)
.map({case (verb, pairs) =>
val className = Type.Name(verb.stripPrefix("@").capitalize)
val classParams = pairs.map(_._2)
q"case class $className[..${cls.tparams}] (..$classParams)"
})
val newCompanion = companion.copy(
templ = companion.templ.copy(stats = Some(
companion.templ.stats.getOrElse(Nil) ++ models
))
)
Term.Block(Seq(cls, newCompanion))
}
}
我对以下代码片段不满意:
modifier match {
case mod"@get" | mod"@put" | mod"@post" => ...
case mod"@patch" => ...
case _ => None
}
上面的代码对我拥有的注释进行“字符串”模式匹配。无论如何要重新使用我必须为这些模式匹配的确切注释:
class get extends StaticAnnotation
class put extends StaticAnnotation
class post extends StaticAnnotation
class patch extends StaticAnnotation