Skip to content

Commit

Permalink
1.22.0
Browse files Browse the repository at this point in the history
  • Loading branch information
dankinsoid committed Jun 16, 2024
1 parent a83437f commit 5d8b9bb
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 37 deletions.
20 changes: 19 additions & 1 deletion Sources/SwiftAPIClient/APIClientCaller.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,24 @@ public struct APIClientCaller<Response, Value, Result> {
try mapper(_mockResult($0))
}
}

/// Maps the response to another type using the provided mapper.
/// - Parameter mapper: A closure that maps the result to a different type.
/// - Returns: A `APIClientCaller` with the mapped result type.
///
/// Example
/// ```swift
/// try await client.call(.http, as: .decodable)
/// ```
public func mapResponse<T>(_ mapper: @escaping (Response) throws -> T) -> APIClientCaller<T, Value, Result> {
APIClientCaller<T, Value, Result> { id, request, configs, serialize in
try _call(id, request, configs) { response, validate in
try serialize(mapper(response), validate)
}
} mockResult: {
try _mockResult($0)
}
}
}

public extension APIClientCaller where Result == Value {
Expand Down Expand Up @@ -207,7 +225,7 @@ public extension APIClient {
updateTotalResponseMetrics(for: request, successful: false)
}

var context = APIErrorContext(
let context = APIErrorContext(
request: request,
response: response as? Data,
fileIDLine: fileIDLine
Expand Down
68 changes: 47 additions & 21 deletions Sources/SwiftAPIClient/Clients/HTTPClient.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Foundation
import HTTPTypes
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif
Expand Down Expand Up @@ -60,39 +61,60 @@ public extension APIClient.Configs {
public extension APIClientCaller where Result == AsyncThrowingValue<Value>, Response == Data {

static var http: APIClientCaller {
.http { request, configs in
let isUpload = request.body != nil
if isUpload, request.method == .get {
configs.logger.warning("It is not allowed to add a body in GET request.")
}
return try await configs.httpClient.data(request, configs)
}
APIClientCaller<Data, Value, AsyncThrowingValue<(Value, HTTPResponse)>>
.httpResponse
.dropHTTPResponse
}
}

extension APIClientCaller where Result == AsyncThrowingValue<Value>, Response == Data {
public extension APIClientCaller where Result == AsyncThrowingValue<(Value, HTTPResponse)>, Response == Data {

static var httpResponse: APIClientCaller {
HTTPClientCaller<Data, Value>.http { request, configs in
let isUpload = request.body != nil
if isUpload, request.method == .get {
configs.logger.warning("It is not allowed to add a body in GET request.")
}
return try await configs.httpClient.data(request, configs)
}
.mapResponse(\.0)
}
}

extension APIClientCaller where Result == AsyncThrowingValue<(Value, HTTPResponse)>, Response == (Data, HTTPResponse) {

static func http(
task: @escaping @Sendable (HTTPRequestComponents, APIClient.Configs) async throws -> (Data, HTTPResponse)
) -> APIClientCaller {
.http(task: task) {
try $2.httpResponseValidator.validate($1, $0, $2)
.http(task: task) {
try $2.httpResponseValidator.validate($1, $0, $2)
} data: {
$0
$0
}
}
}

extension APIClientCaller where Result == AsyncThrowingValue<Value> {
typealias HTTPClientCaller<R, T> = APIClientCaller<(R, HTTPResponse), T, AsyncThrowingValue<(T, HTTPResponse)>>

static func http(
task: @escaping @Sendable (HTTPRequestComponents, APIClient.Configs) async throws -> (Response, HTTPResponse),
validate: @escaping (Response, HTTPResponse, APIClient.Configs) throws -> Void,
data: @escaping (Response) -> Data?
) -> APIClientCaller {
extension APIClientCaller where Result == AsyncThrowingValue<(Value, HTTPResponse)> {

var dropHTTPResponse: APIClientCaller<Response, Value, AsyncThrowingValue<Value>> {
map { asyncCl in
{ try await asyncCl().0 }
}
}
}

extension APIClientCaller where Result == AsyncThrowingValue<(Value, HTTPResponse)> {

static func http<T>(
task: @escaping @Sendable (HTTPRequestComponents, APIClient.Configs) async throws -> (T, HTTPResponse),
validate: @escaping (T, HTTPResponse, APIClient.Configs) throws -> Void,
data: @escaping (T) -> Data?
) -> APIClientCaller where Response == (T, HTTPResponse) {
APIClientCaller { uuid, request, configs, serialize in
{
let value: Response
let value: T
let response: HTTPResponse
let start = Date()
do {
Expand All @@ -116,7 +138,7 @@ extension APIClientCaller where Result == AsyncThrowingValue<Value> {
let duration = Date().timeIntervalSince(start)
let data = data(value)
do {
let result = try serialize(value) {
let result = try serialize((value, response)) {
try validate(value, response, configs)
}
let isError = response.status.kind.isError
Expand All @@ -137,7 +159,7 @@ extension APIClientCaller where Result == AsyncThrowingValue<Value> {
if configs.reportMetrics {
updateHTTPMetrics(for: request, status: response.status, duration: duration, successful: true)
}
return result
return (result, response)
} catch {
if !configs._errorLoggingComponents.isEmpty {
let message = configs._errorLoggingComponents.responseMessage(
Expand All @@ -157,7 +179,11 @@ extension APIClientCaller where Result == AsyncThrowingValue<Value> {
}
}
} mockResult: { value in
{ value }
asyncWithResponse(value)
}
}
}

private func asyncWithResponse<T>(_ value: T) -> AsyncThrowingValue<(T, HTTPResponse)> {
{ return (value, HTTPResponse(status: .ok)) }
}
27 changes: 19 additions & 8 deletions Sources/SwiftAPIClient/Clients/HTTPDownloadClient.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Foundation
import HTTPTypes
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif
Expand Down Expand Up @@ -68,12 +69,22 @@ public extension APIClient.Configs {

public extension APIClientCaller where Result == AsyncThrowingValue<Value>, Response == URL {

static var httpDownload: APIClientCaller {
.http { request, configs in
try await configs.httpDownloadClient.download(request, configs)
} validate: { _, _, _ in
} data: { _ in
nil
}
}
static var httpDownload: APIClientCaller {
APIClientCaller<URL, Value, AsyncThrowingValue<(Value, HTTPResponse)>>
.httpDownloadResponse
.dropHTTPResponse
}
}

public extension APIClientCaller where Result == AsyncThrowingValue<(Value, HTTPResponse)>, Response == URL {

static var httpDownloadResponse: APIClientCaller {
HTTPClientCaller<URL, Value>.http { request, configs in
try await configs.httpDownloadClient.download(request, configs)
} validate: { _, _, _ in
} data: { _ in
nil
}
.mapResponse(\.0)
}
}
25 changes: 19 additions & 6 deletions Sources/SwiftAPIClient/Clients/HTTPPublisher.swift
Original file line number Diff line number Diff line change
@@ -1,16 +1,29 @@
#if canImport(Combine)
import Combine
import Foundation
import HTTPTypes

public extension APIClientCaller where Result == AnyPublisher<Value, Error>, Response == Data {

static var httpPublisher: APIClientCaller {
APIClientCaller<Response, Value, AsyncThrowingValue<Value>>.http.map { value in
Publishers.Run {
try await value()
}
.eraseToAnyPublisher()
}
APIClientCaller<Response, Value, AnyPublisher<(Value, HTTPResponse), Error>>
.httpResponsePublisher
.map { publisher in
publisher.map(\.0)
.eraseToAnyPublisher()
}
}
}

public extension APIClientCaller where Result == AnyPublisher<(Value, HTTPResponse), Error>, Response == Data {

static var httpResponsePublisher: APIClientCaller {
APIClientCaller<Response, Value, AsyncThrowingValue<(Value, HTTPResponse)>>.httpResponse.map { value in
Publishers.Run {
try await value()
}
.eraseToAnyPublisher()
}
}
}
#endif
12 changes: 11 additions & 1 deletion Sources/SwiftAPIClient/Types/Serializer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,19 @@ public extension Serializer where Response == Data, T == String {
return string
}
}

/// A static property to get a `Serializer` that directly returns the response `Data`.
static func string(_ encoding: String.Encoding) -> Self {
Self { data, _ in
guard let string = String(data: data, encoding: encoding) else {
throw Errors.custom("Invalid \(encoding) data")
}
return string
}
}
}

public extension Serializer where Response == Data, T == Void {
public extension Serializer where T == Void {

/// A static property to get a `Serializer` that discards the response data.
static var void: Self {
Expand Down

0 comments on commit 5d8b9bb

Please sign in to comment.