OpenAPI Generatorのあれこれ
このスクラップに書いてることを記事にした
Swift OpenAPI Generator
- Swift Package Pluginで提供されている
- Appleが提供する純正の生成ツール
使用手順
- 以下をAdd Dependenciesする
package pluginを提供する
生成されたコードから使用される、共通の型と抽象化を提供する
生成されたコードはどの特定のHTTPクライアントライブラリにも紐づいていないので、紐付けたいライブラリを選択する。
iOSアプリの場合は、URLSessionを使うが、他の場合はドキュメントに記載されている。
- OpenAPI Generator Pluginを使うためのターゲットを設定できる
ターゲットのBuild Phases のRun Build Tool Plug-insでOpenAPI Generatorを追加する
- 2つのファイルを作成する
OpenAPI Generatorは2つのファイルを期待している
- OpenAPI ドキュメント
- API仕様が定義される
- plug-in 設定ファイル
- どのコードをプラグインが生成するかを決める
生成するために忘れちゃいけない項目
- operationId(MUSTって書いてた)
Unique string used to identify the operation. The id MUST be unique among all operations described in the API. The operationId value is case-sensitive. Tools and libraries MAY use the operationId to uniquely identify an operation, therefore, it is RECOMMENDED to follow common programming naming conventions.
# Appleのチュートリアル
Provide an operationId to give code generators a useful hint of what to call the generated method.
謎のエラー、、
- プラグインを初めて使用するときは、trust&enable allする
原則
- OpenAPI ドキュメントを忠実に表現する
OpenAPI ドキュメントは信頼できる情報源とみなされます。ジェネレーターは、可能な限りドキュメントを反映するコードを生成することを目的としています。これには、仕様構造と、OpenAPI ドキュメントの作成者が使用する識別子が含まれます。
その結果、生成されたコードは必ずしも慣用的な Swift スタイルであるとは限らず、独自のカスタム スタイル ガイドラインに準拠しているとは限りません。たとえば、API 操作識別子は lowerCamelCaseではない場合があります。
生成されたコードを特定のスタイルに準拠させる必要がある場合は、OpenAPI ドキュメントを前処理して識別子を更新し、別のコードを生成することをお勧めします。
大きなドキュメントの場合は、これをプログラムで実行することもできます。Swift で実行する場合は、 Swift OpenAPI Generator で使用されるのと同じライブラリであるOpenAPIKitを使用できます。
- OpenAPI ドキュメントに合わせて進化するコードを生成する
このジェネレーターは、OpenAPI ドキュメントの進化に応じて人間工学的に進化するコードを生成することを目的としています。
その結果、特に単純な操作の場合、生成されたコードが不必要に冗長になる可能性があります。
この具体的な例は、文書化されたシナリオが 1 つしかない場合の列挙型の使用です。これにより、新しいシナリオが OpenAPI ドキュメントに追加されるときに、生成された Swift コードに新しい列挙型ケースを追加できるようになり、生成されたコードのユーザー エクスペリエンスが向上します。
別の例は、入力型または出力型内での空の構造体の生成です。たとえば、API オペレーションに文書化されたヘッダー フィールドがない場合でも、入力型にはヘッダー フィールドのネストされた構造体が含まれます。
- ジェネレーター実装の複雑さを軽減する
一部のジェネレーターは、コード生成プロセスに影響を与える多くのオプションを提供します。プロジェクトの合理化と保守性を維持するために、Swift OpenAPI Generator にはオプションがほとんどありません。
この具体的な例の 1 つは、ユーザーが生成されたコードのアクセス修飾子を構成できないこと、また、生成されたコードは、生成先のターゲット内での名前空間の衝突をまったく考慮していないことです。
代わりに、ユーザーは独自のターゲットにコードを生成し、Swift のモジュール システムを使用して、生成されたコードをそれを使用するコードから分離することをお勧めします。
詳細については、「生成されたコードの API の安定性」を参照してください。
疑問
-
VSCodeのようにPreviewやコード補完をサポートしてくれない?
- Xcodeではプレビューやコード補完はサポートしていないので、VSCodeでSwagger ViewerやOpenAPI Lintを使って書く方が便利そう。(間違えた場合にコードが生成できなかったり、おかしなコードが生成されて使えないといったことが発生する)
-
特殊ケースのコード生成には対応しているか?
- OpenAPIの仕様として書くことはできるが、生成はされないケースというのがあった。
-
APIの一部として展開するのはどうか?
上記のような複雑なルールがあるため、生成されたコードを他の人が信頼できるように公開しないことをお勧めします。
生成されたコードをパッケージのAPIの一部として公開する場合、特にパッケージがセマンティック・バージョニングを使用している場合は、APIを監査して変更を確認することをお勧めします。
生成されたコードを実装の詳細として使用するSwiftライブラリパッケージを維持することは、生成されたシンボルがあなたの公開APIでエクスポートされない限り、サポートされています(そして推奨されています)
-
独自のカスタム定義をするのは想定されていないので、swift-openapi-runtimeが(おそらく)依存しているOpenAPIKitを使ってカスタムできる
-
APIのエンドポイントひとつであっても、ビルドに結構な時間がかかる。
- なんとかしてビルド時間を短くする方法を検討した方が良さそう
-
OpenAPIで定義を忘れると、Any型で生成されることもある。書き方にとにかく注意が必要。これはSwift OpenAPI Generatorに限った話ではないが
-
specの記載が間違っていた時のエラーがわかりにくい
- spectralを入れる。VSCodeのOpenAPI Lintなどを使う。GUIでspecを定義できるサービスを使うなど、あらかじめ間違いを防ぐ仕組みが必要そう。
- Build Phasesにspectralを入れたチェックを入れようとしたが、Run ScriptをRun Build Tool Plug-insの前にすることができず、実行できなかった
- spectralを入れる。VSCodeのOpenAPI Lintなどを使う。GUIでspecを定義できるサービスを使うなど、あらかじめ間違いを防ぐ仕組みが必要そう。
エラーにはならないけど理由がわからず使いたいAPIが見つからないこともある。
-
ドキュメント生成はされるのか?
-
yamlを複数に分けることは対応しているか?
- してない。Pluginを実行するときにopenapi.yamlまたはopenapi.jsonに統合する必要がある
- Content-Typeの検証はできるのか?できない。しないでリクエストの処理を始める。JSONDecoderのところでエラーになるので、エラーが不可解な感じがする
- 生成されたコードはどこにある?
- DerivedDataの中。
- specを変えて、DerivedDataの中のコードも変わっているのに、Xcodeが参照しているコードは古いままの場合がある
本家OpenAPI Generatorとの比較
-
生成されたコードを取り込む手間があった。生成するタイミングも自分で実装する必要があった。
- 今回はAppleがSwift Package Pluginという形で提供したので、導入コストが低くなっている気がする
本家の順番
- 生成する
- 取り込む
- 実行する
- 生成したコードを別途管理する必要もあった
Swift OpenAPI Generator
- 初期設定さえ終われば、ビルドするたびに最新のコードが生成される
- 生成が楽
- source controlに配置する必要もない
感想
- Swiftでモックサーバーの実装をできるのは良さそう
- バックエンドの実装を待たずにモックサーバーの実装をすることができる
- 以前TypeScriptで実装されたモックサーバーのメンテナンスをするといったことがあったが、TypScriptに慣れておらず大変だったりした。
- 違うファイル名で生成はできない
- 違う拡張子にすることもできない(e.g.
yml
) - 間違ったフォーマットの場合はどうなるのか?
specを書くときに便利なもの
自動生成する時のOpenAPI specの書き方
Swift OpenAPI Generatorのメモ
生成されたコードがスネークケースになる
let response = try await client.completions(Operations.completions.Input(
body: .json(Components.Schemas.Completions(
model: "",
prompt: "",
temperature: 0.8,
max_tokens: 200,
top_p: 1,
frequency_penalty: 0.0,
presence_penalty: 1.0
))
))