gpt4 book ai didi

scala - 为 Swagger 实现 Enumeratum 支持

转载 作者:行者123 更新时间:2023-12-04 17:47:17 34 4
gpt4 key购买 nike

我正在使用 Swagger 来注释我的 API,在我们的 API 中,我们非常依赖 enumeratum。如果我什么都不做,swagger 将不会识别它,只会调用它 object

例如,我有这段有效的代码:

sealed trait Mode extends EnumEntry

object Mode extends Enum[Mode] {
override def values = findValues

case object Initial extends Mode
case object Delta extends Mode
}

@ApiModel
case class Foobar(
@ApiModelProperty(dataType = "string", allowedValues = "Initial,Delta")
mode: Mode
)

但是,我想避免重复这些值,因为我的一些类型比这个例子多得多;我不想手动保持同步。

问题是 @ApiModel 需要一个常量引用,所以我不能做类似 reference = Mode.values.mkString(",") 的事情.

我确实尝试了一个带有宏天堂的宏,通常这样我就可以写:

@EnumeratumApiModel(Mode)
sealed trait Mode extends EnumEntry

object Mode extends Enum[Mode] {
override def values = findValues

case object Initial extends Mode
case object Delta extends Mode
}

...但它不起作用,因为宏传递无法访问 Mode 对象。

我必须采取什么解决方案来避免重复注释中的值?

最佳答案

这包括代码,因此对于评论来说太大了。

I tried, that wouldn't work because the @ApiModel annotation wants a String constant as a value (and not a reference to a constant)

这段代码对我来说编译得很好(注意你应该如何避免显式指定类型):

import io.swagger.annotations._
import enumeratum._

@ApiModel(reference = Mode.reference)
sealed trait Mode extends EnumEntry

object Mode extends Enum[Mode] {
final val reference = "enum(Initial,Delta)" // this works!
//final val reference: String = "enum(Initial,Delta)" // surprisingly this doesn't!

override def values = findValues

case object Initial extends Mode
case object Delta extends Mode
}

所以似乎有另一个宏可以生成这样的 reference 字符串,我假设你已经有了一个(或者你可以根据 EnumMacros.findValuesImpl 的代码创建一个)。

更新

这里是一些实际可行的 POC 代码。首先你开始关注 macro annotation :

import scala.language.experimental.macros
import scala.annotation.{StaticAnnotation, compileTimeOnly}
import scala.reflect.macros.whitebox.Context
import scala.collection.immutable._


@compileTimeOnly("enable macro to expand macro annotations")
class SwaggerEnumContainer extends StaticAnnotation {
def macroTransform(annottees: Any*) = macro SwaggerEnumMacros.genListString
}

@compileTimeOnly("enable macro to expand macro annotations")
class SwaggerEnumValue(val readOnly: Boolean = false, val required: Boolean = false) extends StaticAnnotation {
def macroTransform(annottees: Any*) = macro SwaggerEnumMacros.genParamAnnotation

}


class SwaggerEnumMacros(val c: Context) {

import c.universe._

def genListString(annottees: c.Expr[Any]*): c.Expr[Any] = {

val result = annottees.map(_.tree).toList match {
case (xxx@q"object $name extends ..$parents { ..$body }") :: Nil =>
val enclosingObject = xxx.asInstanceOf[ModuleDef]
val q"${tq"$pname[..$ptargs]"}(...$pargss)" = parents.head
val enumTraitIdent = ptargs.head.asInstanceOf[Ident]
val subclassSymbols: List[TermName] = enclosingObject.impl.body.foldLeft(List.empty[TermName])((list, innerTree) => {
innerTree match {
case innerObj: ModuleDefApi =>
val innerParentIdent = innerObj.impl.parents.head.asInstanceOf[Ident]
if (enumTraitIdent.name.equals(innerParentIdent.name))
innerObj.name :: list
else
list

case _ => list
}
})

val reference = subclassSymbols.map(n => n.encodedName.toString).mkString(",")
q"""
object $name extends ..$parents {
final val allowableValues = $reference
..$body
}
"""

}
c.Expr[Any](result)
}

def genParamAnnotation(annottees: c.Expr[Any]*): c.Expr[Any] = {
val annotationParams: AnnotationParams = extractAnnotationParameters(c.prefix.tree)
val baseSwaggerAnnot =
q""" new ApiModelProperty(
dataType = "string",
allowableValues = Mode.allowableValues
) """.asInstanceOf[Apply] // why I have to force cast?

val swaggerAnnot: c.universe.Apply = annotationParams.addArgsTo(baseSwaggerAnnot)

annottees.map(_.tree).toList match {
// field definition
case List(param: ValDef) => c.Expr[Any](decorateValDef(param, swaggerAnnot))
// field in a case class = constructor param
case (param: ValDef) :: (rest@(_ :: _)) => decorateConstructorVal(param, rest, swaggerAnnot)
case _ => c.abort(c.enclosingPosition, "SwaggerEnumValue is expected to be used for value definitions")
}
}

def decorateValDef(valDef: ValDef, swaggerAnnot: Apply): ValDef = {
val q"$mods val $name: $tpt = $rhs" = valDef
val newMods: Modifiers = mods.mapAnnotations(al => swaggerAnnot :: al)
q"$newMods val $name: $tpt = $rhs"
}


def decorateConstructorVal(annottee: c.universe.ValDef, expandees: List[Tree], swaggerAnnot: Apply): c.Expr[Any] = {
val q"$_ val $tgtName: $_ = $_" = annottee
val outputs = expandees.map {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" => {
// paramss is a 2d array so map inside map
val newParams: List[List[ValDef]] = paramss.map(_.map({
case valDef: ValDef if valDef.name == tgtName => decorateValDef(valDef, swaggerAnnot)
case otherParam => otherParam
}))

q"$mods class $tpname[..$tparams] $ctorMods(...$newParams) extends { ..$earlydefns } with ..$parents { $self => ..$stats }"
}

case otherTree => otherTree
}
c.Expr[Any](Block(outputs, Literal(Constant(()))))
}


case class AnnotationParams(readOnly: Boolean, required: Boolean) {
def customCopy(name: String, value: Any) = {
name match {
case "readOnly" => copy(readOnly = value.asInstanceOf[Boolean])
case "required" => copy(required = value.asInstanceOf[Boolean])
case _ => c.abort(c.enclosingPosition, s"Unknown parameter '$name'")
}
}

def addArgsTo(annot: Apply): Apply = {
val additionalArgs: List[AssignOrNamedArg] = List(
AssignOrNamedArg(q"readOnly", q"$readOnly"),
AssignOrNamedArg(q"required", q"$required")
)

Apply(annot.fun, annot.args ++ additionalArgs)
}
}

private def extractAnnotationParameters(tree: Tree): AnnotationParams = tree match {
case ap: Apply =>
val argNames = Array("readOnly", "required")
val defaults = AnnotationParams(readOnly = false, required = false)

ap.args.zipWithIndex.foldLeft(defaults)((acc, argAndIndex) => argAndIndex match {
case (lit: Literal, index: Int) => acc.customCopy(argNames(index), c.eval(c.Expr[Any](lit)))

case (namedArg: AssignOrNamedArg, _: Int) =>
val q"$name = $lit" = namedArg
acc.customCopy(name.asInstanceOf[Ident].name.toString, c.eval(c.Expr[Any](lit)))

case _ => c.abort(c.enclosingPosition, "Failed to parse annotation params: " + argAndIndex)
})
}
}

然后你可以这样做:

sealed trait Mode extends EnumEntry

@SwaggerEnumContainer
object Mode extends Enum[Mode] {

override def values = findValues

case object Initial extends Mode
case object Delta extends Mode
}


@ApiModel
case class Foobar(@ApiModelProperty(dataType = "string", allowableValues = Mode.allowableValues) mode: Mode)

或者你可以这样做,我认为这样更干净一些

@ApiModel
case class Foobar2(
@SwaggerEnumValue mode: Mode,
@SwaggerEnumValue(true) mode2: Mode,
@SwaggerEnumValue(required = true) mode3: Mode,
i: Int, s: String = "abc") {
@SwaggerEnumValue
val modeField: Mode = Mode.Delta
}

请注意,这仍然只是一个 POC。已知缺陷包括:

  1. @SwaggerEnumContainer 无法处理一些假的 allowableValues 已经定义了一些假值(这可能对 IDE 更好)的情况
  2. @SwaggerEnumValue 仅支持原始 @ApiModelProperty
  3. 可用范围内的两个属性

关于scala - 为 Swagger 实现 Enumeratum 支持,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47869348/

34 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com