我想使用scalameta註釋宏在Scala中自動生成REST API模型。具體地,給出:Scalameta:確定特定的註釋
@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
我在scalameta/scalameta中打開了一個PR來將「選擇」提取器添加到contrib模塊。 https://github.com/scalameta/scalameta/pull/800 –
我還在scalameta/paradise中打開了一張票,默認提供'@ParamAnnotation' https://github.com/scalameta/paradise/issues/193 –