diff --git a/Sources/SpellbookHTTP/HTTPClient.swift b/Sources/SpellbookHTTP/HTTPClient.swift index eb2da12..3f4ab4c 100644 --- a/Sources/SpellbookHTTP/HTTPClient.swift +++ b/Sources/SpellbookHTTP/HTTPClient.swift @@ -24,7 +24,14 @@ import SpellbookFoundation import Foundation -open class HTTPClient { +public protocol HTTPClientProtocol { + func data(for request: () throws -> URLRequest, completion: @escaping (Result, Error>) -> Void) + + @available(macOS 12.0, iOS 15, tvOS 15.0, watchOS 8.0, *) + func data(for request: () throws -> URLRequest, delegate: URLSessionTaskDelegate?) async throws -> HTTPResult +} + +open class HTTPClient: HTTPClientProtocol { private var additionalHeaders = Synchronized>(.unfair, .init()) private let session: URLSession @@ -35,14 +42,11 @@ open class HTTPClient { public func updateHeaders(_ update: (inout HTTPParameters) -> Void) { additionalHeaders.write(update) } -} - -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) { + public func data( + for request: () throws -> URLRequest, + completion: @escaping (Result, Error>) -> Void + ) { var urlRequest: URLRequest do { urlRequest = try request() @@ -69,22 +73,45 @@ extension HTTPClient { }.resume() } + @available(macOS 12.0, iOS 15, tvOS 15.0, watchOS 8.0, *) + public func data( + for request: () throws -> 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) + } + + return .init(value: data, response: response) + } +} + +extension HTTPClientProtocol { + public func data(for request: HTTPRequest, completion: @escaping (Result, Error>) -> Void) { + data(for: request.urlRequest, completion: completion) + } + + func data(for request: URLRequest, completion: @escaping (Result, Error>) -> Void) { + data(for: { request }, completion: completion) + } + public func object( _ type: T.Type = T.self, for request: HTTPRequest, decoder: ObjectDecoder, completion: @escaping (Result, Error>) -> Void ) { - object(type, for: try request.urlRequest(), decoder: decoder, completion: completion) + object(type, for: request.urlRequest, decoder: decoder, completion: completion) } public func object( _ type: T.Type = T.self, - for request: @autoclosure () throws -> URLRequest, + for request: () throws -> URLRequest, decoder: ObjectDecoder, completion: @escaping (Result, Error>) -> Void ) { - data(for: try request()) { + data(for: request) { completion($0.flatMap { dataResult in Self.decodeResponse(dataResult.value, decoder: decoder) .map { .init(value: $0, response: dataResult.response) } @@ -92,39 +119,35 @@ extension HTTPClient { } } - private static func decodeResponse(_ data: Data, decoder: ObjectDecoder) -> Result { - do { - let object = try decoder.decode(T.self, data) - return .success(object) - } catch { - return .failure(URLError.badResponse(error)) + public func object( + _ type: T.Type = T.self, + for request: URLRequest, + decoder: ObjectDecoder, + completion: @escaping (Result, Error>) -> Void + ) { + data(for: request) { + completion($0.flatMap { dataResult in + Self.decodeResponse(dataResult.value, decoder: decoder) + .map { .init(value: $0, response: dataResult.response) } + }) } } - - private static func decodeResponse(_ data: Data, decoder: ObjectDecoder) -> Result { - .success(.init()) - } } @available(macOS 12.0, iOS 15, tvOS 15.0, watchOS 8.0, *) -extension HTTPClient { +extension HTTPClientProtocol { public func data( for request: HTTPRequest, delegate: URLSessionTaskDelegate? = nil ) async throws -> HTTPResult { - try await data(for: try request.urlRequest(), delegate: delegate) + try await data(for: 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) - } - - return .init(value: data, response: response) + try await data(for: { request }, delegate: delegate) } public func object( @@ -132,13 +155,21 @@ extension HTTPClient { delegate: URLSessionTaskDelegate? = nil, decoder: ObjectDecoder ) async throws -> HTTPResult { - try await object(for: try request.urlRequest(), delegate: delegate, decoder: decoder) + try await object(for: request.urlRequest, delegate: delegate, decoder: decoder) } public func object( for request: URLRequest, delegate: URLSessionTaskDelegate? = nil, decoder: ObjectDecoder + ) async throws -> HTTPResult { + try await object(for: { request }, delegate: delegate, decoder: decoder) + } + + public func object( + for request: () throws -> 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() @@ -146,6 +177,27 @@ extension HTTPClient { } } +extension HTTPClientProtocol { + private static func decodeResponse( + _ data: Data, + decoder: ObjectDecoder + ) -> Result { + do { + let object = try decoder.decode(T.self, data) + return .success(object) + } catch { + return .failure(URLError.badResponse(error)) + } + } + + private static func decodeResponse( + _ data: Data, + decoder: ObjectDecoder + ) -> Result { + .success(.init()) + } +} + extension URLError { fileprivate static func badResponse(_ underlyingError: Error) -> URLError { URLError(.badServerResponse, userInfo: [NSUnderlyingErrorKey: underlyingError])