この記事の要点
- OpenAPI ドキュメントからクライアント・サーバーのコードを生成する SwiftPM プラグイン Swift OpenAPI Generator が、安定版の 1.0 に到達しました。最初の発表から約半年で、20 名を超えるコントリビュータによる 250 を超えるプルリクエストを経て、機能の追加と API の簡素化が行われています。
- コードはビルド時に自動生成されるため、OpenAPI ドキュメントと常に同期し、ソースリポジトリにコミットする必要がありません。
- OpenAPI 仕様の 3.0 と 3.1 の両方に対応し、
AsyncSequenceを使ったストリーミング、JSON・multipart・URL エンコードフォーム・base64・プレーンテキスト・生バイト列といった主要なコンテンツタイプを、型安全な値型として扱えます。 - HTTP クライアントライブラリや Web フレームワークから生成コードを切り離す、柔軟なクライアント・サーバー・middleware の抽象化が提供されます。
何が発表されたのか
OpenAPI は、HTTP サービスの振る舞いを記述するためのオープンな標準で、豊富なツールのエコシステムを持ちます。インタラクティブなドキュメントの生成で知られていますが、本来の中心的な動機はコード生成です。これにより、サーバー開発では API ファーストのアプローチが取れ、クライアント開発では、すでに OpenAPI 形式で API を記述している多くの既存サービスに対して、型安全でidiomaticな呼び出しコードを生成できます。
現実の API は数百もの操作を持つことが多く、それぞれがリッチなリクエスト・レスポンスの型、ヘッダーフィールド、パラメータなどを伴います。これらのコードを操作ごとに手で書いて保守するのは、反復的で冗長になりがちで、間違いも起きやすい作業です。
Swift OpenAPI Generator は、API 呼び出しを行うコードや API サーバーを実装するコードを生成する SwiftPM プラグインです。コードはビルド時に自動生成されるため、OpenAPI ドキュメントと常に同期し、ソースリポジトリにコミットする必要がありません。
何に使えるのか
たとえば、名前を受け取ってパーソナライズされた挨拶を返す、次のような API エンドポイントを考えます。
% curl 'https://example.com/api/greet?name=Jane'
{
"message": "Hello, Jane"
}
このサービスは、OpenAPI ドキュメント(openapi.yaml)で次のように記述できます。クエリパラメータ name を受け取り、200 レスポンスとして message を持つ JSON を返すことが定義されています。
openapi: '3.1.0'
info:
title: GreetingService
version: 1.0.0
servers:
- url: https://example.com/api
description: Example service deployment.
paths:
/greet:
get:
operationId: getGreeting
parameters:
- name: name
required: false
in: query
description: The name used in the returned greeting.
schema:
type: string
responses:
'200':
description: A success response with a greeting.
content:
application/json:
schema:
$ref: '#/components/schemas/Greeting'
components:
schemas:
Greeting:
type: object
description: A value with the greeting contents.
properties:
message:
type: string
description: The string representation of the greeting.
required:
- message
このドキュメントを入力に、クライアントコードとサーバーコードのどちらでも生成できます。
生成されたクライアント API
クライアントを生成すると、OpenAPI ドキュメントの操作ごとにメソッドを持つ Client 型が生成されます。Swift OpenAPI Generator 向けの統合パッケージを提供する任意の HTTP ライブラリと組み合わせられます。生成コードはビルドシステムが決める場所にビルド時に出力されるため、利用側はサーバー URL と使いたい HTTP の transport を渡して Client を作るだけです。
次の例は、内部の HTTP 通信に URLSession を使うクライアントを作成しています。
import OpenAPIURLSession
import Foundation
let client = Client(
serverURL: URL(string: "http://localhost:8080/api")!,
transport: URLSessionTransport()
)
let response = try await client.getGreeting()
print(try response.ok.body.json.message)
生成されたサーバースタブ
サーバーを生成すると、OpenAPI ドキュメントの操作ごとにメソッド要件を定義したプロトコル APIProtocol が生成されます。これは、Swift OpenAPI Generator 向けの統合パッケージを提供する任意の Web フレームワークと連携するよう設計されています。
API サーバーを実装するには、APIProtocol に適合する型を定義し、各操作の中心となるロジックだけを記述します。サーバーの起動時には、生成された registerHandlers 関数を使って、HTTP リクエストを自分のハンドラへルーティングするよう HTTP サーバーを設定します。
次の例は、Greeting Service API を Handler という型で実装し、HTTP リクエストを処理するよう Vapor の Web サーバーを設定しています。
import OpenAPIRuntime
import OpenAPIVapor
import Vapor
struct Handler: APIProtocol {
func getGreeting(_ input: Operations.getGreeting.Input) async throws -> Operations.getGreeting.Output {
let name = input.query.name ?? "Stranger"
return .ok(.init(body: .json(.init(message: "Hello, \(name)!"))))
}
}
@main struct Server {
static func main() async throws {
let app = Vapor.Application()
let transport = VaporTransport(routesBuilder: app)
let handler = Handler()
try handler.registerHandlers(on: transport, serverURL: URL(string: "/api")!)
try await app.execute()
}
}
パッケージのエコシステム
Swift OpenAPI Generator は、拡張性を高めプロジェクトの依存関係を最小限に抑えるため、複数のリポジトリに分割されています。
- swift-openapi-generator: SwiftPM プラグインと CLI
- swift-openapi-runtime: 生成コードが利用するランタイムライブラリ
- swift-openapi-urlsession: URLSession を使うクライアント transport
- swift-openapi-async-http-client: AsyncHTTPClient を使うクライアント transport
- swift-openapi-vapor: Vapor を使うサーバー transport
- swift-openapi-hummingbird: Hummingbird を使うサーバー transport
導入・今後の位置づけ
導入を始めるには、ドキュメントと、ステップバイステップのチュートリアルが用意されています。エコシステムの他のパッケージと連携するサンプルプロジェクトで試すこともできます。
質問・機能要望・バグ報告は、GitHub の各リポジトリを通じて受け付けられています。