开放式API游戏:Swagger Play


在本文中,我想通过实际示例说明如何将Swagger模块用于Play框架。我会告诉:

  1. 如何固定最新版本的Swagger-Play(允许您使用swagger-api注释并基于OpenAPI规范生成文档的播放模块),以及如何配置swagger-ui(用于可视化生成的文档的JavaScript库)
  2. 我将描述Swagger-Core的主要注释,并讨论它们在Scala中的使用功能。
  3. 我将告诉您如何正确使用数据模型类
  4. 如何解决Swagger中的泛型类型问题,该问题不知道如何使用泛型
  5. 如何教Swagger了解ADT(代数数据类型)
  6. 如何描述收藏

对于在Scala上使用Play框架并打算自动化API文档的每个人,这篇文章都会很有趣。

添加依赖


研究了Internet上的许多资源后,我得出结论,为了结交Swagger和Play Framework,需要安装Swagger Play2模块。

github上的库地址:

https : //github.com/swagger-api/swagger-play

添加依赖项:

libraryDependencies ++= Seq(
  "io.swagger" %% "swagger-play2" % "2.0.1-SNAPSHOT"
)

这里出现了问题:

在撰写本文时,还没有从Maven中央存储库或Sonatype存储库中提取依赖项。

在Maven-central中,所有找到的构建都以Scala 2.12结尾。通常,Scala 2.13没有单个汇编版本。

我真的希望它们会在将来出现。

爬Sonatype-releases库,我发现了该库的当前分支。在github上的地址:

https : //github.com/iterable/swagger-play

因此,我们插入依赖项:

libraryDependencies ++= Seq(
  "com.iterable" %% "swagger-play" % "2.0.1"
)

添加Sonatype存储库:

resolvers += Resolver.sonatypeRepo("releases")

(因为此程序集是Maven中心的,所以没有必要),

现在仍然可以激活application.conf配置文件中的模块。

play.modules.enabled += "play.modules.swagger.SwaggerModule"

以及为路线添加路线:

GET     /swagger.json           controllers.ApiHelpController.getResources

该模块已准备就绪。

现在,Swagger Play模块将生成一个可以在浏览器中查看的json文件。

要充分享受Swagger的功能,还需要下载可视化库:swagger-ui。它提供了一个方便的图形界面来读取swagger.json文件,并具有向服务器发送休息请求的功能,是Postman,Rest-client和其他类似工具的绝佳替代品。

因此,添加取决于:

libraryDependencies += "org.webjars" % "swagger-ui" % "3.25.3"

在控制器中,我们创建一个将调用重定向到静态库index.html文件的方法:

def redirectDocs: Action[AnyContent] = Action {
    Redirect(
       url = "/assets/lib/swagger-ui/index.html",
       queryStringParams = Map("url" -> Seq("/swagger.json")))
  }

好吧,我们在路线文件中规定了路线:

GET   /docs                   controllers.HomeController.redirectDocs()

当然,您需要连接webjars-play库。添加取决于:

libraryDependencies +=  "org.webjars" %% "webjars-play" % "2.8.0"

并将路由添加到路由文件:

GET     /assets/*file               controllers.Assets.at(path="/public", file)

假设我们的应用程序正在运行,我们在浏览器中输入

http://本地主机:9000 / docs

,如果一切都正确完成,我们将进入应用程序的大页面:



该页面尚未包含有关rest-api的数据。为了更改此设置,您需要使用注释,该注释将由Swagger-Play模块扫描。

注解


有关所有swagger-api-core批注的详细说明,请参见:

https : //github.com/swagger-api/swagger-core/wiki/Annotations-1.5.X

在我的项目中,我使用了以下批注:

@Api-记录控制器类作为Swagger资源(用于扫描)

@ApiImplicitParam-描述一个“隐式”参数(例如,在请求正文中指定)

@ApiImplicitParams-用作几个@ApiImplicitParam 批注的容器

@ApiModel-允许您描述

@ApiModelProperty数据模型 -描述并解释

@ApiOperation数据模型方法类字段- (可能是这个名单上的主要注释)

@ApiParam-描述显式指定的请求参数(例如,在查询字符串中)

@ApiResponse-描述服务器对

@ApiResponses请求的响应-充当多个@ApiResponse 批注的容器通常包括其他答案(例如,发生错误代码时)。成功的响应通常在@ApiOperation 批注中描述,

因此,为了使Swagger扫描控制器类,您需要添加@Api 批注

@Api(value = «RestController», produces = «application/json»)
class RestController @Inject()(

Swagger足以在路由文件中找到与控制器方法相关的路由并尝试对其进行描述。



但是仅指定Swagger控制器类显然是不够的。Swagger正在与其他注释一起等待着我们。

Swagger为什么不能自动执行此操作?因为他不知道我们的课程是如何序列化的。在这个项目中,我使用uPickle,有人使用Circe,有人使用Play-JSON。因此,您必须提供指向已接收和已发布类的链接。

由于使用的库是用Java编写的,因此Scala项目中存在许多细微差别。

首先要处理的语法是:嵌套注释不起作用,

例如Java代码:

@ApiResponses(value = {
      @ApiResponse(code = 400, message = "Invalid ID supplied"),
      @ApiResponse(code = 404, message = "Pet not found") })


在Scala中,它将如下所示:

@ApiResponses(value = Array(
      new ApiResponse(code = 400, message = "Invalid ID supplied"),
      new ApiResponse(code = 404, message = "Pet not found") ))


例子1


因此,让我们描述一种在数据库中查找实体的控制器方法:

def find(id: String): Action[AnyContent] = 
    safeAction(AllowRead(DrillObj)).async { implicit request =>
      drillsDao.findById(UUID.fromString(id))
        .map(x => x.fold(NotFound(s"Drill with id=$id not found"))(x => 
            Ok(write(x)))).recover(errorsPf)
    }      


使用注释,我们可以指定方法的描述,从查询字符串获得的输入参数以及服务器的响应。如果成功,则该方法将返回Drill类的实例:

 @ApiOperation(
    value = " ",
    response = classOf[Drill]
  )
  @ApiResponses(value = Array(
    new ApiResponse(code = 404, message = "Drill with id=$id not found")
  ))
  def find(@ApiParam(value = "String rep of UUID, id ") id: String)=
    safeAction(AllowRead(DrillObj)).async { implicit request =>
      drillsDao.findById(UUID.fromString(id))
        .map(x => x.fold(NotFound(s"Drill with id=$id not found"))(x =>
          Ok(write(x)))).recover(errorsPf)
    }




我们得到了很好的描述。Swagger几乎猜到了对象被序列化了什么,只有一个例外:Drill类中的start和end字段是Instant对象,并以Long序列化了。我想将0替换为更合适的值。我们可以通过将@ApiModel,@ApiModelProperty批注应用于我们的类来实现:

@ApiModel
case class Drill(
                id: UUID,
                name: String,
                @ApiModelProperty(
                  dataType = "Long",
                  example = "1585818000000"
                )
                start: Instant,
                @ApiModelProperty(
                  dataType = "Long",
                  example = "1585904400000"
                )
                end: Option[Instant],
                isActive: Boolean
                )


现在,我们对模型有了绝对正确的描述:




例子2


为了描述Post方法(在请求正文中传递输入参数),使用@ApiImplicitParams 批注

 @ApiOperation(value = " ")
  @ApiImplicitParams(Array(
    new ApiImplicitParam(
      value = " ",
      required = true,
      dataTypeClass = classOf[Drill],
      paramType = "body"
    )
  ))
  @ApiResponses(value = Array(
    new ApiResponse(code = 200, message = "ok")
  ))
  def insert() = safeAction(AllowWrite(DrillObj)).async { implicit request =>

例子3


到目前为止,一切都很简单。这是一个更复杂的示例。假设根据类型参数有一个通用类:

case class SessionedResponse[T](
                            val ses: SessionData,
                            val payload: T
                          )

Swagger至少还不了解泛型。我们无法在注释中指出:

@ApiOperation(
    value = " ",
    response = classOf[SessionedResponse[Drill]]
  )


在这种情况下,唯一的方法是为所需的每种类型派生一个通用类型。例如,我们可以将DrillSessionedResponse子类化。
唯一的麻烦是,我们不能从case类继承。幸运的是,在我的项目中,没有什么可以阻止我更改案例类。然后:

class SessionedResponse[T](
                            val ses: SessionData,
                            val payload: T
                          )

object SessionedResponse {
  def apply[T](ses: SessionData, payload: T) = new SessionedResponse[T](ses, payload)
 
}

private[controllers] class DrillSessionedResponse(
          ses: SessionData,
          payload: List[Drill]
) extends SessionedResponse[List[Drill]](ses, payload)

现在,我可以在注释中指定此类:

@ApiOperation(
    value = " ",
    response = classOf[DrillSessionedResponse]
  )

例子4


现在是与ADT相关的更复杂的示例-代数数据类型。

Swagger提供了一种使用ADT的机制:为此,

抽象@ApiModel有2个选项:

1. subTypes-子类的枚举

2. DISCRIMINATOR-子类彼此不同的字段。

在我的案例中,uPickle从case类生成JSON,然后添加$ type字段本身,并case-将对象序列化为字符串。因此,采用区分字段的方法是不可接受的。

我使用了不同的方法。假设有

sealed trait Permission

case class Write(obj: Obj) extends Permission
case class Read(obj: Obj) extends Permission


其中,Obj是另一个由案例对象组成的ADT:

//  permission.drill
case object DrillObj extends Obj

// permission.team
case object TeamObj extends Obj


为了使Swagger理解此模型,而不是真实的类(或特征),它需要为此提供一个专门创建的类,其中包含必要的字段:

@ApiModel(value = "Permission")
case class FakePermission(
       @ApiModelProperty(
         name = "$type",
         allowableValues = "ru.myproject.shared.Read, ru.myproject.shared.Read"
       )
       t: String,
       @ApiModelProperty(allowableValues = "permission.drill, permission.team"
       obj: String
     )

现在我们必须在注释中指定FakePermission而不是Permission

@ApiImplicitParams(Array(
    new ApiImplicitParam(
      value = "",
      required = true,
      dataTypeClass = classOf[FakePermission],
      paramType = "body"
    )
  ))

馆藏


最后一件事我想引起读者的注意。如我所说,Swagger不理解泛型。但是,他知道如何处理集合。

因此,@ApiOperation 批注具有responseContainer参数,您可以将值“ List”传递给该参数。

关于输入参数,显示

dataType = "List[ru.myproject.shared.roles.FakePermission]"

在支持该属性的注释中,将产生所需的结果。虽然,如果您指定scala.collection.List-它不起作用。

结论


在我的项目中,使用Swagger-Core批注,我能够完全描述Rest-API和所有数据模型,包括通用类型和代数数据类型。我认为,使用Swagger-Play模块最适合自动生成API描述。

All Articles