Play FrameworkでSwaggerを使う


SwaggerはJSON API用のドキュメンテーション作成ツールです。

* http://swagger.io/
* http://petstore.swagger.wordnik.com/

こんな画面が生成されて、画面の下の方にある"Try it out"というボタンで実際にリクエストを送信することができます。

![Swagger_UI.png](https://qiita-image-store.s3.amazonaws.com/0/5978/7a6b274f-4673-fcc9-8331-6f6456f2bf50.png "Swagger_UI.png")

## SwaggerとPlay Framework

上の画面はSwagger UIというHTML+JavaScriptのツールによるもので、Swaggerの実体はコードアノテーションを解釈してエンドポイントに関するJSONを返してくれるツールです。

SwaggerのメインリポジトリであるSwagger CoreはScalaで書かれていて、Play Frameworkの用ライブラリも[Swagger Coreのリポジトリ内にあります](https://github.com/swagger-api/swagger-core/tree/master/modules/swagger-play2)。

Play FramworkでSwaggerを使うサンプルアプリケーションも[同じリポジトリにあります](https://github.com/swagger-api/swagger-core/tree/master/samples/scala-play2)。

Swagger CoreはJAX-RS, Servlets, Play Frameworkに対応しているとありますが、[仕様はかなり詳しく書かれていて](https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md)、Scala以外の言語でも使うことができます。言語ごとのサポートツール一覧は[こちら](https://github.com/swagger-api/swagger-spec)にあります。

Play Framworkだとこんなアノテーションになります。

![swagger-core_PetApiController_scala_at_master_·_swagger-api_swagger-core.png](https://qiita-image-store.s3.amazonaws.com/0/5978/34df09ba-3eea-d00b-8d2a-06195e2c2d2a.png "swagger-core_PetApiController_scala_at_master_·_swagger-api_swagger-core.png")

## Play Frameworkでの使い方

Play2用のライブラリのソースコードは[こちら](https://github.com/swagger-api/swagger-core/tree/master/modules/swagger-play2)で、build.sbtにはこのように書くと使うことが使えます。

```
libraryDependencies ++= Seq(
  "com.wordnik" %% "swagger-play2" % "1.3.12"
)
```

conf/routesには[このように](https://github.com/swagger-api/swagger-core/blob/master/samples/scala-play2/conf/routes)書きます。

```
GET     /api-docs               controllers.ApiHelpController.getResources
GET     /api-docs/pet           controllers.ApiHelpController.getResource(path = "/pet")
GET     /api-docs/store         controllers.ApiHelpController.getResource(path = "/store")
```

この例では、このアプリケーションのAPI群一覧やそのメタ情報を返すのが [/api-docs](http://petstore.swagger.wordnik.com/api/api-docs) で、 /api/pet/create や /api/pet/update など /pet/ 以下のパスに関するドキュメントを返すのが [/api-docs/pet](http://petstore.swagger.wordnik.com/api/api-docs/pet) です。

アノテーションは主にControllerに対してつけます（が、Modelにつけるためのアノテーションもあります）。

* https://github.com/swagger-api/swagger-core/tree/master/samples/scala-play2/app/controllers

[Swagger自体の仕様](https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md)はドキュメントが詳しいのですが、アノテーションにはドキュメントが無いっぽいのでコードを見て察する必要があります。

Swagger UIは静的ファイルだけなのでSwaggerと同じアプリケーション内に含む必要はありませんが、クロスオリジンの場合はCORSの設定が必要になります（どこで読んだか忘れました）。

同じアプリケーションに含める場合は、Playのpublicというフォルダ内に[このディレクトリ](https://github.com/swagger-api/swagger-ui/tree/master/dist)を一式置いておけばいいです。

## ハマるところ

### Swagger UIからリクエストするURLがおかしい

Swaggerが出すJSONでbasePathというのがあり、Swagger Play2 Moduleではこのデフォルトが`http://localhost`になっています。

このためSwagger UIでは

```
http://localhost/pets
```

のようなURLにアクセスしようとしてしまって、ポート9000で開発しているときなどに不便です。

これを変更するにはPlayのconf/application.confで

```
swagger.api.basepath="http://localhost:9000"
```

と書く必要があります。

その他認証周りのことなども書く必要がある[みたいです](https://github.com/swagger-api/swagger-core/blob/master/samples/scala-play2/conf/application.conf#L38-L40)。

### Swagger UIのデフォルトのAPIエンドポイントにデモ用のものが入っている

![Swagger_UI.png](https://qiita-image-store.s3.amazonaws.com/0/5978/f584bc1d-db1e-eaa3-97cb-dddd1ab52886.png "Swagger_UI.png")

このままだと使いにくいのでswagger-uiのindex.htmlを変更するか、

```
?url=http://localhost:9000/dev/api-docs
```

のようなクエリパラメータをつけてアクセスするとJSでよしなにやってくれます。

### GETパラメータやパスに含むパラメータ

Swagger UI上でパラメータを弄ってリクエストできますが、デフォルトのbodyに含める方法ではなくGETパラメータを使う方法が最初わかりませんでした。

というのも、サンプルコードではこのように引数につけるアノテーションとしてApiParamというものがあるのですが、[Swaggerの仕様](https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md#fixed-fields-7)では`Possible values are "query", "header", "path", "formData" or "body".`となっているものの、変えられるようなオプションが見当たらないためです。

```scala
  def getPetById(
    @ApiParam(value = "ID of the pet to fetch") @PathParam("id") id: String) = Action {
```

（追記: ↑よく見ると`@PathParam`と書いてありますね。見落としてました。`@QueryParam`というのもあるらしいです。下で紹介する方法はアノテーションの実装コードが完全に分離されるので優れていると思います）

コードを読んでるとApiImplicitParamsという似たようなものを見つけてそれでできることがわかりました。ApiParamはJAX-RSのためにあるものだそうです。

```scala
  @ApiOperation(
    nickname = "userPets",
    value = "ユーザーのペット一覧",
    response = classOf[Pet],
    httpMethod = "GET"
  )
  @ApiImplicitParams(Array(
    new ApiImplicitParam(name = "id", value = "ユーザーID", paramType = "path", required = true),
    new ApiImplicitParam(name = "page", value = "ページ番号", paramType = "query", required = false)
  ))
  def userPets(id: Long, page: Int) = Action {
```

（さっき[README](https://github.com/swagger-api/swagger-core/tree/master/modules/swagger-play2#the-apiparam-annotation)にもこれの解説があることに気づきましたが、ApiImplicitParamとApiParamの説明が逆だと思います）

### レスポンスがクラスと一対一対応ではないとき

`response = classOf[Pet]`のような定義をするとこんな感じでレスポンスのデータ構造が見えたりするのですが、

![Swagger_UI.png](https://qiita-image-store.s3.amazonaws.com/0/5978/be8e4767-477f-64db-f982-566f50c4c6e6.png "Swagger_UI.png")

僕の場合はこうやって`data`というキーをトップレベルに付けるようなヘルパーを作っていたので、

```scala
      JsObject(Seq("data" -> Json.toJson(data)))
```

苦肉の策でアノテーションだけのためにclassを用意しました。

```scala
  case class PetsJsonApiData(works: Seq[Pet])

  @ApiOperation(
    response = classOf[PetsJsonApiData]
  )
```

まあSwagger UIの"Try it Out!"だと実際きちんとしたレスポンスが返ってきて表示されるので、自己満足的な面が強いです。（Swagger Toolでクライアントライブラリの自動生成とかをする場合などはこのようなことが重要になってきます）

### Option

先ほどのデータ構造が見られる部分で、Stringだと""になってIntやLongだと0になるのですが、Optionを使ったり複雑なことをすると"Object"になるみたいでした。

クライアントライブラリの自動生成とかをする場合などは（以下略

僕はそこまで気にしないので放置しました。

## 使ってみた感想

* この手のツールの中では古くからあって枯れてるが、けっこう洗練されててやれることも多くて良い
* Swagger UIは綺麗でよく出来てる
* 最初にコード例見てアノテーションつらそうと思ったけどIDEの補完とかあるので気にならない
 * すべて細かくアノテーションつける必要はなく、デフォルトのままのものとかコメントとかを省けばだいぶ気分的にもボリューム的にも軽い
 * でも実装とコードが混ざってごちゃごちゃになるので、traitを作ってそっちにアノテーションを書くことにして、継承したControllerでoverrideすることにした
* [API Blueprint](https://apiblueprint.org/)と両方使ってみて
 * ドキュメンテーション用ツールとしてはSwaggerのほうがメンテしやすそう
  * ただしアノテーションの無い言語とかIDEがサポートしてない環境で使いたくはないかも
 * API BlueprintはJSONを書くだけなのでプロトタイピング用ツールとしては優秀だった