Open API Generatorが生成したコードのビルドが通らなかったのでテンプレートをカスタマイズしてみた


# 概要
* REST APIのクライアントを作成する際、Open API GeneratorやSwagger Codegenを利用してコードを自動生成することが多いと思います。
* しかし、生成されたコードが期待していたものとちょっと異なることがあります。
* この記事では、自動生成用のテンプレートをカスタマイズして、希望通りのコードを生成する方法を紹介します。

# 環境
* Windows 10 Professional
* Java 1.8.0
* Visual Studio 2017

# カスタマイズ内容
* 今回は、C#用のテンプレートをカスタマイズします。
* 自動生成コードのビルドが通らなかったので、ビルドを通します。
* 他の言語でもテンプレートが異なるだけで手順は同じです。

# Open API Generatorのダウンロード
以下のファイルを任意のフォルダにダウンロードします。

* [openapi-generator-cli-4.2.3.jar](https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/4.2.3/openapi-generator-cli-4.2.3.jar)

以降、Open API Generatorを例に説明を行いますが、Swagger Codegenでもほぼ同じ手順になります。



# サンプルAPI
サンプルとして、helloインターフェイスを定義します。

リクエスト

```
GET /hello
```

レスポンス

```
{
    "message": "Hello world!"
}
```


YAMLファイルは以下になります。

```openapi.yaml
openapi: 3.0.0
servers:
  - url: 'http://localhost:8080/service/v1'
info:
  version: 1.0.0
  title: Service API
tags:
  - name: Hello
paths:
  /hello:
    get:
      tags:
        - Hello
      summary: Hello
      description: hello
      operationId: hello
      responses:
        '200':
          description: successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Message'
components:
  schemas:
    Message:
      type: object
      properties:
        message:
          type: string
```

# コード生成
クライアントコードを生成します。
コード生成の前準備として、openapi-generator-cli-4.2.3.jarのあるフォルダに以下のファイルを作成します。

* openapi.yaml : API定義（前述）
* codegen_config.json : コンフィグレーションファイル
* csharp-client.bat : コード生成バッチファイル

```codegen_config.json
{
    "packageName": "service",
    "optionalAssemblyInfo": false,
    "optionalProjectFile": false
}
```

```csharp-client.bat
cd %~dp0
set executable=openapi-generator-cli-4.2.3.jar

set JAVA_OPTS=%JAVA_OPTS% -Xmx1024M
set args=generate -i openapi.yaml -g csharp -o .gen_client -c codegen_config.json --additional-properties hideGenerationTimestamp=true

java %JAVA_OPTS% -jar %executable% %args% > log.txt 
```
csharp-client.batを実行して、コードを生成します。

```
> csharp-client.bat
```

.gen_client以下のフォルダにコードが生成されます。

```
+ .gen_client
    + src
        + service
            + Api
                + HelloApi.cs
            + Client
                + ApiClient.cs
                + ApiException.cs
                + ApiResponse.cs
                + Configuration.cs
                + ExceptionFactory.cs
                + GlobalConfiguration.cs
                + IApiAccessor.cs
                + IReadableConfiguration.cs
                + OpenAPIDateConverter.cs
            + Model
                + Message.cs
```
# ビルド
Visual Studio 2017で[ファイル]-[新規作成]-[プロジェクト]-[.NET Standard]-[クラスライブラリ (.NET Standard)]を選択して、
プロジェクトを作成します。（ここではHelloClientとします）

プロジェクトを作成したフォルダに自動生成したコードをコピーします。
フォルダ構成が以下の通りになるようにApi, Client, Modelフォルダを配置します。

```
+ HelloClient
    + HelloClient.csproj
    + Api
        + HelloApi.cs
    + Client
        + ApiClient.cs
        + ApiException.cs
        + ApiResponse.cs
        + Configuration.cs        
        + ExceptionFactory.cs
        + GlobalConfiguration.cs
        + IApiAccessor.cs
        + IReadableConfiguration.cs
        + OpenAPIDateConverter.cs
    + Model
        + Message.cs
```

プロジェクトの[依存関係]<右クリック>-[Nugetパッケージの管理]から以下をインストールします。

```
Microsoft.CSharp v4.7.0
System.ComponentModel.Annotations v4.7.0
RestSharp v106.10.1
Newtonsoft.Json v12.0.3
```


ビルドすると、エラー1件とワーニング1件が発生しました。

```
1>Client\ApiClient.cs(137,51,137,69): error CS1503: 引数 2: は 'System.Action<System.IO.Stream>' から 'byte[]' へ変換することはできません。
1>Client\ApiClient.cs(207,34,207,70): warning CS0618: 'RestClient.ExecuteTaskAsync(IRestRequest)' は旧形式です ('Use ExecuteAsync instead')
```

ApiClient.csでエラーになっているので、ApiClient.csを生成するためのテンプレートに間違いがあるのかもしれません。

# テンプレートのカスタマイズ

## テンプレートファイルを探す
まずは、openapi-generator-cli-4.2.3.jarの中からテンプレートファイルを見つけます。
ファイルの拡張子をjarからzipに変更して展開すると、プログラミング言語毎にフォルダが分かれていることが分かります。
C#用のテンプレートはcsharpフォルダに入っており、ApiClient.mustacheがApiClient.csのテンプレートファイルだと推測がつきます。

```
csharp\ApiClient.mustache
```

テンプレートファイルの拡張子が.mustacheなので、テンプレートエンジンとして[mustache](https://mustache.github.io/)を使用していることが分かります。



## テンプレートファイルの抽出
修正したいテンプレートファイルをopenapi-generator-cli-4.2.3.jarから取り出します。

```
> jar -xvf openapi-generator-cli-4.2.3.jar csharp/ApiClient.mustache
 csharp/ApiClient.mustacheが展開されました
```
csharpフォルダにApiClient.mustacheファイルが作成されます。

## テンプレートファイルの編集
抽出したテンプレートファイルを編集します。

### error CS1503: 引数 2: は 'System.Action<System.IO.Stream>' から 'byte[]' へ変換することはできません。

IRestRequest.AddFile()の呼び出しで、引数が足りない（ContentLengthの指定が抜けている）ようなので追加します。

```csharp\ApiClient.mustache
        // Creates and sets up a RestRequest prior to a call.
        private RestRequest PrepareRequest(
           ...)
        {
            ...
            // add file parameter, if any
            foreach(var param in fileParams)
            {
                ...
                // 変更前
                //request.AddFile(param.Value.Name, param.Value.Writer, param.Value.FileName, param.Value.ContentType);
                // 変更後
                request.AddFile(param.Value.Name, param.Value.Writer, param.Value.FileName, param.Value.ContentLength,  param.Value.ContentType); 
                ...
            }
            ...
        }
```


### warning CS0618: 'RestClient.ExecuteTaskAsync(IRestRequest)' は旧形式です ('Use ExecuteAsync instead')

IRestClient.ExecuteTaskAsync()はもう古いみたいなので、ExecuteAsync()に変更します。


```csharp\ApiClient.mustache
        public async System.Threading.Tasks.Task<Object> CallApiAsync(
            ...)
        {
            ...
            // 変更前
            //var response = await RestClient.Execute{{^netStandard}}TaskAsync{{/netStandard}}(request);
            // 変更後
            var response = await RestClient.Execute{{^netStandard}}Async{{/netStandard}}(request);
            ...
        }
```



## テンプレートファイルの更新
jarファイル内のテンプレートファイルを上書きします。

```
> jar -uf openapi-generator-cli-4.2.3.jar csharp/ApiClient.mustache
```

# コード生成＆ビルド
生成されたファイルをプロジェクトフォルダにコピーして、再度コードを生成します。

```
> csharp-client.bat
```

ビルドすると今度は通りました。


```
========== ビルド: 1 正常終了、0 失敗、0 更新不要、0 スキップ ==========
```


# まとめ
* 自動生成用のテンプレートをカスタマイズして、希望通りのコードを生成しました。
* 今回はビルドを通しただけですが、エラー処理やログ出力などをカスタマイズできると便利です。
* 今回はOpen API Generatorをカスタマイズしましたが、Swagger Codegenも同様にカスタマイズ可能です。
* 今回はC#を例にカスタマイズしましたが、他の言語も同様にカスタマイズ可能です。

# 参考
* https://qiita.com/rita_cano_bika/items/f810c27e66946367444d