Skip to content

Commit 0b3f7ff

Browse files
committed
Add initial implementation of FunctionsClient
1 parent 8a86ebd commit 0b3f7ff

File tree

5 files changed

+139
-10
lines changed

5 files changed

+139
-10
lines changed

Package.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@ let package = Package(
1919
targets: [
2020
.target(
2121
name: "Functions",
22-
dependencies: []),
22+
dependencies: []
23+
),
2324
.testTarget(
2425
name: "FunctionsTests",
25-
dependencies: ["Functions"]),
26+
dependencies: ["Functions"]
27+
),
2628
]
2729
)
Lines changed: 90 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,93 @@
1-
public struct functions_swift {
2-
public private(set) var text = "Hello, World!"
1+
import Foundation
32

4-
public init() {
3+
public final class FunctionsClient {
4+
let url: URL
5+
var headers: [String: String]
6+
7+
private let session: URLSession = .shared
8+
9+
public init(url: URL, headers: [String: String] = [:]) {
10+
self.url = url
11+
self.headers = headers
12+
self.headers["X-Client-Info"] = "functions-swift/\(version)"
13+
}
14+
15+
/// Updates the authorization header.
16+
/// - Parameter token: the new JWT token sent in the authorization header
17+
public func setAuth(token: String) {
18+
headers["Authorization"] = "Bearer \(token)"
19+
}
20+
21+
/// Invokes a function.
22+
/// - Parameters:
23+
/// - functionName: the name of the function to invoke.
24+
public func invoke<Response>(
25+
functionName: String,
26+
invokeOptions: FunctionInvokeOptions = .init(),
27+
decode: (Data, HTTPURLResponse) throws -> Response
28+
) async throws -> Response {
29+
let (data, response) = try await rawInvoke(
30+
functionName: functionName,
31+
invokeOptions: invokeOptions
32+
)
33+
return try decode(data, response)
34+
}
35+
36+
/// Invokes a function.
37+
/// - Parameters:
38+
/// - functionName: the name of the function to invoke.
39+
public func invoke<T: Decodable>(
40+
functionName: String,
41+
invokeOptions: FunctionInvokeOptions = .init(),
42+
decoder: JSONDecoder = JSONDecoder()
43+
) async throws -> T {
44+
try await invoke(
45+
functionName: functionName,
46+
invokeOptions: invokeOptions,
47+
decode: { data, _ in try decoder.decode(T.self, from: data) }
48+
)
49+
}
50+
51+
/// Invokes a function.
52+
/// - Parameters:
53+
/// - functionName: the name of the function to invoke.
54+
public func invoke(
55+
functionName: String,
56+
invokeOptions: FunctionInvokeOptions = .init()
57+
) async throws {
58+
try await invoke(
59+
functionName: functionName,
60+
invokeOptions: invokeOptions,
61+
decode: { _, _ in () }
62+
)
63+
}
64+
65+
private func rawInvoke(
66+
functionName: String,
67+
invokeOptions: FunctionInvokeOptions
68+
) async throws -> (Data, HTTPURLResponse) {
69+
let body = invokeOptions.body
70+
let invokeHeaders = invokeOptions.headers
71+
72+
var request = URLRequest(url: url.appendingPathComponent(functionName))
73+
request.httpMethod = "POST"
74+
request.httpBody = body
75+
request.allHTTPHeaderFields = invokeHeaders.merging(headers) { invokeHeader, _ in invokeHeader }
76+
77+
let (data, response) = try await session.data(for: request)
78+
guard let httpResponse = response as? HTTPURLResponse else {
79+
throw URLError(.badServerResponse)
80+
}
81+
82+
guard 200 ..< 300 ~= httpResponse.statusCode else {
83+
throw FunctionsError.httpError(code: httpResponse.statusCode, data: data)
584
}
85+
86+
let isRelayError = httpResponse.value(forHTTPHeaderField: "x-relay-error") == "true"
87+
if isRelayError {
88+
throw FunctionsError.relayError
89+
}
90+
91+
return (data, httpResponse)
92+
}
693
}

Sources/Functions/Types.swift

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import Foundation
2+
3+
public enum FunctionsError: Error, LocalizedError {
4+
case relayError
5+
case httpError(code: Int, data: Data)
6+
7+
public var errorDescription: String? {
8+
switch self {
9+
case .relayError: return "Relay Error invoking the Edge Function"
10+
case let .httpError(code, _): return "Edge Function returned a non-2xx status code: \(code)"
11+
}
12+
}
13+
}
14+
15+
public struct FunctionInvokeOptions {
16+
let headers: [String: String]
17+
let body: Data?
18+
19+
public init<T: Encodable>(headers: [String: String] = [:], body: T) {
20+
var headers = headers
21+
22+
switch body {
23+
case let string as String:
24+
headers["Content-Type"] = "text/plain"
25+
self.body = string.data(using: .utf8)
26+
case let data as Data:
27+
headers["Content-Type"] = "application/octet-stream"
28+
self.body = data
29+
default:
30+
// default, assume this is JSON
31+
headers["Content-Type"] = "application/json"
32+
self.body = try? JSONEncoder().encode(body)
33+
}
34+
35+
self.headers = headers
36+
}
37+
38+
public init(headers: [String: String] = [:]) {
39+
self.headers = headers
40+
body = nil
41+
}
42+
}

Sources/Functions/Version.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
let version = "0.0.1"
Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
import XCTest
21
@testable import Functions
2+
import XCTest
33

4-
final class functions_swiftTests: XCTestCase {
5-
func testExample() throws {
6-
}
7-
}
4+
final class FunctionsTests: XCTestCase {}

0 commit comments

Comments
 (0)