- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我正在使用 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。已知缺陷包括:
@SwaggerEnumContainer
无法处理一些假的 allowableValues
已经定义了一些假值(这可能对 IDE 更好)的情况@SwaggerEnumValue
仅支持原始 @ApiModelProperty
关于scala - 为 Swagger 实现 Enumeratum 支持,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47869348/
我正在使用 Swagger 来注释我的 API,在我们的 API 中,我们非常依赖 enumeratum。如果我什么都不做,swagger 将不会识别它,只会调用它 object。 例如,我有这段有效
我是一名优秀的程序员,十分优秀!