この記事の要点
- HTTP 通信を業界標準の OpenAPI 仕様を起点に効率化するための、オープンソースのライブラリ群が発表されました。中心となる Swift OpenAPI Generator は、OpenAPI ドキュメントからクライアント・サーバーのコードを生成する SwiftPM プラグインです。
- 生成されたコードは、各 API 操作の入力・出力の型安全な表現と、その背後の HTTP リクエスト・レスポンスとの間を変換します。これにより、ネットワーク処理のコードを手書きする必要がなくなり、アプリ本来のロジックに集中できます。
- 任意の HTTP クライアント・サーバーライブラリと組み合わせられるよう、HTTP 部分は
ClientTransport/ServerTransportという transport プロトコルで抽象化されています。URLSession ベースや Vapor ベースなど、既存の transport 実装を選んで使えます。 - 発表時点では安定版 1.0 に向けた開発初期段階で、コミュニティからのフィードバックを得るために早期にオープンソース化されました。
何が発表されたのか
OpenAPI は HTTP サービスを記述するための仕様です。OpenAPI ドキュメントは YAML または JSON で書かれ、ツールから読み取って HTTP リクエストの送受信に必要なコードを生成するといった自動化に利用できます。
OpenAPI は、サービスとそのクライアントの間の API の契約(API contract)を伝える信頼できる唯一の情報源(source of truth)として機能します。これにより、正しい呼び出し方を知るために、不正確な手書きのドキュメントを読んだりネットワーク通信を観察したりする必要がなくなります。サービスを初めて利用するときだけでなく、サービスが進化し続けるときにも、時間がかかり間違いやすい作業を避けられます。
たとえば、/greet エンドポイントへの HTTP GET リクエストを受け付け、任意のクエリパラメータ name を取り、200 OK と JSON のボディを返す GreetingService を考えます。このサービスは次のような OpenAPI ドキュメントで記述できます。
openapi: '3.0.3'
info:
title: GreetingService
version: 1.0.0
servers:
- url: "http://localhost:8080"
description: "Localhost"
paths:
/greet:
get:
operationId: getGreeting
parameters:
- name: name
in: query
schema:
type: string
responses:
'200':
description: A success response with a greeting.
content:
application/json:
schema:
$ref: "#/components/schemas/Greeting"
components:
schemas:
Greeting:
properties:
message:
type: string
required:
- message
Swift OpenAPI Generator は、この OpenAPI ドキュメントを入力として受け取り、HTTP 呼び出しを行うクライアントコード、またはその呼び出しを処理するサーバーコードを生成する SwiftPM プラグインです。生成コードがネットワーク処理を肩代わりするため、クライアント側でもサーバー側でも、利用者は中心となるロジックに集中できます。
何に使えるのか
プラグインの導入
プラグインを使うには、次の依存関係を追加します。
- ビルド時にコード生成を行うパッケージプラグイン(swift-openapi-generator)
- 生成コードが利用するプロトコル定義などを含むランタイムライブラリ(swift-openapi-runtime)
- 選択した HTTP クライアントライブラリやサーバーフレームワークを差し込むための transport 実装
そのうえで、ターゲットにプラグインを有効化し、API を記述した openapi.yaml と、クライアント・サーバーのどちらを生成するかを指定する設定ファイル openapi-generator-config.yaml を追加します。
生成されたクライアントを使う
クライアント(たとえば iOS アプリ)を開発する場合、OpenAPI の操作ごとに 1 つのメソッドを持つプロトコル APIProtocol と、それを実装した構造体 Client が生成されます。型安全なメソッドを呼ぶだけで HTTP 通信が行えます。
import OpenAPIRuntime
import OpenAPIURLSession
// Instantiate your chosen transport library.
let transport: ClientTransport = URLSessionTransport()
// Create a client to connect to a server URL documented in the OpenAPI document.
let client = Client(
serverURL: try Servers.server1(),
transport: transport
)
// Make the HTTP call using a type-safe method.
let response = try await client.getGreeting(.init(query: .init(name: "Jane")))
// Switch over the HTTP response status code.
switch response {
case .ok(let okResponse):
// Switch over the response content type.
switch okResponse.body {
case .json(let greeting):
// Print the greeting message.
print("👋 \(greeting.message)")
}
case .undocumented(statusCode: let statusCode, _):
// Handle HTTP response status codes not documented in the OpenAPI document.
print("🥺 undocumented response: \(statusCode)")
}
レスポンスはステータスコードやコンテンツタイプごとに enum のケースとして表現されるため、ドキュメント化された応答も、ドキュメントにない応答(undocumented)も、switch で網羅的に扱えます。
生成されたサーバースタブを使う
サーバーを開発する場合は、操作ごとのメソッドを持つ APIProtocol と、そのハンドラを transport に登録する registerHandlers メソッドが生成されます。APIProtocol に適合する型でハンドラを実装し、transport に登録してサーバーを起動します。
import OpenAPIRuntime
import OpenAPIVapor
import Vapor
// A server implementation of the GreetingService API.
struct Handler: APIProtocol {
func getGreeting(
_ input: Operations.getGreeting.Input
) async throws -> Operations.getGreeting.Output {
let message = "Hello, \(input.query.name ?? "Stranger")!"
let greeting = Components.Schemas.Greeting(message: message)
return .ok(.init(body: .json(greeting)))
}
}
// Create the Vapor app.
let app = Vapor.Application()
// Create the transport.
let transport: ServerTransport = VaporTransport(routesBuilder: app)
// Create the request handler, which contains your server logic.
let handler = Handler()
// Register the generated routes on the transport.
try handler.registerHandlers(on: transport)
// Start the server.
try app.run()
transport 実装
Swift OpenAPI Generator は、HTTP ライブラリのインターフェースを ClientTransport と ServerTransport というプロトコルで抽象化することで、任意の HTTP クライアント・サーバーライブラリと連携できます。swift-log と同様の API package 方式を採り、拡張性を高めています。発表時点で次のような transport 実装が利用できます。
- クライアント: swift-openapi-urlsession、swift-openapi-async-http-client
- サーバー: swift-openapi-vapor、swift-openapi-hummingbird
これらに当てはまらない場合は、自分で transport を実装することもできます。
導入・今後の位置づけ
このプロジェクトは、コミュニティのフィードバックを取り入れ、安定版 1.0 に到達するために、開発の早い段階でオープンソース化されました。初期は OpenAPI 仕様の バージョン 3.0.3 の機能の実装に重点が置かれ、OpenAPI 3.1 のサポートに向けた作業も進められています。よく使われる機能の大半をすでにサポートしていますが、未実装の機能も残っており、進捗は GitHub の issue で公開管理されています。これらは発表時点の方針であり、実現を約束するものではありません。
リポジトリの確認や issue・プルリクエストの作成、Swift フォーラム でのフィードバックを通じた参加が呼びかけられています。