🚨

Swift OpenAPI Generator 用の OpenAPI ドキュメントを書くときに遭遇したこととその対策

2023/07/06に公開

WWDC23 では、Swift OpenAPI Generator が発表されました。

この Generator を使用するときに重要となるのは言うまでもなく、OpenAPI ドキュメントです。

この Generator をすでに個人プロジェクトを含めたいくつかのプロジェクトに導入してみたので、今回は実際に Swift OpenAPI Generator を使用してみて遭遇したこと(遭遇しそうなこと)と、自分なりの対策について簡単に紹介します。

Swift OpenAPI Generator における OpenAPI ドキュメント

まず前提として Swift OpenAPI Generator のドキュメントの「Project scope and goals」に記載されている、この Generator に関する重要な原則を一つ紹介します。

それは 「Faithfully represent the OpenAPI document」、つまり「OpenAPI ドキュメントを忠実に表現する」 ということです。

これは Swift OpenAPI Generator に限った話ではなく、OpenAPI Tools が提供する OpenAPI Generator など他の自動生成ツールでも同様だとは思いますが、ドキュメントで定義した通りにコードは生成されます。

そのためこのドキュメントを正しく書くことが自動生成においてとても重要です。

このことを念頭に置いて、実際に私が引っかかったポイントを見ていただければ思います。

遭遇したこと(遭遇しそうなこと)

実際に試してみて片手では数えきれない程に引っかかりました。。大きくは2種類遭遇しましたが、今のところは100%で人為的ミスがほとんどです。。後者については過去に別の Generator を使用していた際に引っかかったことはあるので、同様に引っかかる可能性は高いかなと思っています。

  1. タイポや漏れなどの人為的ミス
  2. 書き方のコツ

1. タイポや漏れなどの人為的ミス

Swift OpenAPI Generator を使用する場合は、Xcode プロジェクトに OpenAPI ドキュメントを追加したと思います。
これで Xcode 上で編集できるようになったわけですが、残念ながら Xcode の場合、特に yaml ファイルの視認性が良くないです。ハイライトがあまり効かず、OpenAPI 用のコード補完もありません。(これは json であっても同様ですが。)

OpenAPI ドキュメントを書き慣れていない上にこの状況だと、タイポをしたり必須で定義すべき項目を定義し忘れるなど、何らかのミスが発生します。現に私は5回ほど間違いました。。(間違えすぎ?)

間違えた結果、コード生成ができずにビルドエラーになったり、もっと困ったことにエラーにならずに想定外のコードが生成されてしまうといったことが発生しました。

ビルドエラーは主に OpenAPI ドキュメントが必須の項目を保持していなかった場合、あとはこの Generator が必須としている openapi.yaml または openapi.json そして openapi-generator-config.yaml が想定のディレクトリに存在しない場合などに遭遇しました。

ここでは私が遭遇した実例を2つ紹介しようと思います。

まず一つ目が、OpenAPI ドキュメントで必須である項目を書き忘れ、以下のようなエラーが発生しました。

ただこれだけだとビルドスクリプトの実行時に何か問題が起きたということしかわからないので、ビルドのログを見にいきます。そうするとようやく、必須項目である paths が不足していたためにエラーに自動生成に失敗したことを知ることができます。(実際は paths の記載を忘れたわけではないのですが、ここでは分かりやすくこの例で紹介しています)

これは Xcode の微妙なところだと思っていますが、ビルドエラーが発生した場合、ビルドログを開いてみないとログが見れません。。(Swift OpenAPI Generator に限らず、SwiftPM のプラグインの場合はデバッグがしづらいというのは共通のお悩みだと思いますが。。)

では2つ目の実例を紹介します。

私が呼び出そうとしていた API のリクエストボティには、appIDs という項目があり、openapi.yaml は以下のように定義していました。

	appIDs:
          type: array
          description: "取得するアプリの ID"
          example: ["1673161138", "1573161139"]

これを元に生成されたコードを見ると、以下のようになっていました。
example では string で書いていたので生成されたコードも String 型になると思いきや、OpenAPIArrayContainer という見慣れない型で実装されています。

/// 取得するアプリの ID
///
/// - Remark: Generated from `#/components/schemas/PostAppStoreStateRequestBody/appIDs`.
public var appIDs: OpenAPIRuntime.OpenAPIArrayContainer?

OpenAPIArrayContainer は 型が定義されていないときに使用される構造体です。Any のようなものだという理解です。

つまりせっかく Swift で型安全に書くことができるにも関わらず、仕様ドキュメントに型の定義を書き忘れたために、型が失われてしまっています。
ドキュメントコメントにも、この構造体はランタイム時の負荷が高いため、使用は避け、代わりにちゃんと型を定義するように と書かれていました。

書き忘れてもビルド時にエラーにはならないため、このように想定外な型でコードが生成されてしまいました。

2. 書き方のコツ

この Generator に限らず、コード生成をするときの仕様ドキュメントには、書き方のコツがあります。

今回私は特にエラーには出会いませんでしたが、以前 OpenAPI Tools が提供する Generator を使用したとき、各言語の予約語を使用したことでシンタックスエラーになるという問題が発生したことがありました。

Swift OpenAPI Generator ではどうかはわかりませんが、Generator として 仕様ドキュメントを忠実に再現するという特性上、発生しうる気がしました。

以下の記事がわかりやすくまとめられています。

https://techblog.zozo.com/entry/how-to-write-openapi-for-openapi-generator

対策

遭遇したことに対して、いくつか対策を検討してみました。

対策1. ドキュメントをバリデートする

何らかの方法で、openapi.yamlopenapi.json をバリデーションした上で、コード生成をするという方法です。

1. VSCode で拡張機能を活用する

慣れている人には基礎的すぎるかもしれませんが最初の一歩として、Xcode を使わず、VSCode のような OpenAPI のドキュメントを書く環境が整えやすい IDE で書くことが考えられます。

実際私は VSCode で書いています。 Xcode よりはシンタックスハイライトも効いています。また VSCode の拡張機能には OpenAPI のドキュメントを書くために便利なものがいくつかあります。

以下の記事で紹介されている拡張機能4つがとても便利でした。

  • Swagger Viewer
  • openapi-lint
  • OpenAPI(Swagger) Editor
  • OpenApi Snippets

https://zenn.dev/s_t_pool/articles/954dfe51b950c18d08e9

2. spectral のような CLI ツールを使用する

また別の方法として、spectral という npm モジュールを使うこともできます。

以下のコマンドを実行することで、ドキュメントの検証をしてくれます。

npx spectral lint openapi.yaml

以下のスクショはまさにミスを犯そうとしているところで、先ほど上げた
警告の一つを見てみると、 operationId という項目が定義されていません。これはコード生成時のメソッド名として使用される重要な項目です。

3. OpenAPIKit を使い、Swift でバリデートする

また、Swift でプログラムからバリデートを行うという方法もあります。

OpenAPIKit というライブラリには、バリデーション用のメソッドがあります。

OpenAPIKit は Swift OpenAPI Generator も依存しているライブラリです。メインの機能は、OpenAPI ドキュメントを Swift の型のエンコードやでコードを行うことですが、バリデーションも可能です。

ここでは詳しく説明しませんが、以下のように実装することができます。

do {
    // yaml をデコードする→ここまでは Swift OpenAPI Generator も同様の処理を行っている
    let decoder = YAMLDecoder()
    let openAPIDoc = try decoder.decode(OpenAPI.Document.self, from: fileContents)
    // バリデーションする
    try openAPIDoc.validate()
} catch let error {
    print(OpenAPI.Error(from: error).localizedDescription)
}

独自のバリデーションを作成することもできます。より詳しい使い方は以下の記事で解説しているので、気になる方はぜひご確認ください。

https://zenn.dev/kamimi01/articles/83e2c1b0d37b86

どこまで実用に耐えうるかはわかりませんが、Swift をメインで使用するエンジニアが多い場合は Swift で完結する方が嬉しいと思うので、コード生成が行われる前のフローとして組み込めると便利そうです。

対策2. yaml や json "は" 書かない

仕様ドキュメントは手書きするしかないと思いこんでいましたが、そうではない方法もあるようです。

それが Spotlight Studio です。GUI で入力しながら作っていくことができるそうです。

以前勉強会に参加した時に、
@naoki_mrmt さん
の「Swagger Codegenで楽にSwiftのModelを生成する」の発表で知りました。🙏🏻

こういったツールを使ってそもそも間違えやすいものを手書きしない、という方法もあるのはとても新鮮でした。

おわりに

正直、OpenAPI ドキュメントには書かないといけない必須項目やはまりどころが多く、IDE のサポートなしで正しく書くことは難しいと感じています。

今回の Swift OpenAPI Generator は他の Generator と比較すると導入の手軽さや、Xcode 内で完結するというのがメリットなので、OpenAPI ドキュメントを書くためのサポートをもう少ししてくれると嬉しかったなと思いました。

ただ開発チームによっては OpenAPI ドキュメントはサーバーサイドのチームが書くということもあると思うので、Xcode でそこまで手厚くサポートする必要性は感じられなかったのかなとも想像しました。

以上、Swift OpenAPI Generator を使用して OpenAPI ドキュメントを書くときに遭遇したこととその対策でした。

ここまで読んでいただきありがとうございました!

Discussion