SwaggerドキュメントからOpenAPI Generatorを使ってモックサーバー作成

この記事では以下の内容について記述しています。

* Swaggerで作成したAPIのドキュメントからOpenAPI Generatorをつかってモックサーバを立てる

本記事ではSwaggerについての記述はありません。
そのあたりは@teinen_qiitaさんの[OpenAPI (Swagger) 超入](https://qiita.com/teinen_qiita/items/e440ca7b1b52ec918f1b)を参考にしてみてください。

dockerの利用が前提になっておりますので、dockerを使ったことがない方はその辺りを調べてから読まれる事をおすすめします。
また、kotlinを利用するのでJavaも必須になります、そちらのインストールができていない方もインストールを終わらせておいてください。

## OpenAPI GeneratorとSwagger Codegenについて

[OpenAPI Generator](https://github.com/OpenAPITools/openapi-generator)は[Swagger Codegen](https://github.com/swagger-api/swagger-codegen)からforkされて作られたプロジェクトです。

どちらもプロジェクトとしては動いているのですが、OpenAPI3.0への対応状況を見てるとOpenAPI Generatorのほうが少し対応が早いようです。
大まかな使い方はどちらも一緒ですが、今回はOpenAPI Generatorを使ってモックサーバーの構築を行ってみたいと思います。

## モックサーバー構成

* localhost:8080 -> モックサーバー
* localhost:8081 -> Swagger UI

2つのサーバをそれぞれdockerを利用して起動させます。
本当は、nodejsでサーバーを立てたかったのですが、色々と不具合が多かったので今回はkotlinを使ってサーバーを構築します。

## 環境構築
基本的にはMac環境での構築となりますので、Windowsの方やLinuxの方は適宜読み替えてください。
また、インストールの方法は[公式](https://github.com/OpenAPITools/openapi-generator)に詳しく載っていますので、環境が違うかはそちらをお読みください。

### OpenAPI Generatorのインストール

Home Brewを使ってOpenAPI Generatorのインストールを行います。

```
$ brew install openapi-generator
```

### フォルダ構成
今回のモックサーバー構築にあたってのフォルダ構成です。
swaggerフォルダの中にconfig.jsonとswagger.yml、あとdockerでサーバーを動かすのでdocker-compose.ymlを作成します。
また出力されたファイルを少し整形するためにsed.shを作成します。
openapiは自動で生成させれるので作っておかなくて大丈夫です。

```
.
├── swagger/
│   ├── config.json
│   ├── swagger.yml
│   ├── sed.sh
│   └── docker-compose.yml
└── openapi/
```

#### swagger.yml
```
openapi: 3.0.0
info:
  title: sample API
  description: sample API
  version: 0.0.1
servers:
  - url: 'http://localhost:8080'
paths:
  /profiles:
    get:
      tags:
        - Profile
      operationId: getProfiles
      description: Get Profiles
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                required:
                  - profiles
                type: object
                properties:
                  profiles:
                    type: array
                    items:
                      $ref: '#/components/schemas/Profile'
    post:
      tags:
        - Profile
      operationId: postProfiles
      description: Post Profiles
      requestBody:
        content:
          application/json:
            schema:
              required:
                - profile
              type: object
              properties:
                profile:
                  $ref: '#/components/schemas/Profile'
      responses:
        '201':
          description: OK
          content:
            application/json:
              schema:
                required:
                  - profile
                type: object
                properties:
                  profile:
                    $ref: '#/components/schemas/Profile'
  '/profiles/{userId}':
    get:
      tags:
        - Profile
      operationId: getProfile
      description: Get Profile
      parameters:
        - in: path
          name: userId
          schema:
            type: string
            example: random_user_id
          required: true
          description: User ID
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                required:
                  - profile
                type: object
                properties:
                  profile:
                    $ref: '#/components/schemas/Profile'
components:
  schemas:
    Profile:
      required:
        - user_id
        - name
        - birthday
        - prefectures
        - occupation
        - image
        - cigarette
        - alcohol
      type: object
      properties:
        user_id:
          type: string
          example: random_user_id
        name:
          type: string
          example: テストユーザー
        birthday:
          type: string
          format: date
          example: '1996-10-25'
        prefectures:
          type: string
          example: 東京都
        occupation:
          type: string
          example: 会社員
        image:
          type: string
          example: /user.png
          nullable: true
        cigarette:
          type: integer
          format: int32
          example: 1
        alcohol:
          type: integer
          format: int32
          example: 0

```

#### config.json
Swagger UIとモックサーバーではドメインが異なるためCORSの問題が発生します。
そのため下記の設定をしてCORSに対応するようにします。

```
{
  "featureCORS": true
} 
```

#### sed.sh
CORSの設定をすると書き出されるファイルの記述が一部間違っているため、それをsedを使って修正します。

```
#!/bin/bash
sed -i -e s/^import\ io\.ktor\.features\.HSTS\$/import\ io\.ktor\.features\.HSTS\\$'\n'import\ io\.ktor\.features\.CORS/ openapi/src/main/kotlin/org/openapitools/server/AppMain.kt
sed -i -e s/^import\ io\.ktor\.features\.HSTS\$/import\ io\.ktor\.features\.HSTS\\$'\n'import\ io\.ktor\.features\.CORS/ openapi/src/main/kotlin/org/openapitools/server/Configuration.kt
sed -i -e s/\\/\\/\ anyHost/anyHost/ openapi/src/main/kotlin/org/openapitools/server/Configuration.kt
```

#### docker-compose.yml
モックサーバーとSwagger UIをそれぞれの設定をします。
Swagger UIはイメージがそのままあるので、そちらを利用します。

```
version: '3'

services:
  openapi:
    build:
      context: ../openapi
      dockerfile: ./Dockerfile
    ports:
      - 8080:8080
  swagger-ui:
    image: swaggerapi/swagger-ui
    ports:
      - 8081:8080
    volumes:
      - ./swagger.yml:/usr/share/nginx/html/swagger.yml
    environment:
      API_URL: ./swagger.yml
```

## モックサーバーの作成
あとは下記のコマンドを順繰りに実行していけばサーバーが起動します。

### openapi-generator

```
$ openapi-generator generate -c swagger/config.json -i swagger/swagger.yml -g kotlin-server -o openapi; swagger/sed.sh 
```

### コンパイル
```
$ cd openapi; gradle wrapper; ./gradlew check assemble
```

### dockerのビルド
```
$ cd ../; docker-compose -f swagger/docker-compose.yml build
```

### dockerの起動
```
$ docker-compose -f swagger/docker-compose.yml up -d
```

## 動作の確認
[http://localhost:8081](http://localhost:8081)にアクセスして無事にSwagger UIが起動したら、Swagger UI上でテストをしてみてください。
正しくデータが返ってくれば完成です。

## 最後に
今回記述したコマンドをシェルスクリプト等に保存して一発で起動するようにしておけばswagger.ymlを変更してすぐにモックサーバーを構築できて開発が幸せになれます。