9
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

クラベスAdvent Calendar 2024

Day 3

openapi-typescriptでFile型を扱う際にハマったこと

Last updated at Posted at 2024-12-02

はじめに

こちらは株式会社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を正常に扱えるようになるはずです。

参考

9
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?