はじめに
現在保守しているプロジェクトで openapi-generator および openapi-generator-cli を使っているので、それらに関わる小ネタをいくつか共有したいと思います。
最新の @openapitools/openapi-generator-cli
を使おう
プロジェクトの依存する openapi-generator がいくら古かろうと、最新の @openapitools/openapi-generator-cli
を入れて問題ありません。
# 何も気にしないで最新版を入れればOK(常に最新にしてOK)
# (バージョンは https://www.npmjs.com/package/@openapitools/openapi-generator-cli?activeTab=versions を参照)
$ npm install -D @openapitools/openapi-generator-cli
ただし、このままだと生成されるコードが最新の openapi-generator 準拠になってしまうので、以下のコマンドでプロジェクトが依存する openapi-generator のバージョンを指定しましょう。
$ npx openapi-generator-cli version-manager set 5.4.0
# 2系からコマンドは従来の openapi-generator から openapi-generator-cli になっています
あるいは package.json
と同じフォルダに以下のような openapitools.json
ファイルを放り込んでおけばOKです。先ほどのコマンドでも最終的には同様のファイルが生成されます。
{
"spaces": 2,
"generator-cli": {
"version": "5.4.0"
}
}
// JSON Schemaの指定を行いたい場合は以下を指定しましょう
// "$schema": "./node_modules/@openapitools/openapi-generator-cli/config.schema.json", (※ monorepoの場合パスが違うかもしれないので注意)
// "$schema": "https://raw.githubusercontent.com/OpenAPITools/openapi-generator-cli/refs/heads/master/apps/generator-cli/src/config.schema.json" (※ URLで指定したい場合)
とにかくCLIツール自体は最新のものを使って構いません。設定でバージョンを古いものに固定すればいいのです。
OpenAPIを生成する元となるJSONは一旦ローカルにキャッシュとして保持しよう
CI上などでビルド時に動的にOAS定義を取得してソースコードを生成するのであれば別ですが、リポジトリ上に openapi-generator-cli が生成したソースコードをコミットしておく場合、 ソースコードだけではなく、元になったOAS定義のJSONも保存しておくと助かることが多いです。
生成元のJSONを保存しておくことで、生成元起因の定義の純粋な変更を観測できるようになり、openapi-generator自体の更新による影響の割り出しが行いやすくなります。
以下はスクリプトの例です。一旦 api:download
でローカルにJSONを保存してから api:generate
でそのJSONに基づきソースを生成しています。
"scripts": {
"api": "npm run api:download && npm run api:generate",
"api:download": "curl -o ./doc.json http://localhost:8080/api/doc.json",
"api:generate": "openapi-generator-cli generate -i ./doc.json -g typescript-fetch -o ./src --skip-validate-spec"
}
時には doc.json
を画面側の都合にあわせて部分的に修正したいこともあるでしょう。その場合は以下例の api:tweak
のようなスクリプトを途中工程に挟めばいいです。ついでに、生成されたソースコードを改変したいケース(api:postprocess
)もあり、最終的にこういう感じのスクリプトになることも多いです。
"scripts": {
// 普通はもっと短く書けるのでプロジェクトに応じて調整してください
"api": "npm run api:download && npm run api:tweak && npm run api:generate && npm run api:postprocess",
"api:download": "curl -o ./doc.json http://localhost:8080/api/doc.json",
"api:tweak": "node tools/tweaking-script.js ./doc.json ./doc.tweaked.json",
"api:generate": "openapi-generator-cli generate -i ./doc.tweaked.json -g typescript-fetch -o ./src --skip-validate-spec",
// 弊社PJのケースでいうと、日本語のenumがソースコードに出ないことがあり、ここで書き直したりしています
"api:postprocess": "node tools/postprocess-script.js ./src"
// あとフォーマッタを呼び出したりしてもいいかもしれない……
}
ちなみに --enable-post-process-file
というツール公式のオプションがあり、ファイル単位で後処理を呼び出す場合には使えるのですが、このユースケースには使いづらいと思います。
気に入らないテンプレートは自作に差し替えよう
後のメンテの面倒を考えるとあまりお勧めしませんが、 ジェネレータの生成ソースコードが気に入らない場合、テンプレートとなる一連のMustacheファイルを本来のものとは別のものを指定することができます。
まずは編集元となるテンプレート(ここではtypescript-axios
)を templates/my-typescript-axios
にダウンロードしましょう。
$ npx openapi-generator-cli author template \
-g typescript-axios \
-o ./templates/my-typescript-axios
[main] INFO o.o.codegen.cmd.AuthorTemplate - Extracted templates to './templates/my-typescript-axios' directory. Refer to https://openapi-generator.tech/docs/templating for customization details.
ダウンロードしたテンプレートについていくつか改変を行ったのち、上記のテンプレートフォルダを指定してソースコードを生成します。
$ npx openapi-generator-cli generate \
-i ./doc.json -g typescript-axios \
-o ./src --template-dir ./templates/my-typescript-axios --skip-validate-spec
あるいは公式マニュアルによれば、自分で AbstractTemplatingEngineAdapter
を実装して、そのクラスのFQCNを指定かServiceProvider経由で自作テンプレートエンジンを呼び出せるようです。これは最終手段でしょう。
JavaScript系のAPIソースコードを生成する場合はシングルパラメータによる指定を好もう
古い typescript-*
テンプレートがそうなのですが、しばしば GET
メソッドのAPI生成について以下のような引数多めのソースコードを生成してしまいます。
getDailyApplication(userId: string, year: number, month: number, day: number, options: any = {}) {
// ...
}
こういったAPIの場合、API呼び出し側の year, month, day
の順序が入れ替わってもおそらく誰も気づかないでしょう。一覧検索系のAPIなど、更に引数が多い場合、後々これが悪夢になるのは分かりきっています。
加えて、あるバージョンの SpringFox1 で観測しているのですが、OAS上のGETのパラメータ順序がランダムで入れ替わるケースを観測しています。(以下は実例ではないですが、イメージです)
// コード生成の度に引数が入れ替わるので 2025/4/5 が 2025/5/4 になったりする
getDailyApplication("userId", 2025, 4, 5)
このように、ことOASのAPI生成については引数の順序に依存したコードはバグの原因になるので、生成にあたっては単一プロパティの引数(useSingleRequestParameter=true
)を有効にしましょう。
$ openapi-generator-cli generate \
-i ./doc.json -g typescript-fetch \
-o ./src --skip-validate-spec \
--additional-properties useSingleRequestParameter=true
以下は生成結果と呼び出しコードです。
export interface GetDailyApplicationRequest {
userId: string;
year: number;
month: number;
day: number;
}
getDailyApplication(requestParameters: GetDailyApplicationRequest, initOverrides?: RequestInit) {
// ...
}
// 引数が名前付きになっている
getDailyApplication({
userId: "userId",
year: 2025,
month: 4,
day: 5,
})
既存の引数順序に依存したJS/TSのAPI呼び出しコードの書き換えについて
当然ですが、既存の引数順序に依存したコードを単一パラメータに変更した際、API呼び出し側のコードも上記のオブジェクトプロパティ渡しスタイルへ一斉に変更する必要があります。
私の場合は既存の引数順序とオブジェクトのプロパティをマッピングする定義を作成した上で(新旧のソースを読み込んで得られると思います)、 ts-morph を用いてAPI呼び出し部について一斉に書き換えました。
以下、かなり雑なソースコードですが、ts-morphを呼び出して書き換える例です。
for (const n of src.getDescendantsOfKind(SyntaxKind.CallExpression)) {
const propAccess = n.getFirstChild()?.asKind(SyntaxKind.PropertyAccessExpression)
if (!propAccess) continue
const callee = propAccess.getChildAtIndexIfKind(0, SyntaxKind.Identifier)
if (!callee) continue
const method = propAccess.getChildAtIndexIfKind(2, SyntaxKind.Identifier)
if (!method) continue
const apiName = apiConstMap[callee.getText()]
if (!apiName) {
continue
}
const methodName = method.getText()
const interfaceName = `${apiName}${capitalizeFirstLetter(methodName)}Request`
const foundInterface = getParamInterfaces[interfaceName]
if (!foundInterface) {
console.log("INTERFACE NOT FOUND", {interfaceName})
continue
}
const objArgs: string[] = []
const restArgs: string[] = []
n.getArguments().forEach((arg, index) => {
const propName = foundInterface[index]
const prop = arg.getFullText()
if (propName) {
objArgs.push(`"${propName}": ${prop}`)
} else {
restArgs.push(arg.getFullText())
}
n.removeArgument(0)
})
n.addArgument(`{${objArgs.join(`,\n`)}}`)
n.addArguments(restArgs)
}
あるいは今どきであれば、AIにこういったツールを作ってもらえばいいかもしれません。ASTをいじくるような、人間にとっては書き始めづらいツールのようなものでもササッと作ってくれることが多いです。
まとめ
openapi-generator および openapi-generator-cli について色々な小ネタを紹介してきました。
もうあまりOAS周りは活発ではないので、ここから進化することはあまり無いと思いますが、既存システムの保守改善で今回紹介したテクニックが役立てば幸いです。