Swagger を CentOS6.5 上で動かしてみた

[Myuon Corp. Advent Calendar 2016](http://qiita.com/advent-calendar/2016/myuon) 3日目です。

# Swagger を vagrant で建てた CentOS6.5 上で動かしてみた

Swagger を業務で使ってみようということになり、Vagrant 上に導入してみました。

Vagrant で CentOS6.5 を建てる方法については、割愛して Swagger とはなにか、導入の仕方と動かし方、扱い方をまとめてみようと思います。

## [Swagger](http://swagger.io/) とはなにか

Swagger とは **Restful** な API 仕様の記法と、記述するためのツール群です。
API 仕様を [yaml](http://yaml.org/) か [json](http://www.json.org/) で記述することができます。
何より特徴的なのが yaml, json の編集をブラウザ上で実行する事ができ、ライブプレビューで仕様書風なページが表示されることです。
また、記述したものをプレビューされている仕様書風なページ上で実行することができます。

![スクリーンショット 2016-12-02 20.44.12.png](https://qiita-image-store.s3.amazonaws.com/0/73162/80148951-9bbf-5d99-ecf1-e1e5ed8735e5.png)

実際に作成した仕様書を元に Server, Client のソースコードをジェネレートすることができるみたいですが、そこはまだ触れていないのでいずれまとめたいと思います。

## 建てた Vagrant の環境

今回、Swagger の Editor を Vagrant で建てようということになり、ミニマムな状態の CentOS (6.5) を建てました。

* OS

    ```
$ cat /etc/redhat-release
CentOS release 6.5 (Final)
$ uname -a
Linux swagger 2.6.32-431.el6.x86_64 #1 SMP Fri Nov 22 03:15:09 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux
```

* 容量

    ```
$ df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda1       7.3G  1.6G  5.4G  24% /
tmpfs           295M     0  295M   0% /dev/shm
vagrant         465G  370G   96G  80% /vagrant
```

## Swagger の導入

Swagger には [node.js](https://github.com/swagger-api/swagger-node) 製のもの、 [javascript](https://github.com/swagger-api/swagger-js) 製のものがあるようです。

まずは、エディタを動かしたいので [swagger-editor](https://github.com/swagger-api/swagger-editor) を導入します。

必要なものは以下のものです。

* nodejs
* npm
* wget
* http-server
* swagger-editor

上の3つは yum でインストールしちゃいます。

```
$ sudo yum install -y nodejs
$ sudo yum install -y npm
$ sudo yum install -y wget
```

次の、 [http-server](https://www.npmjs.com/package/http-server) は node.js 製の HTTP サーバです。
npm でインストールします。

```
$ npm install -g http-server
```

単に swagger-editor 内の index.html が HTTP サーバ上で動作すれば良いので、手元に rails があれば public の下に swagger-editor を置いても動作します。
とりあえず動かすのが目的であれば、他のもので代用しても問題ありません。

最後に、swagger-editor の取得です。
wget でソースコードの zip をダウンロードします。

```
$ wget https://github.com/swagger-api/swagger-editor/releases/download/v2.10.4/swagger-editor.zip
```

ダウンロードしたものは解凍しましょう。

```
$ unzip ./swagger-editor.zip
```

## Swagger の動かし方

ようやく、swagger-editor を動かす時が来ました。

```
http-server swagger-editor
```

実行すると、次のような出力が得られます。

```
Starting up http-server, serving swagger-editor
Available on:
  http://127.0.0.1:8081
  http://10.0.2.15:8081
  http://192.168.33.52:8081
Hit CTRL-C to stop the server
```

ブラウザでアクセスすると見事エディタが起動します。
導入はすごく簡単ですね。

## Swagger の扱い方

Swagger の扱い方として、次のものを順にまとめます。

* 書き方
* Type
* プロジェクト

### 書き方

swagger-editor を起動してアクセスすると、まずエディタにはサンプルの yaml が貼り付けられていて、おおよその書き方はそのサンプルのコメントで分かるようになっています。
また、エディタのステータスバーの `File` > `Open Example...` から別のサンプルを開くこともできるので、不足分はそちらで補うこともできそうです。

書き方の解説は yaml 形式で、例に上げるサンプルを `petstore_full.yaml` としてまとめます。

* **構成**

    ---

    ルート階層は次の表のようになっています。

    | key | 型 | 用途 |
    |:--- |:--- |:--- |
    | **swagger** | string | swagger のバージョン |
    | **info** | object | API の情報 |
    | **host** | string | API のホスト |
    | **basePath** | string | API のベースパス |
    | **schemes** | array<string> | 使用可能なスキームのリスト |
    | **paths** | object | API のパスのリスト |
    | **securityDefinitions:** | object | 認証などのセキュリティの定義 |
    | **definitions** | object | モデルのリスト |

    それぞれの解説をします。

    **swagger** はそのまんまですね。
    サンプルでは次のようになっています。

    ```
swagger: "2.0"
```

    ---

    **info** は次のような情報を持ちます。

    | key | 型 | 用途 |
    |:--- |:--- |:--- |
    | description | string | API がどういうものなのかの説明文 |
    | version | string | API のバージョン |
    | title | string | API のタイトル （プロジェクト名） |
    | termsOfService | string | 利用規約ページ |
    | contact | object | `name` をキーとした連絡先 |
    | license | object | `name` をキーとしたライセンス名、 `url` をキーとしたライセンス条文のページのリンク |

    ミニマムで設定するのであれば、 `description` , `title` , `version` だけで良さそうです。
    サンプルでは次のようになっています。

    ```
info:
  description: |
    This is a sample server Petstore server.

    [Learn about Swagger](http://swagger.io) or join the IRC channel `#swagger` on irc.freenode.net.

    For this sample, you can use the api key `special-key` to test the authorization filters
  version: "1.0.0"
  title: Swagger Petstore
  termsOfService: http://helloreverb.com/terms/
  contact:
    name: apiteam@swagger.io
  license:
    name: Apache 2.0
    url: http://www.apache.org/licenses/LICENSE-2.0.html
```

    **host** , **basePath** はそのまんまですね。
    サンプルでは次のようになっています。

    ```
host: petstore.swagger.io
basePath: /v2
```

    **schemes** は利用可能なスキームを列挙します。
    サンプルでは次のようになっています。

    ```
schemes:
  - http
```

    これを増やすと、プレビューページで実行する際のスキームの選択肢が増えます。

    ---

    **paths** は一番重要なところです。
    次のような情報を持ちます。
    ※ 階層が深くなるので `.` で連結して階層を表現します。

    | key | 型 | 用途 |
    |:--- |:--- |:--- |
    | /pets/{petId} | string | リソースへのパス |
    | /pets/{petId}.get | object | |
    | /pets/{petId}.get.tags | array<string> | Swagger の仕様書上でフィルタするためのタグ |
    | /pets/{petId}.get.summary | string | エンドポイントに対する概要 |
    | /pets/{petId}.get.description | string | エンドポイントに対する説明 |
    | /pets/{petId}.get.operationId | string | operationId **FIXME** |
    | /pets/{petId}.get.produces | array<string> | 出力可能なコンテンツタイプのリスト |
    | /pets/{petId}.get.parameters | array<object> | パラメータの定義のリスト |
    | /pets/{petId}.get.parameters[].in | string | パラメータの配置先 `path` か `query |
    | /pets/{petId}.get.parameters[].name | string | パラメータ名 |
    | /pets/{petId}.get.parameters[].description | string | パラメータの説明 |
    | /pets/{petId}.get.parameters[].requied | boolean | 必須かどうか |
    | /pets/{petId}.get.parameters[].type | string | 型 |
    | /pets/{petId}.get.parameters[].format | string | 型のフォーマット |
    | /pets/{petId}.get.response | object | レスポンスの定義 |
    | /pets/{petId}.get.response."404" | object | 404 レスポンスの定義 |
    | /pets/{petId}.get.response."404".description | string | 404 レスポンスの説明 |
    | /pets/{petId}.get.response."200" | object | 200 レスポンスの定義 |
    | /pets/{petId}.get.response."200".description | string | 200 レスポンスの説明 |
    | /pets/{petId}.get.response."200".schema | object | 200 レスポンスのボディ |
    | /pets/{petId}.get.security | array<object> | セキュリティの定義 |
    | /pets/{petId}.get.security.api_key | array? | [] を指定すると未指定にできる **FIXME** |
    | /pets/{petId}.get.security.petstore_auth | array<string> | 後述の `securityDefinitions` への参照となる `key` |

    `/pets/{petId}` のように `{}` を使用することでプレースホルダにすることができます。

    `get` の部分は `post` とすることで POST の定義を作成できます。

    `tags` は、Swagger のプレビューページでフィルタリングするためのもののようです。
    `summary`, `tags`, `produces` はオプショナルです。
    `produces` はルート階層に配置することも可能です。

    `parameters` に定義するオブジェクトは `in` キーに対して `path` を与えると、path で設置したプレースホルダに対応します。
    `in` キーに対して `query` を与えると、 `?parameter.key=` のようなURLのクエリ部に対応します。
    `parameters` の `type` は後述します。

    `response` の子要素のキーは HTTP ステータスコードか `default` という文字列を指定する必要があります。
    `5xx` とかできないのはちょっと不便に思いました。しかも、`default` って、、、。

    `security` の子要素は、後述の `securityDefinitions` を参照することができるようです。

    サンプルでは次のようになっています。

    ```
  /pets/{petId}:
    get:
      tags:
        - pet
      summary: Find pet by ID
      description: Returns a pet when ID < 10.  ID > 10 or nonintegers will simulate API error conditions
      operationId: getPetById
      produces:
        - application/json
        - application/xml
      parameters:
        - in: path
          name: petId
          description: ID of pet that needs to be fetched
          required: true
          type: integer
          format: int64
      responses:
        "404":
          description: Pet not found
        "200":
          description: successful operation
          schema:
            $ref: "#/definitions/Pet"
        "400":
          description: Invalid ID supplied
      security:
        - api_key: []
        - petstore_auth:
          - write_pets
          - read_pets
```

    ---

    **securityDefinitions** は次のような情報を持ちます。

    | key | 型 | 用途 |
    |:--- |:--- |:--- |
    | api_key | object | api_key オブジェクトの定義 |
    | api_key.type | string |  **FIXME** |
    | api_key.name | string |  **FIXME** |
    | api_key.in | string |  **FIXME** |
    | petstore_auth | object |  **FIXME** |
    | petstore_auth.type | string | 認証方式 |
    | petstore_auth.authorizationUrl | string | 認証を行うページへのURL |
    | petstore_auth.flow | string | **FIXME** |
    | petstore_auth.scopes | array<string> | **FIXME** |

    全体的に未確認なので、早急に確認してまとめたいと思います。
    サンプルでは次のようになっています。

    ```
securityDefinitions:
  api_key:
    type: apiKey
    name: api_key
    in: header
  petstore_auth:
    type: oauth2
    authorizationUrl: http://petstore.swagger.io/api/oauth/dialog
    flow: implicit
    scopes:
      write_pets: modify pets in your account
      read_pets: read your pets
```

    ---

    **definitions** はいわゆる **モデル** の定義です。
    次のような情報を持ちます。

    | key | 型 | 用途 |
    |:--- |:--- |:--- |
    | Pet | object | Pet オブジェクトの定義 |
    | Pet.type | string | Pet オブジェクトの型 |
    | Pet.required | array<string> | Pet オブジェクトのプロパティの内必須なもののキーのリスト |
    | Pet.properties | object | Pet オブジェクトのプロパティの定義 |
    | Pet.properties.id | object | Pet オブジェクトのプロパティ `id` の定義 |
    | Pet.properties.id.type | string | Pet オブジェクトのプロパティ `id` の型 |
    | Pet.properties.id.format | string | Pet オブジェクトのプロパティ `id` のフォーマット |
    | Pet.properties.name | string | Pet オブジェクトのプロパティ `name` の定義 |
    | Pet.properties.name | string | Pet オブジェクトのプロパティ `name` の定義 |
    | Pet.properties.name.type | string | Pet オブジェクトのプロパティ `name` の型 |
    | Pet.properties.name.example | string | Pet オブジェクトのプロパティ `name` の例 |

    ルート階層のキー名がモデル名になります。
    具体的なところは `type` の説明を参照してください。

    サンプルでは次のようになっています。

    ```
  Pet:
    type: object
    required:
      - name
      - photoUrls
    properties:
      id:
        type: integer
        format: int64
      category:
        $ref: "#/definitions/Category"
      name:
        type: string
        example: doggie
      photoUrls:
        type: array
        items:
          type: string
      tags:
        type: array
        items:
          $ref: "#/definitions/Tag"
      status:
        type: string
        description: pet status in the store
```

* **Type**

    ---

    type には 次のものがあります。

    * string
    * float
    * boolean
    * array<?>
    * integer
    * object

    `string` , `float` , `boolean` , `array` は特に解説することはありません。
    上の解説の中には出てきませんでしたが `float` も利用できます。
    [Yaml: types](http://yaml.org/type/) を参照してください。
    ※ rails で yaml を利用する分には気づきませんでしたが、 キーに対して子要素もない、設定値もないと言うのは許可されないようです。

    `integer` には `format` がセットになります。
    `format` をキーとして、 `int32` や `int64` を指定する事ができます。

    `object` はいわゆるハッシュです。

    いずれもキーを `$ref` とすることで、設定値を参照にすることができます。
    この時の設定値は例えば `"#/definitions/Pet"` のようになります。

* **プロジェクト**

    ---

    swagger を npm でインストールすると、`swagger` コマンドが利用できるようになります。

    この `swagger` コマンドには `project` という、サブコマンドがあり、次の機能があります。

    ```
  Commands:

    create [options] [name]              Create a folder containing a Swagger project
    start [options] [directory]          Start the project in this or the specified directory
    verify [options] [directory]         Verify that the project is correct (swagger, config, etc)
    edit [options] [directory]           open Swagger editor for this project or the specified project directory
    open [directory]                     open browser as client to the project
    test [options] [directory_or_file]   Run project tests
    generate-test [options] [directory]  Generate the test template
```

    肝となる `edit` は、どうもブラウザを起動して swagger-editor を開くもののようで、Vagrant で建てた VM では次のようなエラーが出ました。

    ```
$ swagger project edit
Starting Swagger Editor.
Opening browser to: http://127.0.0.1:43390/#/edit
{ [Error: Command failed: /bin/sh: xdg-open: command not found
] killed: false, code: 127, signal: null }
```

    いずれ解決してやりたいと思います。
    それはさておき、ひとまずプロジェクトを作ります。

    次のコマンドで、プロジェクトを作成することができます。

    ```
swagger project create <プロジェクト名>
```

    このコマンドを実行すると、次のような内容物を持つディレクトリが生成されます。

    ```
$ ls -l
total 28
drwxr-xr-x 6 vagrant vagrant 4096 Dec  2 05:11 api
-rw-r--r-- 1 vagrant vagrant  546 Dec  2 05:11 app.js
drwxr-xr-x 2 vagrant vagrant 4096 Dec  2 05:11 config
drwxrwxr-x 6 vagrant vagrant 4096 Dec  2 05:11 node_modules
-rw-r--r-- 1 vagrant vagrant  425 Dec  2 05:11 package.json
-rw-r--r-- 1 vagrant vagrant   31 Dec  2 05:11 README.md
drwxr-xr-x 3 vagrant vagrant 4096 Dec  2 05:11 test
```

    作成した swagger の定義ファイルは、 `./api/swagger/` に配置します。

    Git 管理に載せる場合は、まとまっていたほうがスッキリするのでひとまずこのプロジェクトにしたもので扱う予定です。
    もっといい方法がわかったらまとめたいと思います。

    また、他の機能はまだ試せていないので、そちらも試してまとめる予定です。

## まとめ

実際、swagger-editor は日本語に弱いらしく、変な入力になることが多々あります。
あまり使いやすいとは言いづらいですが、候補も出ますし、たまにテキスト全体をカット&ペーストすればエラーもちゃんと出してくれます。

プレビューのページはモデルあたりは非常に見難く、そのまま PDF化すれば納品物にできるぜというものではありません。

ドキュメント化という点では Swagger2Markup などの別のツールもあるみたいですが、まだ触れていないので色々試してまとめていきたいと思います。

それと、こういうものこそ Docker と連携しやすいっぽいですね。

まぁ、何にしろ yaml で記述できる手軽さは良いですね。
ちゃんとメンテされるドキュメンテーションへの第一歩ということで。

---

明日は @nullpopon@github さんです。
どんなことを記事にしてくれるのか楽しみです。

<!--
これを例に構築してみたい
curl -i "https://api.github.com/search/code?q=struct+in:file+language:cpp+repo:boostorg/hana"
-->