4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

参考サイト

公式ドキュメント

設定リファレンス

チュートリアル・サンプル

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?