Swift OpenAPI Generator es el plugin oficial de Apple para generar código Swift seguro en tipos a partir de una especificación OpenAPI 3.0 (YAML o JSON). El enfoque spec-first invierte el flujo habitual: primero defines el contrato de la API y después generas el cliente o el servidor automáticamente. El resultado es que cliente y servidor están siempre sincronizados con la spec.
Añadir el plugin como dependencia SPM
El generador se integra como plugin de Swift Package Manager. Al construir el paquete, genera el código Swift automáticamente:
// Package.swift
// swift-tools-version: 5.9
import PackageDescription
let package = Package(
name: "MiProyecto",
platforms: [.macOS(.v13), .iOS(.v16)],
dependencies: [
.package(url: "https://github.com/apple/swift-openapi-generator", from: "1.0.0"),
.package(url: "https://github.com/apple/swift-openapi-runtime", from: "1.0.0"),
.package(url: "https://github.com/apple/swift-openapi-urlsession", from: "1.0.0"),
// Para el servidor con Vapor:
.package(url: "https://github.com/swift-server/swift-openapi-vapor", from: "1.0.0"),
],
targets: [
.target(
name: "MiCliente",
dependencies: [
.product(name: "OpenAPIRuntime", package: "swift-openapi-runtime"),
.product(name: "OpenAPIURLSession", package: "swift-openapi-urlsession"),
],
plugins: [
.plugin(name: "OpenAPIGenerator", package: "swift-openapi-generator")
]
)
]
)
Definir paths y schemas en openapi.yaml
El fichero Sources/MiCliente/openapi.yaml define el contrato completo de la API:
// openapi.yaml (contenido en formato texto)
// openapi: "3.0.3"
// info:
// title: API de Productos
// version: 1.0.0
// servers:
// - url: https://api.ejemplo.com/v1
// paths:
// /productos:
// get:
// operationId: listarProductos
// summary: Lista todos los productos
// parameters:
// - name: categoria
// in: query
// required: false
// schema:
// type: string
// responses:
// "200":
// description: Lista de productos
// content:
// application/json:
// schema:
// type: array
// items:
// $ref: "#/components/schemas/Producto"
// /productos/{id}:
// get:
// operationId: obtenerProducto
// parameters:
// - name: id
// in: path
// required: true
// schema:
// type: string
// format: uuid
// responses:
// "200":
// content:
// application/json:
// schema:
// $ref: "#/components/schemas/Producto"
// "404":
// description: Producto no encontrado
// components:
// schemas:
// Producto:
// type: object
// required: [id, nombre, precio]
// properties:
// id:
// type: string
// format: uuid
// nombre:
// type: string
// precio:
// type: number
// format: double
El generador crea el fichero openapi-generator-config.yaml para configurar qué generar:
// openapi-generator-config.yaml
// generate:
// - types
// - client # para generar cliente
// # - server # para generar servidor
Generar y usar el cliente con URLSessionTransport
Al compilar, se genera la estructura Client con todos los métodos tipados. URLSessionTransport es el transporte para apps iOS y macOS:
import OpenAPIRuntime
import OpenAPIURLSession
// El generador crea esta interfaz automáticamente:
// struct Client {
// init(serverURL: URL, transport: any ClientTransport)
// func listarProductos(query: Operations.listarProductos.Input.Query) async throws
// -> Operations.listarProductos.Output
// func obtenerProducto(path: Operations.obtenerProducto.Input.Path) async throws
// -> Operations.obtenerProducto.Output
// }
// Uso del cliente generado:
let cliente = Client(
serverURL: URL(string: "https://api.ejemplo.com/v1")!,
transport: URLSessionTransport()
)
// Llamar al endpoint tipado
let respuesta = try await cliente.listarProductos(
query: .init(categoria: "electronica")
)
switch respuesta {
case .ok(let body):
let productos = try body.body.json
print("(productos.count) productos encontrados")
case .undocumented(statusCode: let codigo, _):
print("Respuesta inesperada: (codigo)")
}
Generar el servidor con VaporTransport
Para el servidor, el generador crea un protocolo que el desarrollador implementa:
import OpenAPIRuntime
import OpenAPIVapor
import Vapor
// El generador crea este protocolo:
// protocol APIProtocol {
// func listarProductos(
// _ input: Operations.listarProductos.Input
// ) async throws -> Operations.listarProductos.Output
// }
// Implementación:
struct MiImplementacion: APIProtocol {
func listarProductos(
_ input: Operations.listarProductos.Input
) async throws -> Operations.listarProductos.Output {
let categoria = input.query.categoria
let productos = try await ProductoRepository.shared.listar(categoria: categoria)
let schemas = productos.map { Components.Schemas.Producto(
id: $0.id.uuidString,
nombre: $0.nombre,
precio: $0.precio
)}
return .ok(.init(body: .json(schemas)))
}
func obtenerProducto(
_ input: Operations.obtenerProducto.Input
) async throws -> Operations.obtenerProducto.Output {
guard let uuid = UUID(uuidString: input.path.id),
let producto = try await ProductoRepository.shared.obtener(id: uuid) else {
return .notFound
}
return .ok(.init(body: .json(.init(id: producto.id.uuidString,
nombre: producto.nombre,
precio: producto.precio))))
}
}
// Registrar en Vapor
func routes(_ app: Application) throws {
let transport = VaporTransport(routesBuilder: app)
let handler = MiImplementacion()
try handler.registerHandlers(on: transport)
}
Ventajas del enfoque spec-first
El código generado nunca puede desfasarse respecto a la spec porque se regenera en cada build. Si cambias un campo en openapi.yaml y olvidas actualizar la implementación, el compilador de Swift avisa con un error antes de que el código llegue a producción. Esto elimina toda una categoría de bugs de contratos de API rotos.
Resumen
Swift OpenAPI Generator convierte la spec OpenAPI en código Swift tipado como paso de build. El plugin SPM se añade en Package.swift; openapi.yaml define paths, parámetros y schemas; openapi-generator-config.yaml especifica si generar cliente, servidor o ambos; y los transportes URLSessionTransport y VaporTransport conectan el código generado con la capa de red concreta. El resultado es una API en la que los errores de contrato se detectan en tiempo de compilación, no en producción.
