Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 21 additions & 53 deletions Sources/SessionPlus/Implementation/AbsoluteURLSessionClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@ import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif
#if canImport(Combine)
import Combine
#endif

/// A `Client` implementation that operates expecting all requests use _absolute_ urls.
open class AbsoluteURLSessionClient: Client {
Expand All @@ -15,60 +12,31 @@ open class AbsoluteURLSessionClient: Client {
self.session = URLSession(configuration: sessionConfiguration, delegate: sessionDelegate, delegateQueue: nil)
}

#if (os(macOS) || os(iOS) || os(tvOS) || os(watchOS))
/// Implementation that uses the `URLSession` async/await concurrency apis for handling a `Request`/`Response` interaction.
///
/// The `URLSession` api is only available on Apple platforms, as the `FoundationNetworking` version has not been updated.
public func performRequest(_ request: Request) async throws -> Response {
public func performRequest(_ request: any Request) async throws -> any Response {
let urlRequest = try URLRequest(request: request)
let sessionResponse = try await session.data(for: urlRequest)
return AnyResponse(statusCode: sessionResponse.1.statusCode, headers: sessionResponse.1.headers, data: sessionResponse.0)
}
#endif

#if canImport(Combine)
/// Implementation that uses the `URLSession.DataTaskPublisher` to handle the `Request`/`Response` interaction.
public func performRequest(_ request: Request) -> AnyPublisher<Response, Error> {
let urlRequest: URLRequest
do {
urlRequest = try URLRequest(request: request)
} catch {
return Fail(outputType: Response.self, failure: error).eraseToAnyPublisher()
}

return session
.dataTaskPublisher(for: urlRequest)
.tryMap { taskResponse -> Response in
AnyResponse(statusCode: taskResponse.response.statusCode, headers: taskResponse.response.headers, data: taskResponse.data)
#if canImport(FoundationNetworking)
let sessionResponse = try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<(Data, URLResponse), Error>) in
session.dataTask(with: urlRequest) { data, urlResponse, error in
if let error {
continuation.resume(throwing: error)
return
}

continuation.resume(returning: (data!, urlResponse!))
}
.eraseToAnyPublisher()
}
#endif

/// Implementation that uses the default `URLSessionDataTask` methods for handling a `Request`/`Response` interaction.
public func performRequest(_ request: Request, completion: @escaping (Result<Response, Error>) -> Void) {
let urlRequest: URLRequest
do {
urlRequest = try URLRequest(request: request)
} catch {
completion(.failure(error))
return
.resume()
}
#else
let sessionResponse = try await session.data(for: urlRequest)
#endif

session.dataTask(with: urlRequest) { data, urlResponse, error in
guard error == nil else {
completion(.failure(error!))
return
}

guard let httpResponse = urlResponse else {
completion(.failure(URLError(.cannotParseResponse)))
return
}

let response = AnyResponse(statusCode: httpResponse.statusCode, headers: httpResponse.headers, data: data ?? Data())
completion(.success(response))
}
.resume()
let response = AnyResponse(
statusCode: sessionResponse.1.statusCode,
headers: sessionResponse.1.headers,
data: sessionResponse.0
)

return response
}
}
74 changes: 21 additions & 53 deletions Sources/SessionPlus/Implementation/BaseURLSessionClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@ import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif
#if canImport(Combine)
import Combine
#endif

/// A `Client` implementation that operates with a _base_ URL which all requests use to form the address.
open class BaseURLSessionClient: Client {
Expand All @@ -17,60 +14,31 @@ open class BaseURLSessionClient: Client {
self.session = URLSession(configuration: sessionConfiguration, delegate: sessionDelegate, delegateQueue: nil)
}

#if (os(macOS) || os(iOS) || os(tvOS) || os(watchOS))
/// Implementation that uses the `URLSession` async/await concurrency apis for handling a `Request`/`Response` interaction.
///
/// The `URLSession` api is only available on Apple platforms, as the `FoundationNetworking` version has not been updated.
public func performRequest(_ request: Request) async throws -> Response {
public func performRequest(_ request: any Request) async throws -> any Response {
let urlRequest = try URLRequest(request: request, baseUrl: baseURL)
let sessionResponse = try await session.data(for: urlRequest)
return AnyResponse(statusCode: sessionResponse.1.statusCode, headers: sessionResponse.1.headers, data: sessionResponse.0)
}
#endif

#if canImport(Combine)
/// Implementation that uses the `URLSession.DataTaskPublisher` to handle the `Request`/`Response` interaction.
public func performRequest(_ request: Request) -> AnyPublisher<Response, Error> {
let urlRequest: URLRequest
do {
urlRequest = try URLRequest(request: request, baseUrl: baseURL)
} catch {
return Fail(outputType: Response.self, failure: error).eraseToAnyPublisher()
}

return session
.dataTaskPublisher(for: urlRequest)
.tryMap { taskResponse -> Response in
AnyResponse(statusCode: taskResponse.response.statusCode, headers: taskResponse.response.headers, data: taskResponse.data)
#if canImport(FoundationNetworking)
let sessionResponse = try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<(Data, URLResponse), Error>) in
session.dataTask(with: urlRequest) { data, urlResponse, error in
if let error {
continuation.resume(throwing: error)
return
}

continuation.resume(returning: (data!, urlResponse!))
}
.eraseToAnyPublisher()
}
#endif

/// Implementation that uses the default `URLSessionDataTask` methods for handling a `Request`/`Response` interaction.
public func performRequest(_ request: Request, completion: @escaping (Result<Response, Error>) -> Void) {
let urlRequest: URLRequest
do {
urlRequest = try URLRequest(request: request, baseUrl: baseURL)
} catch {
completion(.failure(error))
return
.resume()
}
#else
let sessionResponse = try await session.data(for: urlRequest)
#endif

session.dataTask(with: urlRequest) { data, urlResponse, error in
guard error == nil else {
completion(.failure(error!))
return
}

guard let httpResponse = urlResponse else {
completion(.failure(URLError(.cannotParseResponse)))
return
}

let response = AnyResponse(statusCode: httpResponse.statusCode, headers: httpResponse.headers, data: data ?? Data())
completion(.success(response))
}
.resume()
let response = AnyResponse(
statusCode: sessionResponse.1.statusCode,
headers: sessionResponse.1.headers,
data: sessionResponse.0
)

return response
}
}
56 changes: 0 additions & 56 deletions Sources/SessionPlus/Interface/Client+Decoding.swift

This file was deleted.

63 changes: 10 additions & 53 deletions Sources/SessionPlus/Interface/Client.swift
Original file line number Diff line number Diff line change
@@ -1,66 +1,23 @@
import Foundation
#if canImport(Combine)
import Combine
#endif

public protocol Client {
#if swift(>=5.5.2)
/// Perform a network `Request`.
///
/// - parameters:
/// - request: The details of the request to perform.
/// - returns: The `Response` to the `Request`.
func performRequest(_ request: Request) async throws -> Response
#endif

#if canImport(Combine)
/// Perform a network `Request`.
///
/// - parameters:
/// - request: The details of the request to perform.
/// - returns: Publisher that emits the `Response` to the `Request`.
func performRequest(_ request: Request) -> AnyPublisher<Response, Error>
#endif

/// Perform a network `Request`.
///
/// - parameters:
/// - request: The details of the request to perform.
/// - completion: Function called with the result of the request.
func performRequest(_ request: Request, completion: @escaping (Result<Response, Error>) -> Void)
func performRequest(_ request: any Request) async throws -> any Response
}

public extension Client {
#if swift(>=5.5.2)
/// Default implementation that uses the `withCheckedThrowingContinuation` api to call the `performRequest(_:)` method.
func performRequest(_ request: Request) async throws -> Response {
try await withCheckedThrowingContinuation({ continuation in
performRequest(request) { result in
switch result {
case .failure(let error):
continuation.resume(throwing: error)
case .success(let response):
continuation.resume(returning: response)
}
}
})
}
#endif

#if canImport(Combine)
/// Default implementation that wraps `performRequest(_:)` in a `Future`.
func performRequest(_ request: Request) -> AnyPublisher<Response, Error> {
Future { promise in
performRequest(request) { result in
switch result {
case .failure(let error):
promise(.failure(error))
case .success(let response):
promise(.success(response))
}
}
}
.eraseToAnyPublisher()
/// Performs a network `Request` and decodes the response to a known type.
///
/// - parameters:
/// - request: The details of the request to perform.
/// - decoder: The `JSONDecoder` that should be used to deserialize the result data.
/// - returns: The decoded `Response` value.
func performRequest<Value>(_ request: any Request, using decoder: JSONDecoder = JSONDecoder()) async throws -> Value where Value: Decodable {
let response = try await performRequest(request)
return try decoder.decode(Value.self, from: response.data)
}
#endif
}
12 changes: 8 additions & 4 deletions Sources/SessionPlusEmulation/EmulatedClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,17 @@ open class EmulatedClient: Client {
responseCache[emulatedRequest.id] = .failure(error)
}

public func performRequest(_ request: Request, completion: @escaping (Result<Response, Error>) -> Void) {
public func performRequest(_ request: any Request) async throws -> any Response {
let id = EmulatedRequest(request).id
guard let result = responseCache[id] else {
completion(.failure(NotFound()))
return
throw NotFound()
}

completion(result)
switch result {
case .success(let response):
return response
case .failure(let error):
throw error
}
}
}
Loading