こんにちは。新人プログラマーの岩本です。
今回はWWDC23で発表されたSwift OpenAPI Generatorについて解説します。
OpenAPI Generatorはとても便利なツールなので、知っておくとより効率的に業務を進めることができるようになります。
ぜひ最後までご覧ください。
OpenAPIとは
そもそもOpenAPIとは、REST APIの記述フォーマットのことを言います。
簡単にいうと、使用するAPIの取り決め書みたいな感じで、どのURLにどんなデータを送れば、どんなレスポンスが返ってくるかなどが書かれています。
記述法はJSON形式とYAML形式の2通りがあり、今回はYAML形式を使用しています。
今回は書き方などは詳しく解説しません。
興味がある方はこちらの記事を読んでみてください。
Swift OpenAPI Generatorとは
Swift OpenAPI Generatorとは、WWDC23で発表されたもので、OpenAPIドキュメントからクライアントやサーバのコードを自動で生成してくれるツールです。
以前まではOpenAPI Toolsが提供しているOpenAPI Generatorが使われていましたが、いくつかの問題点を抱えていました。
これらを解決したのがSwift OpenAPI Generatorです。
Swift OpenAPI GeneratorはSwift Package pluginとして使えるため、コードの自動生成はビルド時に行われます。
そのためドキュメントの更新から生成までをXcode上で行うことができます。
またドキュメントを更新するだけで自動でコードが修正されるため、ドキュメントとコードの一貫性が常に保たれた状態になることも利点です。
自動生成されたコードは DerivedData
に格納されるため、Gitで管理する必要がなくなります。
まとめると、Swift OpenAPI Generatorのメリットはこんな感じです。
Swift Package pluginについては以下の記事が参考になります。
Swift OpenAPI Generatorの使い方
ではここからは具体的な使い方について解説していきます。
今回はAPIクライアントコードの自動生成に絞って解説します。
最終的な成果物はこんな感じです。
サーバからおみくじの結果を受け取り、それを表示させています。
モックサーバを起動
クライアントコードの動作確認のために、ローカルで動作するサーバを立ち上げる必要があります。
今回は僕がSwift OpenAPI Generatorを使ってサーバ側を実装したので、それをローカルで立ち上げます。
レポジトリはこちらです。
レポジトリをローカルにダウンロードし、以下のコマンドを実行します。
cd ダウンロードした先/OmikujiService
swift run
するとサーバが立ち上がるので、Terminalを別ウィンドウで開き、以下のコマンドを入力して動作確認を行なってください。
curl 'http://localhost:8080/api/omikuji?name=Yourname'
必要なYAMLファイルの追加
まずは新しくプロジェクトを作成してください。
プロジェクトを作成したら、 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
というファイルを追加し、以下を記述してください。
generate:
- types
- client # 今回はクライアントコードを生成するのでこれを記述する
# - server サーバのコードを生成するときはこっちを記述する
パッケージの追加
次に必要なパッケージを3つ追加していきます。
プロジェクトのPackage Dependencies
から、以下のパッケージを追加してください。
パッケージ名 | 役割 | レポジトリURL |
---|---|---|
Swift OpenAPI Generator | YAMLからAPI通信に必要なコードを自動生成する | https://github.com/apple/swift-openapi-generator |
Swift OpenAPI Runtime | Swift OpenAPI Generator によって生成されたコードによって使用される共通の抽象化とヘルパー関数を提供する | https://github.com/apple/swift-openapi-runtime |
Swift OpenAPI URLSession | HTTPリクエストを管理する | https://github.com/apple/swift-openapi-urlsession |
Swift OpenAPI Generatorを追加する際は、すべてのTargetをnoneにしてください。
次に、「TARGETS」→「Build Phases」→「Run Build Tool Plug-ins」で OpenAPIGenerator
を追加し、プラグインを有効にします。
ここまでできたらビルドを実行します。
ビルドが通れば、DerivedData
にコードが自動生成されます。
Clientコードを書く
次に自動生成されたコードを使って、クライアントコードを記述していきます。
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)
}
}
詳しくコードを見ていきます。
let client = Client(
serverURL: try Servers.server2(),
transport: URLSessionTransport()
)
Clientは Generatorによって自動生成された構造体です。ここで使用するサーバ、トランスポートなどを指定することができます。
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で定義したメソッドが定義されてるプロトコルです。
internal protocol APIProtocol: Sendable {
func getOmikuji(_ input: Operations.getOmikuji.Input) async throws -> Operations.getOmikuji.Output
}
次にレスポンス部分を見ていきます。
let response = try await client.getOmikuji(query: .init(name: name))
getOmikujiメソッドの返り値は、Operations.getOmikji.Outputとなっています。
Operationsも自動生成された列挙型で、メソッドごとのInputのクエリとヘッダー、Outputの値が定義されています。
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部分を見ていきます。
return try (response.ok.body.json.result, response.ok.body.json.message)
Operations.getOmikuji.Outputは列挙型であり、okとundocumentedの2つの値を持ちます。
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に辿り着きます。
internal struct Ok: Sendable, Hashable {
internal enum Body: Sendable, Hashable {
case json(Components.Schemas.OmikujiResult)
}
}
では作成したクライアントコードを使って、API通信を行っていきます。
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が広く使われるようになると思います。
ぜひご自身の手で色々と試してみてください。
ここまでのご閲覧ありがとうございました!
コメント