Swift OpenAPI Generator は、OpenAPI仕様からSwiftコードを自動生成するツールです。
最近業務で導入してみたのですが、自分の知っている生成方法は一例に過ぎないなと思い、どんな方法があるのか検証してみました。
自動生成の方法
まずは生成の方法です。
手動で実行する方法もありますが、Package Plugin を使用した自動生成の方法が推奨されています。
1. Package Plugin を使用(推奨)
前提条件:
Package.swiftに以下の設定が必要です。
// swift-tools-version: 5.9
import PackageDescription
let package = Package(
name: "MyAPIClient",
platforms: [.iOS(.v13), .macOS(.v10_15)],
dependencies: [
.package(url: "https://github.com/apple/swift-openapi-generator", from: "1.0.0"),
.package(url: "https://github.com/apple/swift-openapi-runtime", from: "1.0.0"),
.package(url: "https://github.com/apple/swift-openapi-urlsession", from: "1.0.0"),
],
targets: [
.target(
name: "MyAPIClient",
dependencies: [
.product(name: "OpenAPIRuntime", package: "swift-openapi-runtime"),
.product(name: "OpenAPIURLSession", package: "swift-openapi-urlsession"),
],
plugins: [
.plugin(name: "OpenAPIGenerator", package: "swift-openapi-generator")
]
),
]
)
必要なファイル:
プロジェクトルートまたはターゲットディレクトリに以下のファイルを配置:
-
openapi.yaml
- OpenAPI仕様書 -
openapi-generator-config.yaml
- 設定ファイル
基本的な使用方法:
# ビルド時に自動的にコード生成
swift build
# クリーンビルド(生成されたコードも削除)
swift package clean
swift build
生成されるファイルの場所:
.build/plugins/outputs/myapiclient/MyAPIClient/destination/OpenAPIGenerator/GeneratedSources/
├── Types.swift
├── Client.swift
└── Server.swift (設定により生成)
2. 手動でコード生成
また、手動でもコマンドによって生成することは可能です
# 基本的な生成コマンド
swift run swift-openapi-generator generate \
openapi.yaml \
--config openapi-generator-config.yaml
# 出力ディレクトリを指定
swift run swift-openapi-generator generate \
openapi.yaml \
--config openapi-generator-config.yaml \
--output-directory Sources/Generated
📑 openapi-generator-config.yaml 完全ガイド
生成される結果は主にopenapi-generator-configに定義されています。各設定がどの様なものか見ていきましょう。
設定ファイルの基本構造
# 必須設定
generate: # 生成するコンポーネントを指定
- types # 型定義(スキーマ)を生成
- client # クライアントコードを生成
- server # サーバーコードを生成
# オプション設定
accessModifier: public # アクセス修飾子(public/internal/package/fileprivate/private)
namingStrategy: defensive # 命名戦略(defensive/optimistic)
filter: # フィルタリング設定
paths: [] # 生成する/除外するパス
schemas: [] # 生成する/除外するスキーマ
# 追加設定
additionalImports: # 追加でインポートするモジュール
- Foundation
- Combine
featureFlags: # 実験的機能フラグ
- ExperimentalObjectOneOf
- ExperimentalAllOf
- ExperimentalStringlyTypedRawValues
# オーバーライド設定
nameOverrides: # 名前のオーバーライド
schemas:
OldName: NewName
operations:
getOldEndpoint: getNewEndpoint
typeOverrides: # 型のオーバーライド
schemas:
CustomDate: Foundation.Date
各設定項目の詳細
1. generate(必須)
生成するコンポーネントを指定します。
クライアントで使う場合 types
client
だけ生成することが多いかなと思います。
値 | 説明 | 生成されるファイル |
---|---|---|
types |
OpenAPIスキーマから型定義を生成 | Types.swift |
client |
HTTPクライアントコードを生成 | Client.swift |
server |
サーバー実装用プロトコルを生成 | Server.swift |
# クライアント開発用
generate:
- types
- client
# サーバー開発用
generate:
- types
- server
# フルスタック開発用
generate:
- types
- client
- server
2. accessModifier(デフォルト: internal)
生成されるコードのアクセスレベルを指定します。
Swift 5.9からはpackageの指定もできるので、より適切な範囲に公開できそうです。
値 | 説明 | 使用例 |
---|---|---|
public |
他のモジュールから参照可能 | ライブラリ開発 |
internal |
同一モジュール内のみ参照可能 | アプリ開発(デフォルト) |
package |
同一パッケージ内で参照可能 | Swift 5.9+ |
fileprivate |
同一ファイル内のみ参照可能 | 特殊用途 |
private |
同一スコープ内のみ参照可能 | 特殊用途 |
# ライブラリとして公開する場合
accessModifier: public
# アプリ内部で使用する場合
accessModifier: internal
3. namingStrategy(デフォルト: defensive)
名前の衝突の避け方を定義しています。
値 | 説明 | 特徴 |
---|---|---|
defensive |
衝突を避けるため接頭辞/接尾辞を追加 | 安全だが冗長 |
optimistic |
可能な限りシンプルな名前を使用 | 簡潔だが衝突リスクあり |
# 安全性重視(推奨)
namingStrategy: defensive
# 簡潔性重視
namingStrategy: optimistic
4. filter
特定のパスやスキーマの生成を制御します。
実践ではあまり利用したことがないのですが、巨大なopenapi.yamlだった場合に役に立ちそうです。
# 特定のパスのみ生成
filter:
paths:
- /users
- /posts
- /comments
# 特定のスキーマのみ生成
filter:
schemas:
- User
- Post
- Comment
# 除外パターン(!で開始)
filter:
paths:
- "!*/admin/*" # adminパスを除外
schemas:
- "!Internal*" # Internal で始まるスキーマを除外
5. additionalImports
生成されたコードに追加でインポートするモジュールを指定します。
あまり他の型に依存しない方が扱いやすそうですが、カスタムのimportを定義できる様です。
additionalImports:
- Foundation # 基本的に必要
- Combine # Combine を使用する場合
- SwiftUI # SwiftUI との統合
- MyCustomModule # カスタムモジュール
6. featureFlags
実験的機能を有効化します。
基本的にはexperimentalと付いているので利用は慎重に検討する必要があ流と思います。
フラグ | 説明 |
---|---|
ExperimentalObjectOneOf |
oneOf のサポート(実験的) |
ExperimentalAllOf |
allOf のサポート(実験的) |
ExperimentalStringlyTypedRawValues |
文字列型の raw value サポート |
ExperimentalObjectOneOf の例
oneOf を使用したユニオン型の生成ができます。
OpenAPI 仕様:
NotificationPayload:
oneOf:
- $ref: '#/components/schemas/EmailNotification'
- $ref: '#/components/schemas/PushNotification'
生成されるSwiftコード:
@frozen public enum NotificationPayload: Codable, Hashable, Sendable {
case emailNotification(Components.Schemas.EmailNotification)
case pushNotification(Components.Schemas.PushNotification)
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let value = try? container.decode(Components.Schemas.EmailNotification.self) {
self = .emailNotification(value)
return
}
if let value = try? container.decode(Components.Schemas.PushNotification.self) {
self = .pushNotification(value)
return
}
throw DecodingError.dataCorrupted(/* ... */)
}
}
ExperimentalAllOf の例
allOf を使用した継承・合成の生成ができます。
OpenAPI 仕様:
ExtendedUser:
allOf:
- $ref: '#/components/schemas/BaseUser'
- type: object
properties:
lastLoginDate:
type: string
format: date-time
生成されるSwiftコード:
public struct ExtendedUser: Codable, Hashable, Sendable {
// BaseUser のプロパティが展開される
public var id: Swift.Int64
public var name: Swift.String
public var email: Swift.String
// 追加のプロパティ
public var lastLoginDate: Foundation.Date
}
ExperimentalStringlyTypedRawValues の例
enum の命名を改善できます。
OpenAPI 仕様:
Priority:
type: string
enum:
- "high-priority"
- "medium-priority"
- "low-priority"
フラグ無効時:
@frozen public enum Priority: String, Codable {
case case0 = "high-priority"
case case1 = "medium-priority"
case case2 = "low-priority"
}
フラグ有効時:
@frozen public enum Priority: String, Codable {
case highPriority = "high-priority"
case mediumPriority = "medium-priority"
case lowPriority = "low-priority"
}
featureFlags:
- ExperimentalObjectOneOf
- ExperimentalAllOf
- ExperimentalStringlyTypedRawValues
7. nameOverrides
自動生成される名前をオーバーライドします。
スキーマ名称はOpenAPIの都合に則っていることが多いですが、ここでClient側の都合に則ってスキーマを変更することが可能です。
nameOverrides:
schemas:
# スキーマ名の変更
user_profile: UserProfile
post_item: Post
operations:
# オペレーション名の変更
get_user_by_id: getUserById
list_all_posts: listPosts
8. typeOverrides
型のマッピングをカスタマイズします。
OpenAPIで定義されている型がSwiftにあるとは限りません。そのためここでカスタムでClient側の都合の型に変換しています
typeOverrides:
schemas:
# カスタム日付型を使用
CustomDate: Foundation.Date
# カスタムUUID型を使用
UniqueID: Foundation.UUID
まとめ
Swift OpenAPI Generator による設定の違いをまとめてみました!
完全に自分用ですが、下記でOpenAPIClientGeneratorを実際に動かしてみたので、合わせてご確認いただけると嬉しいです。
https://github.com/entaku0818/swift-test/tree/main/Sources/OpenAPIClientGenerator
参考サイト
公式ドキュメント
設定リファレンス
チュートリアル・サンプル
- Swift OpenAPI Generator Tutorial
- Getting Started with Swift OpenAPI Generator - WWDC 2023
- Meet Swift OpenAPI Generator - Apple Developer Documentation