From d39b0710122df31d2d41685fab4c8df54e32c5a5 Mon Sep 17 00:00:00 2001 From: "Alkenso (Vladimir Vashurkin)" Date: Tue, 2 Jul 2024 20:20:05 +0300 Subject: [PATCH] Refine HTTPClient and HTTPRequest --- Sources/SpellbookHTTP/HTTPClient.swift | 36 +++++++++++++++++++--- Sources/SpellbookHTTP/HTTPRequest.swift | 41 ++++++++++--------------- 2 files changed, 49 insertions(+), 28 deletions(-) diff --git a/Sources/SpellbookHTTP/HTTPClient.swift b/Sources/SpellbookHTTP/HTTPClient.swift index 9dbd31a..0c4faeb 100644 --- a/Sources/SpellbookHTTP/HTTPClient.swift +++ b/Sources/SpellbookHTTP/HTTPClient.swift @@ -34,9 +34,13 @@ open class HTTPClient { extension HTTPClient { public func data(for request: HTTPRequest, completion: @escaping (Result, Error>) -> Void) { + data(for: try request.urlRequest(), completion: completion) + } + + public func data(for request: @autoclosure () throws -> URLRequest, completion: @escaping (Result, Error>) -> Void) { let urlRequest: URLRequest do { - urlRequest = try request.urlRequest() + urlRequest = try request() } catch { completion(.failure(error)) return @@ -56,11 +60,21 @@ extension HTTPClient { } public func object( + _ type: T.Type = T.self, for request: HTTPRequest, decoder: ObjectDecoder, completion: @escaping (Result, Error>) -> Void ) { - data(for: request) { + object(type, for: try request.urlRequest(), decoder: decoder, completion: completion) + } + + public func object( + _ type: T.Type = T.self, + for request: @autoclosure () throws -> URLRequest, + decoder: ObjectDecoder, + completion: @escaping (Result, Error>) -> Void + ) { + data(for: try request()) { completion($0.flatMap { dataResult in Self.decodeResponse(dataResult.value, decoder: decoder) .map { .init(value: $0, response: dataResult.response) } @@ -84,8 +98,14 @@ extension HTTPClient { for request: HTTPRequest, delegate: URLSessionTaskDelegate? = nil ) async throws -> HTTPResult { - let urlRequest = try request.urlRequest() - let (data, response) = try await session.data(for: urlRequest, delegate: delegate) + try await data(for: try request.urlRequest(), delegate: delegate) + } + + public func data( + for request: URLRequest, + delegate: URLSessionTaskDelegate? = nil + ) async throws -> HTTPResult { + let (data, response) = try await session.data(for: request, delegate: delegate) guard let response = response as? HTTPURLResponse else { throw URLError.badResponseType(response) } @@ -97,6 +117,14 @@ extension HTTPClient { for request: HTTPRequest, delegate: URLSessionTaskDelegate? = nil, decoder: ObjectDecoder + ) async throws -> HTTPResult { + try await object(for: try request.urlRequest(), delegate: delegate, decoder: decoder) + } + + public func object( + for request: URLRequest, + delegate: URLSessionTaskDelegate? = nil, + decoder: ObjectDecoder ) async throws -> HTTPResult { let dataResult = try await data(for: request, delegate: delegate) let object = try Self.decodeResponse(dataResult.value, decoder: decoder).get() diff --git a/Sources/SpellbookHTTP/HTTPRequest.swift b/Sources/SpellbookHTTP/HTTPRequest.swift index d383e32..2d24750 100644 --- a/Sources/SpellbookHTTP/HTTPRequest.swift +++ b/Sources/SpellbookHTTP/HTTPRequest.swift @@ -30,7 +30,7 @@ public struct HTTPRequest { public var port: UInt16? public var query: [QueryItem: String] = [:] - public var headers: [Header: HeaderValue] = [:] + public var headers: [Header: String] = [:] public var body: Body? public init(urlString: String, method: Method) { @@ -68,11 +68,6 @@ extension HTTPRequest { public var rawValue: String public init(rawValue: String) { self.rawValue = rawValue } } - - public struct HeaderValue: RawRepresentable, Hashable { - public var rawValue: String - public init(rawValue: String) { self.rawValue = rawValue } - } } extension HTTPRequest.Header { @@ -83,20 +78,12 @@ extension HTTPRequest.Header: ExpressibleByStringLiteral { public init(stringLiteral value: StringLiteralType) { self.rawValue = value } } -extension HTTPRequest.HeaderValue { - public static func authorization(_ type: HTTPRequest.AuthorizationType, _ token: String) -> Self { - .init(rawValue: "\(type.rawValue) \(token)") +extension HTTPRequest { + public static func authorization(_ type: HTTPRequest.AuthorizationType, _ token: String) -> String { + "\(type.rawValue) \(token)" } } -extension HTTPRequest.HeaderValue: ExpressibleByStringLiteral { - public init(stringLiteral value: StringLiteralType) { self.rawValue = value } -} - -extension HTTPRequest.HeaderValue: ExpressibleByStringInterpolation { - public init(stringInterpolation: DefaultStringInterpolation) { self.rawValue = stringInterpolation.description } -} - extension HTTPRequest { public struct AuthorizationType: RawRepresentable, Hashable { public var rawValue: String @@ -114,8 +101,8 @@ extension HTTPRequest.AuthorizationType: ExpressibleByStringInterpolation { extension HTTPRequest { public struct Body { - internal let data: () throws -> Data - internal let contentType: String? + public let data: () throws -> Data + public let contentType: String? public init(contentType: String?, data: @escaping () throws -> Data) { self.data = data @@ -156,7 +143,10 @@ extension HTTPRequest.Body { extension HTTPRequest { public func urlRequest() throws -> URLRequest { guard var components = URLComponents(string: url) else { - fatalError() + throw URLError(.badURL, userInfo: [ + NSDebugDescriptionErrorKey: "Failed to parse URLComponents from URL", + SBRelatedObjectErrorKey: url, + ]) } if !query.isEmpty { components.queryItems = query.map { URLQueryItem(name: $0.key.rawValue, value: $0.value) } @@ -165,13 +155,13 @@ extension HTTPRequest { components.port = Int(port) } guard let url = components.url else { - fatalError() + throw URLError(.badURL, userInfo: [ + NSDebugDescriptionErrorKey: "Failed to create URL from URLComponents", + SBRelatedObjectErrorKey: url, + ]) } var urlRequest = URLRequest(url: url) - headers.forEach { - urlRequest.setValue($0.value.rawValue, forHTTPHeaderField: $0.key.rawValue) - } urlRequest.httpMethod = method.rawValue if let body { urlRequest.httpBody = try Result { try body.data() } @@ -181,6 +171,9 @@ extension HTTPRequest { urlRequest.addValue(contentType, forHTTPHeaderField: "Content-Type") } } + headers.forEach { + urlRequest.setValue($0.value, forHTTPHeaderField: $0.key.rawValue) + } return urlRequest }