From c806215b51e60ecb6f0cf1ed16800df7f6924ccc Mon Sep 17 00:00:00 2001 From: zunda Date: Mon, 14 Oct 2024 16:52:04 +0900 Subject: [PATCH 1/5] add HTTPTypes --- Package.resolved | 9 +++++++++ Package.swift | 2 ++ 2 files changed, 11 insertions(+) diff --git a/Package.resolved b/Package.resolved index 28d190fa..30d9ac39 100644 --- a/Package.resolved +++ b/Package.resolved @@ -27,6 +27,15 @@ "version" : "1.3.3" } }, + { + "identity" : "swift-http-types", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-http-types", + "state" : { + "revision" : "ae67c8178eb46944fd85e4dc6dd970e1f3ed6ccd", + "version" : "1.3.0" + } + }, { "identity" : "swift-snapshot-testing", "kind" : "remoteSourceControl", diff --git a/Package.swift b/Package.swift index b47e552c..b3db6a73 100644 --- a/Package.swift +++ b/Package.swift @@ -22,6 +22,7 @@ let package = Package( .library(name: "Supabase", targets: ["Supabase", "Functions", "PostgREST", "Auth", "Realtime", "Storage"]), ], dependencies: [ + .package(url: "https://github.com/apple/swift-http-types.git", from: "1.3.0"), .package(url: "https://github.com/apple/swift-crypto.git", "1.0.0" ..< "4.0.0"), .package(url: "https://github.com/pointfreeco/swift-concurrency-extras", from: "1.1.0"), .package(url: "https://github.com/pointfreeco/swift-custom-dump", from: "1.3.2"), @@ -33,6 +34,7 @@ let package = Package( name: "Helpers", dependencies: [ .product(name: "ConcurrencyExtras", package: "swift-concurrency-extras"), + .product(name: "HTTPTypes", package: "swift-http-types"), ] ), .testTarget( From 899d1dbeb9b9f3a9bef3560b54a8ad1d31007700 Mon Sep 17 00:00:00 2001 From: zunda Date: Mon, 14 Oct 2024 18:51:18 +0900 Subject: [PATCH 2/5] replace to HTTPFields from HTTPHeader --- Sources/Auth/AuthAdmin.swift | 10 +- Sources/Auth/AuthClient.swift | 6 +- Sources/Auth/Internal/APIClient.swift | 19 ++- Sources/Auth/Internal/Contants.swift | 6 + Sources/Functions/FunctionsClient.swift | 25 +-- Sources/Functions/Types.swift | 15 +- Sources/Helpers/HTTP/HTTPFields.swift | 37 +++++ Sources/Helpers/HTTP/HTTPHeader.swift | 157 ------------------ Sources/Helpers/HTTP/HTTPRequest.swift | 9 +- Sources/Helpers/HTTP/HTTPResponse.swift | 5 +- Sources/PostgREST/PostgrestBuilder.swift | 30 ++-- Sources/PostgREST/PostgrestClient.swift | 5 +- Sources/PostgREST/PostgrestQueryBuilder.swift | 18 +- Sources/PostgREST/PostgrestRpcBuilder.swift | 11 +- .../PostgREST/PostgrestTransformBuilder.swift | 18 +- Sources/Realtime/RealtimeChannel.swift | 3 +- Sources/Realtime/V2/RealtimeChannelV2.swift | 9 +- Sources/Realtime/V2/Types.swift | 13 +- Sources/Storage/MultipartFormData.swift | 13 +- Sources/Storage/StorageApi.swift | 17 +- Sources/Storage/StorageFileApi.swift | 22 ++- Sources/Supabase/SupabaseClient.swift | 7 +- 22 files changed, 189 insertions(+), 266 deletions(-) create mode 100644 Sources/Helpers/HTTP/HTTPFields.swift delete mode 100644 Sources/Helpers/HTTP/HTTPHeader.swift diff --git a/Sources/Auth/AuthAdmin.swift b/Sources/Auth/AuthAdmin.swift index e3dd0da4..1a31aee8 100644 --- a/Sources/Auth/AuthAdmin.swift +++ b/Sources/Auth/AuthAdmin.swift @@ -7,6 +7,7 @@ import Foundation import Helpers +import HTTPTypes public struct AuthAdmin: Sendable { let clientID: AuthClientID @@ -62,10 +63,10 @@ public struct AuthAdmin: Sendable { users: response.users, aud: response.aud, lastPage: 0, - total: httpResponse.headers["x-total-count"].flatMap(Int.init) ?? 0 + total: httpResponse.headers[.xTotalCount].flatMap(Int.init) ?? 0 ) - let links = httpResponse.headers["link"]?.components(separatedBy: ",") ?? [] + let links = httpResponse.headers[.link]?.components(separatedBy: ",") ?? [] if !links.isEmpty { for link in links { let page = link.components(separatedBy: ";")[0].components(separatedBy: "=")[1].prefix(while: \.isNumber) @@ -82,3 +83,8 @@ public struct AuthAdmin: Sendable { return pagination } } + +extension HTTPField.Name { + static let xTotalCount = Self("x-total-count")! + static let link = Self("link")! +} diff --git a/Sources/Auth/AuthClient.swift b/Sources/Auth/AuthClient.swift index 71299056..138d898a 100644 --- a/Sources/Auth/AuthClient.swift +++ b/Sources/Auth/AuthClient.swift @@ -716,7 +716,7 @@ public final class AuthClient: Sendable { .init( url: configuration.url.appendingPathComponent("user"), method: .get, - headers: ["Authorization": "\(tokenType) \(accessToken)"] + headers: [.authorization: "\(tokenType) \(accessToken)"] ) ).decoded(as: User.self, decoder: configuration.decoder) @@ -803,7 +803,7 @@ public final class AuthClient: Sendable { url: configuration.url.appendingPathComponent("logout"), method: .post, query: [URLQueryItem(name: "scope", value: scope.rawValue)], - headers: [.init(name: "Authorization", value: "Bearer \(accessToken)")] + headers: [.authorization: "Bearer \(accessToken)"] ) ) } catch let AuthError.api(_, _, _, response) where [404, 403, 401].contains(response.statusCode) { @@ -982,7 +982,7 @@ public final class AuthClient: Sendable { var request = HTTPRequest(url: configuration.url.appendingPathComponent("user"), method: .get) if let jwt { - request.headers["Authorization"] = "Bearer \(jwt)" + request.headers[.authorization] = "Bearer \(jwt)" return try await api.execute(request).decoded(decoder: configuration.decoder) } diff --git a/Sources/Auth/Internal/APIClient.swift b/Sources/Auth/Internal/APIClient.swift index bab98fe0..6bfbeec4 100644 --- a/Sources/Auth/Internal/APIClient.swift +++ b/Sources/Auth/Internal/APIClient.swift @@ -1,5 +1,6 @@ import Foundation import Helpers +import HTTPTypes extension HTTPClient { init(configuration: AuthClient.Configuration) { @@ -31,12 +32,12 @@ struct APIClient: Sendable { Dependencies[clientID].http } - func execute(_ request: HTTPRequest) async throws -> HTTPResponse { + func execute(_ request: Helpers.HTTPRequest) async throws -> Helpers.HTTPResponse { var request = request - request.headers = HTTPHeaders(configuration.headers).merged(with: request.headers) + request.headers = HTTPFields(configuration.headers).merging(with: request.headers) - if request.headers[API_VERSION_HEADER_NAME] == nil { - request.headers[API_VERSION_HEADER_NAME] = API_VERSIONS[._20240101]!.name.rawValue + if request.headers[.apiVersionHeaderName] == nil { + request.headers[.apiVersionHeaderName] = API_VERSIONS[._20240101]!.name.rawValue } let response = try await http.send(request) @@ -49,7 +50,7 @@ struct APIClient: Sendable { } @discardableResult - func authorizedExecute(_ request: HTTPRequest) async throws -> HTTPResponse { + func authorizedExecute(_ request: Helpers.HTTPRequest) async throws -> Helpers.HTTPResponse { var sessionManager: SessionManager { Dependencies[clientID].sessionManager } @@ -57,12 +58,12 @@ struct APIClient: Sendable { let session = try await sessionManager.session() var request = request - request.headers["Authorization"] = "Bearer \(session.accessToken)" + request.headers[.authorization] = "Bearer \(session.accessToken)" return try await execute(request) } - func handleError(response: HTTPResponse) -> AuthError { + func handleError(response: Helpers.HTTPResponse) -> AuthError { guard let error = try? response.decoded( as: _RawAPIErrorResponse.self, decoder: configuration.decoder @@ -105,8 +106,8 @@ struct APIClient: Sendable { } } - private func parseResponseAPIVersion(_ response: HTTPResponse) -> Date? { - guard let apiVersion = response.headers[API_VERSION_HEADER_NAME] else { return nil } + private func parseResponseAPIVersion(_ response: Helpers.HTTPResponse) -> Date? { + guard let apiVersion = response.headers[.apiVersionHeaderName] else { return nil } let formatter = ISO8601DateFormatter() formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] diff --git a/Sources/Auth/Internal/Contants.swift b/Sources/Auth/Internal/Contants.swift index de388197..c0c6a805 100644 --- a/Sources/Auth/Internal/Contants.swift +++ b/Sources/Auth/Internal/Contants.swift @@ -6,11 +6,17 @@ // import Foundation +import HTTPTypes let EXPIRY_MARGIN: TimeInterval = 30 let STORAGE_KEY = "supabase.auth.token" let API_VERSION_HEADER_NAME = "X-Supabase-Api-Version" + +extension HTTPField.Name { + static let apiVersionHeaderName = HTTPField.Name(API_VERSION_HEADER_NAME)! +} + let API_VERSIONS: [APIVersion.Name: APIVersion] = [ ._20240101: ._20240101, ] diff --git a/Sources/Functions/FunctionsClient.swift b/Sources/Functions/FunctionsClient.swift index f4b95638..169d24ca 100644 --- a/Sources/Functions/FunctionsClient.swift +++ b/Sources/Functions/FunctionsClient.swift @@ -1,6 +1,7 @@ import ConcurrencyExtras import Foundation import Helpers +import HTTPTypes #if canImport(FoundationNetworking) import FoundationNetworking @@ -25,12 +26,12 @@ public final class FunctionsClient: Sendable { struct MutableState { /// Headers to be included in the requests. - var headers = HTTPHeaders() + var headers = HTTPFields() } private let mutableState = LockIsolated(MutableState()) - var headers: HTTPHeaders { + var headers: HTTPFields { mutableState.headers } @@ -71,9 +72,9 @@ public final class FunctionsClient: Sendable { self.http = http mutableState.withValue { - $0.headers = HTTPHeaders(headers) - if $0.headers["X-Client-Info"] == nil { - $0.headers["X-Client-Info"] = "functions-swift/\(version)" + $0.headers = HTTPFields(headers) + if $0.headers[.xClientInfo] == nil { + $0.headers[.xClientInfo] = "functions-swift/\(version)" } } } @@ -102,9 +103,9 @@ public final class FunctionsClient: Sendable { public func setAuth(token: String?) { mutableState.withValue { if let token { - $0.headers["Authorization"] = "Bearer \(token)" + $0.headers[.authorization] = "Bearer \(token)" } else { - $0.headers["Authorization"] = nil + $0.headers[.authorization] = nil } } } @@ -160,7 +161,7 @@ public final class FunctionsClient: Sendable { private func rawInvoke( functionName: String, invokeOptions: FunctionInvokeOptions - ) async throws -> HTTPResponse { + ) async throws -> Helpers.HTTPResponse { let request = buildRequest(functionName: functionName, options: invokeOptions) let response = try await http.send(request) @@ -168,7 +169,7 @@ public final class FunctionsClient: Sendable { throw FunctionsError.httpError(code: response.statusCode, data: response.data) } - let isRelayError = response.headers["x-relay-error"] == "true" + let isRelayError = response.headers[.xRelayError] == "true" if isRelayError { throw FunctionsError.relayError } @@ -211,17 +212,17 @@ public final class FunctionsClient: Sendable { return stream } - private func buildRequest(functionName: String, options: FunctionInvokeOptions) -> HTTPRequest { + private func buildRequest(functionName: String, options: FunctionInvokeOptions) -> Helpers.HTTPRequest { var request = HTTPRequest( url: url.appendingPathComponent(functionName), method: options.httpMethod ?? .post, query: options.query, - headers: mutableState.headers.merged(with: options.headers), + headers: mutableState.headers.merging(with: options.headers), body: options.body ) if let region = options.region ?? region { - request.headers["x-region"] = region + request.headers[.xRegion] = region } return request diff --git a/Sources/Functions/Types.swift b/Sources/Functions/Types.swift index 9f515fe5..d7e76114 100644 --- a/Sources/Functions/Types.swift +++ b/Sources/Functions/Types.swift @@ -1,5 +1,6 @@ import Foundation import Helpers +import HTTPTypes /// An error type representing various errors that can occur while invoking functions. public enum FunctionsError: Error, LocalizedError { @@ -24,7 +25,7 @@ public struct FunctionInvokeOptions: Sendable { /// Method to use in the function invocation. let method: Method? /// Headers to be included in the function invocation. - let headers: HTTPHeaders + let headers: HTTPFields /// Body data to be sent with the function invocation. let body: Data? /// The Region to invoke the function in. @@ -48,23 +49,23 @@ public struct FunctionInvokeOptions: Sendable { region: String? = nil, body: some Encodable ) { - var defaultHeaders = HTTPHeaders() + var defaultHeaders = HTTPFields() switch body { case let string as String: - defaultHeaders["Content-Type"] = "text/plain" + defaultHeaders[.contentType] = "text/plain" self.body = string.data(using: .utf8) case let data as Data: - defaultHeaders["Content-Type"] = "application/octet-stream" + defaultHeaders[.contentType] = "application/octet-stream" self.body = data default: // default, assume this is JSON - defaultHeaders["Content-Type"] = "application/json" + defaultHeaders[.contentType] = "application/json" self.body = try? JSONEncoder().encode(body) } self.method = method - self.headers = defaultHeaders.merged(with: HTTPHeaders(headers)) + self.headers = defaultHeaders.merging(with: HTTPFields(headers)) self.region = region self.query = query } @@ -84,7 +85,7 @@ public struct FunctionInvokeOptions: Sendable { region: String? = nil ) { self.method = method - self.headers = HTTPHeaders(headers) + self.headers = HTTPFields(headers) self.region = region self.query = query body = nil diff --git a/Sources/Helpers/HTTP/HTTPFields.swift b/Sources/Helpers/HTTP/HTTPFields.swift new file mode 100644 index 00000000..a193533f --- /dev/null +++ b/Sources/Helpers/HTTP/HTTPFields.swift @@ -0,0 +1,37 @@ +import HTTPTypes + +package extension HTTPFields { + init(_ dictionary: [String: String]) { + self.init(dictionary.map { .init(name: .init($0.key)!, value: $0.value) }) + } + + var dictionary: [String: String] { + let keyValues = self.map { + ($0.name.rawName, $0.value) + } + + return .init(keyValues, uniquingKeysWith: { $1 }) + } + + mutating func merge(with other: Self) { + for field in other { + self[field.name] = field.value + } + } + + func merging(with other: Self) -> Self { + var copy = self + + for field in other { + copy[field.name] = field.value + } + + return copy + } +} + +package extension HTTPField.Name { + static let xClientInfo = HTTPField.Name("X-Client-Info")! + static let xRegion = HTTPField.Name("x-region")! + static let xRelayError = HTTPField.Name("x-relay-error")! +} diff --git a/Sources/Helpers/HTTP/HTTPHeader.swift b/Sources/Helpers/HTTP/HTTPHeader.swift deleted file mode 100644 index b3ec53ce..00000000 --- a/Sources/Helpers/HTTP/HTTPHeader.swift +++ /dev/null @@ -1,157 +0,0 @@ -// -// HTTPHeader.swift -// -// -// Created by Guilherme Souza on 23/04/24. -// - -import Foundation - -package struct HTTPHeaders { - var fields: [HTTPHeader] = [] - - package init() {} - - package init(_ fields: [HTTPHeader]) { - self.fields = fields - } - - package init(_ dicionary: [String: String]) { - dicionary.forEach { - update(name: $0, value: $1) - } - } - - package mutating func update(_ field: HTTPHeader) { - if let index = fields.firstIndex(where: { $0.name.lowercased() == field.name.lowercased() }) { - fields[index] = field - } else { - fields.append(field) - } - } - - package mutating func update(name: String, value: String) { - update(HTTPHeader(name: name, value: value)) - } - - package mutating func remove(name: String) { - fields.removeAll { $0.name.lowercased() == name.lowercased() } - } - - package func value(for name: String) -> String? { - fields - .firstIndex(where: { $0.name.lowercased() == name.lowercased() }) - .map { fields[$0].value } - } - - package subscript(_ name: String) -> String? { - get { - value(for: name) - } - set { - if let newValue { - update(name: name, value: newValue) - } else { - remove(name: name) - } - } - } - - package subscript(_ name: String, default defaultValue: String) -> String { - get { - self[name] ?? defaultValue - } - set { - self[name] = newValue - } - } - - package var dictionary: [String: String] { - let namesAndValues = fields.map { ($0.name, $0.value) } - return Dictionary(namesAndValues, uniquingKeysWith: { _, last in last }) - } - - package mutating func merge(with other: HTTPHeaders) { - for field in other.fields { - update(field) - } - } - - package func merged(with other: HTTPHeaders) -> HTTPHeaders { - var copy = self - copy.merge(with: other) - return copy - } -} - -extension HTTPHeaders: ExpressibleByDictionaryLiteral { - public init(dictionaryLiteral elements: (String, String)...) { - elements.forEach { update(name: $0.0, value: $0.1) } - } -} - -extension HTTPHeaders: ExpressibleByArrayLiteral { - package init(arrayLiteral elements: HTTPHeader...) { - self.init(elements) - } -} - -// MARK: - Sequence - -extension HTTPHeaders: Sequence { - public func makeIterator() -> IndexingIterator<[HTTPHeader]> { - fields.makeIterator() - } -} - -// MARK: - Collection - -extension HTTPHeaders: Collection { - public var startIndex: Int { - fields.startIndex - } - - public var endIndex: Int { - fields.endIndex - } - - public subscript(position: Int) -> HTTPHeader { - fields[position] - } - - public func index(after i: Int) -> Int { - fields.index(after: i) - } -} - -// MARK: - CustomStringConvertible - -extension HTTPHeaders: CustomStringConvertible { - /// A textual representation of the headers. - public var description: String { - fields.map(\.description).joined(separator: "\n") - } -} - -package struct HTTPHeader: Sendable, Hashable { - package let name: String - package let value: String - - package init(name: String, value: String) { - self.name = name - self.value = value - } -} - -extension HTTPHeader: CustomStringConvertible { - /// A textual representation of the header. - package var description: String { - "\(name): \(value)" - } -} - -extension HTTPHeaders: Equatable { - package static func == (lhs: Self, rhs: Self) -> Bool { - lhs.dictionary == rhs.dictionary - } -} diff --git a/Sources/Helpers/HTTP/HTTPRequest.swift b/Sources/Helpers/HTTP/HTTPRequest.swift index e840980e..6684e8ba 100644 --- a/Sources/Helpers/HTTP/HTTPRequest.swift +++ b/Sources/Helpers/HTTP/HTTPRequest.swift @@ -6,6 +6,7 @@ // import Foundation +import HTTPTypes #if canImport(FoundationNetworking) import FoundationNetworking @@ -15,14 +16,14 @@ package struct HTTPRequest: Sendable { package var url: URL package var method: HTTPMethod package var query: [URLQueryItem] - package var headers: HTTPHeaders + package var headers: HTTPFields package var body: Data? package init( url: URL, method: HTTPMethod, query: [URLQueryItem] = [], - headers: HTTPHeaders = [:], + headers: HTTPFields = [:], body: Data? = nil ) { self.url = url @@ -36,7 +37,7 @@ package struct HTTPRequest: Sendable { urlString: String, method: HTTPMethod, query: [URLQueryItem] = [], - headers: HTTPHeaders = [:], + headers: HTTPFields = [:], body: Data? ) { guard let url = URL(string: urlString) else { return nil } @@ -46,7 +47,7 @@ package struct HTTPRequest: Sendable { package var urlRequest: URLRequest { var urlRequest = URLRequest(url: query.isEmpty ? url : url.appendingQueryItems(query)) urlRequest.httpMethod = method.rawValue - urlRequest.allHTTPHeaderFields = headers.dictionary + urlRequest.allHTTPHeaderFields = .init(headers.map { ($0.name.rawName, $0.value) }) { $1 } urlRequest.httpBody = body if urlRequest.httpBody != nil, urlRequest.value(forHTTPHeaderField: "Content-Type") == nil { diff --git a/Sources/Helpers/HTTP/HTTPResponse.swift b/Sources/Helpers/HTTP/HTTPResponse.swift index fdb61ad9..bc8a7271 100644 --- a/Sources/Helpers/HTTP/HTTPResponse.swift +++ b/Sources/Helpers/HTTP/HTTPResponse.swift @@ -6,6 +6,7 @@ // import Foundation +import HTTPTypes #if canImport(FoundationNetworking) import FoundationNetworking @@ -13,14 +14,14 @@ import Foundation package struct HTTPResponse: Sendable { package let data: Data - package let headers: HTTPHeaders + package let headers: HTTPFields package let statusCode: Int package let underlyingResponse: HTTPURLResponse package init(data: Data, response: HTTPURLResponse) { self.data = data - headers = HTTPHeaders(response.allHeaderFields as? [String: String] ?? [:]) + headers = HTTPFields(response.allHeaderFields as? [String: String] ?? [:]) statusCode = response.statusCode underlyingResponse = response } diff --git a/Sources/PostgREST/PostgrestBuilder.swift b/Sources/PostgREST/PostgrestBuilder.swift index 2b38ae32..47aa6e90 100644 --- a/Sources/PostgREST/PostgrestBuilder.swift +++ b/Sources/PostgREST/PostgrestBuilder.swift @@ -1,6 +1,7 @@ import ConcurrencyExtras import Foundation import Helpers +import HTTPTypes #if canImport(FoundationNetworking) import FoundationNetworking @@ -13,7 +14,7 @@ public class PostgrestBuilder: @unchecked Sendable { let http: any HTTPClientType struct MutableState { - var request: HTTPRequest + var request: Helpers.HTTPRequest /// The options for fetching data from the PostgREST server. var fetchOptions: FetchOptions @@ -23,7 +24,7 @@ public class PostgrestBuilder: @unchecked Sendable { init( configuration: PostgrestClient.Configuration, - request: HTTPRequest + request: Helpers.HTTPRequest ) { self.configuration = configuration @@ -51,9 +52,9 @@ public class PostgrestBuilder: @unchecked Sendable { /// Set a HTTP header for the request. @discardableResult - public func setHeader(name: String, value: String) -> Self { + public func setHeader(name: HTTPField.Name, value: String) -> Self { mutableState.withValue { - $0.request.headers.update(name: name, value: value) + $0.request.headers[name] = value } return self } @@ -99,23 +100,23 @@ public class PostgrestBuilder: @unchecked Sendable { } if let count = $0.fetchOptions.count { - if let prefer = $0.request.headers["Prefer"] { - $0.request.headers["Prefer"] = "\(prefer),count=\(count.rawValue)" + if let prefer = $0.request.headers[.prefer] { + $0.request.headers[.prefer] = "\(prefer),count=\(count.rawValue)" } else { - $0.request.headers["Prefer"] = "count=\(count.rawValue)" + $0.request.headers[.prefer] = "count=\(count.rawValue)" } } - if $0.request.headers["Accept"] == nil { - $0.request.headers["Accept"] = "application/json" + if $0.request.headers[.accept] == nil { + $0.request.headers[.accept] = "application/json" } - $0.request.headers["Content-Type"] = "application/json" + $0.request.headers[.contentType] = "application/json" if let schema = configuration.schema { if $0.request.method == .get || $0.request.method == .head { - $0.request.headers["Accept-Profile"] = schema + $0.request.headers[.acceptProfile] = schema } else { - $0.request.headers["Content-Profile"] = schema + $0.request.headers[.contentProfile] = schema } } @@ -136,3 +137,8 @@ public class PostgrestBuilder: @unchecked Sendable { return PostgrestResponse(data: response.data, response: response.underlyingResponse, value: value) } } + +extension HTTPField.Name { + static let acceptProfile = Self("Accept-Profile")! + static let contentProfile = Self("Content-Profile")! +} diff --git a/Sources/PostgREST/PostgrestClient.swift b/Sources/PostgREST/PostgrestClient.swift index 4fb1b5ba..d342417a 100644 --- a/Sources/PostgREST/PostgrestClient.swift +++ b/Sources/PostgREST/PostgrestClient.swift @@ -1,6 +1,7 @@ import ConcurrencyExtras import Foundation import Helpers +import HTTPTypes public typealias PostgrestError = Helpers.PostgrestError public typealias HTTPError = Helpers.HTTPError @@ -119,7 +120,7 @@ public final class PostgrestClient: Sendable { request: .init( url: configuration.url.appendingPathComponent(table), method: .get, - headers: HTTPHeaders(configuration.headers) + headers: HTTPFields(configuration.headers) ) ) } @@ -139,7 +140,7 @@ public final class PostgrestClient: Sendable { request: HTTPRequest( url: configuration.url.appendingPathComponent("rpc/\(fn)"), method: .post, - headers: HTTPHeaders(configuration.headers) + headers: HTTPFields(configuration.headers) ) ).rpc(params: params, count: count) } diff --git a/Sources/PostgREST/PostgrestQueryBuilder.swift b/Sources/PostgREST/PostgrestQueryBuilder.swift index ac9a4e54..ed9efdf0 100644 --- a/Sources/PostgREST/PostgrestQueryBuilder.swift +++ b/Sources/PostgREST/PostgrestQueryBuilder.swift @@ -30,7 +30,7 @@ public final class PostgrestQueryBuilder: PostgrestBuilder { $0.request.query.appendOrUpdate(URLQueryItem(name: "select", value: cleanedColumns)) if let count { - $0.request.headers["Prefer"] = "count=\(count.rawValue)" + $0.request.headers[.prefer] = "count=\(count.rawValue)" } if head { $0.request.method = .head @@ -62,11 +62,11 @@ public final class PostgrestQueryBuilder: PostgrestBuilder { if let count { prefersHeaders.append("count=\(count.rawValue)") } - if let prefer = $0.request.headers["Prefer"] { + if let prefer = $0.request.headers[.prefer] { prefersHeaders.insert(prefer, at: 0) } if !prefersHeaders.isEmpty { - $0.request.headers["Prefer"] = prefersHeaders.joined(separator: ",") + $0.request.headers[.prefer] = prefersHeaders.joined(separator: ",") } if let body = $0.request.body, let jsonObject = try JSONSerialization.jsonObject(with: body) as? [[String: Any]] @@ -114,11 +114,11 @@ public final class PostgrestQueryBuilder: PostgrestBuilder { if let count { prefersHeaders.append("count=\(count.rawValue)") } - if let prefer = $0.request.headers["Prefer"] { + if let prefer = $0.request.headers[.prefer] { prefersHeaders.insert(prefer, at: 0) } if !prefersHeaders.isEmpty { - $0.request.headers["Prefer"] = prefersHeaders.joined(separator: ",") + $0.request.headers[.prefer] = prefersHeaders.joined(separator: ",") } if let body = $0.request.body, @@ -154,11 +154,11 @@ public final class PostgrestQueryBuilder: PostgrestBuilder { if let count { preferHeaders.append("count=\(count.rawValue)") } - if let prefer = $0.request.headers["Prefer"] { + if let prefer = $0.request.headers[.prefer] { preferHeaders.insert(prefer, at: 0) } if !preferHeaders.isEmpty { - $0.request.headers["Prefer"] = preferHeaders.joined(separator: ",") + $0.request.headers[.prefer] = preferHeaders.joined(separator: ",") } } return PostgrestFilterBuilder(self) @@ -180,11 +180,11 @@ public final class PostgrestQueryBuilder: PostgrestBuilder { if let count { preferHeaders.append("count=\(count.rawValue)") } - if let prefer = $0.request.headers["Prefer"] { + if let prefer = $0.request.headers[.prefer] { preferHeaders.insert(prefer, at: 0) } if !preferHeaders.isEmpty { - $0.request.headers["Prefer"] = preferHeaders.joined(separator: ",") + $0.request.headers[.prefer] = preferHeaders.joined(separator: ",") } } return PostgrestFilterBuilder(self) diff --git a/Sources/PostgREST/PostgrestRpcBuilder.swift b/Sources/PostgREST/PostgrestRpcBuilder.swift index 08d3fa8b..c8232eba 100644 --- a/Sources/PostgREST/PostgrestRpcBuilder.swift +++ b/Sources/PostgREST/PostgrestRpcBuilder.swift @@ -1,5 +1,6 @@ import Foundation import Helpers +import HTTPTypes struct NoParams: Encodable {} @@ -30,10 +31,10 @@ public final class PostgrestRpcBuilder: PostgrestBuilder { } if let count { - if let prefer = $0.request.headers["Prefer"] { - $0.request.headers["Prefer"] = "\(prefer),count=\(count.rawValue)" + if let prefer = $0.request.headers[.prefer] { + $0.request.headers[.prefer] = "\(prefer),count=\(count.rawValue)" } else { - $0.request.headers["Prefer"] = "count=\(count.rawValue)" + $0.request.headers[.prefer] = "count=\(count.rawValue)" } } } @@ -41,3 +42,7 @@ public final class PostgrestRpcBuilder: PostgrestBuilder { return PostgrestFilterBuilder(self) } } + +extension HTTPField.Name { + static let prefer = Self("Prefer")! +} diff --git a/Sources/PostgREST/PostgrestTransformBuilder.swift b/Sources/PostgREST/PostgrestTransformBuilder.swift index 45c83c5c..89414f6e 100644 --- a/Sources/PostgREST/PostgrestTransformBuilder.swift +++ b/Sources/PostgREST/PostgrestTransformBuilder.swift @@ -24,7 +24,7 @@ public class PostgrestTransformBuilder: PostgrestBuilder { mutableState.withValue { $0.request.query.appendOrUpdate(URLQueryItem(name: "select", value: cleanedColumns)) - if let prefer = $0.request.headers["Prefer"] { + if let prefer = $0.request.headers[.prefer] { var components = prefer.components(separatedBy: ",") if let index = components.firstIndex(where: { $0.hasPrefix("return=") }) { @@ -33,9 +33,9 @@ public class PostgrestTransformBuilder: PostgrestBuilder { components.append("return=representation") } - $0.request.headers["Prefer"] = components.joined(separator: ",") + $0.request.headers[.prefer] = components.joined(separator: ",") } else { - $0.request.headers["Prefer"] = "return=representation" + $0.request.headers[.prefer] = "return=representation" } } return self @@ -141,7 +141,7 @@ public class PostgrestTransformBuilder: PostgrestBuilder { /// Query result must be one row (e.g. using `.limit(1)`), otherwise this returns an error. public func single() -> PostgrestTransformBuilder { mutableState.withValue { - $0.request.headers["Accept"] = "application/vnd.pgrst.object+json" + $0.request.headers[.accept] = "application/vnd.pgrst.object+json" } return self } @@ -149,7 +149,7 @@ public class PostgrestTransformBuilder: PostgrestBuilder { /// Return `value` as a string in CSV format. public func csv() -> PostgrestTransformBuilder { mutableState.withValue { - $0.request.headers["Accept"] = "text/csv" + $0.request.headers[.accept] = "text/csv" } return self } @@ -157,7 +157,7 @@ public class PostgrestTransformBuilder: PostgrestBuilder { /// Return `value` as an object in [GeoJSON](https://geojson.org) format. public func geojson() -> PostgrestTransformBuilder { mutableState.withValue { - $0.request.headers["Accept"] = "application/geo+json" + $0.request.headers[.accept] = "application/geo+json" } return self } @@ -194,10 +194,8 @@ public class PostgrestTransformBuilder: PostgrestBuilder { ] .compactMap { $0 } .joined(separator: "|") - let forMediaType = $0.request.headers["Accept"] ?? "application/json" - $0.request - .headers["Accept"] = - "application/vnd.pgrst.plan+\"\(format)\"; for=\(forMediaType); options=\(options);" + let forMediaType = $0.request.headers[.accept] ?? "application/json" + $0.request.headers[.accept] = "application/vnd.pgrst.plan+\"\(format)\"; for=\(forMediaType); options=\(options);" } return self diff --git a/Sources/Realtime/RealtimeChannel.swift b/Sources/Realtime/RealtimeChannel.swift index 6ee97326..380df82f 100644 --- a/Sources/Realtime/RealtimeChannel.swift +++ b/Sources/Realtime/RealtimeChannel.swift @@ -22,6 +22,7 @@ import ConcurrencyExtras import Foundation import Helpers import Swift +import HTTPTypes /// Container class of bindings to the channel struct Binding { @@ -745,7 +746,7 @@ public class RealtimeChannel { let request = try HTTPRequest( url: broadcastEndpointURL, method: .post, - headers: HTTPHeaders(headers.mapValues { "\($0)" }), + headers: HTTPFields(headers.compactMapValues { $0 }), body: JSONSerialization.data(withJSONObject: body) ) diff --git a/Sources/Realtime/V2/RealtimeChannelV2.swift b/Sources/Realtime/V2/RealtimeChannelV2.swift index 437d20a4..f29b144f 100644 --- a/Sources/Realtime/V2/RealtimeChannelV2.swift +++ b/Sources/Realtime/V2/RealtimeChannelV2.swift @@ -8,6 +8,7 @@ import ConcurrencyExtras import Foundation import Helpers +import HTTPTypes #if canImport(FoundationNetworking) import FoundationNetworking @@ -42,7 +43,7 @@ struct Socket: Sendable { var addChannel: @Sendable (_ channel: RealtimeChannelV2) -> Void var removeChannel: @Sendable (_ channel: RealtimeChannelV2) async -> Void var push: @Sendable (_ message: RealtimeMessageV2) async -> Void - var httpSend: @Sendable (_ request: HTTPRequest) async throws -> HTTPResponse + var httpSend: @Sendable (_ request: Helpers.HTTPRequest) async throws -> Helpers.HTTPResponse } extension Socket { @@ -215,12 +216,12 @@ public final class RealtimeChannelV2: Sendable { let `private`: Bool } - var headers = HTTPHeaders(["content-type": "application/json"]) + var headers: HTTPFields = [.contentType: "application/json"] if let apiKey = socket.apiKey() { - headers["apikey"] = apiKey + headers[.apiKey] = apiKey } if let accessToken = socket.accessToken() { - headers["authorization"] = "Bearer \(accessToken)" + headers[.authorization] = "Bearer \(accessToken)" } let task = Task { [headers] in diff --git a/Sources/Realtime/V2/Types.swift b/Sources/Realtime/V2/Types.swift index 52e7f80b..7c33c8df 100644 --- a/Sources/Realtime/V2/Types.swift +++ b/Sources/Realtime/V2/Types.swift @@ -7,6 +7,7 @@ import Foundation import Helpers +import HTTPTypes #if canImport(FoundationNetworking) import FoundationNetworking @@ -14,7 +15,7 @@ import Helpers /// Options for initializing ``RealtimeClientV2``. public struct RealtimeClientOptions: Sendable { - package var headers: HTTPHeaders + package var headers: HTTPFields var heartbeatInterval: TimeInterval var reconnectDelay: TimeInterval var timeoutInterval: TimeInterval @@ -39,7 +40,7 @@ public struct RealtimeClientOptions: Sendable { fetch: (@Sendable (_ request: URLRequest) async throws -> (Data, URLResponse))? = nil, logger: (any SupabaseLogger)? = nil ) { - self.headers = HTTPHeaders(headers) + self.headers = HTTPFields(headers) self.heartbeatInterval = heartbeatInterval self.reconnectDelay = reconnectDelay self.timeoutInterval = timeoutInterval @@ -50,11 +51,11 @@ public struct RealtimeClientOptions: Sendable { } var apikey: String? { - headers["apikey"] + headers[.apiKey] } var accessToken: String? { - guard let accessToken = headers["Authorization"]?.split(separator: " ").last else { + guard let accessToken = headers[.authorization]?.split(separator: " ").last else { return nil } return String(accessToken) @@ -83,3 +84,7 @@ public enum RealtimeClientStatus: Sendable, CustomStringConvertible { } } } + +extension HTTPField.Name { + static let apiKey = Self("apiKey")! +} diff --git a/Sources/Storage/MultipartFormData.swift b/Sources/Storage/MultipartFormData.swift index 385824e6..522a9d27 100644 --- a/Sources/Storage/MultipartFormData.swift +++ b/Sources/Storage/MultipartFormData.swift @@ -26,6 +26,7 @@ import Foundation import Helpers +import HTTPTypes #if canImport(MobileCoreServices) import MobileCoreServices @@ -79,13 +80,13 @@ class MultipartFormData { } class BodyPart { - let headers: HTTPHeaders + let headers: HTTPFields let bodyStream: InputStream let bodyContentLength: UInt64 var hasInitialBoundary = false var hasFinalBoundary = false - init(headers: HTTPHeaders, bodyStream: InputStream, bodyContentLength: UInt64) { + init(headers: HTTPFields, bodyStream: InputStream, bodyContentLength: UInt64) { self.headers = headers self.bodyStream = bodyStream self.bodyContentLength = bodyContentLength @@ -310,7 +311,7 @@ class MultipartFormData { /// - stream: `InputStream` to encode into the instance. /// - length: Length, in bytes, of the stream. /// - headers: `HTTPHeaders` for the body part. - func append(_ stream: InputStream, withLength length: UInt64, headers: HTTPHeaders) { + func append(_ stream: InputStream, withLength length: UInt64, headers: HTTPFields) { let bodyPart = BodyPart(headers: headers, bodyStream: stream, bodyContentLength: length) bodyParts.append(bodyPart) } @@ -528,12 +529,12 @@ class MultipartFormData { private func contentHeaders( withName name: String, fileName: String? = nil, mimeType: String? = nil - ) -> HTTPHeaders { + ) -> HTTPFields { var disposition = "form-data; name=\"\(name)\"" if let fileName { disposition += "; filename=\"\(fileName)\"" } - var headers: HTTPHeaders = ["Content-Disposition": disposition] - if let mimeType { headers["Content-Type"] = mimeType } + var headers: HTTPFields = [.contentDisposition: disposition] + if let mimeType { headers[.contentType] = mimeType } return headers } diff --git a/Sources/Storage/StorageApi.swift b/Sources/Storage/StorageApi.swift index a635b1a4..f021d3f6 100644 --- a/Sources/Storage/StorageApi.swift +++ b/Sources/Storage/StorageApi.swift @@ -1,5 +1,6 @@ import Foundation import Helpers +import HTTPTypes #if canImport(FoundationNetworking) import FoundationNetworking @@ -29,9 +30,9 @@ public class StorageApi: @unchecked Sendable { } @discardableResult - func execute(_ request: HTTPRequest) async throws -> HTTPResponse { + func execute(_ request: Helpers.HTTPRequest) async throws -> Helpers.HTTPResponse { var request = request - request.headers = HTTPHeaders(configuration.headers).merged(with: request.headers) + request.headers = HTTPFields(configuration.headers).merging(with: request.headers) let response = try await http.send(request) @@ -50,21 +51,21 @@ public class StorageApi: @unchecked Sendable { } } -extension HTTPRequest { +extension Helpers.HTTPRequest { init( url: URL, method: HTTPMethod, query: [URLQueryItem], formData: MultipartFormData, options: FileOptions, - headers: HTTPHeaders = [:] + headers: HTTPFields = [:] ) throws { var headers = headers - if headers["Content-Type"] == nil { - headers["Content-Type"] = formData.contentType + if headers[.contentType] == nil { + headers[.contentType] = formData.contentType } - if headers["Cache-Control"] == nil { - headers["Cache-Control"] = "max-age=\(options.cacheControl)" + if headers[.cacheControl] == nil { + headers[.cacheControl] = "max-age=\(options.cacheControl)" } try self.init( url: url, diff --git a/Sources/Storage/StorageFileApi.swift b/Sources/Storage/StorageFileApi.swift index 0bc43f7e..e8af36bd 100644 --- a/Sources/Storage/StorageFileApi.swift +++ b/Sources/Storage/StorageFileApi.swift @@ -1,5 +1,6 @@ import Foundation import Helpers +import HTTPTypes #if canImport(FoundationNetworking) import FoundationNetworking @@ -50,15 +51,15 @@ public class StorageFileApi: StorageApi, @unchecked Sendable { options: FileOptions? ) async throws -> FileUploadResponse { let options = options ?? defaultFileOptions - var headers = options.headers.map { HTTPHeaders($0) } ?? HTTPHeaders() + var headers = options.headers.map { HTTPFields($0) } ?? HTTPFields() let metadata = options.metadata if method == .post { - headers.update(name: "x-upsert", value: "\(options.upsert)") + headers[.xUpsert] = "\(options.upsert)" } - headers["duplex"] = options.duplex + headers[.duplex] = options.duplex if let metadata { formData.append(encodeMetadata(metadata), withName: "metadata") @@ -529,9 +530,9 @@ public class StorageFileApi: StorageApi, @unchecked Sendable { let url: URL } - var headers = HTTPHeaders() + var headers = HTTPFields() if let upsert = options?.upsert, upsert { - headers["x-upsert"] = "true" + headers[.xUpsert] = "true" } let response = try await execute( @@ -625,10 +626,10 @@ public class StorageFileApi: StorageApi, @unchecked Sendable { options: FileOptions? ) async throws -> SignedURLUploadResponse { let options = options ?? defaultFileOptions - var headers = options.headers.map { HTTPHeaders($0) } ?? HTTPHeaders() + var headers = options.headers.map { HTTPFields($0) } ?? HTTPFields() - headers["x-upsert"] = "\(options.upsert)" - headers["duplex"] = options.duplex + headers[.xUpsert] = "\(options.upsert)" + headers[.duplex] = options.duplex if let metadata = options.metadata { formData.append(encodeMetadata(metadata), withName: "metadata") @@ -667,3 +668,8 @@ public class StorageFileApi: StorageApi, @unchecked Sendable { return cleanedPath } } + +extension HTTPField.Name { + static let duplex = Self("duplex")! + static let xUpsert = Self("x-upsert")! +} diff --git a/Sources/Supabase/SupabaseClient.swift b/Sources/Supabase/SupabaseClient.swift index 65218754..28c75dbb 100644 --- a/Sources/Supabase/SupabaseClient.swift +++ b/Sources/Supabase/SupabaseClient.swift @@ -7,6 +7,7 @@ import IssueReporting @_exported import PostgREST @_exported import Realtime @_exported import Storage +import HTTPTypes #if canImport(FoundationNetworking) import FoundationNetworking @@ -98,7 +99,7 @@ public final class SupabaseClient: Sendable { } } - let _headers: HTTPHeaders + let _headers: HTTPFields /// Headers provided to the inner clients on initialization. /// /// - Note: This collection is non-mutable, if you want to provide different headers, pass it in ``SupabaseClientOptions/GlobalOptions/headers``. @@ -153,12 +154,12 @@ public final class SupabaseClient: Sendable { databaseURL = supabaseURL.appendingPathComponent("/rest/v1") functionsURL = supabaseURL.appendingPathComponent("/functions/v1") - _headers = HTTPHeaders([ + _headers = HTTPFields([ "X-Client-Info": "supabase-swift/\(version)", "Authorization": "Bearer \(supabaseKey)", "Apikey": supabaseKey, ]) - .merged(with: HTTPHeaders(options.global.headers)) + .merging(with: HTTPFields(options.global.headers)) // default storage key uses the supabase project ref as a namespace let defaultStorageKey = "sb-\(supabaseURL.host!.split(separator: ".")[0])-auth-token" From 19bfabc36037fde503c3db888cea8d280626b355 Mon Sep 17 00:00:00 2001 From: zunda Date: Mon, 14 Oct 2024 19:52:48 +0900 Subject: [PATCH 3/5] replace to HTTPTypes.HTTPRequest.Method from HTTPMethod --- Sources/Functions/Types.swift | 2 +- Sources/Helpers/HTTP/HTTPRequest.swift | 18 +++--------------- .../Helpers/HTTP/RetryRequestInterceptor.swift | 7 ++++--- Sources/Storage/StorageApi.swift | 2 +- Sources/Storage/StorageFileApi.swift | 2 +- 5 files changed, 10 insertions(+), 21 deletions(-) diff --git a/Sources/Functions/Types.swift b/Sources/Functions/Types.swift index d7e76114..e409c665 100644 --- a/Sources/Functions/Types.swift +++ b/Sources/Functions/Types.swift @@ -99,7 +99,7 @@ public struct FunctionInvokeOptions: Sendable { case delete = "DELETE" } - var httpMethod: HTTPMethod? { + var httpMethod: HTTPTypes.HTTPRequest.Method? { switch method { case .get: .get diff --git a/Sources/Helpers/HTTP/HTTPRequest.swift b/Sources/Helpers/HTTP/HTTPRequest.swift index 6684e8ba..47e8adc7 100644 --- a/Sources/Helpers/HTTP/HTTPRequest.swift +++ b/Sources/Helpers/HTTP/HTTPRequest.swift @@ -14,14 +14,14 @@ import HTTPTypes package struct HTTPRequest: Sendable { package var url: URL - package var method: HTTPMethod + package var method: HTTPTypes.HTTPRequest.Method package var query: [URLQueryItem] package var headers: HTTPFields package var body: Data? package init( url: URL, - method: HTTPMethod, + method: HTTPTypes.HTTPRequest.Method, query: [URLQueryItem] = [], headers: HTTPFields = [:], body: Data? = nil @@ -35,7 +35,7 @@ package struct HTTPRequest: Sendable { package init?( urlString: String, - method: HTTPMethod, + method: HTTPTypes.HTTPRequest.Method, query: [URLQueryItem] = [], headers: HTTPFields = [:], body: Data? @@ -58,18 +58,6 @@ package struct HTTPRequest: Sendable { } } -package enum HTTPMethod: String, Sendable { - case get = "GET" - case head = "HEAD" - case post = "POST" - case put = "PUT" - case delete = "DELETE" - case connect = "CONNECT" - case trace = "TRACE" - case patch = "PATCH" - case options = "OPTIONS" -} - extension [URLQueryItem] { package mutating func appendOrUpdate(_ queryItem: URLQueryItem) { if let index = firstIndex(where: { $0.name == queryItem.name }) { diff --git a/Sources/Helpers/HTTP/RetryRequestInterceptor.swift b/Sources/Helpers/HTTP/RetryRequestInterceptor.swift index aaab915b..bbee5c90 100644 --- a/Sources/Helpers/HTTP/RetryRequestInterceptor.swift +++ b/Sources/Helpers/HTTP/RetryRequestInterceptor.swift @@ -6,6 +6,7 @@ // import Foundation +import HTTPTypes #if canImport(FoundationNetworking) import FoundationNetworking @@ -25,7 +26,7 @@ package actor RetryRequestInterceptor: HTTPClientInterceptor { package static let defaultExponentialBackoffScale: Double = 0.5 /// The default set of retryable HTTP methods. - package static let defaultRetryableHTTPMethods: Set = [ + package static let defaultRetryableHTTPMethods: Set = [ .delete, .get, .head, .options, .put, .trace, ] @@ -52,7 +53,7 @@ package actor RetryRequestInterceptor: HTTPClientInterceptor { /// The scale factor for exponential backoff. package let exponentialBackoffScale: Double /// The set of retryable HTTP methods. - package let retryableHTTPMethods: Set + package let retryableHTTPMethods: Set /// The set of retryable HTTP status codes. package let retryableHTTPStatusCodes: Set /// The set of retryable URL error codes. @@ -71,7 +72,7 @@ package actor RetryRequestInterceptor: HTTPClientInterceptor { retryLimit: Int = RetryRequestInterceptor.defaultRetryLimit, exponentialBackoffBase: UInt = RetryRequestInterceptor.defaultExponentialBackoffBase, exponentialBackoffScale: Double = RetryRequestInterceptor.defaultExponentialBackoffScale, - retryableHTTPMethods: Set = RetryRequestInterceptor.defaultRetryableHTTPMethods, + retryableHTTPMethods: Set = RetryRequestInterceptor.defaultRetryableHTTPMethods, retryableHTTPStatusCodes: Set = RetryRequestInterceptor.defaultRetryableHTTPStatusCodes, retryableErrorCodes: Set = RetryRequestInterceptor.defaultRetryableURLErrorCodes ) { diff --git a/Sources/Storage/StorageApi.swift b/Sources/Storage/StorageApi.swift index f021d3f6..838ea800 100644 --- a/Sources/Storage/StorageApi.swift +++ b/Sources/Storage/StorageApi.swift @@ -54,7 +54,7 @@ public class StorageApi: @unchecked Sendable { extension Helpers.HTTPRequest { init( url: URL, - method: HTTPMethod, + method: HTTPTypes.HTTPRequest.Method, query: [URLQueryItem], formData: MultipartFormData, options: FileOptions, diff --git a/Sources/Storage/StorageFileApi.swift b/Sources/Storage/StorageFileApi.swift index e8af36bd..b07c35b5 100644 --- a/Sources/Storage/StorageFileApi.swift +++ b/Sources/Storage/StorageFileApi.swift @@ -45,7 +45,7 @@ public class StorageFileApi: StorageApi, @unchecked Sendable { } private func _uploadOrUpdate( - method: HTTPMethod, + method: HTTPTypes.HTTPRequest.Method, path: String, formData: MultipartFormData, options: FileOptions? From cb6c5c1d5aa741bda0aeb3cc1605ca49989cb7af Mon Sep 17 00:00:00 2001 From: zunda Date: Mon, 14 Oct 2024 20:23:56 +0900 Subject: [PATCH 4/5] fix Test --- .../FunctionInvokeOptionsTests.swift | 8 +-- .../FunctionsTests/FunctionsClientTests.swift | 27 +++---- Tests/HelpersTests/HTTPHeadersTests.swift | 71 ------------------- .../PostgrestBuilderTests.swift | 6 +- Tests/RealtimeTests/RealtimeTests.swift | 6 +- Tests/SupabaseTests/SupabaseClientTests.swift | 4 +- 6 files changed, 27 insertions(+), 95 deletions(-) delete mode 100644 Tests/HelpersTests/HTTPHeadersTests.swift diff --git a/Tests/FunctionsTests/FunctionInvokeOptionsTests.swift b/Tests/FunctionsTests/FunctionInvokeOptionsTests.swift index 00447a86..4a781007 100644 --- a/Tests/FunctionsTests/FunctionInvokeOptionsTests.swift +++ b/Tests/FunctionsTests/FunctionInvokeOptionsTests.swift @@ -5,13 +5,13 @@ import XCTest final class FunctionInvokeOptionsTests: XCTestCase { func test_initWithStringBody() { let options = FunctionInvokeOptions(body: "string value") - XCTAssertEqual(options.headers["Content-Type"], "text/plain") + XCTAssertEqual(options.headers[.contentType], "text/plain") XCTAssertNotNil(options.body) } func test_initWithDataBody() { let options = FunctionInvokeOptions(body: "binary value".data(using: .utf8)!) - XCTAssertEqual(options.headers["Content-Type"], "application/octet-stream") + XCTAssertEqual(options.headers[.contentType], "application/octet-stream") XCTAssertNotNil(options.body) } @@ -20,7 +20,7 @@ final class FunctionInvokeOptionsTests: XCTestCase { let value: String } let options = FunctionInvokeOptions(body: Body(value: "value")) - XCTAssertEqual(options.headers["Content-Type"], "application/json") + XCTAssertEqual(options.headers[.contentType], "application/json") XCTAssertNotNil(options.body) } @@ -31,7 +31,7 @@ final class FunctionInvokeOptionsTests: XCTestCase { headers: ["Content-Type": contentType], body: "binary value".data(using: .utf8)! ) - XCTAssertEqual(options.headers["Content-Type"], contentType) + XCTAssertEqual(options.headers[.contentType], contentType) XCTAssertNotNil(options.body) } } diff --git a/Tests/FunctionsTests/FunctionsClientTests.swift b/Tests/FunctionsTests/FunctionsClientTests.swift index 18486907..e4972dce 100644 --- a/Tests/FunctionsTests/FunctionsClientTests.swift +++ b/Tests/FunctionsTests/FunctionsClientTests.swift @@ -1,6 +1,7 @@ import ConcurrencyExtras @testable import Functions import Helpers +import HTTPTypes import TestHelpers import XCTest @@ -22,8 +23,8 @@ final class FunctionsClientTests: XCTestCase { ) XCTAssertEqual(client.region, "sa-east-1") - XCTAssertEqual(client.headers["Apikey"], apiKey) - XCTAssertNotNil(client.headers["X-Client-Info"]) + XCTAssertEqual(client.headers[.init("Apikey")!], apiKey) + XCTAssertNotNil(client.headers[.init("X-Client-Info")!]) } func testInvoke() async throws { @@ -53,9 +54,9 @@ final class FunctionsClientTests: XCTestCase { XCTAssertEqual(request?.url, url) XCTAssertEqual(request?.method, .post) - XCTAssertEqual(request?.headers["Apikey"], apiKey) - XCTAssertEqual(request?.headers["X-Custom-Key"], "value") - XCTAssertEqual(request?.headers["X-Client-Info"], "functions-swift/\(Functions.version)") + XCTAssertEqual(request?.headers[.init("Apikey")!], apiKey) + XCTAssertEqual(request?.headers[.init("X-Custom-Key")!], "value") + XCTAssertEqual(request?.headers[.init("X-Client-Info")!], "functions-swift/\(Functions.version)") } func testInvokeWithCustomMethod() async throws { @@ -109,7 +110,7 @@ final class FunctionsClientTests: XCTestCase { try await sut.invoke("hello-world") let request = await http.receivedRequests.last - XCTAssertEqual(request?.headers["x-region"], "ca-central-1") + XCTAssertEqual(request?.headers[.xRegion], "ca-central-1") } func testInvokeWithRegion() async throws { @@ -126,7 +127,7 @@ final class FunctionsClientTests: XCTestCase { try await sut.invoke("hello-world", options: .init(region: .caCentral1)) let request = await http.receivedRequests.last - XCTAssertEqual(request?.headers["x-region"], "ca-central-1") + XCTAssertEqual(request?.headers[.xRegion], "ca-central-1") } func testInvokeWithoutRegion() async throws { @@ -143,7 +144,7 @@ final class FunctionsClientTests: XCTestCase { try await sut.invoke("hello-world") let request = await http.receivedRequests.last - XCTAssertNil(request?.headers["x-region"]) + XCTAssertNil(request?.headers[.xRegion]) } func testInvoke_shouldThrow_URLError_badServerResponse() async { @@ -190,7 +191,7 @@ final class FunctionsClientTests: XCTestCase { http: HTTPClientMock().any { _ in try .stub( body: Empty(), - headers: ["x-relay-error": "true"] + headers: [.xRelayError: "true"] ) } ) @@ -206,16 +207,16 @@ final class FunctionsClientTests: XCTestCase { func test_setAuth() { sut.setAuth(token: "access.token") - XCTAssertEqual(sut.headers["Authorization"], "Bearer access.token") + XCTAssertEqual(sut.headers[.authorization], "Bearer access.token") } } -extension HTTPResponse { +extension Helpers.HTTPResponse { static func stub( body: any Encodable, statusCode: Int = 200, - headers: HTTPHeaders = .init() - ) throws -> HTTPResponse { + headers: HTTPFields = .init() + ) throws -> Helpers.HTTPResponse { let data = try JSONEncoder().encode(body) let response = HTTPURLResponse( url: URL(string: "http://127.0.0.1")!, diff --git a/Tests/HelpersTests/HTTPHeadersTests.swift b/Tests/HelpersTests/HTTPHeadersTests.swift deleted file mode 100644 index 07bdc2f0..00000000 --- a/Tests/HelpersTests/HTTPHeadersTests.swift +++ /dev/null @@ -1,71 +0,0 @@ -// -// HTTPHeadersTests.swift -// -// -// Created by Guilherme Souza on 24/04/24. -// - -@testable import Helpers -import XCTest - -final class HTTPHeadersTests: XCTestCase { - func testInitWithDictionary() { - let headers = HTTPHeaders(["Content-Type": "application/json"]) - XCTAssertEqual(headers["content-type"], "application/json") - } - - func testUpdate() { - var headers = HTTPHeaders() - headers.update(name: "Content-Type", value: "application/json") - XCTAssertEqual(headers["content-type"], "application/json") - } - - func testRemove() { - var headers = HTTPHeaders(["Content-Type": "application/json"]) - headers.remove(name: "Content-Type") - XCTAssertNil(headers["content-type"]) - } - - func testValueForName() { - let headers = HTTPHeaders(["Content-Type": "application/json"]) - XCTAssertEqual(headers.value(for: "Content-Type"), "application/json") - } - - func testSubscript() { - var headers = HTTPHeaders(["Content-Type": "application/json"]) - headers["Content-Type"] = "text/html" - XCTAssertEqual(headers["content-type"], "text/html") - } - - func testDictionary() { - let headers = HTTPHeaders(["Content-Type": "application/json"]) - XCTAssertEqual(headers.dictionary, ["Content-Type": "application/json"]) - } - - func testMerge() { - var headers1 = HTTPHeaders(["Content-Type": "application/json"]) - let headers2 = HTTPHeaders(["Accept": "application/json"]) - headers1.merge(with: headers2) - XCTAssertEqual(headers1.dictionary, ["Content-Type": "application/json", "Accept": "application/json"]) - } - - func testMerged() { - let headers1 = HTTPHeaders(["Content-Type": "application/json"]) - let headers2 = HTTPHeaders(["Accept": "application/json"]) - let mergedHeaders = headers1.merged(with: headers2) - XCTAssertEqual(mergedHeaders.dictionary, ["Content-Type": "application/json", "Accept": "application/json"]) - } -} - -final class HTTPHeaderTests: XCTestCase { - func testInit() { - let header = HTTPHeader(name: "Content-Type", value: "application/json") - XCTAssertEqual(header.name, "Content-Type") - XCTAssertEqual(header.value, "application/json") - } - - func testDescription() { - let header = HTTPHeader(name: "Content-Type", value: "application/json") - XCTAssertEqual(header.description, "Content-Type: application/json") - } -} diff --git a/Tests/PostgRESTTests/PostgrestBuilderTests.swift b/Tests/PostgRESTTests/PostgrestBuilderTests.swift index b9d62a69..1fa049a4 100644 --- a/Tests/PostgRESTTests/PostgrestBuilderTests.swift +++ b/Tests/PostgRESTTests/PostgrestBuilderTests.swift @@ -13,11 +13,11 @@ final class PostgrestBuilderTests: XCTestCase { func testCustomHeaderOnAPerCallBasis() throws { let postgrest1 = PostgrestClient(url: url, headers: ["apikey": "foo"], logger: nil) - let postgrest2 = try postgrest1.rpc("void_func").setHeader(name: "apikey", value: "bar") + let postgrest2 = try postgrest1.rpc("void_func").setHeader(name: .init("apikey")!, value: "bar") // Original client object isn't affected - XCTAssertEqual(postgrest1.from("users").select().mutableState.request.headers["apikey"], "foo") + XCTAssertEqual(postgrest1.from("users").select().mutableState.request.headers[.init("apikey")!], "foo") // Derived client object uses new header value - XCTAssertEqual(postgrest2.mutableState.request.headers["apikey"], "bar") + XCTAssertEqual(postgrest2.mutableState.request.headers[.init("apikey")!], "bar") } } diff --git a/Tests/RealtimeTests/RealtimeTests.swift b/Tests/RealtimeTests/RealtimeTests.swift index 64c0b89f..e8ff0570 100644 --- a/Tests/RealtimeTests/RealtimeTests.swift +++ b/Tests/RealtimeTests/RealtimeTests.swift @@ -265,9 +265,9 @@ final class RealtimeTests: XCTestCase { expectNoDifference( request?.headers, [ - "content-type": "application/json", - "apikey": "anon.api.key", - "authorization": "Bearer anon.api.key", + .contentType: "application/json", + .apiKey: "anon.api.key", + .authorization: "Bearer anon.api.key", ] ) diff --git a/Tests/SupabaseTests/SupabaseClientTests.swift b/Tests/SupabaseTests/SupabaseClientTests.swift index d11a3e31..c487177d 100644 --- a/Tests/SupabaseTests/SupabaseClientTests.swift +++ b/Tests/SupabaseTests/SupabaseClientTests.swift @@ -78,7 +78,9 @@ final class SupabaseClientTests: XCTestCase { XCTAssertEqual(realtimeURL.absoluteString, "https://project-ref.supabase.co/realtime/v1") let realtimeOptions = client.realtimeV2.options - let expectedRealtimeHeader = client._headers.merged(with: ["custom_realtime_header_key": "custom_realtime_header_value"]) + let expectedRealtimeHeader = client._headers.merging(with: [ + .init("custom_realtime_header_key")!: "custom_realtime_header_value"] + ) expectNoDifference(realtimeOptions.headers, expectedRealtimeHeader) XCTAssertIdentical(realtimeOptions.logger as? Logger, logger) From 1f54086ab2dc263589ec185c6abbeb142d148fba Mon Sep 17 00:00:00 2001 From: zunda Date: Mon, 14 Oct 2024 23:01:44 +0900 Subject: [PATCH 5/5] add setHeader(name: String, value: String) -> Self --- Sources/PostgREST/PostgrestBuilder.swift | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Sources/PostgREST/PostgrestBuilder.swift b/Sources/PostgREST/PostgrestBuilder.swift index 47aa6e90..ce02e3c2 100644 --- a/Sources/PostgREST/PostgrestBuilder.swift +++ b/Sources/PostgREST/PostgrestBuilder.swift @@ -52,7 +52,13 @@ public class PostgrestBuilder: @unchecked Sendable { /// Set a HTTP header for the request. @discardableResult - public func setHeader(name: HTTPField.Name, value: String) -> Self { + public func setHeader(name: String, value: String) -> Self { + return self.setHeader(name: .init(name)!, value: value) + } + + /// Set a HTTP header for the request. + @discardableResult + internal func setHeader(name: HTTPField.Name, value: String) -> Self { mutableState.withValue { $0.request.headers[name] = value }