From 6e0f334aded163e307b5f5573ee20fd1d68f26bf Mon Sep 17 00:00:00 2001 From: "novr (Nobuhisa Komiya)" Date: Fri, 17 Nov 2023 00:41:08 +0900 Subject: [PATCH 1/2] chore: bump --- Package.resolved | 21 +++++++++++++++------ Package.swift | 4 ++-- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/Package.resolved b/Package.resolved index cd442b2..6839f0f 100644 --- a/Package.resolved +++ b/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/swift-cloud/Compute", "state" : { - "revision" : "cc89dba27ade5713a2ac167fc752c4f4fe00439a", - "version" : "2.17.0" + "revision" : "542e212eb2f8eed7740205de30cc57e9e0845cfe", + "version" : "2.18.0" } }, { @@ -14,8 +14,17 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-crypto", "state" : { - "revision" : "33a20e650c33f6d72d822d558333f2085effa3dc", - "version" : "2.5.0" + "revision" : "60f13f60c4d093691934dc6cfdf5f508ada1f894", + "version" : "2.6.0" + } + }, + { + "identity" : "swift-http-types", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-http-types", + "state" : { + "revision" : "99d066e29effa8845e4761dd3f2f831edfdf8925", + "version" : "1.0.0" } }, { @@ -23,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-openapi-runtime.git", "state" : { - "revision" : "ef2b34c4eb3175988dd318df7849d2052f106466", - "version" : "0.2.5" + "revision" : "a51b3bd6f2151e9a6f792ca6937a7242c4758768", + "version" : "0.3.6" } } ], diff --git a/Package.swift b/Package.swift index a677440..328654f 100644 --- a/Package.swift +++ b/Package.swift @@ -18,8 +18,8 @@ let package = Package( targets: ["OpenAPICompute"]), ], dependencies: [ - .package(url: "https://github.com/apple/swift-openapi-runtime.git", .upToNextMinor(from: "0.2.0")), - .package(url: "https://github.com/swift-cloud/Compute", from: "2.17.0"), + .package(url: "https://github.com/apple/swift-openapi-runtime.git", from: "0.3.6"), + .package(url: "https://github.com/swift-cloud/Compute", from: "2.18.0"), ], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite. From 6f3d9363cb21470647312a56936cacbe0d1e0c81 Mon Sep 17 00:00:00 2001 From: "novr (Nobuhisa Komiya)" Date: Fri, 17 Nov 2023 10:35:22 +0900 Subject: [PATCH 2/2] feat: update for open-api-runtime 0.3.6 --- Sources/OpenAPICompute/ComputeTransport.swift | 154 +++++++++--------- .../ComputeTransportTests.swift | 5 +- 2 files changed, 79 insertions(+), 80 deletions(-) diff --git a/Sources/OpenAPICompute/ComputeTransport.swift b/Sources/OpenAPICompute/ComputeTransport.swift index c065495..69b1f99 100644 --- a/Sources/OpenAPICompute/ComputeTransport.swift +++ b/Sources/OpenAPICompute/ComputeTransport.swift @@ -1,5 +1,6 @@ import Foundation import OpenAPIRuntime +import HTTPTypes import Compute public final class ComputeTransport { @@ -12,38 +13,36 @@ public final class ComputeTransport { extension ComputeTransport: ServerTransport { public func register( - _ handler: @escaping @Sendable (OpenAPIRuntime.Request, OpenAPIRuntime.ServerRequestMetadata) async throws -> OpenAPIRuntime.Response, - method: OpenAPIRuntime.HTTPMethod, - path: [OpenAPIRuntime.RouterPathComponent], - queryItemNames: Set) throws { - router.on( - try Compute.HTTPMethod(method), - Self.makeComputePath(from: path) + _ handler: @Sendable @escaping (HTTPRequest, HTTPBody?, ServerRequestMetadata) async throws -> (HTTPResponse, HTTPBody?), + method: HTTPRequest.Method, + path: String + ) throws { + router.on( + method, + path ) { computeRequest in - let request = try await OpenAPIRuntime.Request(computeRequest) + let request = try await HTTPTypes.HTTPRequest(computeRequest) + let body = try await OpenAPIRuntime.HTTPBody(computeRequest) let requestMetadata = try OpenAPIRuntime.ServerRequestMetadata( from: computeRequest, - forPath: path, - extractingQueryItemNamed: queryItemNames + forPath: path ) - return try await handler(request, requestMetadata) + return try await handler(request, body, requestMetadata) } } - - /// Make compute path string from RouterPathComponent array - static func makeComputePath(from path: [OpenAPIRuntime.RouterPathComponent]) -> String { - path.map(\.computePathComponent).joined(separator: "/") - } } -extension RouterPathComponent { - /// Return path component as String - var computePathComponent: String { - switch self { - case .constant(let string): - return string - case .parameter(let parameter): - return ":\(parameter)" +extension [PathComponent] { + init(_ path: String) { + self = path.split( + separator: "/", + omittingEmptySubsequences: false + ).map { parameter in + if parameter.first == "{", parameter.last == "}" { + return .parameter(String(parameter.dropFirst().dropLast())) + } else { + return .constant(String(parameter)) + } } } } @@ -52,17 +51,26 @@ extension Compute.Router { @discardableResult func on( - _ method: Compute.HTTPMethod, + _ method: HTTPRequest.Method, _ path: String, - use closure: @escaping (IncomingRequest) async throws -> OpenAPIRuntime.Response + use closure: @escaping (IncomingRequest) async throws -> (HTTPTypes.HTTPResponse, OpenAPIRuntime.HTTPBody?) ) -> Compute.Router { let handler: (IncomingRequest, OutgoingResponse) async throws -> Void = { request, response in let result = try await closure(request) - response.status(result.statusCode) - for header in result.headerFields { - response.header(header.name, header.value) + response.status(result.0.status.code) + result.0.headerFields.forEach { + response.header($0.name.rawName, $0.value) + } + guard let body = result.1 else { + try await response.end() + return + } + switch body.length { + case let .known(length): + try await response.send(Data(collecting: body, upTo: length)) + case .unknown: + try await response.send(Data(collecting: body, upTo: .max)) } - try await response.send(result.body) } switch method { case .get: @@ -98,54 +106,51 @@ enum ComputeTransportError: Error { case missingRequiredPathParameter(String) } -extension Compute.PathComponent { - init(_ pathComponent: OpenAPIRuntime.RouterPathComponent) { - switch pathComponent { - case .constant(let value): self = .constant(value) - case .parameter(let value): self = .parameter(value) - } - } -} - -extension OpenAPIRuntime.Request { +extension HTTPTypes.HTTPRequest { init(_ computeRequest: Compute.IncomingRequest) async throws { - let headerFields: [OpenAPIRuntime.HeaderField] = .init(computeRequest.headers) - let bodyData = try await computeRequest.body.data() - let method = try OpenAPIRuntime.HTTPMethod(computeRequest.method) + let headerFields: HTTPTypes.HTTPFields = .init(computeRequest.headers) + let method = try HTTPTypes.HTTPRequest.Method(computeRequest.method) + let queries = computeRequest.url.query.map { "?\($0)" } ?? "" self.init( - path: computeRequest.url.path, - query: computeRequest.url.query, method: method, - headerFields: headerFields, - body: bodyData + scheme: computeRequest.url.scheme, + authority: computeRequest.url.host(), + path: computeRequest.url.path() + queries, + headerFields: headerFields ) } } -extension OpenAPIRuntime.ServerRequestMetadata { - init( - from computeRequest: Compute.IncomingRequest, - forPath path: [RouterPathComponent], - extractingQueryItemNamed queryItemNames: Set - ) throws { - self.init( - pathParameters: try .init(from: computeRequest, forPath: path), - queryParameters: .init(from: computeRequest, queryItemNames: queryItemNames) +extension OpenAPIRuntime.HTTPBody { + convenience init(_ computeRequest: Compute.IncomingRequest) async throws { + let contentLength = computeRequest.headers.entries().first { $0.key == "content-length"}.map { Int($0.value) } + await self.init( + try computeRequest.body.data(), + length: contentLength?.map { .known($0) } ?? .unknown, + iterationBehavior: .single ) } } -extension Dictionary where Key == String, Value == String { - init(from computeRequest: Compute.IncomingRequest, forPath path: [RouterPathComponent]) throws { - let keysAndValues = try path.compactMap { item -> (String, String)? in - guard case let .parameter(name) = item else { +extension OpenAPIRuntime.ServerRequestMetadata { + init(from computeRequest: Compute.IncomingRequest, forPath path: String) throws { + self.init(pathParameters: try .init(from: computeRequest, forPath: path)) + } +} + +extension Dictionary { + init(from computeRequest: Compute.IncomingRequest, forPath path: String) throws { + let keysAndValues = try [PathComponent](path).compactMap { component throws -> String? in + guard case let .parameter(parameter) = component else { return nil } - guard let value = computeRequest.pathParams.get(name) else { - throw ComputeTransportError.missingRequiredPathParameter(name) + return parameter + }.map { parameter -> (String, Substring) in + guard let value = computeRequest.searchParams[parameter] else { + throw ComputeTransportError.missingRequiredPathParameter(parameter) } - return (name, value) + return (parameter, Substring(value)) } let pathParameterDictionary = try Dictionary(keysAndValues, uniquingKeysWith: { _, _ in throw ComputeTransportError.duplicatePathParameter(keysAndValues.map(\.0)) @@ -154,25 +159,18 @@ extension Dictionary where Key == String, Value == String { } } -extension Array where Element == URLQueryItem { - init(from computeRequest: Compute.IncomingRequest, queryItemNames: Set) { - let queryParameters = queryItemNames.sorted().compactMap { name -> URLQueryItem? in - guard let value = computeRequest.searchParams[name] else { +extension HTTPTypes.HTTPFields { + init(_ headers: Compute.Headers) { + self.init(headers.entries().compactMap { name, value in + guard let name = HTTPField.Name(name) else { return nil } - return .init(name: name, value: value) - } - self = queryParameters - } -} - -extension Array where Element == OpenAPIRuntime.HeaderField { - init(_ headers: Compute.Headers) { - self = headers.entries().map { .init(name: $0.key, value: $0.value) } + return HTTPField(name: name, value: value) + }) } } -extension OpenAPIRuntime.HTTPMethod { +extension HTTPTypes.HTTPRequest.Method { init(_ method: Compute.HTTPMethod) throws { switch method { case .get: self = .get @@ -188,7 +186,7 @@ extension OpenAPIRuntime.HTTPMethod { } extension Compute.HTTPMethod { - init(_ method: OpenAPIRuntime.HTTPMethod) throws { + init(_ method: HTTPTypes.HTTPRequest.Method) throws { switch method { case .get: self = .get case .put: self = .put diff --git a/Tests/OpenAPIComputeTests/ComputeTransportTests.swift b/Tests/OpenAPIComputeTests/ComputeTransportTests.swift index b0a40c2..17de7c7 100644 --- a/Tests/OpenAPIComputeTests/ComputeTransportTests.swift +++ b/Tests/OpenAPIComputeTests/ComputeTransportTests.swift @@ -2,6 +2,7 @@ import XCTest @testable import OpenAPICompute import Compute import OpenAPIRuntime +import HTTPTypes final class ComputeTransportTests: XCTestCase { @@ -29,7 +30,7 @@ final class ComputeTransportTests: XCTestCase { XCTAssertEqual(name, "TRACE") } - try XCTAssert(function: OpenAPIRuntime.HTTPMethod.init(_:), behavesAccordingTo: [ + try XCTAssert(function: HTTPTypes.HTTPRequest.Method.init(_:), behavesAccordingTo: [ (.get, .get), (.put, .put), (.post, .post), @@ -38,7 +39,7 @@ final class ComputeTransportTests: XCTestCase { (.head, .head), (.patch, .patch), ]) - try XCTAssertThrowsError(OpenAPIRuntime.HTTPMethod(.query)) { + try XCTAssertThrowsError(HTTPTypes.HTTPRequest.Method(.query)) { guard case let ComputeTransportError.unsupportedHTTPMethod(name) = $0 else { XCTFail() return