-
Meet Swift OpenAPI Generator
Discover how Swift OpenAPI Generator can help you work with HTTP server APIs whether you're extending an iOS app or writing a server in Swift. We'll show you how this package plugin can streamline your workflow and simplify your codebase by generating code from an OpenAPI document.
Capítulos
- 0:44 - Considerations when making API calls
- 1:52 - Meet OpenAPI
- 6:15 - Making API calls from your app
- 12:33 - Adapting as the API evolves
- 14:23 - Testing your app with mocks
- 16:12 - Server development in Swift
- 19:24 - Adding a new operation
Recursos
- URLSession Transport for Swift OpenAPI Generator
- Swift OpenAPI Generator Runtime
- Swift OpenAPI Generator package plugin
Videos relacionados
WWDC22
-
Buscar este video…
-
-
4:17 - Example OpenAPI document
openapi: "3.0.3" info: title: "GreetingService" version: "1.0.0" servers: - url: "http://localhost:8080/api" description: "Production" paths: /greet: get: operationId: "getGreeting" parameters: - name: "name" required: false in: "query" description: "Personalizes the greeting." schema: type: "string" responses: "200": description: "Returns a greeting" content: application/json: schema: $ref: "#/components/schemas/Greeting" -
7:05 - CatService openapi.yaml
openapi: "3.0.3" info: title: CatService version: 1.0.0 servers: - url: http://localhost:8080/api description: "Localhost cats 🙀" paths: /emoji: get: operationId: getEmoji parameters: - name: count required: false in: query description: "The number of cats to return. 😽😽😽" schema: type: integer responses: '200': description: "Returns a random emoji, of a cat, ofc! 😻" content: text/plain: schema: type: string -
8:03 - Making API calls from your app
import SwiftUI import OpenAPIRuntime import OpenAPIURLSession #Preview { ContentView() } struct ContentView: View { @State private var emoji = "🫥" var body: some View { VStack { Text(emoji).font(.system(size: 100)) Button("Get cat!") { Task { try? await updateEmoji() } } } .padding() .buttonStyle(.borderedProminent) } let client: Client init() { self.client = Client( serverURL: try! Servers.server1(), transport: URLSessionTransport() ) } func updateEmoji() async throws { let response = try await client.getEmoji(Operations.getEmoji.Input()) switch response { case let .ok(okResponse): switch okResponse.body { case .text(let text): emoji = text } case .undocumented(statusCode: let statusCode, _): print("cat-astrophe: \(statusCode)") emoji = "🙉" } } } -
9:48 - CatServiceClient openapi-generator-config.yaml
generate: - types - client -
13:24 - Adapting as the API evolves
import SwiftUI import OpenAPIRuntime import OpenAPIURLSession #Preview { ContentView() } struct ContentView: View { @State private var emoji = "🫥" var body: some View { VStack { Text(emoji).font(.system(size: 100)) Button("Get cat!") { Task { try? await updateEmoji() } } Button("More cats!") { Task { try? await updateEmoji(count: 3) } } } .padding() .buttonStyle(.borderedProminent) } let client: Client init() { self.client = Client( serverURL: try! Servers.server1(), transport: URLSessionTransport() ) } func updateEmoji(count: Int = 1) async throws { let response = try await client.getEmoji(Operations.getEmoji.Input( query: Operations.getEmoji.Input.Query(count: count) )) switch response { case let .ok(okResponse): switch okResponse.body { case .text(let text): emoji = text } case .undocumented(statusCode: let statusCode, _): print("cat-astrophe: \(statusCode)") emoji = "🙉" } } } -
15:06 - Testing your app with mocks
import SwiftUI import OpenAPIRuntime import OpenAPIURLSession #Preview { ContentView(client: MockClient()) } struct ContentView<C: APIProtocol>: View { @State private var emoji = "🫥" var body: some View { VStack { Text(emoji).font(.system(size: 100)) Button("Get cat!") { Task { try? await updateEmoji() } } Button("More cats!") { Task { try? await updateEmoji(count: 3) } } } .padding() .buttonStyle(.borderedProminent) } let client: C init(client: C) { self.client = client } init() where C == Client { self.client = Client( serverURL: try! Servers.server1(), transport: URLSessionTransport() ) } func updateEmoji(count: Int = 1) async throws { let response = try await client.getEmoji(Operations.getEmoji.Input( query: Operations.getEmoji.Input.Query(count: count) )) switch response { case let .ok(okResponse): switch okResponse.body { case .text(let text): emoji = text } case .undocumented(statusCode: let statusCode, _): print("cat-astrophe: \(statusCode)") emoji = "🙉" } } } struct MockClient: APIProtocol { func getEmoji(_ input: Operations.getEmoji.Input) async throws -> Operations.getEmoji.Output { let count = input.query.count ?? 1 let emojis = String(repeating: "🤖", count: count) return .ok(Operations.getEmoji.Output.Ok( body: .text(emojis) )) } } -
16:58 - Implementing a backend server
import Foundation import OpenAPIRuntime import OpenAPIVapor import Vapor struct Handler: APIProtocol { func getEmoji(_ input: Operations.getEmoji.Input) async throws -> Operations.getEmoji.Output { let candidates = "🐱😹😻🙀😿😽😸😺😾😼" let chosen = String(candidates.randomElement()!) let count = input.query.count ?? 1 let emojis = String(repeating: chosen, count: count) return .ok(Operations.getEmoji.Output.Ok(body: .text(emojis))) } } @main struct CatService { public static func main() throws { let app = Vapor.Application() let transport = VaporTransport(routesBuilder: app) let handler = Handler() try handler.registerHandlers(on: transport, serverURL: Servers.server1()) try app.run() } } -
18:43 - CatService Package.swift
// swift-tools-version: 5.8 import PackageDescription let package = Package( name: "CatService", platforms: [ .macOS(.v13), ], dependencies: [ .package( url: "https://github.com/apple/swift-openapi-generator", .upToNextMinor(from: "0.1.0") ), .package( url: "https://github.com/apple/swift-openapi-runtime", .upToNextMinor(from: "0.1.0") ), .package( url: "https://github.com/swift-server/swift-openapi-vapor", .upToNextMinor(from: "0.1.0") ), .package( url: "https://github.com/vapor/vapor", .upToNextMajor(from: "4.69.2") ), ], targets: [ .executableTarget( name: "CatService", dependencies: [ .product(name: "OpenAPIRuntime", package: "swift-openapi-runtime"), .product(name: "OpenAPIVapor", package: "swift-openapi-vapor"), .product(name: "Vapor", package: "vapor"), ], resources: [ .process("Resources/cat.mp4"), ], plugins: [ .plugin(name: "OpenAPIGenerator", package: "swift-openapi-generator"), ] ), ] ) -
19:08 - CatService openapi.yaml
openapi: "3.0.3" info: title: CatService version: 1.0.0 servers: - url: http://localhost:8080/api description: "Localhost cats 🙀" paths: /emoji: get: operationId: getEmoji parameters: - name: count required: false in: query description: "The number of cats to return. 😽😽😽" schema: type: integer responses: '200': description: "Returns a random emoji, of a cat, ofc! 😻" content: text/plain: schema: type: string -
19:10 - CatService openapi-generator-config.yaml
generate: - types - server -
20:11 - Adding an operation to the OpenAPI document
openapi: "3.0.3" info: title: CatService version: 1.0.0 servers: - url: http://localhost:8080/api description: "Localhost cats 🙀" paths: /emoji: get: operationId: getEmoji parameters: - name: count required: false in: query description: "The number of cats to return. 😽😽😽" schema: type: integer responses: '200': description: "Returns a random emoji, of a cat, ofc! 😻" content: text/plain: schema: type: string /clip: get: operationId: getClip responses: '200': description: "Returns a cat video! 😽" content: video/mp4: schema: type: string format: binary -
20:26 - Adding a new API operation
import Foundation import OpenAPIRuntime import OpenAPIVapor import Vapor struct Handler: APIProtocol { func getClip(_ input: Operations.getClip.Input) async throws -> Operations.getClip.Output { let clipResourceURL = Bundle.module.url(forResource: "cat", withExtension: "mp4")! let clipData = try Data(contentsOf: clipResourceURL) return .ok(Operations.getClip.Output.Ok(body: .binary(clipData))) } func getEmoji(_ input: Operations.getEmoji.Input) async throws -> Operations.getEmoji.Output { let candidates = "🐱😹😻🙀😿😽😸😺😾😼" let chosen = String(candidates.randomElement()!) let count = input.query.count ?? 1 let emojis = String(repeating: chosen, count: count) return .ok(Operations.getEmoji.Output.Ok(body: .text(emojis))) } } @main struct CatService { public static func main() throws { let app = Vapor.Application() let transport = VaporTransport(routesBuilder: app) let handler = Handler() try handler.registerHandlers(on: transport, serverURL: Servers.server1()) try app.run() } }
-