Skip to content

Commit

Permalink
Merge pull request #20 from mbarnach/main
Browse files Browse the repository at this point in the history
Linux compatibility
  • Loading branch information
kean committed Apr 3, 2022
2 parents 49d1ac9 + 9abd65d commit 451ec7d
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 27 deletions.
17 changes: 10 additions & 7 deletions Sources/Get/APIClient.swift
Expand Up @@ -3,6 +3,9 @@
// Copyright (c) 2021-2022 Alexander Grebenyuk (github.com/kean).

import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif

public protocol APIClientDelegate {
func client(_ client: APIClient, willSendRequest request: inout URLRequest) async throws
Expand All @@ -16,7 +19,7 @@ public actor APIClient {
private let serializer: Serializer
private let delegate: APIClientDelegate
private let loader = DataLoader()

public struct Configuration {
public var host: String
public var port: Int?
Expand All @@ -31,7 +34,7 @@ public actor APIClient {
public var delegate: APIClientDelegate?
/// The (optional) URLSession delegate that allows you to monitor the underlying URLSession.
public var sessionDelegate: URLSessionDelegate?

public init(host: String, sessionConfiguration: URLSessionConfiguration = .default, delegate: APIClientDelegate? = nil) {
self.host = host
self.sessionConfiguration = sessionConfiguration
Expand Down Expand Up @@ -69,12 +72,12 @@ public actor APIClient {
}
}
}

/// Sends the given request and returns a response with a decoded response value.
public func send<T: Decodable>(_ request: Request<T>) async throws -> Response<T> {
try await send(request, decode)
}

private func decode<T: Decodable>(_ data: Data) async throws -> T {
if T.self == Data.self {
return data as! T
Expand All @@ -91,13 +94,13 @@ public actor APIClient {
public func send(_ request: Request<Void>) async throws -> Response<Void> {
try await send(request) { _ in () }
}

private func send<T>(_ request: Request<T>, _ decode: @escaping (Data) async throws -> T) async throws -> Response<T> {
let response = try await data(for: request)
let value = try await decode(response.value)
return response.map { _ in value } // Keep metadata
}

/// Returns response data for the given request.
public func data<T>(for request: Request<T>) async throws -> Response<Data> {
let request = try await makeRequest(for: request)
Expand Down Expand Up @@ -169,7 +172,7 @@ public actor APIClient {

public enum APIError: Error, LocalizedError {
case unacceptableStatusCode(Int)

public var errorDescription: String? {
switch self {
case .unacceptableStatusCode(let statusCode):
Expand Down
36 changes: 29 additions & 7 deletions Sources/Get/Helpers.swift
Expand Up @@ -3,6 +3,9 @@
// Copyright (c) 2021-2022 Alexander Grebenyuk (github.com/kean).

import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif

struct AnyEncodable: Encodable {
private let value: Encodable
Expand Down Expand Up @@ -70,7 +73,7 @@ actor Serializer {
final class DataLoader: NSObject, URLSessionDataDelegate {
private var handlers = [URLSessionTask: TaskHandler]()
private typealias Completion = (Result<(Data, URLResponse, URLSessionTaskMetrics?), Error>) -> Void

/// Loads data with the given request.
func data(for request: URLRequest, session: URLSession) async throws -> (Data, URLResponse, URLSessionTaskMetrics?) {
final class Box { var task: URLSessionTask? }
Expand All @@ -85,7 +88,7 @@ final class DataLoader: NSObject, URLSessionDataDelegate {
}
})
}

private func loadData(with request: URLRequest, session: URLSession, completion: @escaping Completion) -> URLSessionTask {
let task = session.dataTask(with: request)
session.delegateQueue.addOperation {
Expand All @@ -104,7 +107,7 @@ final class DataLoader: NSObject, URLSessionDataDelegate {
handler.completion(.failure(error ?? URLError(.unknown)))
}
}

func urlSession(_ session: URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) {
handlers[task]?.metrics = metrics
}
Expand All @@ -131,45 +134,63 @@ final class DataLoader: NSObject, URLSessionDataDelegate {
}

/// Allows users to monitor URLSession.
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
final class URLSessionProxyDelegate: NSObject, URLSessionTaskDelegate, URLSessionDataDelegate {
private var delegate: URLSessionDelegate
#if !os(Linux)
private let interceptedSelectors: Set<Selector>
#endif
private let loader: DataLoader

static func make(loader: DataLoader, delegate: URLSessionDelegate?) -> URLSessionDelegate {
guard let delegate = delegate else { return loader }
return URLSessionProxyDelegate(loader: loader, delegate: delegate)
}

init(loader: DataLoader, delegate: URLSessionDelegate) {
self.loader = loader
self.delegate = delegate
#if !os(Linux)
self.interceptedSelectors = [
#selector(URLSessionDataDelegate.urlSession(_:dataTask:didReceive:)),
#selector(URLSessionTaskDelegate.urlSession(_:task:didCompleteWithError:)),
#selector(URLSessionTaskDelegate.urlSession(_:task:didFinishCollecting:))
]
#endif
}

// MARK: URLSessionDelegate

func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
loader.urlSession(session, dataTask: dataTask, didReceive: data)
#if !os(Linux)
(delegate as? URLSessionDataDelegate)?.urlSession?(session, dataTask: dataTask, didReceive: data)
#else
(delegate as? URLSessionDataDelegate)?.urlSession(session, dataTask: dataTask, didReceive: data)
#endif
}

func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
loader.urlSession(session, task: task, didCompleteWithError: error)
#if !os(Linux)
(delegate as? URLSessionTaskDelegate)?.urlSession?(session, task: task, didCompleteWithError: error)
#else
(delegate as? URLSessionTaskDelegate)?.urlSession(session, task: task, didCompleteWithError: error)
#endif
}

func urlSession(_ session: URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) {
loader.urlSession(session, task: task, didFinishCollecting: metrics)
#if !os(Linux)
(delegate as? URLSessionTaskDelegate)?.urlSession?(session, task: task, didFinishCollecting: metrics)
#else
(delegate as? URLSessionTaskDelegate)?.urlSession(session, task: task, didFinishCollecting: metrics)
#endif
}

// MARK: Proxy

#if !os(Linux)
override func responds(to aSelector: Selector!) -> Bool {
if interceptedSelectors.contains(aSelector) {
return true
Expand All @@ -180,6 +201,7 @@ final class URLSessionProxyDelegate: NSObject, URLSessionTaskDelegate, URLSessio
override func forwardingTarget(for selector: Selector!) -> Any? {
interceptedSelectors.contains(selector) ? nil : delegate
}
#endif
}

extension OperationQueue {
Expand Down
29 changes: 16 additions & 13 deletions Sources/Get/Request.swift
Expand Up @@ -3,6 +3,9 @@
// Copyright (c) 2021-2022 Alexander Grebenyuk (github.com/kean).

import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif

public struct Request<Response> {
public var method: String
Expand All @@ -11,14 +14,14 @@ public struct Request<Response> {
var body: AnyEncodable?
public var headers: [String: String]?
public var id: String?

public init(method: String, path: String, query: [(String, String?)]? = nil, headers: [String : String]? = nil) {
self.method = method
self.path = path
self.query = query
self.headers = headers
}

public init<U: Encodable>(method: String, path: String, query: [(String, String?)]? = nil, body: U?, headers: [String: String]? = nil) {
self.method = method
self.path = path
Expand All @@ -34,43 +37,43 @@ public struct Request<Response> {
public static func post(_ path: String, query: [(String, String?)]? = nil, headers: [String: String]? = nil) -> Request {
Request(method: "POST", path: path, query: query, headers: headers)
}

public static func post<U: Encodable>(_ path: String, query: [(String, String?)]? = nil, body: U?, headers: [String: String]? = nil) -> Request {
Request(method: "POST", path: path, query: query, body: body, headers: headers)
}

public static func put(_ path: String, query: [(String, String?)]? = nil, headers: [String: String]? = nil) -> Request {
Request(method: "PUT", path: path, query: query, headers: headers)
}

public static func put<U: Encodable>(_ path: String, query: [(String, String?)]? = nil, body: U?, headers: [String: String]? = nil) -> Request {
Request(method: "PUT", path: path, query: query, body: body, headers: headers)
}

public static func patch(_ path: String, query: [(String, String?)]? = nil, headers: [String: String]? = nil) -> Request {
Request(method: "PATCH", path: path, query: query, headers: headers)
}

public static func patch<U: Encodable>(_ path: String, query: [(String, String?)]? = nil, body: U?, headers: [String: String]? = nil) -> Request {
Request(method: "PATCH", path: path, query: query, body: body, headers: headers)
}

public static func delete(_ path: String, query: [(String, String?)]? = nil, headers: [String: String]? = nil) -> Request {
Request(method: "DELETE", path: path, query: query, headers: headers)
}

public static func delete<U: Encodable>(_ path: String, query: [(String, String?)]? = nil, body: U?, headers: [String: String]? = nil) -> Request {
Request(method: "DELETE", path: path, query: query, body: body, headers: headers)
}

public static func options(_ path: String, query: [(String, String?)]? = nil, headers: [String: String]? = nil) -> Request {
Request(method: "OPTIONS", path: path, query: query, headers: headers)
}

public static func head(_ path: String, query: [(String, String?)]? = nil, headers: [String: String]? = nil) -> Request {
Request(method: "HEAD", path: path, query: query, headers: headers)
}

public static func trace(_ path: String, query: [(String, String?)]? = nil, headers: [String: String]? = nil) -> Request {
Request(method: "TRACE", path: path, query: query, headers: headers)
}
Expand All @@ -86,15 +89,15 @@ public struct Response<T> {
public var response: URLResponse
public var statusCode: Int? { (response as? HTTPURLResponse)?.statusCode }
public var metrics: URLSessionTaskMetrics?

public init(value: T, data: Data, request: URLRequest, response: URLResponse, metrics: URLSessionTaskMetrics? = nil) {
self.value = value
self.data = data
self.request = request
self.response = response
self.metrics = metrics
}

func map<U>(_ closure: (T) -> U) -> Response<U> {
Response<U>(value: closure(value), data: data, request: request, response: response, metrics: metrics)
}
Expand Down

0 comments on commit 451ec7d

Please sign in to comment.