forked from apple/swift-openapi-runtime
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathUniversalClient.swift
More file actions
158 lines (150 loc) · 5.51 KB
/
UniversalClient.swift
File metadata and controls
158 lines (150 loc) · 5.51 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftOpenAPIGenerator open source project
//
// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
#if canImport(Darwin)
import Foundation
#else
// `@preconcrrency` is for `URL`.
@preconcurrency import Foundation
#endif
/// OpenAPI document-agnostic HTTP client used by OpenAPI document-specific,
/// generated clients to perform request serialization, middleware and transport
/// invocation, and response deserialization.
///
/// Do not call this directly, only invoked by generated code.
@_spi(Generated)
public struct UniversalClient: Sendable {
/// The URL of the server, used as the base URL for requests made by the
/// client.
public let serverURL: URL
/// Converter for encoding/decoding data.
public let converter: Converter
/// Type capable of sending HTTP requests and receiving HTTP responses.
public var transport: any ClientTransport
/// Middlewares to be invoked before `transport`.
public var middlewares: [any ClientMiddleware]
/// Internal initializer that takes an initialized `Converter`.
internal init(
serverURL: URL,
converter: Converter,
transport: any ClientTransport,
middlewares: [any ClientMiddleware]
) {
self.serverURL = serverURL
self.converter = converter
self.transport = transport
self.middlewares = middlewares
}
/// Creates a new client.
public init(
serverURL: URL = .defaultOpenAPIServerURL,
configuration: Configuration = .init(),
transport: any ClientTransport,
middlewares: [any ClientMiddleware] = []
) {
self.init(
serverURL: serverURL,
converter: Converter(configuration: configuration),
transport: transport,
middlewares: middlewares
)
}
/// Performs the HTTP operation.
///
/// Should only be called by generated code, not directly.
///
/// An operation consists of three steps:
/// 1. Convert Input into an HTTP request.
/// 2. Invoke the `ClientTransport` to perform the HTTP call, wrapped by middlewares.
/// 3. Convert the HTTP response into Output.
///
/// It wraps any thrown errors and attaches appropriate context.
///
/// - Parameters:
/// - input: Operation-specific input value.
/// - operationID: The OpenAPI operation identifier.
/// - serializer: Creates an HTTP request from the provided Input value.
/// - deserializer: Creates an Output value from the provided HTTP response.
/// - Returns: The Output value produced by `deserializer`.
public func send<OperationInput, OperationOutput>(
input: OperationInput,
forOperation operationID: String,
serializer: @Sendable (OperationInput) throws -> Request,
deserializer: @Sendable (Response) throws -> OperationOutput
) async throws -> OperationOutput where OperationInput: Sendable, OperationOutput: Sendable {
@Sendable
func wrappingErrors<R>(
work: () async throws -> R,
mapError: (any Error) -> any Error
) async throws -> R {
do {
return try await work()
} catch {
throw mapError(error)
}
}
let baseURL = serverURL
func makeError(
request: Request? = nil,
baseURL: URL? = nil,
response: Response? = nil,
error: any Error
) -> any Error {
ClientError(
operationID: operationID,
operationInput: input,
request: request,
baseURL: baseURL,
response: response,
underlyingError: error
)
}
let request: Request = try await wrappingErrors {
try serializer(input)
} mapError: { error in
makeError(error: error)
}
let response: Response = try await wrappingErrors {
var next: @Sendable (Request, URL) async throws -> Response = { (_request, _url) in
try await wrappingErrors {
try await transport.send(
_request,
baseURL: _url,
operationID: operationID
)
} mapError: { error in
RuntimeError.transportFailed(error)
}
}
for middleware in middlewares.reversed() {
let tmp = next
next = {
try await middleware.intercept(
$0,
baseURL: $1,
operationID: operationID,
next: tmp
)
}
}
return try await next(request, baseURL)
} mapError: { error in
makeError(request: request, baseURL: baseURL, error: error)
}
return try await wrappingErrors {
try deserializer(response)
} mapError: { error in
makeError(request: request, baseURL: baseURL, response: response, error: error)
}
}
}