Swift OpenAPI Generator

repository·main·Indexed 23 days ago

https://github.com/apple/swift-openapi-generator

A Swift package plugin that generates type-safe client and server code from OpenAPI specifications in YAML or JSON. It automates HTTP request and response handling to keep code in sync with API definitions, supporting various transport layers including URLSession, AsyncHTTPClient, Vapor, and Hummingbird. The tool includes support for ClientMiddleware, ServerMiddleware, bidirectional event streams, and APIProtocol-based mocking for unit testing.

Tokens
36.2K
Snippets
90
Records
192
Agent score
80%

What's inside swift-openapi-generator

  1. Explore the Swift OpenAPI Generator package ecosystem

    main

    The project is modularized to allow for different HTTP clients and web frameworks. Depending on your needs, you may need to import specific transport packages:

    • Clients:
      • apple/swift-openapi-urlsession: ClientTransport using URLSession.
      • swift-server/swift-openapi-async-http-client: ClientTransport using AsyncHTTPClient.
    • Servers:
      • vapor/swift-openapi-vapor: ServerTransport using Vapor.
      • hummingbird-project/swift-openapi-hummingbird: ServerTransport using Hummingbird.
      • awslabs/swift-openapi-lambda: ServerTransport using AWS Lambda.
  2. Explore Swift OpenAPI Generator integration examples

    main

    The repository provides several categories of examples to demonstrate how to integrate the generator with different transports, content types, and ecosystem tools:

    Transports and Frameworks

    • URLSession: CLI client using Apple's URLSession API.
    • AsyncHTTPClient: CLI client using the AsyncHTTPClient library.
    • Vapor: CLI server using the Vapor web framework.
    • Hummingbird: CLI server using the Hummingbird web framework.
    • iOS/SwiftUI: An iOS client app with a mock server for testing.
    • Curated Client Library: A pattern for hiding generated APIs behind a hand-written interface to allow decoupled versioning.

    Content Types and Streaming

    • Various Content Types: Handling JSON, URL-encoded bodies, plain text, raw bytes, and multipart bodies.
    • Event Streams: Handling JSON Lines, JSON Sequence, and Server-sent Events (SSE).
    • Bidirectional Streams: Handling bidirectional event streams for both clients and servers.

    Integrations and Middleware

    • Integrations: Using Swagger UI for interactive documentation, Postgres for persistence, and Swift Argument Parser for CLI tools.
    • Middleware: Implementing logging (OSLog, SwiftLog), metrics (SwiftMetrics), tracing (Swift Distributed Tracing), retrying logic, and authentication (injecting or inspecting token headers).
  3. Overview of the Swift OpenAPI Generator ecosystem

    main

    The project is modularized into several repositories to allow for different HTTP clients and web frameworks. The core logic resides in the generator and runtime, while specific transports are provided by separate packages:

    RepositoryDescription
    apple/swift-openapi-generatorSwift package plugin and CLI
    apple/swift-openapi-runtimeRuntime library used by the generated code
    apple/swift-openapi-urlsessionClientTransport using URLSession
    swift-server/swift-openapi-async-http-clientClientTransport using AsyncHTTPClient
    frameo-net/swift-okhttpClientTransport using OkHttp
    vapor/swift-openapi-vaporServerTransport using Vapor
    hummingbird-project/swift-openapi-hummingbirdServerTransport using Hummingbird
    awslabs/swift-openapi-lambdaServerTransport using AWS Lambda
  4. Marking standalone schemas as optional

    main

    To make a standalone schema optional, you must define it as nullable. The syntax depends on the OpenAPI/JSON Schema version used:

    • OpenAPI 3.0.3 (JSON Schema Draft 5): Use the nullable: true field.
    • OpenAPI 3.1.0 (JSON Schema 2020-12): Include null in the type array.

    Nullability is propagated through references; the generator checks the target schema of a reference to determine optionality.

    # OpenAPI 3.0.3
    MyOptionalString:
      type: string
      nullable: true
    # OpenAPI 3.1.0
    MyOptionalString:
      type: [string, null]
  5. Understand identifier mapping changes (SOAR-0001)

    main

    The Swift OpenAPI Generator uses a specific mapping strategy to convert OpenAPI property names (which may contain unsupported characters) into valid Swift identifiers.

    As of version 0.2.0, the generator uses a combination of wordified representations for printable ASCII characters and hex encoding for other characters to prevent name collisions. This replaces the older method of simply substituting all unsupported characters with an underscore (_).

    Key behaviors:

    • Printable ASCII (20-7E): Characters are replaced with a wordified representation (e.g., space becomes _space_, asterisk becomes _ast_).
    • Other characters: Encoded as hex digits prefixed with x (e.g., _x2026_).
    • Delimiters: Underscores (_) are used to separate these encoded segments.

    Warning: This is an API-breaking change. Upgrading to a version implementing SOAR-0001 will result in different generated symbol names. If your code relies on specific generated property names, you must update your code to match the new mapping before upgrading.

    # Example Mapping Table
    a b    | a_space_b
    a*b    | a_ast_b
    ab_    | ab_
    ab*    | ab_ast_
    /ab    | _sol_ab
    Hu&J_?kin | Hu_amp_J__quest_kin
    $nake… | _dollar_nake_x2026_
    message | message
  6. How the generator selects types for boxing

    main

    The generator uses an algorithm to minimize the number of boxed types, as boxing can be less performant than non-recursive value types.

    The Algorithm:

    1. It iterates through types defined in #/components/schemas in the order they appear in the OpenAPI document.
    2. It walks the references for each type.
    3. When a reference cycle is detected, it selects the first type in the cycle (the one that closed the cycle) to be boxed.

    Example: If the reference path is A -> B -> C -> B, the algorithm identifies that the cycle closes at B, so it chooses type B for boxing.

  7. Identify non-stable behaviors in Swift OpenAPI Generator

    main

    The following behaviors are not considered part of the Swift OpenAPI Generator API and may change without warning. You should be prepared for these to change when updating the generator:

    • The number and names of files generated by the CLI and plugins.
    • The SPI (Service Provider Interface) provided by the OpenAPIRuntime library (marked with @_spi(Generated)).
    • The business logic within the generated code (any code that is not part of the generated code's own public API).
    • The diagnostics emitted by the generator, including their severity levels and printed descriptions.
  8. Filter OpenAPI documents to reduce generated code size

    main

    You can use the filter configuration key in your openapi-generator-config.yaml to instruct the generator to only process a subset of your OpenAPI document. This is highly effective for large APIs (like GitHub's) where you only need a specific set of endpoints or types.

    Filtering reduces:

    • The amount of unused code generated.
    • Compilation times.
    • The overall footprint of the generated code in your codebase.

    When you specify a filter, the generator includes the requested items along with the transitive closure of all components they depend on. For example, if you include a path, all schemas referenced by that path's operations and responses will also be included.

    # openapi-generator-config.yaml
    generate:
    - types
    - client
    
    filter:
      tags:
      - issues
  9. Use the Converter helper method naming convention

    main

    Helper methods follow a deterministic naming pattern. If you are debugging generated code, you can predict the method name using this pattern:

    {set,get}{required/optional/omit if both}{location}As{strategy}

    Example Patterns:

    • setRequiredRequestBodyAsJSON(value:)
    • getOptionalQueryItemAsURI()
    • setHeaderFieldAsURI(value:)
    method name: {set,get}{required/optional/omit if both}{location}As{strategy}
    method parameters: value or type of value
  10. How the generator handles recursive types

    main

    Recursive types are schemas that hold a value of themselves, either directly or through another type (e.g., a tree structure or a person with a partner). Because Swift structs and enums require a fixed size at compile time, they cannot natively support infinite nesting.

    To resolve this, the Swift OpenAPI Generator uses a technique called boxing. Boxing introduces a reference type into the reference cycle, allowing the outer type to maintain its original API and value semantics while supporting recursion.

    Key behaviors:

    • Enums: Boxed using the indirect keyword.
    • Structs: Boxed by moving properties into a private final class Storage and using a CopyOnWriteBox wrapper to maintain value semantics.
    • Arrays and Dictionaries: These are already reference types under the hood and are considered 'already boxed'. If a recursive cycle only involves arrays or dictionaries, the parent struct/enum does not need boxing.
    • Pure Reference Schemas: The generator will not box a $ref type directly (as they are typealias in Swift); instead, it boxes the next eligible type in the cycle.