Skip to content

Commit

Permalink
1.7.14
Browse files Browse the repository at this point in the history
  • Loading branch information
dankinsoid committed Apr 13, 2024
1 parent 24206a7 commit cfd9dcc
Show file tree
Hide file tree
Showing 18 changed files with 768 additions and 737 deletions.
36 changes: 18 additions & 18 deletions Sources/SwiftAPIClient/APIClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,24 +46,24 @@ public struct APIClient: @unchecked Sendable, RequestBuilder {
}
}

/// Initializes a new network client with an empty request components.
/// - Warning: You must specify the request components before making any requests.
public init() {
self.init { _ in
HTTPRequestComponents()
}
}
/// Initializes a new network client with a URL string.
/// - Parameter string: The URL string to be used for creating requests.
public init(string: String) {
self.init { _ in
guard let url = URL(string: string) else {
throw Errors.custom("Invalid URL string: \(string)")
}
return HTTPRequestComponents(url: url)
}
}
/// Initializes a new network client with an empty request components.
/// - Warning: You must specify the request components before making any requests.
public init() {
self.init { _ in
HTTPRequestComponents()
}
}

/// Initializes a new network client with a URL string.
/// - Parameter string: The URL string to be used for creating requests.
public init(string: String) {
self.init { _ in
guard let url = URL(string: string) else {
throw Errors.custom("Invalid URL string: \(string)")
}
return HTTPRequestComponents(url: url)
}
}

/// Configures the client with specific configuration values.
/// - Parameters:
Expand Down
44 changes: 22 additions & 22 deletions Sources/SwiftAPIClient/APIClientCaller.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public struct APIClientCaller<Response, Value, Result> {

private let _call: (
UUID,
HTTPRequestComponents,
HTTPRequestComponents,
APIClient.Configs,
@escaping (Response, () throws -> Void) throws -> Value
) throws -> Result
Expand Down Expand Up @@ -198,10 +198,10 @@ public extension APIClient {
return try serializer.serialize(response, configs)
} catch {
if let data = response as? Data, let failure = configs.errorDecoder.decodeError(data, configs) {
try configs.errorHandler(failure, configs)
try configs.errorHandler(failure, configs)
throw failure
}
try configs.errorHandler(error, configs)
try configs.errorHandler(error, configs)
throw error
}
}
Expand All @@ -217,7 +217,7 @@ public extension APIClient {
)
configs.logger.log(level: configs.logLevel, "\(message)")
}
try configs.errorHandler(error, configs)
try configs.errorHandler(error, configs)
}
throw error
}
Expand All @@ -226,26 +226,26 @@ public extension APIClient {

public extension APIClient.Configs {

var errorHandler: (Error, APIClient.Configs) throws -> Void {
get { self[\.errorHandler] ?? { _, _ in } }
set { self[\.errorHandler] = newValue }
}
var errorHandler: (Error, APIClient.Configs) throws -> Void {
get { self[\.errorHandler] ?? { _, _ in } }
set { self[\.errorHandler] = newValue }
}
}

public extension APIClient {

/// Sets the error handler.
func errorHandler(_ handler: @escaping (Error, APIClient.Configs) throws -> Void) -> APIClient {
configs { configs in
let current = configs.errorHandler
configs.errorHandler = { failure, configs in
do {
try current(failure, configs)
} catch {
try handler(error, configs)
throw error
}
}
}
}
/// Sets the error handler.
func errorHandler(_ handler: @escaping (Error, APIClient.Configs) throws -> Void) -> APIClient {
configs { configs in
let current = configs.errorHandler
configs.errorHandler = { failure, configs in
do {
try current(failure, configs)
} catch {
try handler(error, configs)
throw error
}
}
}
}
}
8 changes: 4 additions & 4 deletions Sources/SwiftAPIClient/APIClientConfigs.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ public extension APIClient {
return result
}

/// Creates a new `APIClient` instance with the current configurations and the provided request.
public func client(for request: HTTPRequestComponents) -> APIClient {
APIClient(request: request).configs(\.self, self)
}
/// Creates a new `APIClient` instance with the current configurations and the provided request.
public func client(for request: HTTPRequestComponents) -> APIClient {
APIClient(request: request).configs(\.self, self)
}
}
}

Expand Down
4 changes: 2 additions & 2 deletions Sources/SwiftAPIClient/Clients/HTTPClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public extension APIClientCaller where Result == AsyncThrowingValue<Value>, Resp

static var http: APIClientCaller {
.http { request, configs in
let isUpload = request.body != nil
let isUpload = request.body != nil
if isUpload, request.method == .get {
configs.logger.warning("It is not allowed to add a body in GET request.")
}
Expand Down Expand Up @@ -105,7 +105,7 @@ extension APIClientCaller where Result == AsyncThrowingValue<Value> {
error: error,
duration: duration
)
configs.logger.log(level: configs.logLevel, "\(message)")
configs.logger.log(level: configs.logLevel, "\(message)")
}
throw error
}
Expand Down
26 changes: 13 additions & 13 deletions Sources/SwiftAPIClient/Clients/URLSession+Client.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,27 @@ public extension HTTPClient {
/// - Returns: An `HTTPClient` that uses the given `URLSession` to fetch data.
static var urlSession: Self {
HTTPClient { request, configs in
guard
let url = request.url,
let httpRequest = request.request,
var urlRequest = URLRequest(httpRequest: httpRequest)
else {
guard
let url = request.url,
let httpRequest = request.request,
var urlRequest = URLRequest(httpRequest: httpRequest)
else {
throw Errors.custom("Invalid request")
}
urlRequest.url = url
urlRequest.url = url
#if os(Linux)
return try await asyncMethod { completion in
configs.urlSession.uploadTask(with: urlRequest, body: request.body, completionHandler: completion)
}
#else
if #available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *) {
let (data, response) = try await customErrors {
try await configs.urlSession.data(for: urlRequest, body: request.body)
try await configs.urlSession.data(for: urlRequest, body: request.body)
}
return (data, response.http)
} else {
return try await asyncMethod { completion in
configs.urlSession.uploadTask(with: urlRequest, body: request.body, completionHandler: completion)
configs.urlSession.uploadTask(with: urlRequest, body: request.body, completionHandler: completion)
}
}
#endif
Expand All @@ -42,10 +42,10 @@ public extension HTTPDownloadClient {

static var urlSession: Self {
HTTPDownloadClient { request, configs in
guard var urlRequest = request.urlRequest else {
guard var urlRequest = request.urlRequest else {
throw Errors.custom("Invalid request")
}
urlRequest.timeoutInterval = configs.timeoutInterval
urlRequest.timeoutInterval = configs.timeoutInterval
return try await asyncMethod { completion in
configs.urlSession.downloadTask(with: urlRequest, completionHandler: completion)
}
Expand All @@ -55,8 +55,8 @@ public extension HTTPDownloadClient {

private extension URLSession {

#if os(Linux)
#else
#if os(Linux)
#else
@available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)
func data(for request: URLRequest, body: RequestBody?) async throws -> (Data, URLResponse) {
switch body {
Expand All @@ -68,7 +68,7 @@ private extension URLSession {
return try await data(for: request)
}
}
#endif
#endif

func uploadTask(with request: URLRequest, body: RequestBody?, completionHandler: @escaping @Sendable (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {
switch body {
Expand Down
116 changes: 58 additions & 58 deletions Sources/SwiftAPIClient/Modifiers/RateLimitModifier.swift
Original file line number Diff line number Diff line change
@@ -1,68 +1,68 @@
import Foundation
import HTTPTypes

extension APIClient {
public extension APIClient {

/// When the rate limit is exceeded, the request will be repeated after the specified interval and all requests with the same identifier will be suspended.
/// - Parameters:
/// - id: The identifier to use for rate limiting. Default to the base URL of the request.
/// - interval: The interval to wait before repeating the request. Default to 30 seconds.
/// - statusCodes: The set of status codes that indicate a rate limit exceeded. Default to `[429]`.
/// - maxRepeatCount: The maximum number of times the request can be repeated. Default to 3.
public func waitIfRateLimitExceeded<ID: Hashable>(
id: @escaping (HTTPRequestComponents) -> ID,
interval: TimeInterval = 30,
statusCodes: Set<HTTPResponse.Status> = [.tooManyRequests],
maxRepeatCount: Int = 3
) -> Self {
httpClientMiddleware(RateLimitMiddleware(id: id, interval: interval, statusCodes: statusCodes, maxCount: maxRepeatCount))
}
/// When the rate limit is exceeded, the request will be repeated after the specified interval and all requests with the same identifier will be suspended.
/// - Parameters:
/// - id: The identifier to use for rate limiting. Default to the base URL of the request.
/// - interval: The interval to wait before repeating the request. Default to 30 seconds.
/// - statusCodes: The set of status codes that indicate a rate limit exceeded. Default to `[429]`.
/// - maxRepeatCount: The maximum number of times the request can be repeated. Default to 3.
func waitIfRateLimitExceeded<ID: Hashable>(
id: @escaping (HTTPRequestComponents) -> ID,
interval: TimeInterval = 30,
statusCodes: Set<HTTPResponse.Status> = [.tooManyRequests],
maxRepeatCount: Int = 3
) -> Self {
httpClientMiddleware(RateLimitMiddleware(id: id, interval: interval, statusCodes: statusCodes, maxCount: maxRepeatCount))
}

/// When the rate limit is exceeded, the request will be repeated after the specified interval and all requests with the same base URL will be suspended.
/// - Parameters:
/// - interval: The interval to wait before repeating the request. Default to 30 seconds.
/// - statusCodes: The set of status codes that indicate a rate limit exceeded. Default to `[429]`.
/// - maxRepeatCount: The maximum number of times the request can be repeated. Default to 3.
public func waitIfRateLimitExceeded(
interval: TimeInterval = 30,
statusCodes: Set<HTTPResponse.Status> = [.tooManyRequests],
maxRepeatCount: Int = 3
) -> Self {
waitIfRateLimitExceeded(
id: { $0.url?.host ?? UUID().uuidString },
interval: interval,
statusCodes: statusCodes,
maxRepeatCount: maxRepeatCount
)
}
/// When the rate limit is exceeded, the request will be repeated after the specified interval and all requests with the same base URL will be suspended.
/// - Parameters:
/// - interval: The interval to wait before repeating the request. Default to 30 seconds.
/// - statusCodes: The set of status codes that indicate a rate limit exceeded. Default to `[429]`.
/// - maxRepeatCount: The maximum number of times the request can be repeated. Default to 3.
func waitIfRateLimitExceeded(
interval: TimeInterval = 30,
statusCodes: Set<HTTPResponse.Status> = [.tooManyRequests],
maxRepeatCount: Int = 3
) -> Self {
waitIfRateLimitExceeded(
id: { $0.url?.host ?? UUID().uuidString },
interval: interval,
statusCodes: statusCodes,
maxRepeatCount: maxRepeatCount
)
}
}

private struct RateLimitMiddleware<ID: Hashable>: HTTPClientMiddleware {

let id: (HTTPRequestComponents) -> ID
let interval: TimeInterval
let statusCodes: Set<HTTPResponse.Status>
let maxCount: Int

func execute<T>(
request: HTTPRequestComponents,
configs: APIClient.Configs,
next: @escaping (HTTPRequestComponents, APIClient.Configs) async throws -> (T, HTTPResponse)
) async throws -> (T, HTTPResponse) {
let id = id(request)
await waitForSynchronizedAccess(id: id, of: Void.self)
var res = try await next(request, configs)
var count: UInt = 0
while
statusCodes.contains(res.1.status),
count < maxCount
{
count += 1
try await withThrowingSynchronizedAccess(id: id) {
try await Task.sleep(nanoseconds: UInt64(interval * 1_000_000_000))
}
res = try await next(request, configs)
}
return res
}
let id: (HTTPRequestComponents) -> ID
let interval: TimeInterval
let statusCodes: Set<HTTPResponse.Status>
let maxCount: Int

func execute<T>(
request: HTTPRequestComponents,
configs: APIClient.Configs,
next: @escaping (HTTPRequestComponents, APIClient.Configs) async throws -> (T, HTTPResponse)
) async throws -> (T, HTTPResponse) {
let id = id(request)
await waitForSynchronizedAccess(id: id, of: Void.self)
var res = try await next(request, configs)
var count: UInt = 0
while
statusCodes.contains(res.1.status),
count < maxCount
{
count += 1
try await withThrowingSynchronizedAccess(id: id) {
try await Task.sleep(nanoseconds: UInt64(interval * 1_000_000_000))
}
res = try await next(request, configs)
}
return res
}
}
2 changes: 1 addition & 1 deletion Sources/SwiftAPIClient/Modifiers/RequestCompression.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ private struct CompressionMiddleware: HTTPClientMiddleware {

var urlRequest = request
urlRequest.headers[.contentEncoding] = "deflate"
urlRequest.body = try .data(deflate(data))
urlRequest.body = try .data(deflate(data))
return try await next(urlRequest, configs)
}
}
Expand Down
Loading

0 comments on commit cfd9dcc

Please sign in to comment.