アノテーションからドキュメントを作れるswaggerの使い方

swagger の概要と、基本的な使い方をサンプルを交えて書く。

なお、終始 PHP なので気をつけて下さい。

swagger の概要

開発ドキュメントをどうやって書くかという話題は常々ある。そして一番素直なアプローチを取ると、大抵 wiki みたいなアプリケーションを用意して、そこにドキュメント書こうぜという話になる。しかし、この管理は割とすぐ破綻してしまう。 理由はいろいろあるが、最新のコードに追従できなくなるとか、統一されたフォーマットを保証できず書き手によっては何を書いているのか全くわからないとかそういうケースが多い。

前者について、 swagger はアノテーションでドキュメントを書けるので違いは起きづらくなる。( それでも不足だが自分でライブラリ作って補った

また後者に関していえば、swagger は API の仕様を記述するためのフォーマットを提供してくれる。

つまり、swagger は API の記述方法について考える手間を肩代わりし、また違反があれば解析の段階でエラーを吐くのでドキュメントの一貫性保証もしてくれる。

swagger のアーキテクチャ

swagger とは厳密には API の仕様を記述するためのフォーマットのことを指す。しかし、実際には以下のドキュメントを生成してくれるライブラリの総称として使われることが多い。

ライブラリの「総称」といったのは、上記ドキュメントを生成する手順は以下の2つのレイヤーからなるため。

1. swagger(-core?)

  • アノテーションを解析し、jsonファイルを生成する。

  • このレイヤーは色々な言語で行える

  • ちなみに各々の言語で名前がバラバラで、一貫した呼び名がわからないので(-core?)という疑問形で書いている

2. swagger-ui

  • 上記で吐き出されたjsonファイルをパースし、ドキュメント化する。

以下にサンプルを通して、アノテーションの記述からドキュメントの生成までの一連の流れを紹介する。

swagger を使ってみる

完成品は以下に上げてあるのでそこだけみてもよいかもしれない。また、各ブロックの説明と commit を対応させるように書いたので、commit だけ追ってもいける。

silex で適当なAPIを作る

とりあえずサンプルとして使う適当な API を作ってみる。API の作成自体はメインの話題ではないのでさらっと流すが、今回はお手軽にやるために silex を使っている。

<?php
$app->get('/plain', function () use ($app) {
    $response = [
        'id'   => 0,
        'name' => 'kohsaka'
    ];

    return $app->json($response);
});

$app->get('/nested', function () use ($app) {
    $response = [
        'id'    => 0,
        'name'  => 'kohsaka',
        'birth' => [
            'month' => 'August',
            'day'   => '3'
        ]
    ];

    return $app->json($response);
});

$app->get('/collection', function () use ($app) {
    $response = [
        [
            'id'   => 0,
            'name' => 'kohsaka'
        ],
        [
            'id'   => 1,
            'name' => 'sonoda'
        ],
        [
            'id'   => 2,
            'name' => 'minami'
        ]
    ];

    return $app->json($response);
});

$app->get('/parameter/{id}', function ($id) use ($app) {
    $index = [
        [
            'id'   => 0,
            'name' => 'kohsaka'
        ],
        [
            'id'   => 1,
            'name' => 'sonoda'
        ],
        [
            'id'   => 2,
            'name' => 'minami'
        ]
    ];

    return $app->json($index[$id]);
});

サンプルプロジェクトのコードの差分 - create sample api

アノテーションを足していく

API ができたので、随時アノテーションを付け足してドキュメントを作っていく。

アノテーションの書き方については、公式ドキュメントや swagger-php の Examples を見るのが良い。

パラメータがいっぱいあってげんなりすると思うので注釈する。なお、各々のパラメータは階層構造になっており、この階層構造を外れて記述することはできない。

  • @SWG\Resource
  • 似たエンドポイントはここでひとまとめにする。例えば GET /users, GET /users/{id} のようなもの
    • @SWG\Api
    • 一つのエンドポイントごとに書く情報
      • @SWG\Operations
      • 一つのエンドポイントに対して複数の HTTP メソッドが割り当てられている場合必要になる
        • @SWG\Operation
        • 一つの HTTP メソッドごとに書く情報
          • @SWG\Parameters
          • SWG\Parameter のコレクション
            • @SWG\Parameter
            • 想定されるリクエストパラメータに関する情報
          • @SWG\ResponseMessages
          • SWG\ResponseMessage のコレクション
            • @SWG\ResponseMessage
            • レスポンスメッセージに関する情報
  • @SWG\Model
  • レスポンスパラメータに関する情報をまとめるやつ(としか言い様がない)
    • @SWG\Property
    • レスポンスパラメータごとに書く情報
      • @SWG\Items
      • SWG\Property がコレクション構造を取る場合これで表現する

書いといて何だが多分よくわからないと思うので、以下の実例と見比べて見たほうがよい。

シンプルなAPI構造の記述方法

手始めに最も単純な構造の /plain エンドポイントの APIアノテーションを足してみる。

<?php
/**
 * @SWG\Resource(
 *     resourcePath="plain",
 *     @SWG\Api(
 *         path="/plain",
 *         description="plain api structure",
 *         @SWG\Operation(
 *             method="GET",type="SimpleMember",nickname="plain"
 *         )
 *     )
 * )
 *
 * @SWG\Model(
 *     id="SimpleMember",
 *     @SWG\Property(name="id", type="integer", required=true, description="user id"),
 *     @SWG\Property(name="name", type="string", required=true, description="user name")
 * )
 */
$app->get('/plain', function () use ($app) {
    $response = [
        'id'   => 0,
        'name' => 'kohsaka'
    ];

    return $app->json($response);
});

レスポンスは、@SWG\Operation の type と @SWG\Model の id を対応させることで表現する。

サンプルプロジェクトのコード差分 - add annotation to plain API

ネストするAPI構造の記述方法

レスポンスパラメータがネストする構造をとっている場合は、以下のように model を複数指定する。

<?php
/**
 * @SWG\Resource(
 *     resourcePath="nested",
 *     @SWG\Api(
 *         path="/nested",
 *         description="nested api structure",
 *         @SWG\Operation(
 *             method="GET",type="NestedMember",nickname="nested"
 *         )
 *     )
 * )
 *
 * @SWG\Model(
 *     id="NestedMember",
 *     @SWG\Property(name="id", type="integer", required=true, description="user id"),
 *     @SWG\Property(name="name", type="string", required=true, description="user name"),
 *     @SWG\Property(name="birth", type="Birth", required=true, description="birth day,month")
 * )
 *
 * @SWG\Model(
 *     id="Birth",
 *     @SWG\Property(name="month", type="string", required=true, description="month"),
 *     @SWG\Property(name="day", type="integer", required=true, description="day")
 * )
 */
$app->get('/nested', function () use ($app) {
    $response = [
        'id'    => 0,
        'name'  => 'kohsaka',
        'birth' => [
            'month' => 'August',
            'day'   => '3'
        ]
    ];

    return $app->json($response);
});

サンプルプロジェクトのコード差分 - add annotation to nested API

コレクションになっているAPI構造の記述方法

レスポンスがコレクションの構造をとっている場合は、@SWG\Items でコレクションするモデルの id を指定する。(実はこれ以外にも書き方はけどややこしいので飛ばす。)

<?php
/**
 * @SWG\Resource(
 *     resourcePath="collection",
 *     @SWG\Api(
 *         path="/collection",
 *         description="nested api structure",
 *         @SWG\Operation(
 *             method="GET",type="CollectionMember",nickname="collection"
 *         )
 *     )
 * )
 *
 * @SWG\Model(
 *     id="CollectionMember",
 *     @SWG\Property(
 *          name="member collection",
 *          type="array",
 *          @SWG\Items("SimpleMember"),
 *          required=true,
 *          description="member array"
 *      )
 * )
 */
$app->get('/collection', function () use ($app) {
    $response = [
        [
            'id'   => 0,
            'name' => 'kohsaka'
        ],
        [
            'id'   => 1,
            'name' => 'sonoda'
        ],
        [
            'id'   => 2,
            'name' => 'minami'
        ]
    ];

    return $app->json($response);
});

ちなみに、SimpleMember のように、既に出てきた model は再利用できる。

サンプルプロジェクトのコード差分 - add annotation to collection API

リクエストパラメータを付与の記述方法

リクエストパラメータのドキュメントを書きたい場合、@SWG\Parameters で表現できる。

<?php
/**
 * @SWG\Resource(
 *     resourcePath="parameter",
 *     @SWG\Api(
 *         path="/parameter/{id}",
 *         description="nested api structure",
 *         @SWG\Operation(
 *             method="GET",type="SimpleMember",nickname="nested",
 *             @SWG\Parameters(
 *                 @SWG\Parameter(
 *                     name="id", paramType="path",type="string",
 *                     required=true, description="specify member id"
 *                 )
 *             )
 *         )
 *     )
 * )
 */
$app->get('/parameter/{id}', function ($id) use ($app) {
    $index = [
        [
            'id'   => 0,
            'name' => 'kohsaka'
        ],
        [
            'id'   => 1,
            'name' => 'sonoda'
        ],
        [
            'id'   => 2,
            'name' => 'minami'
        ]
    ];

    return $app->json($index[$id]);
});

サンプルプロジェクトのコード差分 - add annotation to parameter API

swagger を実行し json ファイルを得る

ひと通りアノテーションを書いたので、これらのアノテーションから json ファイルを作る。 この手順は swagger-php をインストールしてコマンドを叩けばOK。これを swagger-ui が食う。

というわけで composer から swagger-php を入れる。

     ],
     "require": {
         "php": ">=5.4.0",
-        "silex/silex": "~1.1"
+        "silex/silex": "~1.1",
+        "zircote/swagger-php": "0.9.0"
     }
 }

はい

composer update

一応 swagger コマンドが使えるか見る。

 ~/xxx/swagger-assert-sandbox/ [master*] ./vendor/bin/swagger -h
Swagger-PHP 0.9.0
-----------------
Generate Swagger JSON documents for a PHP project.

Usage: (略)

大丈夫そうならこんな感じで実行。

 ~/xxx/swagger-assert-sandbox/ [master*] ./vendor/bin/swagger web --default-base-path 'http://localhost:8080' -o ./web/data/

-o 以下のディレクトリに json ファイルがちゃんと書きだされていたら成功。 -h で確認できるが、最初の引数がアノテーションの解析対象にするディレクトリ、default-base-path は swagger-ui のリクエスト先となるURL、-o は json ファイルの書き出し先になる。

サンプルプロジェクトのコード差分 - create swagger json

swagger-ui に json ファイルを食わせる

最後に、生成した json ファイルを swagger-ui に食わせる。これは結構簡単で、swagger-ui リポジトリを clone して、dist 以下をドキュメントルート以下に配置すればよい。

一点、dist 以下にある index.html の url を自分の json ファイルがあるパスに変える必要がある点だけ注意する。サンプルプロジェクトだとこんな感じになる。

  <script type="text/javascript">
    $(function () {
      window.swaggerUi = new SwaggerUi({
-      url: "http://petstore.swagger.wordnik.com/api/api-docs",
+      url: "http://localhost:8080/data/",
      dom_id: "swagger-ui-container",
      supportedSubmitMethods: ['get', 'post', 'put', 'delete'],
      onComplete: function(swaggerApi, swaggerUi){
        log("Loaded SwaggerUI");

サンプルプロジェクトのコード差分 - introduce swagger-ui

完成品を見てみる

うまくいくとこんな感じのドキュメントができる。

なお、サンプルプロジェクトは以下の手順で試せると思う。

git clone git@github.com:gong023/swagger-assert-sandbox.git
cd swagger-assert-sandbox
composer install
./bin/swagger
./bin/server
# http://localhost:8080/swagger-ui にアクセス

こんな感じで、何はなくとも swagger の面倒臭さだけは伝わったと思う。 面倒くさいが、代わりに仰々しいまでのドキュメントを得ることができる。API の実行環境が手軽に手に入るのも利点と言えば利点ではある。 swagger はカジュアル用途には向かないが、そうでない場合威力を発揮できる代物だと思う。