1
1
import ConcurrencyExtras
2
2
import Foundation
3
3
import HTTPTypes
4
+ import HTTPTypesFoundation
5
+ import OpenAPIURLSession
4
6
5
7
#if canImport(FoundationNetworking)
6
8
import FoundationNetworking
@@ -31,6 +33,7 @@ public final class FunctionsClient: Sendable {
31
33
var headers = HTTPFields ( )
32
34
}
33
35
36
+ private let client : Client
34
37
private let http : any HTTPClientType
35
38
private let mutableState = LockIsolated ( MutableState ( ) )
36
39
private let sessionConfiguration : URLSessionConfiguration
@@ -85,6 +88,7 @@ public final class FunctionsClient: Sendable {
85
88
headers: headers,
86
89
region: region,
87
90
http: http,
91
+ client: Client ( serverURL: url, transport: URLSessionTransport ( ) ) ,
88
92
sessionConfiguration: sessionConfiguration
89
93
)
90
94
}
@@ -94,11 +98,13 @@ public final class FunctionsClient: Sendable {
94
98
headers: [ String : String ] ,
95
99
region: String ? ,
96
100
http: any HTTPClientType ,
101
+ client: Client ,
97
102
sessionConfiguration: URLSessionConfiguration = . default
98
103
) {
99
104
self . url = url
100
105
self . region = region
101
106
self . http = http
107
+ self . client = client
102
108
self . sessionConfiguration = sessionConfiguration
103
109
104
110
mutableState. withValue {
@@ -140,6 +146,39 @@ public final class FunctionsClient: Sendable {
140
146
}
141
147
}
142
148
149
+ /// Inokes a functions returns the raw response and body.
150
+ /// - Parameters:
151
+ /// - functionName: The name of the function to invoke.
152
+ /// - options: Options for invoking the function. (Default: empty `FunctionInvokeOptions`)
153
+ /// - Returns: The raw response and body.
154
+ public func invoke(
155
+ _ functionName: String ,
156
+ options: FunctionInvokeOptions = . init( )
157
+ ) async throws -> ( HTTPTypes . HTTPResponse , HTTPBody ) {
158
+ try await self . invoke ( functionName, options: options) { ( $0, $1) }
159
+ }
160
+
161
+ /// Invokes a function and decodes the response.
162
+ ///
163
+ /// - Parameters:
164
+ /// - functionName: The name of the function to invoke.
165
+ /// - options: Options for invoking the function. (Default: empty `FunctionInvokeOptions`)
166
+ /// - decode: A closure to decode the response data and `HTTPResponse` into a `Response`
167
+ /// object.
168
+ /// - Returns: The decoded `Response` object.
169
+ public func invoke< Response> (
170
+ _ functionName: String ,
171
+ options: FunctionInvokeOptions = . init( ) ,
172
+ decode: ( HTTPTypes . HTTPResponse , HTTPBody ) async throws -> Response
173
+ ) async throws -> Response {
174
+ let ( _, response, body) = try await _invoke (
175
+ functionName: functionName,
176
+ invokeOptions: options
177
+ )
178
+
179
+ return try await decode ( response, body)
180
+ }
181
+
143
182
/// Invokes a function and decodes the response.
144
183
///
145
184
/// - Parameters:
@@ -148,15 +187,20 @@ public final class FunctionsClient: Sendable {
148
187
/// - decode: A closure to decode the response data and HTTPURLResponse into a `Response`
149
188
/// object.
150
189
/// - Returns: The decoded `Response` object.
190
+ @available ( * , deprecated, message: " Use `invoke` with HTTPBody instead. " )
151
191
public func invoke< Response> (
152
192
_ functionName: String ,
153
193
options: FunctionInvokeOptions = . init( ) ,
154
194
decode: ( Data , HTTPURLResponse ) throws -> Response
155
195
) async throws -> Response {
156
- let response = try await rawInvoke (
157
- functionName: functionName, invokeOptions: options
196
+ let ( request, response, body) = try await _invoke (
197
+ functionName: functionName,
198
+ invokeOptions: options
158
199
)
159
- return try decode ( response. data, response. underlyingResponse)
200
+
201
+ let data = try await Data ( collecting: body, upTo: . max)
202
+
203
+ return try decode ( data, HTTPURLResponse ( httpResponse: response, url: request. url ?? self . url) !)
160
204
}
161
205
162
206
/// Invokes a function and decodes the response as a specific type.
@@ -171,8 +215,9 @@ public final class FunctionsClient: Sendable {
171
215
options: FunctionInvokeOptions = . init( ) ,
172
216
decoder: JSONDecoder = JSONDecoder ( )
173
217
) async throws -> T {
174
- try await invoke ( functionName, options: options) { data, _ in
175
- try decoder. decode ( T . self, from: data)
218
+ try await invoke ( functionName, options: options) { _, body in
219
+ let data = try await Data ( collecting: body, upTo: . max)
220
+ return try decoder. decode ( T . self, from: data)
176
221
}
177
222
}
178
223
@@ -185,124 +230,47 @@ public final class FunctionsClient: Sendable {
185
230
_ functionName: String ,
186
231
options: FunctionInvokeOptions = . init( )
187
232
) async throws {
188
- try await invoke ( functionName, options: options) { _, _ in ( ) }
233
+ try await invoke ( functionName, options: options) { ( _, _: HTTPBody ) in ( ) }
189
234
}
190
235
191
- private func rawInvoke (
236
+ private func _invoke (
192
237
functionName: String ,
193
238
invokeOptions: FunctionInvokeOptions
194
- ) async throws -> Helpers . HTTPResponse {
195
- let request = buildRequest ( functionName: functionName, options: invokeOptions)
196
- let response = try await http . send ( request)
239
+ ) async throws -> ( HTTPTypes . HTTPRequest , HTTPTypes . HTTPResponse , HTTPBody ) {
240
+ let ( request, requestBody ) = buildRequest ( functionName: functionName, options: invokeOptions)
241
+ let ( response, responseBody ) = try await client . send ( request, body : requestBody )
197
242
198
- guard 200 ..< 300 ~= response. statusCode else {
199
- throw FunctionsError . httpError ( code: response. statusCode, data: response. data)
243
+ guard response. status. kind == . successful else {
244
+ let data = try await Data ( collecting: responseBody, upTo: . max)
245
+ throw FunctionsError . httpError ( code: response. status. code, data: data)
200
246
}
201
247
202
- let isRelayError = response. headers [ . xRelayError] == " true "
248
+ let isRelayError = response. headerFields [ . xRelayError] == " true "
203
249
if isRelayError {
204
250
throw FunctionsError . relayError
205
251
}
206
252
207
- return response
208
- }
209
-
210
- /// Invokes a function with streamed response.
211
- ///
212
- /// Function MUST return a `text/event-stream` content type for this method to work.
213
- ///
214
- /// - Parameters:
215
- /// - functionName: The name of the function to invoke.
216
- /// - invokeOptions: Options for invoking the function.
217
- /// - Returns: A stream of Data.
218
- ///
219
- /// - Warning: Experimental method.
220
- /// - Note: This method doesn't use the same underlying `URLSession` as the remaining methods in the library.
221
- public func _invokeWithStreamedResponse(
222
- _ functionName: String ,
223
- options invokeOptions: FunctionInvokeOptions = . init( )
224
- ) -> AsyncThrowingStream < Data , any Error > {
225
- let ( stream, continuation) = AsyncThrowingStream < Data , any Error > . makeStream ( )
226
- let delegate = StreamResponseDelegate ( continuation: continuation)
227
-
228
- let session = URLSession (
229
- configuration: sessionConfiguration, delegate: delegate, delegateQueue: nil )
230
-
231
- let urlRequest = buildRequest ( functionName: functionName, options: invokeOptions) . urlRequest
232
-
233
- let task = session. dataTask ( with: urlRequest)
234
- task. resume ( )
235
-
236
- continuation. onTermination = { _ in
237
- task. cancel ( )
238
-
239
- // Hold a strong reference to delegate until continuation terminates.
240
- _ = delegate
241
- }
242
-
243
- return stream
253
+ return ( request, response, responseBody)
244
254
}
245
255
246
- private func buildRequest( functionName : String , options : FunctionInvokeOptions )
247
- -> Helpers . HTTPRequest
248
- {
249
- var request = HTTPRequest (
250
- url : url . appendingPathComponent ( functionName ) ,
256
+ private func buildRequest(
257
+ functionName : String ,
258
+ options : FunctionInvokeOptions
259
+ ) -> ( HTTPTypes . HTTPRequest , HTTPBody ? ) {
260
+ var request = HTTPTypes . HTTPRequest (
251
261
method: FunctionInvokeOptions . httpMethod ( options. method) ?? . post,
252
- query: options. query,
253
- headers: mutableState. headers. merging ( with: options. headers) ,
254
- body: options. body,
255
- timeoutInterval: FunctionsClient . requestIdleTimeout
262
+ url: url. appendingPathComponent ( functionName) . appendingQueryItems ( options. query) ,
263
+ headerFields: mutableState. headers. merging ( with: options. headers)
256
264
)
257
265
258
- if let region = options. region ?? region {
259
- request. headers [ . xRegion] = region
260
- }
261
-
262
- return request
263
- }
264
- }
265
-
266
- final class StreamResponseDelegate : NSObject , URLSessionDataDelegate , Sendable {
267
- let continuation : AsyncThrowingStream < Data , any Error > . Continuation
268
-
269
- init ( continuation: AsyncThrowingStream < Data , any Error > . Continuation ) {
270
- self . continuation = continuation
271
- }
272
-
273
- func urlSession( _: URLSession , dataTask _: URLSessionDataTask , didReceive data: Data ) {
274
- continuation. yield ( data)
275
- }
276
-
277
- func urlSession( _: URLSession , task _: URLSessionTask , didCompleteWithError error: ( any Error ) ? ) {
278
- continuation. finish ( throwing: error)
279
- }
266
+ // TODO: Check how to assign FunctionsClient.requestIdleTimeout
280
267
281
- func urlSession(
282
- _: URLSession , dataTask _: URLSessionDataTask , didReceive response: URLResponse ,
283
- completionHandler: @escaping ( URLSession . ResponseDisposition ) -> Void
284
- ) {
285
- defer {
286
- completionHandler ( . allow)
268
+ if let region = options. region ?? region {
269
+ request. headerFields [ . xRegion] = region
287
270
}
288
271
289
- guard let httpResponse = response as? HTTPURLResponse else {
290
- continuation. finish ( throwing: URLError ( . badServerResponse) )
291
- return
292
- }
272
+ let body = options. body. map ( HTTPBody . init)
293
273
294
- guard 200 ..< 300 ~= httpResponse. statusCode else {
295
- let error = FunctionsError . httpError (
296
- code: httpResponse. statusCode,
297
- data: Data ( )
298
- )
299
- continuation. finish ( throwing: error)
300
- return
301
- }
302
-
303
- let isRelayError = httpResponse. value ( forHTTPHeaderField: " x-relay-error " ) == " true "
304
- if isRelayError {
305
- continuation. finish ( throwing: FunctionsError . relayError)
306
- }
274
+ return ( request, body)
307
275
}
308
276
}
0 commit comments