Rails + apipieでDartのクライアントコードを生成する

## 背景

こちらの記事の続きです。

[Docker\+Rails6\+apipieを使ってAPI定義をSwagger UIに表示 \- Qiita](https://qiita.com/yukke722/items/bcf6d2237258631a1b30)

Dart/Flutterでアプリ開発をするメンバーに上記の定義ファイルを送ったところ、「Dartのクライアントコードを自動生成できない」と言われてしまいました。

今回はDartのクライアントコードを自動生成することをゴールに、apipieの設定を書き換えます。

## 環境
```
Ruby: 2.7.1
Rails: 6.0.3
```


## Flutter/Dartとは？

Flutterについてざっくりまとめるとこんな感じでしょうか。

* モバイルアプリフレームワークの１つ
* Googleが開発
* Dartという言語を使う

ReactNativeなどはWeb系経験者がアプリに手を出しやすくするものだが、DartはWeb系の経験がなくとも手を出しやすい、ようです。

また、DartはTypeScriptと似たaltJSです。
基本的にFlutter用の言語で、altJSとして使うんだったらTypeScriptを使うほうが良さげ、なようです。

cf. [Flutterとは何か？ 使うメリットや特徴を理解する \(1/4\)：CodeZine（コードジン）](https://codezine.jp/article/detail/12718)


## apipieの調整

さて実装面。
アプリ担当からは以下のエラーを受け取りました。

```
E/flutter (17797): [ERROR:flutter/lib/ui/ui_dart_state.cc(166)] Unhandled Exception: ApiException 500: Exception during deserialization. (Inner exception: ApiException 500: Could not find a suitable class for deserialization)
E/flutter (17797): 
E/flutter (17797): #0      ApiClient._deserialize (package:openapi/api_client.dart:57:5)
E/flutter (17797): #1      ApiClient._deserialize.<anonymous closure> (package:openapi/api_client.dart:50:43)
E/flutter (17797): #2      MappedIterator.moveNext (dart:_internal/iterable.dart:392:20)
E/flutter (17797): #3      MapBase._fillMapWithIterables (dart:collection/maps.dart:83:39)
E/flutter (17797): #4      new LinkedHashMap.fromIterables (dart:collection/linked_hash_map.dart:127:13)
E/flutter (17797): #5      ApiClient._deserialize (package:openapi/api_client.dart:49:26)
E/flutter (17797): #6      ApiClient.deserialize (package:openapi/api_client.dart:67:12)
E/flutter (17797): #7      UsersApi.postApiV1UsersToken (package:openapi/api/users_api.dart:77:49)
```

当方Dartは未経験でして、エラーを見てもなんのこっちゃだったので、アプリ担当に定義ファイルをいじってもらい動くようにしてもらいました。

その結果、以下3つが主な原因とわかりました。

* basePathの指定がない
* "additionalProperties": falseに指定している
* レスポンスにtitleがない

### basePathを指定する

`api_base_url`が空欄だと警告がでるそうなので追加します。

```ruby:apipie.rb
-  config.api_base_url            = ""
+  config.api_base_url            = "/api"

```

※ [前回の記事](https://qiita.com/yukke722/items/bcf6d2237258631a1b30#apipierb%E3%82%92%E8%AA%BF%E6%95%B4) にて、わざわざ`api_base_url`を消していたようです。。

### "additionalProperties": falseに指定している

`additionalProperties`とは、「propertiesで指定したプロパティ以外も受け付けるかどうか」を指定するオプションのようです。

trueならpropertiesに記載がないプロパティも受け付けるが、falseだとダメなようです。

cf. [OpenAPIのadditional_propertyオプションってなに？](https://yukke722.hatenablog.jp/)

クライアントコード生成時にエラーが起きた原因は、これがfalseになっていたから、のようです。

apipieでは初期値がfalseのため、trueに設定しましょう。

```ruby:apipie.rb
+ config.swagger_allow_additional_properties_in_response = true
```

cf. [Swagger\-Specific Configuration Parameters](https://github.com/Apipie/apipie-rails#swagger-specific-configuration-parameters)

なお、レスポンスする全てのプロパティ（`status, message`など意図して返すもの）を含んでいる状態で生成を試みてもエラーは起きるようです。
（ここは未解決...）

### レスポンスにtitleがない

API定義（swagger.json)に`title`がない場合、クラス名が`InlineResponse200`となってしまうようです。

```swagger.json
            "schema": {
               "type": "object",
+              "title": "Token",
               "properties": {
```

apipieのリファレンスを見ても該当箇所に`title`を追加する記述は見つかりませんでした...。

が、 [Response Description](https://github.com/Apipie/apipie-rails#response-description) に似た実装があり、試したところうまくいきました！

```rails:api/v1/users_controller.rb

+  def_param_group :user_login_token do
+    property :status, Integer, desc: "status code"
+    property :message, String
+    property :detail, String, desc: "user token"
+  end

 api :POST, "/v1/users/token", "get access token"
  description "ログイン認証をしてトークンを返す"
  formats ["json"]
  param :email, String, desc: "メールアドレス", required: true
  param :password, String, desc: "パスワード", required: true
-  returns code: 200, desc: "return user token"
+  returns :user_login_token, desc: "return user token"
  error code: 401, desc: "Unauthorized."
```

以上の修正をすることで、Dartのクライアントコードを生成できるようになりました。

### 余談:ブラウザからDartのクライアントコードを作る

1. [Swagger Editor](https://editor.swagger.io/) にAPI定義ファイルをペーストする
2. ヘッダーの`Generate Client`から`dart`を選択する

という方法で、クライアントコードを生成できます。
今回程度の動作確認であれば十分できますね。