【自動化で効率UP】Swift OpenAPI Generator入門

Swift

こんにちは。新人プログラマーの岩本です。

今回はWWDC23で発表されたSwift OpenAPI Generatorについて解説します。

OpenAPI Generatorはとても便利なツールなので、知っておくとより効率的に業務を進めることができるようになります。

ぜひ最後までご覧ください。

この記事は新卒エンジニアが執筆しています。
そのため内容に間違いや不備がある場合があります。
もし間違いを発見しましたら、どんどん指摘していただけると幸いです。

OpenAPIとは

そもそもOpenAPIとは、REST APIの記述フォーマットのことを言います。

簡単にいうと、使用するAPIの取り決め書みたいな感じで、どのURLにどんなデータを送れば、どんなレスポンスが返ってくるかなどが書かれています。

記述法はJSON形式とYAML形式の2通りがあり、今回はYAML形式を使用しています。

今回は書き方などは詳しく解説しません。

興味がある方はこちらの記事を読んでみてください。

OpenAPI (Swagger) 超入門 - Qiita
はじめに業務でOpenAPIを用いたAPI設計を行っているため、備忘録も兼ねてまとめる。入門用なので、基本的な部分のみ解説。概要OpenAPIWhat Is OpenAPI?OpenAP…

Swift OpenAPI Generatorとは

Swift OpenAPI Generatorとは、WWDC23で発表されたもので、OpenAPIドキュメントからクライアントやサーバのコードを自動で生成してくれるツールです。

以前まではOpenAPI Toolsが提供しているOpenAPI Generatorが使われていましたが、いくつかの問題点を抱えていました。

  • ドキュメントを用意し、コマンドを叩いてコードを自動生成し、差分をGitHubにプッシュするなど、使うまでのステップが面倒
  • ドキュメントの更新が大きいと自動生成するコードも増え、結果的にPRの差分がデカくなりレビューが大変

れらを解決したのがSwift OpenAPI Generatorです。

Swift OpenAPI GeneratorはSwift Package pluginとして使えるため、コードの自動生成はビルド時に行われます。

そのためドキュメントの更新から生成までをXcode上で行うことができます。

またドキュメントを更新するだけで自動でコードが修正されるため、ドキュメントとコードの一貫性が常に保たれた状態になることも利点です。

自動生成されたコードは DerivedData に格納されるため、Gitで管理する必要がなくなります。

まとめると、Swift OpenAPI Generatorのメリットはこんな感じです。

  • すべての動作がXcode上で完結する
  • ドキュメントとコードの一貫性が保たれる
  • 自動生成コードはGitで管理しなくていい

Swift Package pluginについては以下の記事が参考になります。

Swift Package プラグインの全体像 - Qiita
概要Swift 5.6 から Swift Package にプラグインが追加され、 SPM から外部ツールの実行を含めた色々な処理ができるようになりました。 Swift 5.8 の時点では以下2種…

Swift OpenAPI Generatorの使い方

ではここからは具体的な使い方について解説していきます。

今回はAPIクライアントコードの自動生成に絞って解説します。

最終的な成果物はこんな感じです。

サーバからおみくじの結果を受け取り、それを表示させています。

モックサーバを起動

クライアントコードの動作確認のために、ローカルで動作するサーバを立ち上げる必要があります。

今回は僕がSwift OpenAPI Generatorを使ってサーバ側を実装したので、それをローカルで立ち上げます。

レポジトリはこちらです。

GitHub - Yuzuki0709/OmikiujiServer
Contribute to Yuzuki0709/OmikiujiServer development by creating an account on GitHub.

レポジトリをローカルにダウンロードし、以下のコマンドを実行します。

Bash
cd ダウンロードした先/OmikujiService
Bash
swift run

するとサーバが立ち上がるので、Terminalを別ウィンドウで開き、以下のコマンドを入力して動作確認を行なってください。

Bash
curl 'http://localhost:8080/api/omikuji?name=Yourname'

必要なYAMLファイルの追加

まずは新しくプロジェクトを作成してください。

プロジェクトを作成したら、 openapi.yaml という名前でファイルを追加し、以下を記述してください。

openapi.yaml(または.json)以外の名前にすると、Generatorが認識できないので注意

openapi.yaml
openapi: '3.1.0'
info:
  title: OmikujiService
  version: 1.0.0
servers:
  - url: https://example.com/api
    description:
  - url: http://127.0.0.1:8080/api
    description: Localhost deployment.
paths:
  /omikuji:
    get:
      operationId: getOmikuji
      summary: Get a random fortune
      parameters:
        - name: name
          required: true
          in: query
          description: The name of the person to receive the fortune
          schema:
            type: string
      responses:
        '200':
          description: A personalized fortune result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OmikujiResult'

components:
  schemas:
    OmikujiResult:
      type: object
      properties:
        result:
          type: string
        message:
          type: string
      required:
        - result
        - message

このYAMLファイルは、/omikuji というパスにアクセスしたときに、スキーマとして定義されてる型を返すということを記述してあるものです。

次に、openapi-generator-config.yaml というファイルを追加し、以下を記述してください。

こちらもopenapi-generator-config.yaml という名前じゃないとダメなので注意

openapi-generator-config.yaml
generate:
    - types
    - client # 今回はクライアントコードを生成するのでこれを記述する
#   - server サーバのコードを生成するときはこっちを記述する

パッケージの追加

次に必要なパッケージを3つ追加していきます。

プロジェクトのPackage Dependencies から、以下のパッケージを追加してください。

パッケージ名役割レポジトリURL
Swift OpenAPI GeneratorYAMLからAPI通信に必要なコードを自動生成するhttps://github.com/apple/swift-openapi-generator
Swift OpenAPI RuntimeSwift OpenAPI Generator によって生成されたコードによって使用される共通の抽象化とヘルパー関数を提供するhttps://github.com/apple/swift-openapi-runtime
Swift OpenAPI URLSessionHTTPリクエストを管理するhttps://github.com/apple/swift-openapi-urlsession

Swift OpenAPI Generatorを追加する際は、すべてのTargetをnoneにしてください。

Screenshot

次に、「TARGETS」→「Build Phases」→「Run Build Tool Plug-ins」で OpenAPIGenerator を追加し、プラグインを有効にします。

ここまでできたらビルドを実行します。

ビルドが通れば、DerivedData にコードが自動生成されます。

Clientコードを書く

次に自動生成されたコードを使って、クライアントコードを記述していきます。

OmikujiClient.swift を追加し、以下のコードを記述してください。

OmikujiClient.swift
import OpenAPIURLSession

struct OmikujiClient {
    func getOmikuji(name: String) async throws -> (String, String) {
    
		    // Clientは自動生成された構造体
        let client = Client(
            serverURL: try Servers.server2(),
            transport: URLSessionTransport()
        )
        
        // Responseの値は、Operations.getOmikji.Output
        let response = try await client.getOmikuji(query: .init(name: name))
        return try (response.ok.body.json.result, response.ok.body.json.message)
    }
}

詳しくコードを見ていきます。

OmikujiClient.swift
let client = Client(
		serverURL: try Servers.server2(),
		transport: URLSessionTransport()
)

Clientは Generatorによって自動生成された構造体です。ここで使用するサーバ、トランスポートなどを指定することができます。

Client.swift
internal struct Client: APIProtocol {
    internal init(
        serverURL: Foundation.URL,
        configuration: Configuration = .init(),
        transport: any ClientTransport,
        middlewares: [any ClientMiddleware] = []
    ) {
        self.client = .init(
            serverURL: serverURL,
            configuration: configuration,
            transport: transport,
            middlewares: middlewares
        )
    }
    
    // ...
    
     internal func getOmikuji(_ input: Operations.getOmikuji.Input) async throws -> Operations.getOmikuji.Output {
         // ...
     }
}

またClientはAPIProtocolを継承しています。

APIProtocolも自動生成されたコードで、YAMLで定義したメソッドが定義されてるプロトコルです。

Types.swift
internal protocol APIProtocol: Sendable {
    func getOmikuji(_ input: Operations.getOmikuji.Input) async throws -> Operations.getOmikuji.Output
}

次にレスポンス部分を見ていきます。

OmikujiClient.swift
let response = try await client.getOmikuji(query: .init(name: name))

getOmikujiメソッドの返り値は、Operations.getOmikji.Outputとなっています。

Operationsも自動生成された列挙型で、メソッドごとのInputのクエリとヘッダー、Outputの値が定義されています。

Types.swift
internal enum Operations {
    internal enum getOmikuji {
        internal struct Input: Sendable, Hashable {
            internal struct Query: Sendable, Hashable {
                internal var name: Swift.String
                // ...
            }
            // ...
            internal struct Headers: Sendable, Hashable {
		            // ...
            }
        }
        
        internal enum Output: Sendable, Hashable {
            // ...
        }
    }
}

次に、return部分を見ていきます。

OmikujiClient.swift
return try (response.ok.body.json.result, response.ok.body.json.message)

Operations.getOmikuji.Outputは列挙型であり、okとundocumentedの2つの値を持ちます。

Types.swift
internal enum Output: Sendable, Hashable {
    internal struct Ok: Sendable, Hashable { .. }
    
    case ok(Operations.getOmikuji.Output.Ok)
    case undocumented(statusCode: Swift.Int, OpenAPIRuntime.UndocumentedPayload)
}

okはOperations.getOmikuji.Output.Ok型のAssociatedValueを持ちます。

Okは構造体であり、BodyというEnumを持ち、最終的にはyamlファイルで定義したOmikujiResult型のAssociatedValueを持つjsonに辿り着きます。

Types.swift
internal struct Ok: Sendable, Hashable {
    internal enum Body: Sendable, Hashable {
        case json(Components.Schemas.OmikujiResult)
    }
}

では作成したクライアントコードを使って、API通信を行っていきます。

ContentView.swift に以下のコードを記述してください。

ContentView.swift
struct ContentView: View {
    private let client = OmikujiClient()
    @State private var omikujiResult: (String, String)?

    var body: some View {
        VStack {
            Button("おみくじを引く") {
                Task { @MainActor in
                    omikujiResult = try? await client.getOmikuji(name: "your name")
                }
            }

            if let omikujiResult {
                Text(omikujiResult.0)
                Text(omikujiResult.1)
            }
        }
        .padding()
    }
}

アプリを実行し、ボタンをタップするとおみくじの結果を受け取ることができると思います。

(受け取れない方は、モックサーバが起動しているかを確認してください。)

まとめ

今回はSwift OpenAPI Generatorについて解説しました。

今まではOpenAPI Toolsが提供しているOpenAPI Generatorが使われていましたが、これからはSwift OpenAPI Generatorが広く使われるようになると思います。

ぜひご自身の手で色々と試してみてください。

ここまでのご閲覧ありがとうございました!

参考にした記事

swift-openapi-generator Documentation – Swift Package Index
Swift OpenAPI Generator で Swift でのスキーマ駆動開発がもっと便利になる - はじめに -

コメント

タイトルとURLをコピーしました