🤖

生成AIの力を借りてFastAPIからいい感じの型定義を生成する

2024/09/22に公開

よくある FastAPI からの型自動生成のやつ。
出力全般ほぼ Claude 頼みだが、間に挟まる主観的な判断の一部まで任せられたのが面白い + 今後も使いそうなのでメモ。

フロー整理

  1. FastAPI から OpenAPI の JSON ファイルを取得
  2. (任意)生成後に認識しづらい箇所を LLM で修正
  3. 加工後の openapi.json + openapi-generator-cli で型定義ファイルを生成
  4. クライアント側でコール

必要に応じて 2,3 をループ

コードメモ

2. LLM による修正フロー

スキーマによるところがかなり大きいので、あまり詳細を乗せず方針メインで記述。
というよりこの 「読みにくい」箇所の判断もおおかた LLM に任せている
(どのパスにも重複している文字、除去しても判別に困らない文字、…など方向性を与えて置換する処理を書いてもらっている)

基本方針

  • パス名から関数名を生成
    • 不要なパスの除去(/api/v1, /v2など)
    • 大文字/小文字変換
    • キャメルケースに変換
    • ...
  • openapi-generator-cli に対応するための修正
    • operationId の付与

generateOpsIds.cjs

function removePath(str, reg) {
  return str.replace(reg, "")
}

function capitalize(str) {
  return str.charAt(0).toUpperCase() + str.slice(1)
}

function camelCase(str) {
  return str.replace(/-([a-z])/g, (g) => g[1].toUpperCase())
}

function uncapitalize(str) {
  return str.charAt(0).toLowerCase() + str.slice(1)
}

...

3. 実行ファイル例

ここまでの処理を $ npm run generate で実行してくれるようなスクリプトを作成(してもらう)。
下記は一部パスだけ書き換えてあるが、基本生成物そのまま。

generate-api-client.sh

#!/bin/bash

# エラーが発生したら即座に終了
set -e

# スクリプトのディレクトリを取得
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
PROJECT_ROOT="$(dirname "$(dirname "$SCRIPT_DIR")")"

# 一時ファイルのパス
TEMP_OPENAPI_JSON="$PROJECT_ROOT/openapi.json"
TEMP_OPENAPI_WITH_IDS_JSON="$PROJECT_ROOT/openapi_with_operation_ids.json"

echo "Fetching OpenAPI JSON..."
# FASTAPI_URL -> 実際のURLに置き換え
curl -o $TEMP_OPENAPI_JSON FASTAPI_URL/openapi.json || { echo "Failed to fetch OpenAPI JSON"; exit 1; }

# echo "Generating operation IDs..."
node $SCRIPT_DIR/generateOpsIds.cjs || { echo "Failed to generate operation IDs"; exit 1; }

echo "Generating API client..."
openapi-generator-cli generate -i $TEMP_OPENAPI_WITH_IDS_JSON -g typescript-axios -o $PROJECT_ROOT/src/api --skip-validate-spec || { echo "Failed to generate API client"; exit 1; }

echo "Cleaning up temporary files..."
rm $TEMP_OPENAPI_JSON $TEMP_OPENAPI_WITH_IDS_JSON

echo "API client generation completed successfully."

package.json

  "scripts": {
    ...
    "generate": "bash path/to/generate-api-client.sh"
  },

4. クライアント側で利用

型補完が効いたデータフェッチが可能(嬉しい)

// apiInstance.ts
import {
  XXXXXApi,
  YYYYYApi,
  Configuration
} from "@/api" // $PROJECT_ROOT/src/api と対応
const apiConfig = new Configuration({
  basePath: API_BASE_URL,
})
const xxxxApi = new XXXXXApi(apiConfig)
const yyyyApi = new YYYYYApi(apiConfig)

// useXXXX.ts
//  - call sample1
await xxxxApi.postXXXX()

//  - call sample2
await yyyyApi.postYYYY({
  arg1,
  arg2,
  arg3,
  ...
})

Discussion