[AHC Transport] Async bodies + swift-http-types adoption (#16)

[AHC Transport] Async bodies + swift-http-types adoption

### Motivation

AHC transport changes of the approved proposals apple/swift-openapi-generator#255 and apple/swift-openapi-generator#254.

### Modifications

- Adapts to the runtime changes, depends on HTTPTypes now.
- Both request and response streaming works.

### Result

Transport works with the 0.3.0 runtime API of.

### Test Plan

Adapted tests.


Reviewed by: dnadoba, simonjbeaumont

Builds:
     ✔︎ pull request validation (5.8) - Build finished. 
     ✔︎ pull request validation (5.9) - Build finished. 
     ✔︎ pull request validation (nightly) - Build finished. 
     ✔︎ pull request validation (soundness) - Build finished. 

https://github.com/swift-server/swift-openapi-async-http-client/pull/16
This commit is contained in:
Honza Dvorsky
2023-09-27 17:58:10 +02:00
committed by GitHub
parent f228a33bdf
commit 0a0c648c90
5 changed files with 99 additions and 44 deletions
@@ -16,6 +16,7 @@ import AsyncHTTPClient
import NIOCore
import NIOHTTP1
import NIOFoundationCompat
import HTTPTypes
#if canImport(Darwin)
import Foundation
#else
@@ -100,7 +101,7 @@ public struct AsyncHTTPClientTransport: ClientTransport {
internal enum Error: Swift.Error, CustomStringConvertible, LocalizedError {
/// Invalid URL composed from base URL and received request.
case invalidRequestURL(request: OpenAPIRuntime.Request, baseURL: URL)
case invalidRequestURL(request: HTTPRequest, baseURL: URL)
// MARK: CustomStringConvertible
@@ -108,7 +109,7 @@ public struct AsyncHTTPClientTransport: ClientTransport {
switch self {
case let .invalidRequestURL(request: request, baseURL: baseURL):
return
"Invalid request URL from request path: \(request.path), query: \(request.query ?? "<nil>") relative to base URL: \(baseURL.absoluteString)"
"Invalid request URL from request path: \(request.path ?? "<nil>") relative to base URL: \(baseURL.absoluteString)"
}
}
@@ -150,13 +151,17 @@ public struct AsyncHTTPClientTransport: ClientTransport {
// MARK: ClientTransport
public func send(
_ request: OpenAPIRuntime.Request,
_ request: HTTPRequest,
body: HTTPBody?,
baseURL: URL,
operationID: String
) async throws -> OpenAPIRuntime.Response {
let httpRequest = try Self.convertRequest(request, baseURL: baseURL)
) async throws -> (HTTPResponse, HTTPBody?) {
let httpRequest = try Self.convertRequest(request, body: body, baseURL: baseURL)
let httpResponse = try await invokeSession(with: httpRequest)
let response = try await Self.convertResponse(httpResponse)
let response = try await Self.convertResponse(
method: request.method,
httpResponse: httpResponse
)
return response
}
@@ -164,43 +169,79 @@ public struct AsyncHTTPClientTransport: ClientTransport {
/// Converts the shared Request type into URLRequest.
internal static func convertRequest(
_ request: OpenAPIRuntime.Request,
_ request: HTTPRequest,
body: HTTPBody?,
baseURL: URL
) throws -> HTTPClientRequest {
guard var baseUrlComponents = URLComponents(string: baseURL.absoluteString) else {
guard
var baseUrlComponents = URLComponents(string: baseURL.absoluteString),
let requestUrlComponents = URLComponents(string: request.path ?? "")
else {
throw Error.invalidRequestURL(request: request, baseURL: baseURL)
}
baseUrlComponents.percentEncodedPath += request.path
baseUrlComponents.percentEncodedQuery = request.query
baseUrlComponents.percentEncodedPath += requestUrlComponents.percentEncodedPath
baseUrlComponents.percentEncodedQuery = requestUrlComponents.percentEncodedQuery
guard let url = baseUrlComponents.url else {
throw Error.invalidRequestURL(request: request, baseURL: baseURL)
}
var clientRequest = HTTPClientRequest(url: url.absoluteString)
clientRequest.method = request.method.asHTTPMethod
for header in request.headerFields {
clientRequest.headers.add(name: header.name.lowercased(), value: header.value)
clientRequest.headers.add(name: header.name.canonicalName, value: header.value)
}
if let body = request.body {
clientRequest.body = .bytes(body)
if let body {
let length: HTTPClientRequest.Body.Length
switch body.length {
case .unknown:
length = .unknown
case .known(let count):
length = .known(count)
}
clientRequest.body = .stream(
body.map { .init(bytes: $0) },
length: length
)
}
return clientRequest
}
/// Converts the received URLResponse into the shared Response.
internal static func convertResponse(
_ httpResponse: HTTPClientResponse
) async throws -> OpenAPIRuntime.Response {
let headerFields: [OpenAPIRuntime.HeaderField] = httpResponse
.headers
.map { .init(name: $0, value: $1) }
let body = try await httpResponse.body.collect(upTo: .max)
let bodyData = Data(buffer: body, byteTransferStrategy: .noCopy)
let response = OpenAPIRuntime.Response(
statusCode: Int(httpResponse.status.code),
headerFields: headerFields,
body: bodyData
method: HTTPRequest.Method,
httpResponse: HTTPClientResponse
) async throws -> (HTTPResponse, HTTPBody?) {
var headerFields: HTTPFields = [:]
for header in httpResponse.headers {
headerFields[.init(header.name)!] = header.value
}
let length: HTTPBody.Length
if let lengthHeaderString = headerFields[.contentLength],
let lengthHeader = Int(lengthHeaderString)
{
length = .known(lengthHeader)
} else {
length = .unknown
}
let body: HTTPBody?
switch method {
case .head, .connect, .trace:
body = nil
default:
body = HTTPBody(
httpResponse.body.map { $0.readableBytesView },
length: length,
iterationBehavior: .single
)
}
let response = HTTPResponse(
status: .init(code: Int(httpResponse.status.code)),
headerFields: headerFields
)
return response
return (response, body)
}
// MARK: Private
@@ -215,7 +256,7 @@ public struct AsyncHTTPClientTransport: ClientTransport {
}
}
extension OpenAPIRuntime.HTTPMethod {
extension HTTPTypes.HTTPRequest.Method {
var asHTTPMethod: NIOHTTP1.HTTPMethod {
switch self {
case .get:
@@ -20,7 +20,7 @@ Add the package dependency in your `Package.swift`:
```swift
.package(
url: "https://github.com/swift-server/swift-openapi-async-http-client",
.upToNextMinor(from: "0.2.0")
.upToNextMinor(from: "0.3.0")
),
```