From 5718552c2f81af7c8a3375ed52bae08e62b14c9e Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Thu, 7 Sep 2023 17:59:38 +0200 Subject: [PATCH 1/9] [WIP] [AHC Transport] Async bodies + swift-http-types adoption --- Package.swift | 3 + .../AsyncHTTPClientTransport.swift | 73 +++++++++++++------ .../Test_AsyncHTTPClientTransport.swift | 41 ++++++----- 3 files changed, 74 insertions(+), 43 deletions(-) diff --git a/Package.swift b/Package.swift index b7d99bb..ac0bde8 100644 --- a/Package.swift +++ b/Package.swift @@ -42,6 +42,7 @@ let package = Package( .package(url: "https://github.com/swift-server/async-http-client.git", from: "1.19.0"), .package(url: "https://github.com/apple/swift-openapi-runtime", "0.1.3" ..< "0.3.0"), .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"), +// .package(url: "https://github.com/guoye-zhang/swift-nio-extras", branch: "http-types"), ], targets: [ .target( @@ -50,6 +51,8 @@ let package = Package( .product(name: "OpenAPIRuntime", package: "swift-openapi-runtime"), .product(name: "AsyncHTTPClient", package: "async-http-client"), .product(name: "NIOFoundationCompat", package: "swift-nio"), +// .product(name: "NIOHTTPTypes", package: "swift-nio-extras"), +// .product(name: "NIOHTTPTypesHTTP1", package: "swift-nio-extras"), ], swiftSettings: swiftSettings ), diff --git a/Sources/OpenAPIAsyncHTTPClient/AsyncHTTPClientTransport.swift b/Sources/OpenAPIAsyncHTTPClient/AsyncHTTPClientTransport.swift index 748c093..d485e7e 100644 --- a/Sources/OpenAPIAsyncHTTPClient/AsyncHTTPClientTransport.swift +++ b/Sources/OpenAPIAsyncHTTPClient/AsyncHTTPClientTransport.swift @@ -16,6 +16,7 @@ import AsyncHTTPClient import NIOCore import NIOHTTP1 import NIOFoundationCompat +import HTTPTypes #if canImport(Darwin) import Foundation #else @@ -91,7 +92,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 @@ -99,7 +100,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 ?? "") relative to base URL: \(baseURL.absoluteString)" + "Invalid request URL from request path: \(request.path ?? "") relative to base URL: \(baseURL.absoluteString)" } } @@ -141,11 +142,12 @@ 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) return response @@ -155,24 +157,35 @@ 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 { throw Error.invalidRequestURL(request: request, baseURL: baseURL) } - baseUrlComponents.percentEncodedPath += request.path - baseUrlComponents.percentEncodedQuery = request.query + baseUrlComponents.percentEncodedPath += request.soar_pathOnly + baseUrlComponents.percentEncodedQuery = request.soar_query.map(String.init) 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 } @@ -180,18 +193,32 @@ public struct AsyncHTTPClientTransport: ClientTransport { /// 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 + ) 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( + sequence: httpResponse.body.map { HTTPBody.ByteChunk($0.readableBytesView) }, + length: length, + iterationBehavior: .single ) - return response + let response = HTTPResponse( + status: .init(code: Int(httpResponse.status.code)), + headerFields: headerFields + ) + return (response, body) } // MARK: Private @@ -206,7 +233,7 @@ public struct AsyncHTTPClientTransport: ClientTransport { } } -extension OpenAPIRuntime.HTTPMethod { +extension HTTPTypes.HTTPRequest.Method { var asHTTPMethod: NIOHTTP1.HTTPMethod { switch self { case .get: diff --git a/Tests/OpenAPIAsyncHTTPClientTests/Test_AsyncHTTPClientTransport.swift b/Tests/OpenAPIAsyncHTTPClientTests/Test_AsyncHTTPClientTransport.swift index 03b732b..0ed4a1e 100644 --- a/Tests/OpenAPIAsyncHTTPClientTests/Test_AsyncHTTPClientTransport.swift +++ b/Tests/OpenAPIAsyncHTTPClientTests/Test_AsyncHTTPClientTransport.swift @@ -17,6 +17,7 @@ import NIOCore import NIOPosix import AsyncHTTPClient @testable import OpenAPIAsyncHTTPClient +import HTTPTypes class Test_AsyncHTTPClientTransport: XCTestCase { @@ -37,17 +38,17 @@ class Test_AsyncHTTPClientTransport: XCTestCase { } func testConvertRequest() throws { - let request: OpenAPIRuntime.Request = .init( - path: "/hello%20world/Maria", - query: "greeting=Howdy", + let request: HTTPRequest = .init( + soar_path: "/hello%20world/Maria?greeting=Howdy", method: .post, headerFields: [ - .init(name: "content-type", value: "application/json") - ], - body: try Self.testData + .contentType: "application/json" + ] ) + let requestBody = try HTTPBody(data: Self.testData) let httpRequest = try AsyncHTTPClientTransport.convertRequest( request, + body: requestBody, baseURL: try XCTUnwrap(URL(string: "http://example.com/api/v1")) ) XCTAssertEqual(httpRequest.url, "http://example.com/api/v1/hello%20world/Maria?greeting=Howdy") @@ -70,23 +71,20 @@ class Test_AsyncHTTPClientTransport: XCTestCase { ], body: .bytes(Self.testBuffer) ) - let response = try await AsyncHTTPClientTransport.convertResponse(httpResponse) - XCTAssertEqual(response.statusCode, 200) + let (response, responseBody) = try await AsyncHTTPClientTransport.convertResponse(httpResponse) + XCTAssertEqual(response.status.code, 200) XCTAssertEqual( response.headerFields, [ - .init(name: "content-type", value: "application/json") + .contentType: "application/json" ] ) - XCTAssertEqual(response.body, try Self.testData) + let bufferedResponseBody = try await responseBody.collectAsData(upTo: .max) + XCTAssertEqual(bufferedResponseBody, try Self.testData) } func testSend() async throws { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) - let httpClient = HTTPClient( - eventLoopGroupProvider: .shared(eventLoopGroup), - configuration: .init() - ) + let httpClient = HTTPClient() defer { try! httpClient.syncShutdown() } @@ -94,19 +92,22 @@ class Test_AsyncHTTPClientTransport: XCTestCase { configuration: .init(client: httpClient), requestSender: TestSender.test ) - let request: OpenAPIRuntime.Request = .init( - path: "/api/v1/hello/Maria", + let request: HTTPRequest = .init( + soar_path: "/api/v1/hello/Maria", method: .get, headerFields: [ - .init(name: "x-request", value: "yes") + .init("x-request")!: "yes" ] ) - let response = try await transport.send( + let (response, responseBody) = try await transport.send( request, + body: nil, baseURL: Self.testUrl, operationID: "sayHello" ) - XCTAssertEqual(response.statusCode, 200) + let bufferedResponseBody = try await responseBody.collectAsString(upTo: .max) + XCTAssertEqual(bufferedResponseBody, "[{}]") + XCTAssertEqual(response.status.code, 200) } } From 0a4ad870c924604f06cfd8f8f1947e7a70b79c25 Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Tue, 12 Sep 2023 22:30:28 +0200 Subject: [PATCH 2/9] Feedback: further cleanup of the HTTPBody API --- Sources/OpenAPIAsyncHTTPClient/AsyncHTTPClientTransport.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/OpenAPIAsyncHTTPClient/AsyncHTTPClientTransport.swift b/Sources/OpenAPIAsyncHTTPClient/AsyncHTTPClientTransport.swift index d485e7e..4054f44 100644 --- a/Sources/OpenAPIAsyncHTTPClient/AsyncHTTPClientTransport.swift +++ b/Sources/OpenAPIAsyncHTTPClient/AsyncHTTPClientTransport.swift @@ -210,7 +210,7 @@ public struct AsyncHTTPClientTransport: ClientTransport { } let body = HTTPBody( - sequence: httpResponse.body.map { HTTPBody.ByteChunk($0.readableBytesView) }, + httpResponse.body.map { $0.readableBytesView }, length: length, iterationBehavior: .single ) From 567f46b6eea4f2a077f8629838e5b1bc040527b5 Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Tue, 12 Sep 2023 22:44:12 +0200 Subject: [PATCH 3/9] Fix test --- .../Test_AsyncHTTPClientTransport.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/OpenAPIAsyncHTTPClientTests/Test_AsyncHTTPClientTransport.swift b/Tests/OpenAPIAsyncHTTPClientTests/Test_AsyncHTTPClientTransport.swift index 0ed4a1e..c02fda4 100644 --- a/Tests/OpenAPIAsyncHTTPClientTests/Test_AsyncHTTPClientTransport.swift +++ b/Tests/OpenAPIAsyncHTTPClientTests/Test_AsyncHTTPClientTransport.swift @@ -79,7 +79,7 @@ class Test_AsyncHTTPClientTransport: XCTestCase { .contentType: "application/json" ] ) - let bufferedResponseBody = try await responseBody.collectAsData(upTo: .max) + let bufferedResponseBody = try await Data(collecting: responseBody, upTo: .max) XCTAssertEqual(bufferedResponseBody, try Self.testData) } @@ -105,7 +105,7 @@ class Test_AsyncHTTPClientTransport: XCTestCase { baseURL: Self.testUrl, operationID: "sayHello" ) - let bufferedResponseBody = try await responseBody.collectAsString(upTo: .max) + let bufferedResponseBody = try await String(collecting: responseBody, upTo: .max) XCTAssertEqual(bufferedResponseBody, "[{}]") XCTAssertEqual(response.status.code, 200) } From fba582b5ea57787eb74bc97641871577e57d4bec Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Wed, 13 Sep 2023 17:02:05 +0200 Subject: [PATCH 4/9] Review feedback: make response body optional --- .../AsyncHTTPClientTransport.swift | 29 +++++++++++++------ .../Test_AsyncHTTPClientTransport.swift | 9 ++++-- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/Sources/OpenAPIAsyncHTTPClient/AsyncHTTPClientTransport.swift b/Sources/OpenAPIAsyncHTTPClient/AsyncHTTPClientTransport.swift index 4054f44..c6699b8 100644 --- a/Sources/OpenAPIAsyncHTTPClient/AsyncHTTPClientTransport.swift +++ b/Sources/OpenAPIAsyncHTTPClient/AsyncHTTPClientTransport.swift @@ -146,10 +146,13 @@ public struct AsyncHTTPClientTransport: ClientTransport { body: HTTPBody?, baseURL: URL, operationID: String - ) async throws -> (HTTPResponse, HTTPBody) { + ) 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 } @@ -192,8 +195,9 @@ public struct AsyncHTTPClientTransport: ClientTransport { /// Converts the received URLResponse into the shared Response. internal static func convertResponse( - _ httpResponse: HTTPClientResponse - ) async throws -> (HTTPResponse, HTTPBody) { + method: HTTPRequest.Method, + httpResponse: HTTPClientResponse + ) async throws -> (HTTPResponse, HTTPBody?) { var headerFields: HTTPFields = [:] for header in httpResponse.headers { @@ -209,11 +213,18 @@ public struct AsyncHTTPClientTransport: ClientTransport { length = .unknown } - let body = HTTPBody( - httpResponse.body.map { $0.readableBytesView }, - length: length, - iterationBehavior: .single - ) + 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 diff --git a/Tests/OpenAPIAsyncHTTPClientTests/Test_AsyncHTTPClientTransport.swift b/Tests/OpenAPIAsyncHTTPClientTests/Test_AsyncHTTPClientTransport.swift index c02fda4..4defadf 100644 --- a/Tests/OpenAPIAsyncHTTPClientTests/Test_AsyncHTTPClientTransport.swift +++ b/Tests/OpenAPIAsyncHTTPClientTests/Test_AsyncHTTPClientTransport.swift @@ -71,7 +71,11 @@ class Test_AsyncHTTPClientTransport: XCTestCase { ], body: .bytes(Self.testBuffer) ) - let (response, responseBody) = try await AsyncHTTPClientTransport.convertResponse(httpResponse) + let (response, maybeResponseBody) = try await AsyncHTTPClientTransport.convertResponse( + method: .get, + httpResponse: httpResponse + ) + let responseBody = try XCTUnwrap(maybeResponseBody) XCTAssertEqual(response.status.code, 200) XCTAssertEqual( response.headerFields, @@ -99,12 +103,13 @@ class Test_AsyncHTTPClientTransport: XCTestCase { .init("x-request")!: "yes" ] ) - let (response, responseBody) = try await transport.send( + let (response, maybeResponseBody) = try await transport.send( request, body: nil, baseURL: Self.testUrl, operationID: "sayHello" ) + let responseBody = try XCTUnwrap(maybeResponseBody) let bufferedResponseBody = try await String(collecting: responseBody, upTo: .max) XCTAssertEqual(bufferedResponseBody, "[{}]") XCTAssertEqual(response.status.code, 200) From 12e53c44da896338ce7b81a0da5a00c7e48b9c44 Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Mon, 18 Sep 2023 15:58:33 +0200 Subject: [PATCH 5/9] Remove commented-out dependencies --- Package.swift | 3 --- 1 file changed, 3 deletions(-) diff --git a/Package.swift b/Package.swift index ac0bde8..b7d99bb 100644 --- a/Package.swift +++ b/Package.swift @@ -42,7 +42,6 @@ let package = Package( .package(url: "https://github.com/swift-server/async-http-client.git", from: "1.19.0"), .package(url: "https://github.com/apple/swift-openapi-runtime", "0.1.3" ..< "0.3.0"), .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"), -// .package(url: "https://github.com/guoye-zhang/swift-nio-extras", branch: "http-types"), ], targets: [ .target( @@ -51,8 +50,6 @@ let package = Package( .product(name: "OpenAPIRuntime", package: "swift-openapi-runtime"), .product(name: "AsyncHTTPClient", package: "async-http-client"), .product(name: "NIOFoundationCompat", package: "swift-nio"), -// .product(name: "NIOHTTPTypes", package: "swift-nio-extras"), -// .product(name: "NIOHTTPTypesHTTP1", package: "swift-nio-extras"), ], swiftSettings: swiftSettings ), From cb8bf06ff346df6205454de9681793a7cca5723d Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Mon, 25 Sep 2023 15:00:02 +0200 Subject: [PATCH 6/9] Adapted to runtime changes --- .../Test_AsyncHTTPClientTransport.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/OpenAPIAsyncHTTPClientTests/Test_AsyncHTTPClientTransport.swift b/Tests/OpenAPIAsyncHTTPClientTests/Test_AsyncHTTPClientTransport.swift index 9efbae3..716dd89 100644 --- a/Tests/OpenAPIAsyncHTTPClientTests/Test_AsyncHTTPClientTransport.swift +++ b/Tests/OpenAPIAsyncHTTPClientTests/Test_AsyncHTTPClientTransport.swift @@ -45,7 +45,7 @@ class Test_AsyncHTTPClientTransport: XCTestCase { .contentType: "application/json" ] ) - let requestBody = try HTTPBody(data: Self.testData) + let requestBody = try HTTPBody(Self.testData) let httpRequest = try AsyncHTTPClientTransport.convertRequest( request, body: requestBody, From 7339fca8644e18c8bf6b88a997169637a9ba7d6a Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Tue, 26 Sep 2023 17:30:22 +0200 Subject: [PATCH 7/9] Stop using the generated SPI --- .../AsyncHTTPClientTransport.swift | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Sources/OpenAPIAsyncHTTPClient/AsyncHTTPClientTransport.swift b/Sources/OpenAPIAsyncHTTPClient/AsyncHTTPClientTransport.swift index 2bd3072..26efd0d 100644 --- a/Sources/OpenAPIAsyncHTTPClient/AsyncHTTPClientTransport.swift +++ b/Sources/OpenAPIAsyncHTTPClient/AsyncHTTPClientTransport.swift @@ -173,11 +173,14 @@ public struct AsyncHTTPClientTransport: ClientTransport { 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.soar_pathOnly - baseUrlComponents.percentEncodedQuery = request.soar_query.map(String.init) + baseUrlComponents.percentEncodedPath += requestUrlComponents.percentEncodedPath + baseUrlComponents.percentEncodedQuery = requestUrlComponents.percentEncodedQuery guard let url = baseUrlComponents.url else { throw Error.invalidRequestURL(request: request, baseURL: baseURL) } From 85ba6a9a21929b90c9fc1e563df8f72c83e9f271 Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Wed, 27 Sep 2023 08:39:27 +0200 Subject: [PATCH 8/9] Adapt to removed utils in runtime --- .../Test_AsyncHTTPClientTransport.swift | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Tests/OpenAPIAsyncHTTPClientTests/Test_AsyncHTTPClientTransport.swift b/Tests/OpenAPIAsyncHTTPClientTests/Test_AsyncHTTPClientTransport.swift index 716dd89..e899261 100644 --- a/Tests/OpenAPIAsyncHTTPClientTests/Test_AsyncHTTPClientTransport.swift +++ b/Tests/OpenAPIAsyncHTTPClientTests/Test_AsyncHTTPClientTransport.swift @@ -39,8 +39,10 @@ class Test_AsyncHTTPClientTransport: XCTestCase { func testConvertRequest() throws { let request: HTTPRequest = .init( - soar_path: "/hello%20world/Maria?greeting=Howdy", method: .post, + scheme: nil, + authority: nil, + path: "/hello%20world/Maria?greeting=Howdy", headerFields: [ .contentType: "application/json" ] @@ -93,8 +95,10 @@ class Test_AsyncHTTPClientTransport: XCTestCase { requestSender: TestSender.test ) let request: HTTPRequest = .init( - soar_path: "/api/v1/hello/Maria", method: .get, + scheme: nil, + authority: nil, + path: "/api/v1/hello/Maria", headerFields: [ .init("x-request")!: "yes" ] From a23e6ae5bcb4f50e7403bab2b2cb3c5d51706893 Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Wed, 27 Sep 2023 10:31:50 +0200 Subject: [PATCH 9/9] Bump versions to 0.3.0 --- Package.swift | 2 +- README.md | 2 +- .../OpenAPIAsyncHTTPClient/Documentation.docc/Documentation.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Package.swift b/Package.swift index b7d99bb..2c1c322 100644 --- a/Package.swift +++ b/Package.swift @@ -40,7 +40,7 @@ let package = Package( dependencies: [ .package(url: "https://github.com/apple/swift-nio", from: "2.58.0"), .package(url: "https://github.com/swift-server/async-http-client.git", from: "1.19.0"), - .package(url: "https://github.com/apple/swift-openapi-runtime", "0.1.3" ..< "0.3.0"), + .package(url: "https://github.com/apple/swift-openapi-runtime", .upToNextMinor(from: "0.3.0")), .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"), ], targets: [ diff --git a/README.md b/README.md index b9d442c..666c32b 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,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") ), ``` diff --git a/Sources/OpenAPIAsyncHTTPClient/Documentation.docc/Documentation.md b/Sources/OpenAPIAsyncHTTPClient/Documentation.docc/Documentation.md index 054a552..25d9032 100644 --- a/Sources/OpenAPIAsyncHTTPClient/Documentation.docc/Documentation.md +++ b/Sources/OpenAPIAsyncHTTPClient/Documentation.docc/Documentation.md @@ -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") ), ```