June 12, 2023·5 min read·engineeringswiftopenapicode-generationhttp

Swift OpenAPI Generator makes HTTP contracts part of the build

A SwiftPM plugin generates client and server interfaces from OpenAPI, while transport protocols keep HTTP library selection separate from the contract.

Handwritten HTTP integration code repeats a contract that already exists elsewhere. A path, a query parameter, and a response schema become assumptions in a client implementation. When those assumptions change independently of the service description, a compiler cannot identify the disagreement unless the contract participates in the build.

The June 12 Swift OpenAPI Generator announcement introduces a SwiftPM plugin that generates client code and server interfaces from an OpenAPI document. Generated operations translate between typed Swift input and output values and HTTP requests and responses. The project is open-sourcing the implementation early, with a stable 1.0 release still ahead.

I like Swift's use of types to make absence explicit. Applying that discipline to an HTTP contract is useful because a missing query value and a required response property should not become indistinguishable assumptions in handwritten parsing code.

The document describes the operation

OpenAPI is a language-independent description of an HTTP service, written as YAML or JSON. The OpenAPI 3.0.3 specification defines operations, parameters, and response descriptions. Swift OpenAPI Generator initially focuses on that version, with support for OpenAPI 3.1 still in progress.

The announcement uses a greeting service with a GET operation at /greet. It accepts an optional name query parameter and returns a JSON object with a required message property. Those details describe more than the shape of a decoded model. They connect a method and path to the inputs and responses of an operation.

A parameter description adapted from that example makes optionality explicit.

parameters:
  - name: name
    in: query
    required: false
    schema:
      type: string

In OpenAPI 3.0.3, a query parameter defaults to not required when that field is omitted. Path parameters have a different rule and must be required. The location and requiredness of a value are therefore parts of the contract, alongside the value's schema.

The operation identifier also matters. The specification requires it to be unique among operations in the API and permits tooling to use it to identify an operation. In the announcement, getGreeting supplies the generated method name. A useful source document must describe these identities deliberately, since they become names that application code consumes.

Generation happens in a target's build

Adoption involves the generator package, its runtime library, and a transport implementation. The generator performs the build-time work. The runtime supplies protocols used by generated code and extension libraries, while the transport connects that code to a chosen HTTP library or server framework.

The target includes openapi.yaml and openapi-generator-config.yaml. The former describes the API, and the latter controls whether the plugin generates client or server code. Enabling the plugin and declaring runtime and transport dependencies makes generation part of building that target.

This organization puts the source document near the code that depends on it. A changed operation description can change generated Swift declarations during a build. Application code that no longer matches those declarations then has a type-checking problem in a place where the developer can act on it.

That does not mean every service disagreement becomes a compiler error. A running remote service may fail to follow its document, and the generator cannot determine business rules absent from the schema. Its contribution is to remove the manual duplication of the parts that the contract actually describes.

Client responses retain HTTP distinctions

The generated client provides APIProtocol, with a method for each operation, and a Client struct implementing it. A call uses typed input that groups the query values and returns an operation-specific output.

The announcement's client example switches first on the HTTP result and then on the body representation. A documented success with JSON is a different case from an undocumented status. The application therefore encounters the response contract in the structure of ordinary Swift control flow.

That is better than a convenience method that always returns the success model and leaves every other HTTP outcome implicit. The generated response still requires application policy. It supplies a place to express that policy with knowledge of the documented alternatives.

An undocumented response remains possible. The example explicitly handles it with the received status code. A contract-based client can make documented cases convenient without pretending that a remote peer is incapable of returning anything else.

This is the aspect of generation I find most persuasive. It can preserve the distinction between expected protocol outcomes and unexpected responses while reducing the mechanical work required to represent both.

A server implements the same operation protocol

On the server side, application code implements APIProtocol. The greeting handler accepts a generated input and returns a generated output. It can choose a fallback for an absent name and construct the documented successful JSON response.

Generated handler registration connects those methods to HTTP routes through a server transport. The announcement demonstrates this using Vapor. The handler contains service behavior, while the generated layer deals with translating the described operation into and out of HTTP.

Sharing the protocol shape between client and server establishes a common interpretation of the document. It does not force both sides to run the same HTTP stack. A client application and a server have different execution environments, and the transport abstraction accommodates that difference.

Transport protocols preserve framework choice

The runtime abstracts HTTP libraries through ClientTransport and ServerTransport. The announcement compares this with an API-package approach such as swift-log. Generated contract code depends on that interface rather than one mandatory networking implementation.

Available examples include URLSession and AsyncHTTPClient transports for clients, and Vapor and Hummingbird transports for servers. A developer can also implement a transport. The choice affects how HTTP communication happens without requiring a separate handwritten implementation of each operation's contract.

The initial release does not claim complete coverage of OpenAPI. The announcement says commonly used features are supported and tracks remaining work publicly. OpenAPI 3.1 support is ongoing, so a document's specification version and constructs remain relevant adoption constraints.

The practical division is clear in the greeting example. OpenAPI defines the optional name and required message, generated Swift represents those choices, and the transport carries the HTTP exchange. Application code still decides the greeting and handles the result cases exposed by the generated operation.