【備忘録】CSVを返すAPIの定義を検討する

業務でCSVをレスポンスとして返すAPIをOpenAPIで定義することになったのでメモを作成します。
このAPIはSPAのバックエンドとして稼働します。

## APIの概要

- リクエスト： リクエストボディでSQLの検索条件を指定する
- レスポンス： 取得したデータが書き込まれたCSVを返却する

今回はリクエストやデータ取得のロジックは既に決まっているので、レスポンスの定義のみを検討します。

## 作業メモ

### 論点整理

最近は要件が明確になっていない作業を進めるときはまずChatGPTにお願いして大まかな叩き台を作ってもらうことが多いです。今回もその方法をとります。

```:プロンプト
OpenAPIでCSVをレスポンスとして返却するAPIを定義します。

1.  レスポンスを定義する際に考慮すべき点を挙げて、それらについてあなたの実装方針を検討してください
2. 1.の作業結果を踏まえてAPI定義のyamlファイルを作成してください
```

```:chatGPTが生成したテキスト①
OpenAPIでCSVをレスポンスとして返却する際に考慮すべき点は以下の通りです。

1. Content-Type: 必ずtext/csvを明示することで、レスポンスがCSV形式であることをクライアントに伝えます。
2. エンコーディング: CSVのエンコーディング（例：UTF-8）を明記するか、またはクライアントが指定できるようにするかを検討する必要があります。
3. ファイル名: Content-Disposition ヘッダーを用いて、ダウンロード時のファイル名を指定できるようにするかを検討する必要があります。
4. エラーレスポンス: CSV生成や取得に問題があった場合のエラーレスポンスの形式を定義します。
5. レスポンスのサイズ: 大量のデータを返却する場合、ページングやフィルタリングを検討することも考慮する必要があります。


実装方針:

1. Content-Typeをtext/csvとする。
2. エンコーディングはUTF-8をデフォルトとする。特に指定がなければこれを使用する。
3. レスポンスヘッダーにContent-Dispositionを設定して、ダウンロード時のファイル名をdata.csvとする。
4. エラー時はJSON形式でエラーメッセージを返却する。
5. ページングやフィルタリングの要件が具体的に分からないので、この例では省略します。
```

```sample.yaml
openapi: 3.0.0
info:
  title: CSV API
  version: 1.0.0

# 異常系のレスポンスは今回のスコープ外のため⋮
paths:
  /data:
    get:
      summary: Get data in CSV format
      responses:
        '200':
          description: Successfully retrieved data
          content:
            text/csv:
              schema:
                type: string
                example: "id,name,age\n1,John,25\n2,Jane,28"
          headers:
            Content-Disposition:
              description: Specifies the filename for the downloaded CSV
              schema:
                type: string
                example: attachment; filename=data.csv
```

論点が5つ挙げられました。

1. Content-Type
1. エンコーディング
1. ファイル名
1. エラーレスポンス
1. レスポンスのサイズ

上記5つの論点のうち、まずは今回のタスクで検討しなくてもよいものを精査します。

- Content-Type: 考慮していなかったので**要検討**
- エンコーディング：UTF-8固定なので検討不要
- ファイル名： 動的にファイル名を指定する必要があるので**要検討**
- エラーレスポンス： 既に共通仕様として定義しているため考慮不要
- レスポンスのサイズ： 最大1000件程度のため考慮不要

Content-Typeとファイル名が検討事項として残りました。(ファイル名というより、突き詰めるとヘッダ全体を考慮する必要がありそうです。)

### 個別の論点検討

#### Content-Type

ChatGPTは```text/csv```を提案してくれましたが念のために他に候補がないかも確認しておきます。

```:プロンプト②
CSVのContent-Typeについてtext/csv以外の候補があれば教えてください。
また、それらのメリット/デメリットを比較した上でどれを採用することが推奨されるか提案してください

回答にはあなたが参照した資料のURLを記載してください
```

```:ChatGPTが生成したテキスト②
CSVに関するContent-Typeとしては、以下の主要なものが挙げられます。

1. text/csv
2. application/csv
3. application/octet-stream
それぞれのメリット・デメリットを以下に示します。

1. text/csv
メリット:
RFC 4180に基づく、CSVに関連する公式なMIMEタイプとして広く認識されています。
クライアントやツールがCSVとして認識する確率が高い。
デメリット:
特定のケースでは、テキストのように表示されてしまう可能性があるが、これはContent-Dispositionヘッダーで管理できます。
2. application/csv
メリット:
一部のシステムやアプリケーションでデータを扱う際に、application/*の方が適していると考えられる場合がある。
デメリット:
text/csvに比べて標準的ではないため、認識されない可能性が高まります。
不必要に特殊なケースのためのものと誤解される恐れがあります。
3. application/octet-stream
メリット:
任意のファイルタイプに使用でき、ファイルとしてダウンロードを促進します。
クライアント側でtext/csvやapplication/csvを正しく解釈しない古いシステムやツールでも、ファイルとして扱うことができる。
デメリット:
非特定のバイナリデータを示すため、クライアントは内容について何も知らされていません。
ブラウザは通常、application/octet-streamのコンテンツを表示するのではなく、ダウンロードするように促します。

推奨する選択:
通常のケースでは、text/csvを選択することを推奨します。これはRFC 4180（参考URL: https://tools.ietf.org/html/rfc4180）に基づく公式なMIMEタイプであるため、広く認識されると期待されます。特別な理由がない限り、この選択が最も無難です。

特定のシステムやツールとの互換性を考慮する場合や、ダウンロードを主な目的とする場合は、application/octet-streamも適切な選択となる場合があります。
```

今回作成するAPIはSPAのバックエンドサーバとして利用されるので、application/octet-streamも候補になりそうです。

```text/csv```と```application/octet-stream```を候補としてチームで検討する方針とします。

### ファイル名(およびヘッダ)

続いてヘッダについても検討します。

```:プロンプト③
レスポンスのheadersについて検討すべき点を挙げてあなたの実装案を出してください
```

```md:ChatGPTの回答③

APIのレスポンスヘッダーは、メタデータや操作指示などの追加情報を提供するために使用されます。以下に、レスポンスのヘッダーに関して検討すべき点と、実装の提案を示します。

1. 検討すべきレスポンスヘッダー

- Content-Disposition:
  - ダウンロードの振る舞いを指定するために使用。例えば、直接表示ではなくダウンロードを強制する場合や、提供されるファイル名を指定する場合に使用します。


- Content-Type:
  - レスポンスのメディアタイプを示す。CSVの場合、通常はtext/csvまたはapplication/octet-streamが使用されます。

- Content-Length:
  - レスポンスのサイズを示す。ダウンロードの進行状況の表示などで役立ちます。

- Cache-Control & Expires:
  - キャッシュの動作を指示する。ダウンロードファイルが頻繁に更新される場合や、一時的なファイルの場合にはキャッシュを無効にすることを検討する必要があります。

- ETag:
  - レスポンスのバージョンを示す。クライアントがキャッシュしたコンテンツが最新かどうかを判断するのに役立ちます。

- Access-Control-Allow-Origin:
  - CORSヘッダー。SPAが異なるオリジンからのリクエストをサポートしている場合に、このヘッダーの設定が必要です。
```

実装例は長いので省略


以下のようなヘッダを検討する必要がありそうです。

- Content-Disposition: コンテンツをWebページとして表示するか、ダウンロードするかを示す。
  - 今回はCSVファイルをダウンロードしたいので```attachment```とします。また、ファイル名を動的に生成するので```filename```は指定しません。
- Content-Type: ```content```で定義するため定義不要
- Content-Length: レスポンスのサイズを保持する。ダウンロード状況を表示する必要がある場合は必要になりそう。今回は要件が未確定のため別途検討
- Cache-Control / Expires: キャッシュの挙動を示す。CSVファイルは頻繁にはダウンロードされなさそうなのでキャッシュは無効化してよさそう
- Etag: レスポンスのバージョンを示す。キャッシュを使わないなら考慮不要
- Access-Control-Allow-Origin:CORSのヘッダー。別途検討

この辺りはフロントエンド開発者と相談しながら決めるのが良さそうです。


### 参考資料

- [OpenAPI Specification](https://swagger.io/specification/)
- [HTTP ヘッダー](https://developer.mozilla.org/ja/docs/Web/HTTP/Headers)