3

scalameta注釈マクロを使用してScalaでREST APIモデルを自動生成したい。具体的には、与えられた:Scalameta:特定の注釈を特定する

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)) 
    } 
} 

私は不幸だ

@Resource case class User(
@get    id   : Int, 
@get @post @patch name   : String, 
@get @post   email   : String, 
        registeredOn : Long 
) 

は、私が作成したいです次のコードスニップ:

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 

答えて

3

それは(コンパイル時に)実行時の反射のビットを使用してget()抽出と[email protected] stringly型付けされた注釈を交換することが可能です。 加えて、我々はまた、ユーザーが完全にすべての次のコード例はimport scala.meta._を想定@metarest.get@_root_.metarest.get

で注釈を修飾できるようにしたいとしましょう。 @get@metarest.get@_root_.metarest.getのツリー構造

@ mod"@get".structure 
res4: String = """ Mod.Annot(Ctor.Ref.Name("get")) 
""" 
@ mod"@metarest.get".structure 
res5: String = """ 
Mod.Annot(Ctor.Ref.Select(Term.Name("metarest"), Ctor.Ref.Name("get"))) 
""" 
@ mod"@_root_.metarest.get".structure 
res6: String = """ 
Mod.Annot(Ctor.Ref.Select(Term.Select(Term.Name("_root_"), Term.Name("metarest")), Ctor.Ref.Name("get"))) 
""" 

あるセレクタはCtor.Ref.SelectまたはTerm.Selectのいずれかであり、名前はTerm.NameまたはCtor.Ref.Nameのどちらかです。

てみましょう最初のカスタムセレクタ抽出

object Select { 
    def unapply(tree: Tree): Option[(Term, Name)] = tree match { 
    case Term.Select(a, b) => Some(a -> b) 
    case Ctor.Ref.Select(a, b) => Some(a -> b) 
    case _ => None 
    } 
} 

を作成するとこの設定で

object ParamAnnotation { 
    /* isSuffix(c, a.b.c) // true 
    * isSuffix(b.c, a.b.c) // true 
    * isSuffix(a.b.c, a.b.c) // true 
    * isSuffix(_root_.a.b.c, a.b.c) // true 
    * isSuffix(d.c, a.b.c) // false 
    */ 
    def isSuffix(maybeSuffix: Term, fullName: Term): Boolean = 
    (maybeSuffix, fullName) match { 
     case (a: Name, b: Name) => a.value == b.value 
     case (Select(q"_root_", a), b: Name) => a.value == b.value 
     case (a: Name, Select(_, b)) => a.value == b.value 
     case (Select(aRest, a), Select(bRest, b)) => 
     a.value == b.value && isSuffix(aRest, bRest) 
     case _ => false 
    } 

    // Returns true if `mod` matches the tree structure of `@T` 
    def modMatchesType[T: ClassTag](mod: Mod): Boolean = mod match { 
    case Mod.Annot(term: Term.Ref) => 
     isSuffix(term, termRefForType[T]) 
    case _ => false 
    } 

    // Parses `T.getClass.getName` into a Term.Ref 
    // Uses runtime reflection, but this happens only at compile time. 
    def termRefForType[T](implicit ev: ClassTag[T]): Term.Ref = 
    ev.runtimeClass.getName.parse[Term].get.asInstanceOf[Term.Ref] 
} 

いくつかのヘルパーユーティリティを作成し、我々は unapplyブールでget定義にコンパニオンオブジェクトを追加することができます抽出器

class get extends StaticAnnotation 
object get { 
    def unapply(mod: Mod): Boolean = ParamAnnotation.modMatchesType[get](mod) 
} 

postputために同じことを、私たちは今、ユーザーがインポート

import metarest.{get => GET} 

上の例getのために名前を変更した場合、このアプローチはまだ動作しないこと

// before 
case mod"@get" | mod"@put" | mod"@post" => Some(decltype -> default) 
// after 
case get() | put() | post() => Some(decltype -> default) 

ノートを書くことができます私があれば中止をお勧めしますアノテーションが期待どおりに一致しません。

// before 
case _ => None 
// after 
case unexpected => abort("Unexpected modifier $unexpected. Expected one of: put, get post") 

PS。object get { def unapply(mod: Mod): Boolean = ... }部は、例えば@ParamAnnotion class get extends StaticAnnotation

+0

Iがのcon​​tribモジュールに 'SELECT'抽出を追加するscalameta/scalametaでPRを開放するために、いくつかの@ParamAnnotationマクロ注釈によって生成することができる定型です。 https://github.com/scalameta/scalameta/pull/800 –

+1

私はデフォルトで '@ ParamAnnotation'を提供するスケーラメタ/パラダイスでチケットをオープンしました。https://github.com/scalameta/paradise/issues/193 –

関連する問題