diff --git a/Sources/Functions/FunctionsClient.swift b/Sources/Functions/FunctionsClient.swift index f4b95638e..2ee8451bf 100644 --- a/Sources/Functions/FunctionsClient.swift +++ b/Sources/Functions/FunctionsClient.swift @@ -54,7 +54,7 @@ public final class FunctionsClient: Sendable { if let logger { interceptors.append(LoggerInterceptor(logger: logger)) } - + interceptors.append(RetryRequestInterceptor.default) let http = HTTPClient(fetch: fetch, interceptors: interceptors) self.init(url: url, headers: headers, region: region, http: http) diff --git a/Sources/Helpers/HTTP/HTTPHeader.swift b/Sources/Helpers/HTTP/HTTPHeader.swift index b3ec53cea..350056692 100644 --- a/Sources/Helpers/HTTP/HTTPHeader.swift +++ b/Sources/Helpers/HTTP/HTTPHeader.swift @@ -23,7 +23,7 @@ package struct HTTPHeaders { } package mutating func update(_ field: HTTPHeader) { - if let index = fields.firstIndex(where: { $0.name.lowercased() == field.name.lowercased() }) { + if let index = fields.firstIndex(where: { $0.canonicalName == field.canonicalName }) { fields[index] = field } else { fields.append(field) @@ -35,12 +35,12 @@ package struct HTTPHeaders { } package mutating func remove(name: String) { - fields.removeAll { $0.name.lowercased() == name.lowercased() } + fields.removeAll { $0.canonicalName == name.lowercased() } } package func value(for name: String) -> String? { fields - .firstIndex(where: { $0.name.lowercased() == name.lowercased() }) + .firstIndex(where: { $0.canonicalName == name.lowercased() }) .map { fields[$0].value } } @@ -71,6 +71,11 @@ package struct HTTPHeaders { return Dictionary(namesAndValues, uniquingKeysWith: { _, last in last }) } + var canonicalDictionary: [String: String] { + let namesAndValues = fields.map { ($0.canonicalName, $0.value) } + return Dictionary(namesAndValues, uniquingKeysWith: { _, last in last }) + } + package mutating func merge(with other: HTTPHeaders) { for field in other.fields { update(field) @@ -137,9 +142,13 @@ package struct HTTPHeader: Sendable, Hashable { package let name: String package let value: String + package let canonicalName: String + package init(name: String, value: String) { self.name = name self.value = value + + canonicalName = name.lowercased() } } @@ -152,6 +161,6 @@ extension HTTPHeader: CustomStringConvertible { extension HTTPHeaders: Equatable { package static func == (lhs: Self, rhs: Self) -> Bool { - lhs.dictionary == rhs.dictionary + lhs.canonicalDictionary == rhs.canonicalDictionary } } diff --git a/Sources/Helpers/HTTP/RetryRequestInterceptor.swift b/Sources/Helpers/HTTP/RetryRequestInterceptor.swift index aaab915b6..58bf224e0 100644 --- a/Sources/Helpers/HTTP/RetryRequestInterceptor.swift +++ b/Sources/Helpers/HTTP/RetryRequestInterceptor.swift @@ -17,6 +17,9 @@ import Foundation /// of failure, with exponential backoff between retries. You can configure the retry behavior by specifying /// the retry limit, exponential backoff base, scale, retryable HTTP methods, HTTP status codes, and URL error codes. package actor RetryRequestInterceptor: HTTPClientInterceptor { + /// A ``RetryRequestInterceptor`` instance with default values. + package static let `default` = RetryRequestInterceptor() + /// The default retry limit for the interceptor. package static let defaultRetryLimit = 2 /// The default base value for exponential backoff. diff --git a/Sources/PostgREST/PostgrestBuilder.swift b/Sources/PostgREST/PostgrestBuilder.swift index 2b38ae324..9fd7e0f85 100644 --- a/Sources/PostgREST/PostgrestBuilder.swift +++ b/Sources/PostgREST/PostgrestBuilder.swift @@ -31,6 +31,7 @@ public class PostgrestBuilder: @unchecked Sendable { if let logger = configuration.logger { interceptors.append(LoggerInterceptor(logger: logger)) } + interceptors.append(RetryRequestInterceptor.default) http = HTTPClient(fetch: configuration.fetch, interceptors: interceptors) diff --git a/Sources/Storage/StorageApi.swift b/Sources/Storage/StorageApi.swift index a635b1a4f..c2f7d32cb 100644 --- a/Sources/Storage/StorageApi.swift +++ b/Sources/Storage/StorageApi.swift @@ -21,6 +21,7 @@ public class StorageApi: @unchecked Sendable { if let logger = configuration.logger { interceptors.append(LoggerInterceptor(logger: logger)) } + interceptors.append(RetryRequestInterceptor.default) http = HTTPClient( fetch: configuration.session.fetch,