Skip to content

Commit

Permalink
[Generator] Async bodies + swift-http-types adoption (#245)
Browse files Browse the repository at this point in the history
[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
  • Loading branch information
czechboy0 authored Oct 2, 2023
1 parent 00494d4 commit b65592a
Show file tree
Hide file tree
Showing 47 changed files with 1,446 additions and 954 deletions.
8 changes: 4 additions & 4 deletions Examples/GreetingService/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ let package = Package(
.macOS(.v13)
],
dependencies: [
.package(url: "https://github.com/apple/swift-openapi-generator", .upToNextMinor(from: "0.2.0")),
.package(url: "https://github.com/apple/swift-openapi-runtime", .upToNextMinor(from: "0.2.0")),
.package(url: "https://github.com/apple/swift-openapi-urlsession", .upToNextMinor(from: "0.2.0")),
.package(url: "https://github.com/swift-server/swift-openapi-vapor", .upToNextMinor(from: "0.2.0")),
.package(url: "https://github.com/apple/swift-openapi-generator", .upToNextMinor(from: "0.3.0")),
.package(url: "https://github.com/apple/swift-openapi-runtime", .upToNextMinor(from: "0.3.0")),
.package(url: "https://github.com/apple/swift-openapi-urlsession", .upToNextMinor(from: "0.3.0")),
.package(url: "https://github.com/swift-server/swift-openapi-vapor", .upToNextMinor(from: "0.3.0")),
.package(url: "https://github.com/vapor/vapor", from: "4.76.0"),
],
targets: [
Expand Down
4 changes: 2 additions & 2 deletions IntegrationTest/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ let package = Package(
)
],
dependencies: [
.package(url: "https://github.com/apple/swift-openapi-generator", .upToNextMinor(from: "0.2.0")),
.package(url: "https://github.com/apple/swift-openapi-runtime", .upToNextMinor(from: "0.2.0")),
.package(url: "https://github.com/apple/swift-openapi-generator", .upToNextMinor(from: "0.3.0")),
.package(url: "https://github.com/apple/swift-openapi-runtime", .upToNextMinor(from: "0.3.0")),
],
targets: [
.target(
Expand Down
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ let package = Package(
// Tests-only: Runtime library linked by generated code, and also
// helps keep the runtime library new enough to work with the generated
// code.
.package(url: "https://github.com/apple/swift-openapi-runtime", .upToNextMinor(from: "0.2.4")),
.package(url: "https://github.com/apple/swift-openapi-runtime", .upToNextMinor(from: "0.3.0")),

// Build and preview docs
.package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"),
Expand Down
44 changes: 42 additions & 2 deletions Sources/PetstoreConsumerTestCore/Assertions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,58 @@
//===----------------------------------------------------------------------===//
import Foundation
import XCTest
import OpenAPIRuntime

public func XCTAssertEqualStringifiedData(
_ expression1: @autoclosure () throws -> Data,
_ expression1: @autoclosure () throws -> Data?,
_ expression2: @autoclosure () throws -> String,
_ message: @autoclosure () -> String = "",
file: StaticString = #filePath,
line: UInt = #line
) {
do {
let actualString = String(decoding: try expression1(), as: UTF8.self)
guard let value1 = try expression1() else {
XCTFail("First value is nil", file: file, line: line)
return
}
let actualString = String(decoding: value1, as: UTF8.self)
XCTAssertEqual(actualString, try expression2(), file: file, line: line)
} catch {
XCTFail(error.localizedDescription, file: file, line: line)
}
}

public func XCTAssertEqualStringifiedData<S: Sequence>(
_ expression1: @autoclosure () throws -> S?,
_ expression2: @autoclosure () throws -> String,
_ message: @autoclosure () -> String = "",
file: StaticString = #filePath,
line: UInt = #line
) where S.Element == UInt8 {
do {
guard let value1 = try expression1() else {
XCTFail("First value is nil", file: file, line: line)
return
}
let actualString = String(decoding: Array(value1), as: UTF8.self)
XCTAssertEqual(actualString, try expression2(), file: file, line: line)
} catch {
XCTFail(error.localizedDescription, file: file, line: line)
}
}

public func XCTAssertEqualStringifiedData(
_ expression1: @autoclosure () throws -> HTTPBody?,
_ expression2: @autoclosure () throws -> String,
_ message: @autoclosure () -> String = "",
file: StaticString = #filePath,
line: UInt = #line
) async throws {
let data: Data
if let body = try expression1() {
data = try await Data(collecting: body, upTo: .max)
} else {
data = .init()
}
XCTAssertEqualStringifiedData(data, try expression2(), message(), file: file, line: line)
}
63 changes: 21 additions & 42 deletions Sources/PetstoreConsumerTestCore/Common.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,18 @@
//===----------------------------------------------------------------------===//
import OpenAPIRuntime
import Foundation
import HTTPTypes

public enum TestError: Swift.Error, LocalizedError, CustomStringConvertible, Sendable {
case noHandlerFound(method: HTTPMethod, path: [RouterPathComponent])
case noHandlerFound(method: HTTPRequest.Method, path: String)
case invalidURLString(String)
case unexpectedValue(any Sendable)
case unexpectedMissingRequestBody

public var description: String {
switch self {
case .noHandlerFound(let method, let path):
return "No handler found for method \(method.name) and path \(path.stringPath)"
return "No handler found for method \(method) and path \(path)"
case .invalidURLString(let string):
return "Invalid URL string: \(string)"
case .unexpectedValue(let value):
Expand All @@ -48,40 +49,31 @@ public extension Date {
}
}

public extension Array where Element == RouterPathComponent {
var stringPath: String {
map(\.description).joined(separator: "/")
}
}
public extension HTTPResponse {

public extension Response {
init(
statusCode: Int,
headers: [HeaderField] = [],
encodedBody: String
) {
self.init(
statusCode: statusCode,
headerFields: headers,
body: Data(encodedBody.utf8)
)
func withEncodedBody(_ encodedBody: String) throws -> (HTTPResponse, HTTPBody) {
(self, .init(encodedBody))
}

static var listPetsSuccess: Self {
.init(
statusCode: 200,
headers: [
.init(name: "content-type", value: "application/json")
],
encodedBody: #"""
static var listPetsSuccess: (HTTPResponse, HTTPBody) {
get throws {
try Self(
status: .ok,
headerFields: [
.contentType: "application/json"
]
)
.withEncodedBody(
#"""
[
{
"id": 1,
"name": "Fluffz"
}
]
"""#
)
)
}
}
}

Expand Down Expand Up @@ -111,21 +103,8 @@ public extension Data {
}
}

public extension Request {
init(
path: String,
query: String? = nil,
method: HTTPMethod,
headerFields: [HeaderField] = [],
encodedBody: String
) throws {
let body = Data(encodedBody.utf8)
self.init(
path: path,
query: query,
method: method,
headerFields: headerFields,
body: body
)
public extension HTTPRequest {
func withEncodedBody(_ encodedBody: String) -> (HTTPRequest, HTTPBody) {
(self, .init(encodedBody))
}
}
12 changes: 8 additions & 4 deletions Sources/PetstoreConsumerTestCore/TestClientTransport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@
//===----------------------------------------------------------------------===//
import OpenAPIRuntime
import Foundation
import HTTPTypes

public struct TestClientTransport: ClientTransport {

public typealias CallHandler = @Sendable (Request, URL, String) async throws -> Response
public typealias CallHandler = @Sendable (HTTPRequest, HTTPBody?, URL, String) async throws -> (
HTTPResponse, HTTPBody?
)

public let callHandler: CallHandler

Expand All @@ -25,10 +28,11 @@ public struct TestClientTransport: ClientTransport {
}

public func send(
_ request: Request,
_ request: HTTPRequest,
body: HTTPBody?,
baseURL: URL,
operationID: String
) async throws -> Response {
try await callHandler(request, baseURL, operationID)
) async throws -> (HTTPResponse, HTTPBody?) {
try await callHandler(request, body, baseURL, operationID)
}
}
24 changes: 13 additions & 11 deletions Sources/PetstoreConsumerTestCore/TestServerTransport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,23 @@
//
//===----------------------------------------------------------------------===//
import OpenAPIRuntime
import HTTPTypes

public final class TestServerTransport: ServerTransport {

public struct OperationInputs: Equatable {
public var method: HTTPMethod
public var path: [RouterPathComponent]
public var queryItemNames: Set<String>
public var method: HTTPRequest.Method
public var path: String

public init(method: HTTPMethod, path: [RouterPathComponent], queryItemNames: Set<String>) {
public init(method: HTTPRequest.Method, path: String) {
self.method = method
self.path = path
self.queryItemNames = queryItemNames
}
}

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

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

public func register(
_ handler: @escaping Handler,
method: HTTPMethod,
path: [RouterPathComponent],
queryItemNames: Set<String>
_ handler: @Sendable @escaping (HTTPRequest, HTTPBody?, ServerRequestMetadata) async throws -> (
HTTPResponse, HTTPBody?
),
method: HTTPRequest.Method,
path: String
) throws {
registered.append(
Operation(
inputs: .init(method: method, path: path, queryItemNames: queryItemNames),
inputs: .init(method: method, path: path),
closure: handler
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,7 @@ struct FunctionSignatureDescription: Equatable, Codable {
var keywords: [FunctionKeyword] = []

/// The return type name of the function, such as `Int`.
var returnType: String? = nil
var returnType: Expression? = nil
}

/// A description of a function definition.
Expand Down Expand Up @@ -479,7 +479,7 @@ struct FunctionDescription: Equatable, Codable {
kind: FunctionKind,
parameters: [ParameterDescription] = [],
keywords: [FunctionKeyword] = [],
returnType: String? = nil,
returnType: Expression? = nil,
body: [CodeBlock]? = nil
) {
self.signature = .init(
Expand All @@ -505,7 +505,7 @@ struct FunctionDescription: Equatable, Codable {
kind: FunctionKind,
parameters: [ParameterDescription] = [],
keywords: [FunctionKeyword] = [],
returnType: String? = nil,
returnType: Expression? = nil,
body: [Expression]
) {
self.init(
Expand Down Expand Up @@ -858,6 +858,17 @@ struct OptionalChainingDescription: Equatable, Codable {
var referencedExpr: Expression
}

/// A description of a tuple.
///
/// For example: `(foo, bar)`.
struct TupleDescription: Equatable, Codable {

/// The member expressions.
///
/// For example, in `(foo, bar)`, `members` is `[foo, bar]`.
var members: [Expression]
}

/// A Swift expression.
indirect enum Expression: Equatable, Codable {

Expand Down Expand Up @@ -928,6 +939,11 @@ indirect enum Expression: Equatable, Codable {
///
/// For example, in `foo?`, `referencedExpr` is `foo`.
case optionalChaining(OptionalChainingDescription)

/// A tuple expression.
///
/// For example: `(foo, bar)`.
case tuple(TupleDescription)
}

/// A code block item, either a declaration or an expression.
Expand Down Expand Up @@ -1076,7 +1092,7 @@ extension Declaration {
kind: FunctionKind,
parameters: [ParameterDescription],
keywords: [FunctionKeyword] = [],
returnType: String? = nil,
returnType: Expression? = nil,
body: [CodeBlock]? = nil
) -> Self {
.function(
Expand Down Expand Up @@ -1432,6 +1448,15 @@ extension Expression {
func optionallyChained() -> Self {
.optionalChaining(.init(referencedExpr: self))
}

/// Returns a new tuple expression.
///
/// For example, in `(foo, bar)`, `members` is `[foo, bar]`.
/// - Parameter expressions: The member expressions.
/// - Returns: A tuple expression.
static func tuple(_ expressions: [Expression]) -> Self {
.tuple(.init(members: expressions))
}
}

extension MemberAccessDescription {
Expand Down
11 changes: 10 additions & 1 deletion Sources/_OpenAPIGeneratorCore/Renderer/TextBasedRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,13 @@ struct TextBasedRenderer: RendererProtocol {
renderedExpression(description.referencedExpr) + "?"
}

/// Renders the specified tuple expression.
func renderedTupleDescription(
_ description: TupleDescription
) -> String {
"(" + description.members.map(renderedExpression).joined(separator: ", ") + ")"
}

/// Renders the specified expression.
func renderedExpression(_ expression: Expression) -> String {
switch expression {
Expand Down Expand Up @@ -316,6 +323,8 @@ struct TextBasedRenderer: RendererProtocol {
return renderedInOutDescription(inOut)
case .optionalChaining(let optionalChaining):
return renderedOptionalChainingDescription(optionalChaining)
case .tuple(let tuple):
return renderedTupleDescription(tuple)
}
}

Expand Down Expand Up @@ -606,7 +615,7 @@ struct TextBasedRenderer: RendererProtocol {
}
if let returnType = signature.returnType {
words.append("->")
words.append(returnType)
words.append(renderedExpression(returnType))
}
return words.joinedWords()
}
Expand Down
Loading

0 comments on commit b65592a

Please sign in to comment.