Swagger を用いたお手軽 API 作成

　入社以前にAPIの作成に用いていた Swagger が便利だった記憶があるので、当時浅い理解で雑に使っていたことがもったいなかったと思い、紹介と勉強のために軽くしたためる。

### Swagger とは
> The design and documentation platform for teams and individuals working with the OpenAPI Specification.
[Swagger Tools](https://swagger.io/tools/)

　大きく3つのツール

- Editor  
  yml/json の形式でAPIの定義を書くことで後述のUIを吐き出すツール
- UI  
  上記Editorに適切なyml/jsonをインポートすることでAPIがどのようなやりとりをするのか視覚的に見せるツール
- Codegen  
  作成したAPIを各種言語で使えるようにExportするツール、HTMLで吐き出すとUIの機能をGitHub等で管理できる

で構成される。以前触った時は上の2つには触れたが、Codegenの威力に触れる機会が少なかったので、EditorとCodegenを中心に紹介したい。
　弊社はマンガコンテンツを配信しているので、マンガを例にAPIを書いていく。

### Editor/UI  
　yml全体は[GitHub](https://github.com/Mao0857/Swagger2020/blob/master/yml/swagger.yml)に置いておき、エンドポイントやdefinitionを少し紹介する(一部改変)。リンク先のymlファイルを[Swagger Editor](https://editor.swagger.io/)に貼り付けながら読むと理解が深まりやすいと思われる。

```
paths:
  /mangas:
    get:
      tags:
      - manga
      operationId: getMangas
      responses:
        200:
          description: successful operation
          schema:
            type: array
            items:
              $ref: '#/definitions/Manga'
definitions:
  Manga:
    type: object
    required:
    - id
    properties:
      id:
        type: integer
        format: int64
        example: 1
      name:
        type: string
        example: クレッフィーの大冒険！
      releaseDate:
        type: string
        format: date-time
        example: '2020-12-01 00:00:00.000'
      chapters:
        type: array
        minLength: 1
        items:
          $ref: '#/definitions/Chapter'
        example:
        - {id: 1, name: '威張れ！クレッフィー！'}
        - {id: 2, name: '電磁波！クレッフィー！'}
  Chapter:
    required:
    - id
    - name
    properties:
      id:
        type: integer
        example: 1
      name:
        type: string
        example: 威張れ！クレッフィー！
      pages:
        type: array
        items:
          type: string
          format: url
        example:
        - '/image/1'
        - '/image/2'
```

　なんとなく見ればわかるが、path以下で各種エンドポイントをかき、返す値やプロパティーにdefinitionsで定義しているオブジェクトを指定することができる。get methodだけだとわかりにくいので、コメントを送信するエンドポイントもみてみる。

```
path:
  ...
  /comments:
    get:
    ...
    put:
      tags:
      - comment
      summary: Send comment to title
      operationId: putCommentByTitleId
      parameters:
      - name: titleId
        in: query
        type: integer
        format: int64
        description: ID of title id
        required: true
      - name: userId
        in: query
        type: integer
        format: int64
        description: ID of user id
        required: true
      - name: body
        in: query
        type: string
        description: comment
        required: true
      responses:
        200:
          description: successful operation
        400:
          description: Bad Request
definitions:
  ...
  Comment:
    required:
    - userId
    - body
    properties:
      id:
        type: integer
        example: 1
      userId:
        type: integer
        example: 1
      titleId:
        type: integer
        example: 1
      body:
        type: string
        example: IBARU JAPAN
```

　query paramとして、titleId,userId,bodyを必須とするputエンドポイントを定義している[^1]。[認証周りの設定](https://swagger.io/docs/specification/authentication/)等もでき、それなりに幅の広いAPIが作れるようだ。
[^1]: parametersの`in`にはqueryの他にbody,header,formData,pathが指定できる。
　軽くUIを紹介すると、pathごとに以下のようなカード(?)が作られ、視覚的にどのようなparam,queryが必要になるのか、responseの形はどうなっているのかなどがわかるようになっている(exampleの値を定義しておくと嬉しいのはこの箇所)。
<img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/927098/240da826-df25-422b-4354-714c992304b7.png">

### Codegen  
　Editorで書いたyml/jsonは Client SDK, Server Stub, Documentに吐き出すことができる。

#### Client SDK
　[GitHub](https://github.com/Mao0857/Swagger2020/tree/master/client)にはjavascriptのSDKを置いておく。他にも Java, Android, Objective-C, C#, PHP, Perl, Python に吐き出すことができる。
　[Chapter get](https://github.com/Mao0857/Swagger2020/blob/master/client/src/api/ChapterApi.js#L65-#L96)の例を置く。

```
[getChapterById: /chapter/{id} のエンドポイントを定義]
this.getChapterById = function(id, callback) {
  var postBody = null;
  // verify the required parameter 'id' is set
  if (id === undefined || id === null) {
    throw new Error("Missing the required parameter 'id' when calling getChapterById");
  }
  var pathParams = { 'id': id };
  var queryParams = {  };
  var collectionQueryParams = {  };
  var headerParams = {  };
  var formParams = {  };
  var authNames = [];
  var contentTypes = [];
  var accepts = [];
  var returnType = Chapter; ・・・ (*)
  return this.apiClient.callApi(
    '/chapters/{id}', 'GET',
    pathParams, queryParams, collectionQueryParams, headerParams, formParams, postBody,
    authNames, contentTypes, accepts, returnType, callback
  );
}
```
　最後に呼び出されている `this.apiClient.callApi()` は [ここ](https://github.com/Mao0857/Swagger2020/blob/master/client/src/ApiClient.js#L379)に定義されている関数で、 `(*)returnType` は `src/model/Chapters.js` に定義される[^2]。
[^2]: `src/model` 以下には yml/json で definitions に書かれているものが吐き出されている。
　作られた SDK は

```
[JavaScript]
var Swaggerapi = require('swaggerapi');
var api = new Swaggerapi.ChapterApi();
var id = 1; // get id from path
var callback = function(error, data, response) {
  if (error) {
    console.error(error);
  } else {
    console.log('API called successfully. Returned data: ' + data);
  }
};
api.getChapterById(id, callback);
```
このように用いられる。callbackを上記のように定義することで、apiの結果であるdataを出力している。
　ソースコードだけでなく、ドキュメントが出力されるのも特徴の一つで、[ChapterApi](https://github.com/Mao0857/Swagger2020/blob/master/client/docs/ChapterApi.md)にあるように、Markdown形式で必要なパラメーターや先にあげたSDK使用例のようなサンプルコードも乗っている。その返り値のプロパティーも[リンク先](https://github.com/Mao0857/Swagger2020/blob/master/client/docs/Chapter.md)のように出力される。これを用いることでクライアントエンジニアは出力されたSDKとドキュメントさえ読めばサーバーと通信可能なアプリケーションを作れる。

#### Server Stub
　[Server](https://github.com/Mao0857/Swagger2020/tree/master/server/SymfonyBundle-php)も出力可能だ。PHPを例に出力しているが、Client SDK同様、さまざまな言語に対応している。
　[ChapterController](https://github.com/Mao0857/Swagger2020/blob/master/server/SymfonyBundle-php/Controller/ChapterController.php#L59-#L122)をみてみる。

```
public function getChapterByIdAction(Request $request, $id)
{
    $produces = [];
    $clientAccepts = $request->headers->has('Accept') ? $request->headers->get('Accept') : '*/*';
    $responseFormat = $this->getOutputFormat($clientAccepts, $produces);
    if ($responseFormat === null) {
        return new Response('', 406);
    }
    $id = $this->deserialize($id, 'int', 'string');

    // Validate the input values
    $asserts = [];
    $asserts[] = new Assert\NotNull();
    $asserts[] = new Assert\Type("int");
    $response = $this->validate($id, $asserts);
    if ($response instanceof Response) {
        return $response;
    }

    try {
        $handler = $this->getApiHandler();
        $responseCode = 200;
        $responseHeaders = [];
        $result = $handler->getChapterById($id, $responseCode, $responseHeaders);
        $message = 'successful operation';
        switch ($responseCode) {
            case 200:
                $message = 'successful operation';
                break;
        }

        return new Response(
            $result ? $this->serialize($result, $responseFormat) : '',
            $responseCode,
            array_merge($responseHeaders, [
                'Content-Type' => $responseFormat,
                'X-Swagger-Message' => $message
            ])
        );
    } catch (Exception $fallthrough) {
        return $this->createErrorResponse(new HttpException(500, 'An unsuspected error occurred.', $fallthrough));
    }
}
```
　[routing.yml](https://github.com/Mao0857/Swagger2020/blob/master/server/SymfonyBundle-php/Resources/config/routing.yml)に書かれているパスへのアクセスをコントローラーに投げらている。`ChapterApiInterface`を継承した`ChapterApi`を定義し、その中で`$id`に当たるチャプターの情報を返すようにすれば、サーバーサイドが出来上がる[^3]。ChapterModelも出力されているので実装のコストはかからない。
[^3]: [README#getting-started](https://github.com/Mao0857/Swagger2020/blob/master/server/SymfonyBundle-php/README.md#getting-started)参照
　Client同様、Markdown形式のAPI Referenceが出力される。そのためサーバーエンジニアも出力されたStubとドキュメントさえ読めばクライアントから送られてくるパラメーター及びレスポンスとしてどのようなものが期待されているのかが明確にわかるようになっている。

#### Document   
　毎回yml/jsonをSwagger UIにコピーしないとSwaggerのいい点である視覚的・直感的な理解はできないわけではない。`/html/index.html` を開くと
![スクリーンショット 2020-12-15 12.30.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/927098/c8bb7bc2-8a61-14dd-e177-62dca08c771b.png)
このようなページが開く。SwaggerEditorを用いて表示を毎回行う必要はなく、htmlファイルを開けばそれ以上の機能が再現できる。エンドポイント一覧がページ全体の左側にまとまっているため、必要なエンドポイントをすぐにリンクで飛んで確認できる。curlコマンドが写っているが、それだけでなく、各種言語のサンプルコードも吐き出される[^4]。

[^4]: Client SDKを動かすサンプル。

### 最後に
　実際に動かすところまでできれば行きたかったが、そこまで進めることができずに残念に思う。その実、動かしてみないとわからないことも多いと思われ、また浅い理解に終わってしまった。次触ることがあれば動かしてみたい。
　最大の特徴としてはクライエントエンジニアはSDK及び一緒に出力されたリファレンスを、サーバーエンジニアも出力されたStubとリファレンスをそれぞれ見れば意思疎通が可能なことにある。大元のひとつのyml/jsonからそれぞれのエンジニアにとって必要なリファレンス、ソースコードが出力されるため、お互いが編集する必要の出る箇所はyml/jsonのみにとどまる。複数箇所を編集する必要がなくなるため、引き継ぎやドキュメントの更新忘れなどでの人的エラーが起こりにくくなる。
　まだまだ伝えきれていない魅力・私自身が理解していない仕様などがたくさんある、間違い・勘違いがあればご指摘いただきたい。割とテストコードを改変して遊んだ程度に止まってしまったので、[ドキュメント](https://swagger.io/docs/specification/about/)をちゃんと読んできちんとしたものを作れると力になりそう。  
　ところで、どうして"Swagger"なんだろう。「威張る」って意味だよな、、、と思っていだが、形容詞が「粋な・スマートな」という意味らしく、命名もなんとなく納得。