Skip to content

Commit b65592a

Browse files
authored
[Generator] Async bodies + swift-http-types adoption (#245)
[Generator] Async bodies + swift-http-types adoption ### Motivation Generator changes of the approved proposals #255 and #254. ### Modifications - Adapts to the runtime changes. - Most changes are tests updating to the new generated structure. - As usual, easiest to start with the diff to the file-based reference tests to understand the individual changes, and then review the rest of the PR. - To see how to use the generated code, check out some streaming examples in https://github.com/apple/swift-openapi-generator/pull/245/files#diff-2be042f4d1d5896dc213e3a5e451b168bd1f0143e76753f4a5be466a455255eb ### Result Generator works with the 0.3.0 runtime API of. ### Test Plan Adapted tests. Reviewed by: simonjbeaumont Builds: ✔︎ pull request validation (5.8) - Build finished. ✔︎ pull request validation (5.9) - Build finished. ✔︎ pull request validation (compatibility test) - Build finished. ✔︎ pull request validation (docc test) - Build finished. ✔︎ pull request validation (nightly) - Build finished. ✔︎ pull request validation (soundness) - Build finished. ✖︎ pull request validation (integration test) - Build finished. #245
1 parent 00494d4 commit b65592a

File tree

47 files changed

+1446
-954
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1446
-954
lines changed

Examples/GreetingService/Package.swift

+4-4
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ let package = Package(
2020
.macOS(.v13)
2121
],
2222
dependencies: [
23-
.package(url: "https://github.com/apple/swift-openapi-generator", .upToNextMinor(from: "0.2.0")),
24-
.package(url: "https://github.com/apple/swift-openapi-runtime", .upToNextMinor(from: "0.2.0")),
25-
.package(url: "https://github.com/apple/swift-openapi-urlsession", .upToNextMinor(from: "0.2.0")),
26-
.package(url: "https://github.com/swift-server/swift-openapi-vapor", .upToNextMinor(from: "0.2.0")),
23+
.package(url: "https://github.com/apple/swift-openapi-generator", .upToNextMinor(from: "0.3.0")),
24+
.package(url: "https://github.com/apple/swift-openapi-runtime", .upToNextMinor(from: "0.3.0")),
25+
.package(url: "https://github.com/apple/swift-openapi-urlsession", .upToNextMinor(from: "0.3.0")),
26+
.package(url: "https://github.com/swift-server/swift-openapi-vapor", .upToNextMinor(from: "0.3.0")),
2727
.package(url: "https://github.com/vapor/vapor", from: "4.76.0"),
2828
],
2929
targets: [

IntegrationTest/Package.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ let package = Package(
3232
)
3333
],
3434
dependencies: [
35-
.package(url: "https://github.com/apple/swift-openapi-generator", .upToNextMinor(from: "0.2.0")),
36-
.package(url: "https://github.com/apple/swift-openapi-runtime", .upToNextMinor(from: "0.2.0")),
35+
.package(url: "https://github.com/apple/swift-openapi-generator", .upToNextMinor(from: "0.3.0")),
36+
.package(url: "https://github.com/apple/swift-openapi-runtime", .upToNextMinor(from: "0.3.0")),
3737
],
3838
targets: [
3939
.target(

Package.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ let package = Package(
8989
// Tests-only: Runtime library linked by generated code, and also
9090
// helps keep the runtime library new enough to work with the generated
9191
// code.
92-
.package(url: "https://github.com/apple/swift-openapi-runtime", .upToNextMinor(from: "0.2.4")),
92+
.package(url: "https://github.com/apple/swift-openapi-runtime", .upToNextMinor(from: "0.3.0")),
9393

9494
// Build and preview docs
9595
.package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"),

Sources/PetstoreConsumerTestCore/Assertions.swift

+42-2
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,58 @@
1313
//===----------------------------------------------------------------------===//
1414
import Foundation
1515
import XCTest
16+
import OpenAPIRuntime
1617

1718
public func XCTAssertEqualStringifiedData(
18-
_ expression1: @autoclosure () throws -> Data,
19+
_ expression1: @autoclosure () throws -> Data?,
1920
_ expression2: @autoclosure () throws -> String,
2021
_ message: @autoclosure () -> String = "",
2122
file: StaticString = #filePath,
2223
line: UInt = #line
2324
) {
2425
do {
25-
let actualString = String(decoding: try expression1(), as: UTF8.self)
26+
guard let value1 = try expression1() else {
27+
XCTFail("First value is nil", file: file, line: line)
28+
return
29+
}
30+
let actualString = String(decoding: value1, as: UTF8.self)
2631
XCTAssertEqual(actualString, try expression2(), file: file, line: line)
2732
} catch {
2833
XCTFail(error.localizedDescription, file: file, line: line)
2934
}
3035
}
36+
37+
public func XCTAssertEqualStringifiedData<S: Sequence>(
38+
_ expression1: @autoclosure () throws -> S?,
39+
_ expression2: @autoclosure () throws -> String,
40+
_ message: @autoclosure () -> String = "",
41+
file: StaticString = #filePath,
42+
line: UInt = #line
43+
) where S.Element == UInt8 {
44+
do {
45+
guard let value1 = try expression1() else {
46+
XCTFail("First value is nil", file: file, line: line)
47+
return
48+
}
49+
let actualString = String(decoding: Array(value1), as: UTF8.self)
50+
XCTAssertEqual(actualString, try expression2(), file: file, line: line)
51+
} catch {
52+
XCTFail(error.localizedDescription, file: file, line: line)
53+
}
54+
}
55+
56+
public func XCTAssertEqualStringifiedData(
57+
_ expression1: @autoclosure () throws -> HTTPBody?,
58+
_ expression2: @autoclosure () throws -> String,
59+
_ message: @autoclosure () -> String = "",
60+
file: StaticString = #filePath,
61+
line: UInt = #line
62+
) async throws {
63+
let data: Data
64+
if let body = try expression1() {
65+
data = try await Data(collecting: body, upTo: .max)
66+
} else {
67+
data = .init()
68+
}
69+
XCTAssertEqualStringifiedData(data, try expression2(), message(), file: file, line: line)
70+
}

Sources/PetstoreConsumerTestCore/Common.swift

+21-42
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,18 @@
1313
//===----------------------------------------------------------------------===//
1414
import OpenAPIRuntime
1515
import Foundation
16+
import HTTPTypes
1617

1718
public enum TestError: Swift.Error, LocalizedError, CustomStringConvertible, Sendable {
18-
case noHandlerFound(method: HTTPMethod, path: [RouterPathComponent])
19+
case noHandlerFound(method: HTTPRequest.Method, path: String)
1920
case invalidURLString(String)
2021
case unexpectedValue(any Sendable)
2122
case unexpectedMissingRequestBody
2223

2324
public var description: String {
2425
switch self {
2526
case .noHandlerFound(let method, let path):
26-
return "No handler found for method \(method.name) and path \(path.stringPath)"
27+
return "No handler found for method \(method) and path \(path)"
2728
case .invalidURLString(let string):
2829
return "Invalid URL string: \(string)"
2930
case .unexpectedValue(let value):
@@ -48,40 +49,31 @@ public extension Date {
4849
}
4950
}
5051

51-
public extension Array where Element == RouterPathComponent {
52-
var stringPath: String {
53-
map(\.description).joined(separator: "/")
54-
}
55-
}
52+
public extension HTTPResponse {
5653

57-
public extension Response {
58-
init(
59-
statusCode: Int,
60-
headers: [HeaderField] = [],
61-
encodedBody: String
62-
) {
63-
self.init(
64-
statusCode: statusCode,
65-
headerFields: headers,
66-
body: Data(encodedBody.utf8)
67-
)
54+
func withEncodedBody(_ encodedBody: String) throws -> (HTTPResponse, HTTPBody) {
55+
(self, .init(encodedBody))
6856
}
6957

70-
static var listPetsSuccess: Self {
71-
.init(
72-
statusCode: 200,
73-
headers: [
74-
.init(name: "content-type", value: "application/json")
75-
],
76-
encodedBody: #"""
58+
static var listPetsSuccess: (HTTPResponse, HTTPBody) {
59+
get throws {
60+
try Self(
61+
status: .ok,
62+
headerFields: [
63+
.contentType: "application/json"
64+
]
65+
)
66+
.withEncodedBody(
67+
#"""
7768
[
7869
{
7970
"id": 1,
8071
"name": "Fluffz"
8172
}
8273
]
8374
"""#
84-
)
75+
)
76+
}
8577
}
8678
}
8779

@@ -111,21 +103,8 @@ public extension Data {
111103
}
112104
}
113105

114-
public extension Request {
115-
init(
116-
path: String,
117-
query: String? = nil,
118-
method: HTTPMethod,
119-
headerFields: [HeaderField] = [],
120-
encodedBody: String
121-
) throws {
122-
let body = Data(encodedBody.utf8)
123-
self.init(
124-
path: path,
125-
query: query,
126-
method: method,
127-
headerFields: headerFields,
128-
body: body
129-
)
106+
public extension HTTPRequest {
107+
func withEncodedBody(_ encodedBody: String) -> (HTTPRequest, HTTPBody) {
108+
(self, .init(encodedBody))
130109
}
131110
}

Sources/PetstoreConsumerTestCore/TestClientTransport.swift

+8-4
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,13 @@
1313
//===----------------------------------------------------------------------===//
1414
import OpenAPIRuntime
1515
import Foundation
16+
import HTTPTypes
1617

1718
public struct TestClientTransport: ClientTransport {
1819

19-
public typealias CallHandler = @Sendable (Request, URL, String) async throws -> Response
20+
public typealias CallHandler = @Sendable (HTTPRequest, HTTPBody?, URL, String) async throws -> (
21+
HTTPResponse, HTTPBody?
22+
)
2023

2124
public let callHandler: CallHandler
2225

@@ -25,10 +28,11 @@ public struct TestClientTransport: ClientTransport {
2528
}
2629

2730
public func send(
28-
_ request: Request,
31+
_ request: HTTPRequest,
32+
body: HTTPBody?,
2933
baseURL: URL,
3034
operationID: String
31-
) async throws -> Response {
32-
try await callHandler(request, baseURL, operationID)
35+
) async throws -> (HTTPResponse, HTTPBody?) {
36+
try await callHandler(request, body, baseURL, operationID)
3337
}
3438
}

Sources/PetstoreConsumerTestCore/TestServerTransport.swift

+13-11
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,23 @@
1212
//
1313
//===----------------------------------------------------------------------===//
1414
import OpenAPIRuntime
15+
import HTTPTypes
1516

1617
public final class TestServerTransport: ServerTransport {
1718

1819
public struct OperationInputs: Equatable {
19-
public var method: HTTPMethod
20-
public var path: [RouterPathComponent]
21-
public var queryItemNames: Set<String>
20+
public var method: HTTPRequest.Method
21+
public var path: String
2222

23-
public init(method: HTTPMethod, path: [RouterPathComponent], queryItemNames: Set<String>) {
23+
public init(method: HTTPRequest.Method, path: String) {
2424
self.method = method
2525
self.path = path
26-
self.queryItemNames = queryItemNames
2726
}
2827
}
2928

30-
public typealias Handler = @Sendable (Request, ServerRequestMetadata) async throws -> Response
29+
public typealias Handler = @Sendable (HTTPRequest, HTTPBody?, ServerRequestMetadata) async throws -> (
30+
HTTPResponse, HTTPBody?
31+
)
3132

3233
public struct Operation {
3334
public var inputs: OperationInputs
@@ -43,14 +44,15 @@ public final class TestServerTransport: ServerTransport {
4344
public private(set) var registered: [Operation] = []
4445

4546
public func register(
46-
_ handler: @escaping Handler,
47-
method: HTTPMethod,
48-
path: [RouterPathComponent],
49-
queryItemNames: Set<String>
47+
_ handler: @Sendable @escaping (HTTPRequest, HTTPBody?, ServerRequestMetadata) async throws -> (
48+
HTTPResponse, HTTPBody?
49+
),
50+
method: HTTPRequest.Method,
51+
path: String
5052
) throws {
5153
registered.append(
5254
Operation(
53-
inputs: .init(method: method, path: path, queryItemNames: queryItemNames),
55+
inputs: .init(method: method, path: path),
5456
closure: handler
5557
)
5658
)

Sources/_OpenAPIGeneratorCore/Layers/StructuredSwiftRepresentation.swift

+29-4
Original file line numberDiff line numberDiff line change
@@ -441,7 +441,7 @@ struct FunctionSignatureDescription: Equatable, Codable {
441441
var keywords: [FunctionKeyword] = []
442442

443443
/// The return type name of the function, such as `Int`.
444-
var returnType: String? = nil
444+
var returnType: Expression? = nil
445445
}
446446

447447
/// A description of a function definition.
@@ -479,7 +479,7 @@ struct FunctionDescription: Equatable, Codable {
479479
kind: FunctionKind,
480480
parameters: [ParameterDescription] = [],
481481
keywords: [FunctionKeyword] = [],
482-
returnType: String? = nil,
482+
returnType: Expression? = nil,
483483
body: [CodeBlock]? = nil
484484
) {
485485
self.signature = .init(
@@ -505,7 +505,7 @@ struct FunctionDescription: Equatable, Codable {
505505
kind: FunctionKind,
506506
parameters: [ParameterDescription] = [],
507507
keywords: [FunctionKeyword] = [],
508-
returnType: String? = nil,
508+
returnType: Expression? = nil,
509509
body: [Expression]
510510
) {
511511
self.init(
@@ -858,6 +858,17 @@ struct OptionalChainingDescription: Equatable, Codable {
858858
var referencedExpr: Expression
859859
}
860860

861+
/// A description of a tuple.
862+
///
863+
/// For example: `(foo, bar)`.
864+
struct TupleDescription: Equatable, Codable {
865+
866+
/// The member expressions.
867+
///
868+
/// For example, in `(foo, bar)`, `members` is `[foo, bar]`.
869+
var members: [Expression]
870+
}
871+
861872
/// A Swift expression.
862873
indirect enum Expression: Equatable, Codable {
863874

@@ -928,6 +939,11 @@ indirect enum Expression: Equatable, Codable {
928939
///
929940
/// For example, in `foo?`, `referencedExpr` is `foo`.
930941
case optionalChaining(OptionalChainingDescription)
942+
943+
/// A tuple expression.
944+
///
945+
/// For example: `(foo, bar)`.
946+
case tuple(TupleDescription)
931947
}
932948

933949
/// A code block item, either a declaration or an expression.
@@ -1076,7 +1092,7 @@ extension Declaration {
10761092
kind: FunctionKind,
10771093
parameters: [ParameterDescription],
10781094
keywords: [FunctionKeyword] = [],
1079-
returnType: String? = nil,
1095+
returnType: Expression? = nil,
10801096
body: [CodeBlock]? = nil
10811097
) -> Self {
10821098
.function(
@@ -1432,6 +1448,15 @@ extension Expression {
14321448
func optionallyChained() -> Self {
14331449
.optionalChaining(.init(referencedExpr: self))
14341450
}
1451+
1452+
/// Returns a new tuple expression.
1453+
///
1454+
/// For example, in `(foo, bar)`, `members` is `[foo, bar]`.
1455+
/// - Parameter expressions: The member expressions.
1456+
/// - Returns: A tuple expression.
1457+
static func tuple(_ expressions: [Expression]) -> Self {
1458+
.tuple(.init(members: expressions))
1459+
}
14351460
}
14361461

14371462
extension MemberAccessDescription {

Sources/_OpenAPIGeneratorCore/Renderer/TextBasedRenderer.swift

+10-1
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,13 @@ struct TextBasedRenderer: RendererProtocol {
285285
renderedExpression(description.referencedExpr) + "?"
286286
}
287287

288+
/// Renders the specified tuple expression.
289+
func renderedTupleDescription(
290+
_ description: TupleDescription
291+
) -> String {
292+
"(" + description.members.map(renderedExpression).joined(separator: ", ") + ")"
293+
}
294+
288295
/// Renders the specified expression.
289296
func renderedExpression(_ expression: Expression) -> String {
290297
switch expression {
@@ -316,6 +323,8 @@ struct TextBasedRenderer: RendererProtocol {
316323
return renderedInOutDescription(inOut)
317324
case .optionalChaining(let optionalChaining):
318325
return renderedOptionalChainingDescription(optionalChaining)
326+
case .tuple(let tuple):
327+
return renderedTupleDescription(tuple)
319328
}
320329
}
321330

@@ -606,7 +615,7 @@ struct TextBasedRenderer: RendererProtocol {
606615
}
607616
if let returnType = signature.returnType {
608617
words.append("->")
609-
words.append(returnType)
618+
words.append(renderedExpression(returnType))
610619
}
611620
return words.joinedWords()
612621
}

0 commit comments

Comments
 (0)