Skip to content

Swift OpenAPI Generator

Web & HTTPNetworking/HTTPSwift

What it is

Apple's build plugin that generates type-safe Swift client and server code from an OpenAPI document.

Add the plugin to the target with an openapi.yaml and a config file. Generated code appears at build time and is never committed.

Installation

.package(url: "https://github.com/apple/swift-openapi-generator", from: "1.5.0")

Getting started

The smallest useful thing you can do with it, and what each part means.

Generated client
// Package.swift
.target(
    name: "BookClient",
    dependencies: [
        .product(name: "OpenAPIRuntime", package: "swift-openapi-runtime"),
        .product(name: "OpenAPIURLSession", package: "swift-openapi-urlsession"),
    ],
    plugins: [.plugin(name: "OpenAPIGenerator", package: "swift-openapi-generator")]
)

// Usage — every operation and response is typed from the document.
let client = Client(
    serverURL: try Servers.server1(),
    transport: URLSessionTransport()
)

let response = try await client.getBook(path: .init(id: 42))

switch response {
case .ok(let ok):
    let book = try ok.body.json
    print(book.title)
case .notFound:
    print("no such book")
case .undocumented(let status, _):
    print("unexpected status \(status)")
}
The switch must handle every documented response, so an endpoint that can return 404 cannot be consumed as if it always succeeds. `undocumented` forces you to acknowledge anything the spec did not describe.

Advanced usage

Where the library earns its place over a simpler alternative.

Middleware and server generation
struct AuthMiddleware: ClientMiddleware {
    let token: String

    func intercept(
        _ request: HTTPRequest, body: HTTPBody?, baseURL: URL,
        operationID: String,
        next: (HTTPRequest, HTTPBody?, URL) async throws -> (HTTPResponse, HTTPBody?)
    ) async throws -> (HTTPResponse, HTTPBody?) {
        var request = request
        request.headerFields[.authorization] = "Bearer \(token)"
        return try await next(request, body, baseURL)
    }
}

let client = Client(serverURL: url, transport: transport,
                    middlewares: [AuthMiddleware(token: token)])

// The same document can generate a server protocol for Vapor to implement.
Generating both sides from one document is the strongest use: the compiler then enforces that the server implements exactly what the client expects.

Errors and fixes

The failures you are most likely to hit, and what actually resolves them.

Type has no member for an operation
The operationId is missing from the document, or the file was not picked up. Check the plugin's config and file naming.
Everything falls into the undocumented case
The spec does not describe those responses. Add them to the document — the generator can only expose what is declared.

Best practices

  • Do not commit generated code; the plugin regenerates it on every build.
  • Document every response status in the OpenAPI file, or callers land in the undocumented case.
  • Use middleware for authentication rather than passing tokens to each call.
  • Version the OpenAPI document alongside the code so changes are reviewable.

Background

Why it exists, and what it was reacting to.

Rather than hand-writing request structs and response models for every endpoint, the generator derives them from the API's own contract at build time, so client and server cannot drift apart silently.