はじめに
こちらは株式会社CLAVESアドベントカレンダーの関連記事です。
https://qiita.com/advent-calendar/2024/claves
openapi-typescriptとは
openapi-typescriptは、OpenAPI/Swagger仕様書からTypeScriptの型定義を自動生成してくれるライブラリの一つです。
プロジェクトでこちらのライブラリを採用した際に、つまづいた点について残しておきます。
openapi-typescriptでFileを扱う際のトラブル
以下のようなopenapi定義を例に挙げます。
一般的なファイルアップロードのリクエストボディの定義です。
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
properties:
filename:
type: string
description: Name of the file
file:
type: string
format: binary
description: The image file to upload
required:
- file
こちらを元に、ドキュメントの通りにnpx openapi-typescript
コマンドで型生成を行うと、以下のような型が生成されます。
requestBody: {
content: {
"multipart/form-data": {
/** @description Name of the file */
filename?: string;
/**
* Format: binary
* @description The image file to upload
*/
file: string;
};
};
};
fileについて、string型で生成されているのがわかると思います。
Format: binaryはコメント状では記載がありますが、型生成上は無視されてしまっています。
これについては、以下のissue内で公式からのコメントがありました。
「Support for File Upload #1214」
つまるところ、
- openapi-typescriptはformatプロパティを基本的に無視している。
- この原則から逸脱したかったら、transform APIを用意しているから使ってね。
ということのようです。
対応策
format: binary
について、Blobとして扱いたいケースもあると思うので、対応していきましょう。
型生成
transform APIのドキュメントを参考に、用いて、型生成のスクリプトをカスタマイズします。
// src/api/script/generate-api-types.ts
import fs from 'node:fs'
import openapiTS, { astToString } from 'openapi-typescript'
import path from 'path'
import { factory } from 'typescript'
const BLOB = factory.createTypeReferenceNode(factory.createIdentifier('Blob'))
async function generateTypes() {
const ast = await openapiTS(
// OpenAPI スキーマファイルのパスを指定
new URL(
'../../../openapi.yaml',
import.meta.url,
),
{
transform(schemaObject) {
// binary format の場合、Blob型に変換
if (schemaObject.format === 'binary') {
return BLOB
}
},
},
)
const contents = astToString(ast)
// 生成したい場所にファイルを出力
fs.writeFileSync(path.resolve(process.cwd(), './src/api/index.ts'), contents)
}
generateTypes()
実際に実行します。
npx tsx src/api/script/generate-api-types.ts
出力結果は以下です。
requestBody: {
content: {
"multipart/form-data": {
/** @description Name of the file */
filename?: string;
/**
* Format: binary
* @description The image file to upload
*/
file: Blob;
};
};
};
Format: binary
が指定されたfileについて、Blob
として型生成されました。
openapi-fetchでのリクエスト時
openapi-typescript
を用いる際、openapi-fetch
を共に採用するケースが多いと思います。
openapi-fetchで、今回のようなファイルアップロードAPIへリクエストする際にもハマりどころがあります。
それは、openapi-fetchではデフォルトだと multipart/form-data
指定がopenapi定義上に存在しても、JSON.stringify
されてしまう点です。
multipart/form-data
を使用する場合はbodySerializerを使うようにドキュメントに指示があるので、そちらに沿ってリクエストを作成してみましょう。
const { data, error } = await apiClient.POST("/upload/image", {
body: {
filename: imageName,
file: image,
},
bodySerializer(body: UploadBody) {
const formData = new FormData();
if (body.filename) {
formData.append("filename", body.filename);
}
formData.append("file", body.file);
return formData;
},
});
これでmultipart/form-data
を正常に扱えるようになるはずです。
参考