diff --git a/sloth/src/main/scala-2/internal/Macros.scala b/sloth/src/main/scala-2/internal/Macros.scala index e6514be6..56b5d4f1 100644 --- a/sloth/src/main/scala-2/internal/Macros.scala +++ b/sloth/src/main/scala-2/internal/Macros.scala @@ -38,7 +38,7 @@ class Translator[C <: Context](val c: C) { //TODO rename overloaded methods to fun1, fun2, fun3 or append TypeSignature instead of number? private def validateAllMethods(methods: List[(MethodSymbol, Type)]): List[Either[String, (MethodSymbol, Type)]] = - methods.groupBy(m => methodPathPart(m._1)).map { + methods.groupBy(m => methodPathInfo(m._1)).map { case (_, x :: Nil) => Right(x) case (k, _) => Left(s"""method $k is overloaded (rename the method or add a @PathName("other-name"))""") }.toList @@ -47,6 +47,11 @@ class Translator[C <: Context](val c: C) { case Apply(Select(New(annotation), _), Literal(Constant(name)) :: Nil) if annotation.tpe =:= typeOf[sloth.PathName] => name.toString } + private def findMeta(annotations: Seq[Annotation]) = annotations.map(_.tree).collect { + case Apply(Select(New(annotation), _), _) if annotation.tpe <:< typeOf[sloth.Meta] => annotation.tpe.typeSymbol.name.toString + case Apply(Select(New(annotation), _), Literal(Constant(name)) :: Nil) if annotation.tpe =:= typeOf[sloth.MetaName] => name.toString + }.toVector + private def eitherSeq[A, B](list: List[Either[A, B]]): Either[List[A], List[B]] = list.partition(_.isLeft) match { case (Nil, rights) => Right(for (Right(i) <- rights) yield i) case (lefts, _) => Left(for (Left(s) <- lefts) yield s) @@ -75,11 +80,17 @@ class Translator[C <: Context](val c: C) { } //TODO what about fqn for trait to not have overlaps? - def traitPathPart(tpe: Type): String = - findPathName(tpe.typeSymbol.annotations).getOrElse(tpe.typeSymbol.name.toString) - - def methodPathPart(m: MethodSymbol): String = - findPathName(m.annotations).getOrElse(m.name.toString) + def traitPathInfo(tpe: Type): (String, Vector[String]) = + ( + findPathName(tpe.typeSymbol.annotations).getOrElse(tpe.typeSymbol.name.toString), + findMeta(tpe.typeSymbol.annotations) + ) + + def methodPathInfo(m: MethodSymbol): (String, Vector[String]) = + ( + findPathName(m.annotations).getOrElse(m.name.toString), + findMeta(m.annotations) + ) def paramAsValDef(p: Symbol): ValDef = q"val ${p.name.toTermName}: ${p.typeSignature}" def paramsAsValDefs(m: Type): List[List[ValDef]] = m.paramLists.map(_.map(paramAsValDef)) @@ -134,10 +145,10 @@ object TraitMacro { val validMethods = t.supportedMethodsInType(traitTag.tpe, resultTag.tpe) - val traitPathPart = t.traitPathPart(traitTag.tpe) + val (traitPathPart, traitMeta) = t.traitPathInfo(traitTag.tpe) val methodImplList = validMethods.collect { case (symbol, method) => - val methodPathPart = t.methodPathPart(symbol) - val path = RequestPath(traitPathPart, methodPathPart) + val (methodPathPart, methodMeta) = t.methodPathInfo(symbol) + val path = RequestPath(traitPathPart, methodPathPart, traitMeta ++ methodMeta) val parameters = t.paramsAsValDefs(method) val paramsType = t.paramsType(method) val paramListValue = t.wrapAsParamsType(method) @@ -189,10 +200,10 @@ object RouterMacro { val validMethods = t.supportedMethodsInType(traitTag.tpe, resultTag.tpe) - val traitPathPart = t.traitPathPart(traitTag.tpe) + val (traitPathPart, traitMeta) = t.traitPathInfo(traitTag.tpe) val methodTuples = validMethods.map { case (symbol, method) => - val methodPathPart = t.methodPathPart(symbol) - val path = RequestPath(traitPathPart, methodPathPart) + val (methodPathPart, methodMeta) = t.methodPathInfo(symbol) + val path = RequestPath(traitPathPart, methodPathPart, traitMeta ++ methodMeta) val paramsType = t.paramsType(method) val argParams = t.objectToParams(method, TermName("args")) val innerReturnType = t.getInnerTypeOutOfReturnType(resultTag.tpe, method.finalResultType) @@ -242,10 +253,10 @@ object ChecksumMacro { case class ParamSignature(name: String, tpe: Type) { def checksum: Int = (name, typeChecksum(tpe)).hashCode } - case class MethodSignature(name: String, params: List[ParamSignature], result: Type) { - def checksum: Int = (name, params.map(_.checksum), typeChecksum(result)).hashCode + case class MethodSignature(name: String, meta: Seq[String], params: List[ParamSignature], result: Type) { + def checksum: Int = (name, meta, params.map(_.checksum), typeChecksum(result)).hashCode } - case class ApiSignature(name: String, methods: Set[MethodSignature]) { + case class ApiSignature(name: String, meta: Seq[String], methods: Set[MethodSignature]) { def checksum: Int = (name, methods.map(_.checksum)).hashCode } @@ -279,21 +290,21 @@ object ChecksumMacro { tpe.typeSymbol.fullName, caseAccessors.map(a => (a.name.toString, typeChecksum(a.typeSignatureIn(tpe).finalResultType))), directSubClasses.map(typeChecksum).toSet - ).hashCode + ).hashCode } val definedMethods = t.definedMethodsInType(traitTag.tpe) - val dataMethods:Set[MethodSignature] = definedMethods.map { case (symbol, method) => - val name = t.methodPathPart(symbol) + val dataMethods: Set[MethodSignature] = definedMethods.map { case (symbol, method) => + val (name, meta) = t.methodPathInfo(symbol) val resultType = method.finalResultType val params = paramsOfType(method) - MethodSignature(name, params, resultType) + MethodSignature(name, meta, params, resultType) }.toSet - val name = t.traitPathPart(traitTag.tpe) - val apiSignature = ApiSignature(name, dataMethods) + val (name, meta) = t.traitPathInfo(traitTag.tpe) + val apiSignature = ApiSignature(name, meta, dataMethods) val checksum = apiSignature.checksum diff --git a/sloth/src/main/scala-3/internal/Macros.scala b/sloth/src/main/scala-3/internal/Macros.scala index 1cee333c..39e59bbd 100644 --- a/sloth/src/main/scala-3/internal/Macros.scala +++ b/sloth/src/main/scala-3/internal/Macros.scala @@ -7,10 +7,17 @@ import scala.annotation.meta.param import scala.NonEmptyTuple import scala.quoted.runtime.StopMacroExpansion +private implicit val toExprVectorString: ToExpr[Vector[String]] = new ToExpr[Vector[String]] { + def apply(vector: Vector[String])(using Quotes): Expr[Vector[String]] = { + import quotes.reflect._ + '{Vector(${Varargs(vector.map(Expr(_)))}: _*)} + } +} + private implicit val toExprRequestPath: ToExpr[RequestPath] = new ToExpr[RequestPath] { def apply(path: RequestPath)(using Quotes): Expr[RequestPath] = { import quotes.reflect._ - '{ RequestPath(${Expr(path.apiName)}, ${Expr(path.methodName)}) } + '{ RequestPath(${Expr(path.apiName)}, ${Expr(path.methodName)}, ${Expr(path.meta)}) } } } @@ -23,6 +30,17 @@ private def getPathName(using Quotes)(symbol: quotes.reflect.Symbol): String = { }.getOrElse(symbol.name) } +private def getMeta(using Quotes)(symbol: quotes.reflect.Symbol): Vector[String] = { + import quotes.reflect.* + + symbol.annotations.collect { + case Apply(Select(New(annotation), _), _) if annotation.tpe <:< TypeRepr.of[Meta] => + annotation.tpe.typeSymbol.name + case Apply(Select(New(annotation), _), Literal(constant) :: Nil) if annotation.tpe =:= TypeRepr.of[MetaName] => + constant.value.asInstanceOf[String] + }.toVector +} + private def getTypeConstructor(using Quotes)(tpe: quotes.reflect.TypeRepr): quotes.reflect.TypeRepr = { import quotes.reflect.* @@ -87,8 +105,8 @@ def createTypeTreeTuple(using Quotes)(tupleTypesList: List[quotes.reflect.TypeRe private def checkMethodErrors[Trait: Type, Result[_]: Type](using q: Quotes)(methods: Seq[quotes.reflect.Symbol]): Unit = { import quotes.reflect.* - val duplicateErrors = methods.groupBy(getPathName).collect { case (name, symbols) if symbols.size > 1 => - val message = s"Method $name is overloaded, please rename one of the methods or use the PathName annotation to disambiguate" + val duplicateErrors = methods.groupBy(m => (getPathName(m), getMeta(m))).collect { case ((name, meta), symbols) if symbols.size > 1 => + val message = s"Method $name (meta=$meta) is overloaded, please rename one of the methods or use the PathName or Meta annotation to disambiguate" (message, symbols.flatMap(_.pos).lastOption) } @@ -156,6 +174,7 @@ object TraitMacro { checkMethodErrors[Trait, Result](methods) val traitPathPart = getPathName(TypeRepr.of[Trait].typeSymbol) + val traitMeta = getMeta(TypeRepr.of[Trait].typeSymbol) def decls(cls: Symbol): List[Symbol] = methods.map { method => val methodType = TypeRepr.of[Trait].memberType(method) @@ -168,7 +187,8 @@ object TraitMacro { val result = ValDef.let(Symbol.spliceOwner, implInstance.asTerm) { implRef => val body = (cls.declaredMethods.zip(methods)).map { case (method, origMethod) => val methodPathPart = getPathName(origMethod) - val path = RequestPath(traitPathPart, methodPathPart) + val methodMeta = getMeta(origMethod) + val path = RequestPath(traitPathPart, methodPathPart, traitMeta ++ methodMeta) val pathExpr = Expr(path) DefDef(method, { argss => @@ -245,6 +265,7 @@ object RouterMacro { checkMethodErrors[Trait, Result](methods) val traitPathPart = getPathName(TypeRepr.of[Trait].typeSymbol) + val traitMeta = getMeta(TypeRepr.of[Trait].typeSymbol) type FunctionInput = PickleType type FunctionOutput = Either[ServerFailure, Result[PickleType]] @@ -252,7 +273,8 @@ object RouterMacro { val result = ValDef.let(Symbol.spliceOwner, implInstance.asTerm) { implRef => val methodDefinitions = methods.map { method => val methodPathPart = getPathName(method) - val path = RequestPath(traitPathPart, methodPathPart) + val methodMeta = getMeta(method) + val path = RequestPath(traitPathPart, methodPathPart, traitMeta ++ methodMeta) val pathExpr = Expr(path) val returnType = getInnerTypeOutOfReturnType[Trait, Result](method) diff --git a/sloth/src/main/scala/Annotations.scala b/sloth/src/main/scala/Annotations.scala index 59800051..0df11679 100644 --- a/sloth/src/main/scala/Annotations.scala +++ b/sloth/src/main/scala/Annotations.scala @@ -3,3 +3,18 @@ package sloth import scala.annotation.StaticAnnotation final class PathName(val name: String) extends StaticAnnotation + +final class MetaName extends StaticAnnotation + +trait Meta extends StaticAnnotation +object Meta { + object http { + final class Get extends Meta + final class Head extends Meta + final class Post extends Meta + final class Put extends Meta + final class Delete extends Meta + final class Options extends Meta + final class Patch extends Meta + } +} diff --git a/sloth/src/main/scala/Request.scala b/sloth/src/main/scala/Request.scala index eaf9de88..61b4c774 100644 --- a/sloth/src/main/scala/Request.scala +++ b/sloth/src/main/scala/Request.scala @@ -1,5 +1,5 @@ package sloth -case class RequestPath(apiName: String, methodName: String, meta: Set[String] = Set.empty) +case class RequestPath(apiName: String, methodName: String, meta: Vector[String] = Vector.empty) case class Request[T](path: RequestPath, payload: T)