Skip to content

Commit 1eaf236

Browse files
authored
[Runtime] Async bodies + swift-http-types adoption (#47)
[Runtime] Async bodies + swift-http-types adoption ### Motivation Runtime changes of the approved proposals apple/swift-openapi-generator#255 and apple/swift-openapi-generator#254. ### Blockers of merging this - [x] 1.0 of swift-http-types ### Modifications ⚠️ Contains breaking changes, this will land to main and then 0.3.0 will be tagged, so not backwards compatible with 0.2.0. - add a dependency on https://github.com/apple/swift-http-types - remove our currency types `Request`, `Response`, `HeaderField` - replace them with the types provided by `HTTPTypes` - remove `...AsString` and the whole string-based serialization strategy, which was only ever used by bodies, but with streaming, we can't safely stream string chunks, only byte chunks, so we instead provide utils on `HTTPBody` to create it from string, and to collect it into a string. This means that the underlying type for the `text/plain` content type is now `HTTPBody` (a sequence of byte chunks) as opposed to `String` - adapted Converter extensions to work with the new types - added some internal utils for working with the query section of a path, as `HTTPTypes` doesn't provide that, the `path` property contains both the path and the query components (in `setEscapedQueryItem`) - adapted error types - adapted printing of request/response types, now no bytes of the body are printed, as they cannot be assumed to be consumable more than once - adjusted the transport and middleware protocols, as described in the proposal - removed the `queryParameters` property from `ServerRequestMetadata`, as now we parse the full query ourselves, instead of relying on the server transport to do it for us - removed `RouterPathComponent` as now we pass the full path string to the server transport in the `register` function, allowing transport with more flexible routers to handle mixed path components, e.g. `/foo/{bar}.zip`. - introduced `HTTPBody`, as described by the proposal - adjusted UniversalClient and UniversalServer - moved from String to Substring in a few types, to allow more passthrough of parsed string data without copying ### Result SOAR-0004 and SOAR-0005 implemented. ### Test Plan Introduced and adjusted tests for all of the above. Reviewed by: Builds: ✔︎ pull request validation (5.8) - Build finished. ✔︎ pull request validation (5.9) - Build finished. ✔︎ pull request validation (docc test) - Build finished. ✔︎ pull request validation (nightly) - Build finished. ✔︎ pull request validation (soundness) - Build finished. ✖︎ pull request validation (api breakage) - Build finished. ✖︎ pull request validation (integration test) - Build finished. #47
1 parent ef2b34c commit 1eaf236

33 files changed

+1976
-2219
lines changed

.swift-format

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
"UseLetInEveryBoundCaseVariable" : false,
4848
"UseShorthandTypeNames" : true,
4949
"UseSingleLinePropertyGetter" : false,
50-
"UseSynthesizedInitializer" : true,
50+
"UseSynthesizedInitializer" : false,
5151
"UseTripleSlashForDocumentationComments" : true,
5252
"UseWhereClausesInForLoops" : false,
5353
"ValidateDocumentationComments" : false

Package.swift

+5-2
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,18 @@ let package = Package(
3434
.library(
3535
name: "OpenAPIRuntime",
3636
targets: ["OpenAPIRuntime"]
37-
),
37+
)
3838
],
3939
dependencies: [
40+
.package(url: "https://github.com/apple/swift-http-types", from: "1.0.0"),
4041
.package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"),
4142
],
4243
targets: [
4344
.target(
4445
name: "OpenAPIRuntime",
45-
dependencies: [],
46+
dependencies: [
47+
.product(name: "HTTPTypes", package: "swift-http-types")
48+
],
4649
swiftSettings: swiftSettings
4750
),
4851
.testTarget(

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ Add the package dependency in your `Package.swift`:
1515
```swift
1616
.package(
1717
url: "https://github.com/apple/swift-openapi-runtime",
18-
.upToNextMinor(from: "0.2.0")
18+
.upToNextMinor(from: "0.3.0")
1919
),
2020
```
2121

Sources/OpenAPIRuntime/Conversion/Converter+Client.swift

+34-74
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
//
1313
//===----------------------------------------------------------------------===//
1414
import Foundation
15+
import HTTPTypes
1516

1617
extension Converter {
1718

@@ -20,15 +21,10 @@ extension Converter {
2021
/// - headerFields: The header fields where to add the "accept" header.
2122
/// - contentTypes: The array of acceptable content types by the client.
2223
public func setAcceptHeader<T: AcceptableProtocol>(
23-
in headerFields: inout [HeaderField],
24+
in headerFields: inout HTTPFields,
2425
contentTypes: [AcceptHeaderContentType<T>]
2526
) {
26-
headerFields.append(
27-
.init(
28-
name: "accept",
29-
value: contentTypes.map(\.rawValue).joined(separator: ", ")
30-
)
31-
)
27+
headerFields[.accept] = contentTypes.map(\.rawValue).joined(separator: ", ")
3228
}
3329

3430
// | client | set | request path | URI | required | renderedPath |
@@ -60,7 +56,7 @@ extension Converter {
6056

6157
// | client | set | request query | URI | both | setQueryItemAsURI |
6258
public func setQueryItemAsURI<T: Encodable>(
63-
in request: inout Request,
59+
in request: inout HTTPRequest,
6460
style: ParameterStyle?,
6561
explode: Bool?,
6662
name: String,
@@ -84,40 +80,12 @@ extension Converter {
8480
)
8581
}
8682

87-
// | client | set | request body | string | optional | setOptionalRequestBodyAsString |
88-
public func setOptionalRequestBodyAsString<T: Encodable>(
89-
_ value: T?,
90-
headerFields: inout [HeaderField],
91-
contentType: String
92-
) throws -> Data? {
93-
try setOptionalRequestBody(
94-
value,
95-
headerFields: &headerFields,
96-
contentType: contentType,
97-
convert: convertToStringData
98-
)
99-
}
100-
101-
// | client | set | request body | string | required | setRequiredRequestBodyAsString |
102-
public func setRequiredRequestBodyAsString<T: Encodable>(
103-
_ value: T,
104-
headerFields: inout [HeaderField],
105-
contentType: String
106-
) throws -> Data {
107-
try setRequiredRequestBody(
108-
value,
109-
headerFields: &headerFields,
110-
contentType: contentType,
111-
convert: convertToStringData
112-
)
113-
}
114-
11583
// | client | set | request body | JSON | optional | setOptionalRequestBodyAsJSON |
11684
public func setOptionalRequestBodyAsJSON<T: Encodable>(
11785
_ value: T?,
118-
headerFields: inout [HeaderField],
86+
headerFields: inout HTTPFields,
11987
contentType: String
120-
) throws -> Data? {
88+
) throws -> HTTPBody? {
12189
try setOptionalRequestBody(
12290
value,
12391
headerFields: &headerFields,
@@ -129,9 +97,9 @@ extension Converter {
12997
// | client | set | request body | JSON | required | setRequiredRequestBodyAsJSON |
13098
public func setRequiredRequestBodyAsJSON<T: Encodable>(
13199
_ value: T,
132-
headerFields: inout [HeaderField],
100+
headerFields: inout HTTPFields,
133101
contentType: String
134-
) throws -> Data {
102+
) throws -> HTTPBody {
135103
try setRequiredRequestBody(
136104
value,
137105
headerFields: &headerFields,
@@ -142,38 +110,38 @@ extension Converter {
142110

143111
// | client | set | request body | binary | optional | setOptionalRequestBodyAsBinary |
144112
public func setOptionalRequestBodyAsBinary(
145-
_ value: Data?,
146-
headerFields: inout [HeaderField],
113+
_ value: HTTPBody?,
114+
headerFields: inout HTTPFields,
147115
contentType: String
148-
) throws -> Data? {
116+
) throws -> HTTPBody? {
149117
try setOptionalRequestBody(
150118
value,
151119
headerFields: &headerFields,
152120
contentType: contentType,
153-
convert: convertDataToBinary
121+
convert: { $0 }
154122
)
155123
}
156124

157125
// | client | set | request body | binary | required | setRequiredRequestBodyAsBinary |
158126
public func setRequiredRequestBodyAsBinary(
159-
_ value: Data,
160-
headerFields: inout [HeaderField],
127+
_ value: HTTPBody,
128+
headerFields: inout HTTPFields,
161129
contentType: String
162-
) throws -> Data {
130+
) throws -> HTTPBody {
163131
try setRequiredRequestBody(
164132
value,
165133
headerFields: &headerFields,
166134
contentType: contentType,
167-
convert: convertDataToBinary
135+
convert: { $0 }
168136
)
169137
}
170138

171139
// | client | set | request body | urlEncodedForm | codable | optional | setOptionalRequestBodyAsURLEncodedForm |
172140
public func setOptionalRequestBodyAsURLEncodedForm<T: Encodable>(
173141
_ value: T,
174-
headerFields: inout [HeaderField],
142+
headerFields: inout HTTPFields,
175143
contentType: String
176-
) throws -> Data? {
144+
) throws -> HTTPBody? {
177145
try setOptionalRequestBody(
178146
value,
179147
headerFields: &headerFields,
@@ -185,9 +153,9 @@ extension Converter {
185153
// | client | set | request body | urlEncodedForm | codable | required | setRequiredRequestBodyAsURLEncodedForm |
186154
public func setRequiredRequestBodyAsURLEncodedForm<T: Encodable>(
187155
_ value: T,
188-
headerFields: inout [HeaderField],
156+
headerFields: inout HTTPFields,
189157
contentType: String
190-
) throws -> Data {
158+
) throws -> HTTPBody {
191159
try setRequiredRequestBody(
192160
value,
193161
headerFields: &headerFields,
@@ -196,27 +164,16 @@ extension Converter {
196164
)
197165
}
198166

199-
// | client | get | response body | string | required | getResponseBodyAsString |
200-
public func getResponseBodyAsString<T: Decodable, C>(
201-
_ type: T.Type,
202-
from data: Data,
203-
transforming transform: (T) -> C
204-
) throws -> C {
205-
try getResponseBody(
206-
type,
207-
from: data,
208-
transforming: transform,
209-
convert: convertFromStringData
210-
)
211-
}
212-
213167
// | client | get | response body | JSON | required | getResponseBodyAsJSON |
214168
public func getResponseBodyAsJSON<T: Decodable, C>(
215169
_ type: T.Type,
216-
from data: Data,
170+
from data: HTTPBody?,
217171
transforming transform: (T) -> C
218-
) throws -> C {
219-
try getResponseBody(
172+
) async throws -> C {
173+
guard let data else {
174+
throw RuntimeError.missingRequiredResponseBody
175+
}
176+
return try await getBufferingResponseBody(
220177
type,
221178
from: data,
222179
transforming: transform,
@@ -226,15 +183,18 @@ extension Converter {
226183

227184
// | client | get | response body | binary | required | getResponseBodyAsBinary |
228185
public func getResponseBodyAsBinary<C>(
229-
_ type: Data.Type,
230-
from data: Data,
231-
transforming transform: (Data) -> C
186+
_ type: HTTPBody.Type,
187+
from data: HTTPBody?,
188+
transforming transform: (HTTPBody) -> C
232189
) throws -> C {
233-
try getResponseBody(
190+
guard let data else {
191+
throw RuntimeError.missingRequiredResponseBody
192+
}
193+
return try getResponseBody(
234194
type,
235195
from: data,
236196
transforming: transform,
237-
convert: convertBinaryToData
197+
convert: { $0 }
238198
)
239199
}
240200
}

Sources/OpenAPIRuntime/Conversion/Converter+Common.swift

+9-8
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
//
1313
//===----------------------------------------------------------------------===//
1414
import Foundation
15+
import HTTPTypes
1516

1617
extension Converter {
1718

@@ -21,8 +22,8 @@ extension Converter {
2122
/// - Parameter headerFields: The header fields to inspect for the content
2223
/// type header.
2324
/// - Returns: The content type value, or nil if not found or invalid.
24-
public func extractContentTypeIfPresent(in headerFields: [HeaderField]) -> OpenAPIMIMEType? {
25-
guard let rawValue = headerFields.firstValue(name: "content-type") else {
25+
public func extractContentTypeIfPresent(in headerFields: HTTPFields) -> OpenAPIMIMEType? {
26+
guard let rawValue = headerFields[.contentType] else {
2627
return nil
2728
}
2829
return OpenAPIMIMEType(rawValue)
@@ -72,7 +73,7 @@ extension Converter {
7273

7374
// | common | set | header field | URI | both | setHeaderFieldAsURI |
7475
public func setHeaderFieldAsURI<T: Encodable>(
75-
in headerFields: inout [HeaderField],
76+
in headerFields: inout HTTPFields,
7677
name: String,
7778
value: T?
7879
) throws {
@@ -97,7 +98,7 @@ extension Converter {
9798

9899
// | common | set | header field | JSON | both | setHeaderFieldAsJSON |
99100
public func setHeaderFieldAsJSON<T: Encodable>(
100-
in headerFields: inout [HeaderField],
101+
in headerFields: inout HTTPFields,
101102
name: String,
102103
value: T?
103104
) throws {
@@ -111,7 +112,7 @@ extension Converter {
111112

112113
// | common | get | header field | URI | optional | getOptionalHeaderFieldAsURI |
113114
public func getOptionalHeaderFieldAsURI<T: Decodable>(
114-
in headerFields: [HeaderField],
115+
in headerFields: HTTPFields,
115116
name: String,
116117
as type: T.Type
117118
) throws -> T? {
@@ -133,7 +134,7 @@ extension Converter {
133134

134135
// | common | get | header field | URI | required | getRequiredHeaderFieldAsURI |
135136
public func getRequiredHeaderFieldAsURI<T: Decodable>(
136-
in headerFields: [HeaderField],
137+
in headerFields: HTTPFields,
137138
name: String,
138139
as type: T.Type
139140
) throws -> T {
@@ -155,7 +156,7 @@ extension Converter {
155156

156157
// | common | get | header field | JSON | optional | getOptionalHeaderFieldAsJSON |
157158
public func getOptionalHeaderFieldAsJSON<T: Decodable>(
158-
in headerFields: [HeaderField],
159+
in headerFields: HTTPFields,
159160
name: String,
160161
as type: T.Type
161162
) throws -> T? {
@@ -169,7 +170,7 @@ extension Converter {
169170

170171
// | common | get | header field | JSON | required | getRequiredHeaderFieldAsJSON |
171172
public func getRequiredHeaderFieldAsJSON<T: Decodable>(
172-
in headerFields: [HeaderField],
173+
in headerFields: HTTPFields,
173174
name: String,
174175
as type: T.Type
175176
) throws -> T {

0 commit comments

Comments
 (0)