From 12412289ce0dcc0423b8e94c6b4f6ef8bc8cc942 Mon Sep 17 00:00:00 2001 From: dankinsoid <30962149+dankinsoid@users.noreply.github.com> Date: Thu, 11 Apr 2024 17:21:12 +0300 Subject: [PATCH] 1.7.1 --- README.md | 2 +- .../Modifiers/RateLimitModifier.swift | 68 +++++++++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 Sources/SwiftAPIClient/Modifiers/RateLimitModifier.swift diff --git a/README.md b/README.md index bd57433..e92a125 100644 --- a/README.md +++ b/README.md @@ -326,7 +326,7 @@ import PackageDescription let package = Package( name: "SomeProject", dependencies: [ - .package(url: "https://github.com/dankinsoid/swift-api-client.git", from: "1.7.0") + .package(url: "https://github.com/dankinsoid/swift-api-client.git", from: "1.7.1") ], targets: [ .target( diff --git a/Sources/SwiftAPIClient/Modifiers/RateLimitModifier.swift b/Sources/SwiftAPIClient/Modifiers/RateLimitModifier.swift new file mode 100644 index 0000000..52e51d6 --- /dev/null +++ b/Sources/SwiftAPIClient/Modifiers/RateLimitModifier.swift @@ -0,0 +1,68 @@ +import Foundation +import HTTPTypes + +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: @escaping (HTTPRequestComponents) -> ID, + interval: TimeInterval = 30, + statusCodes: Set = [.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 = [.tooManyRequests], + maxRepeatCount: Int = 3 + ) -> Self { + waitIfRateLimitExceeded( + id: { $0.url?.baseURL?.absoluteString ?? UUID().uuidString }, + interval: interval, + statusCodes: statusCodes, + maxRepeatCount: maxRepeatCount + ) + } +} + +private struct RateLimitMiddleware: HTTPClientMiddleware { + + let id: (HTTPRequestComponents) -> ID + let interval: TimeInterval + let statusCodes: Set + let maxCount: Int + + func execute( + 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 + } +}