Skip to content

fix: improve FunctionRegion type safety and API consistency #759

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 5 additions & 23 deletions Sources/Functions/FunctionsClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public final class FunctionsClient: Sendable {
let url: URL

/// The Region to invoke the functions in.
let region: String?
let region: FunctionRegion?

struct MutableState {
/// Headers to be included in the requests.
Expand All @@ -51,7 +51,7 @@ public final class FunctionsClient: Sendable {
public convenience init(
url: URL,
headers: [String: String] = [:],
region: String? = nil,
region: FunctionRegion? = nil,
logger: (any SupabaseLogger)? = nil,
fetch: @escaping FetchHandler = { try await URLSession.shared.data(for: $0) }
) {
Expand All @@ -68,7 +68,7 @@ public final class FunctionsClient: Sendable {
convenience init(
url: URL,
headers: [String: String] = [:],
region: String? = nil,
region: FunctionRegion? = nil,
logger: (any SupabaseLogger)? = nil,
fetch: @escaping FetchHandler = { try await URLSession.shared.data(for: $0) },
sessionConfiguration: URLSessionConfiguration
Expand All @@ -92,7 +92,7 @@ public final class FunctionsClient: Sendable {
init(
url: URL,
headers: [String: String],
region: String?,
region: FunctionRegion?,
http: any HTTPClientType,
sessionConfiguration: URLSessionConfiguration = .default
) {
Expand All @@ -109,24 +109,6 @@ public final class FunctionsClient: Sendable {
}
}

/// Initializes a new instance of `FunctionsClient`.
///
/// - Parameters:
/// - url: The base URL for the functions.
/// - headers: Headers to be included in the requests. (Default: empty dictionary)
/// - region: The Region to invoke the functions in.
/// - logger: SupabaseLogger instance to use.
/// - fetch: The fetch handler used to make requests. (Default: URLSession.shared.data(for:))
public convenience init(
url: URL,
headers: [String: String] = [:],
region: FunctionRegion? = nil,
logger: (any SupabaseLogger)? = nil,
fetch: @escaping FetchHandler = { try await URLSession.shared.data(for: $0) }
) {
self.init(url: url, headers: headers, region: region?.rawValue, logger: logger, fetch: fetch)
}

/// Updates the authorization header.
///
/// - Parameter token: The new JWT token sent in the authorization header.
Expand Down Expand Up @@ -256,7 +238,7 @@ public final class FunctionsClient: Sendable {
)

if let region = options.region ?? region {
request.headers[.xRegion] = region
request.headers[.xRegion] = region.rawValue
}

return request
Expand Down
79 changes: 25 additions & 54 deletions Sources/Functions/Types.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public struct FunctionInvokeOptions: Sendable {
/// Body data to be sent with the function invocation.
let body: Data?
/// The Region to invoke the function in.
let region: String?
let region: FunctionRegion?
/// The query to be included in the function invocation.
let query: [URLQueryItem]

Expand All @@ -40,12 +40,11 @@ public struct FunctionInvokeOptions: Sendable {
/// - headers: Headers to be included in the function invocation. (Default: empty dictionary)
/// - region: The Region to invoke the function in.
/// - body: The body data to be sent with the function invocation. (Default: nil)
@_disfavoredOverload
public init(
method: Method? = nil,
query: [URLQueryItem] = [],
headers: [String: String] = [:],
region: String? = nil,
region: FunctionRegion? = nil,
body: some Encodable
) {
var defaultHeaders = HTTPFields()
Expand Down Expand Up @@ -76,12 +75,11 @@ public struct FunctionInvokeOptions: Sendable {
/// - query: The query to be included in the function invocation.
/// - headers: Headers to be included in the function invocation. (Default: empty dictionary)
/// - region: The Region to invoke the function in.
@_disfavoredOverload
public init(
method: Method? = nil,
query: [URLQueryItem] = [],
headers: [String: String] = [:],
region: String? = nil
region: FunctionRegion? = nil
) {
self.method = method
self.headers = HTTPFields(headers)
Expand Down Expand Up @@ -116,56 +114,29 @@ public struct FunctionInvokeOptions: Sendable {
}
}

public enum FunctionRegion: String, Sendable {
case apNortheast1 = "ap-northeast-1"
case apNortheast2 = "ap-northeast-2"
case apSouth1 = "ap-south-1"
case apSoutheast1 = "ap-southeast-1"
case apSoutheast2 = "ap-southeast-2"
case caCentral1 = "ca-central-1"
case euCentral1 = "eu-central-1"
case euWest1 = "eu-west-1"
case euWest2 = "eu-west-2"
case euWest3 = "eu-west-3"
case saEast1 = "sa-east-1"
case usEast1 = "us-east-1"
case usWest1 = "us-west-1"
case usWest2 = "us-west-2"
}

extension FunctionInvokeOptions {
/// Initializes the `FunctionInvokeOptions` structure.
///
/// - Parameters:
/// - method: Method to use in the function invocation.
/// - headers: Headers to be included in the function invocation. (Default: empty dictionary)
/// - region: The Region to invoke the function in.
/// - body: The body data to be sent with the function invocation. (Default: nil)
public init(
method: Method? = nil,
headers: [String: String] = [:],
region: FunctionRegion? = nil,
body: some Encodable
) {
self.init(
method: method,
headers: headers,
region: region?.rawValue,
body: body
)
public struct FunctionRegion: RawRepresentable, Sendable {
public let rawValue: String
public init(rawValue: String) {
self.rawValue = rawValue
}

/// Initializes the `FunctionInvokeOptions` structure.
///
/// - Parameters:
/// - method: Method to use in the function invocation.
/// - headers: Headers to be included in the function invocation. (Default: empty dictionary)
/// - region: The Region to invoke the function in.
public init(
method: Method? = nil,
headers: [String: String] = [:],
region: FunctionRegion? = nil
) {
self.init(method: method, headers: headers, region: region?.rawValue)
public static let apNortheast1 = FunctionRegion(rawValue: "ap-northeast-1")
public static let apNortheast2 = FunctionRegion(rawValue: "ap-northeast-2")
public static let apSouth1 = FunctionRegion(rawValue: "ap-south-1")
public static let apSoutheast1 = FunctionRegion(rawValue: "ap-southeast-1")
public static let apSoutheast2 = FunctionRegion(rawValue: "ap-southeast-2")
public static let caCentral1 = FunctionRegion(rawValue: "ca-central-1")
public static let euCentral1 = FunctionRegion(rawValue: "eu-central-1")
public static let euWest1 = FunctionRegion(rawValue: "eu-west-1")
public static let euWest2 = FunctionRegion(rawValue: "eu-west-2")
public static let euWest3 = FunctionRegion(rawValue: "eu-west-3")
public static let saEast1 = FunctionRegion(rawValue: "sa-east-1")
public static let usEast1 = FunctionRegion(rawValue: "us-east-1")
public static let usWest1 = FunctionRegion(rawValue: "us-west-1")
public static let usWest2 = FunctionRegion(rawValue: "us-west-2")
}
extension FunctionRegion: ExpressibleByStringLiteral {
public init(stringLiteral value: String) {
self.init(rawValue: value)
}
}
9 changes: 2 additions & 7 deletions Sources/Supabase/Types.swift
Original file line number Diff line number Diff line change
Expand Up @@ -106,15 +106,10 @@ public struct SupabaseClientOptions: Sendable {

public struct FunctionsOptions: Sendable {
/// The Region to invoke the functions in.
public let region: String?

@_disfavoredOverload
public init(region: String? = nil) {
self.region = region
}
public let region: FunctionRegion?

public init(region: FunctionRegion? = nil) {
self.init(region: region?.rawValue)
self.region = region
}
}

Expand Down
25 changes: 23 additions & 2 deletions Tests/FunctionsTests/FunctionsClientTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ final class FunctionsClientTests: XCTestCase {
headers: [
"apikey": apiKey
],
region: region,
region: region.flatMap(FunctionRegion.init(rawValue:)),
fetch: { request in
try await self.session.data(for: request)
},
Expand All @@ -49,7 +49,7 @@ final class FunctionsClientTests: XCTestCase {
headers: ["apikey": apiKey],
region: .saEast1
)
XCTAssertEqual(client.region, "sa-east-1")
XCTAssertEqual(client.region?.rawValue, "sa-east-1")

XCTAssertEqual(client.headers[.init("apikey")!], apiKey)
XCTAssertNotNil(client.headers[.init("X-Client-Info")!])
Expand Down Expand Up @@ -203,6 +203,27 @@ final class FunctionsClientTests: XCTestCase {
try await sut.invoke("hello-world", options: .init(region: .caCentral1))
}

func testInvokeWithRegion_usingExpressibleByLiteral() async throws {
Mock(
url: url.appendingPathComponent("hello-world"),
statusCode: 200,
data: [.post: Data()]
)
.snapshotRequest {
#"""
curl \
--request POST \
--header "X-Client-Info: functions-swift/0.0.0" \
--header "apikey: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0" \
--header "x-region: ca-central-1" \
"http://localhost:5432/functions/v1/hello-world"
"""#
}
.register()

try await sut.invoke("hello-world", options: .init(region: "ca-central-1"))
}

func testInvokeWithoutRegion() async throws {
region = nil

Expand Down
2 changes: 1 addition & 1 deletion Tests/SupabaseTests/SupabaseClientTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ final class SupabaseClientTests: XCTestCase {
expectNoDifference(client.headers, client.storage.configuration.headers)
expectNoDifference(client.headers, client.rest.configuration.headers)

XCTAssertEqual(client.functions.region, "ap-northeast-1")
XCTAssertEqual(client.functions.region?.rawValue, "ap-northeast-1")

let realtimeURL = client.realtimeV2.url
XCTAssertEqual(realtimeURL.absoluteString, "https://project-ref.supabase.co/realtime/v1")
Expand Down
Loading