Skip to content

Commit e76d746

Browse files
authored
Refactored Function Calling (#13476)
1 parent bcc293c commit e76d746

File tree

1 file changed

+64
-70
lines changed

1 file changed

+64
-70
lines changed

FirebaseFunctions/Sources/Functions.swift

Lines changed: 64 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -441,13 +441,10 @@ enum FunctionsConstants {
441441
timeoutInterval: timeout)
442442
let fetcher = fetcherService.fetcher(with: request)
443443

444-
// Encode the data in the body.
445-
let data = data ?? NSNull()
446-
// Force unwrap to match the old invalid argument thrown.
447-
let encoded = try! serializer.encode(data)
448-
let body = ["data": encoded]
449-
450444
do {
445+
let data = data ?? NSNull()
446+
let encoded = try serializer.encode(data)
447+
let body = ["data": encoded]
451448
let payload = try JSONSerialization.data(withJSONObject: body)
452449
fetcher.bodyData = payload
453450
} catch {
@@ -488,79 +485,76 @@ enum FunctionsConstants {
488485
fetcher.allowedInsecureSchemes = ["http"]
489486
}
490487

491-
fetcher.beginFetch { data, error in
492-
// If there was an HTTP error, convert it to our own error domain.
493-
var localError: Error?
494-
if let error = error as NSError? {
495-
if error.domain == kGTMSessionFetcherStatusDomain {
496-
localError = FunctionsErrorForResponse(
497-
status: error.code,
498-
body: data,
499-
serializer: self.serializer
500-
)
501-
} else if error.domain == NSURLErrorDomain, error.code == NSURLErrorTimedOut {
502-
localError = FunctionsErrorCode.deadlineExceeded.generatedError(userInfo: nil)
503-
}
504-
// If there was an error, report it to the user and stop.
505-
if let localError {
506-
completion(.failure(localError))
507-
} else {
508-
completion(.failure(error))
509-
}
510-
return
511-
} else {
512-
// If there wasn't an HTTP error, see if there was an error in the body.
513-
if let bodyError = FunctionsErrorForResponse(
514-
status: 200,
515-
body: data,
516-
serializer: self.serializer
517-
) {
518-
completion(.failure(bodyError))
519-
return
520-
}
521-
}
522-
523-
// Porting: this check is new since we didn't previously check if `data` was nil.
524-
guard let data = data else {
525-
completion(.failure(FunctionsErrorCode.internal.generatedError(userInfo: nil)))
526-
return
527-
}
528-
529-
let responseJSONObject: Any
488+
fetcher.beginFetch { [self] data, error in
489+
let result: Result<HTTPSCallableResult, any Error>
530490
do {
531-
responseJSONObject = try JSONSerialization.jsonObject(with: data)
491+
let data = try responseData(data: data, error: error)
492+
let json = try responseDataJSON(from: data)
493+
// TODO: Refactor `decode(_:)` so it either returns a non-optional object or throws
494+
let payload = try serializer.decode(json)
495+
// TODO: Remove `as Any` once `decode(_:)` is refactored
496+
result = .success(HTTPSCallableResult(data: payload as Any))
532497
} catch {
533-
completion(.failure(error))
534-
return
498+
result = .failure(error)
535499
}
536500

537-
guard let responseJSON = responseJSONObject as? NSDictionary else {
538-
let userInfo = [NSLocalizedDescriptionKey: "Response was not a dictionary."]
539-
completion(.failure(FunctionsErrorCode.internal.generatedError(userInfo: userInfo)))
540-
return
501+
DispatchQueue.main.async {
502+
completion(result)
541503
}
504+
}
505+
}
542506

543-
// TODO(klimt): Allow "result" instead of "data" for now, for backwards compatibility.
544-
let dataJSON = responseJSON["data"] ?? responseJSON["result"]
545-
guard let dataJSON = dataJSON as AnyObject? else {
546-
let userInfo = [NSLocalizedDescriptionKey: "Response is missing data field."]
547-
completion(.failure(FunctionsErrorCode.internal.generatedError(userInfo: userInfo)))
548-
return
507+
private func responseData(data: Data?, error: (any Error)?) throws -> Data {
508+
// Case 1: `error` is not `nil` -> always throws
509+
if let error = error as NSError? {
510+
let localError: (any Error)?
511+
if error.domain == kGTMSessionFetcherStatusDomain {
512+
localError = FunctionsErrorForResponse(
513+
status: error.code,
514+
body: data,
515+
serializer: serializer
516+
)
517+
} else if error.domain == NSURLErrorDomain, error.code == NSURLErrorTimedOut {
518+
localError = FunctionsErrorCode.deadlineExceeded.generatedError(userInfo: nil)
519+
} else {
520+
localError = nil
549521
}
550522

551-
let resultData: Any?
552-
do {
553-
resultData = try self.serializer.decode(dataJSON)
554-
} catch {
555-
completion(.failure(error))
556-
return
557-
}
523+
throw localError ?? error
524+
}
525+
526+
// Case 2: `data` is `nil` -> always throws
527+
guard let data else {
528+
throw FunctionsErrorCode.internal.generatedError(userInfo: nil)
529+
}
558530

559-
// TODO: Force unwrap... gross
560-
let result = HTTPSCallableResult(data: resultData!)
561-
// TODO: This copied comment appears to be incorrect - it's impossible to have a nil callable result
562-
// If there's no result field, this will return nil, which is fine.
563-
completion(.success(result))
531+
// Case 3: `data` is not `nil` but might specify a custom error -> throws conditionally
532+
if let bodyError = FunctionsErrorForResponse(
533+
status: 200,
534+
body: data,
535+
serializer: serializer
536+
) {
537+
throw bodyError
564538
}
539+
540+
// Case 4: `error` is `nil`; `data` is not `nil`; `data` doesn’t specify an error -> OK
541+
return data
542+
}
543+
544+
private func responseDataJSON(from data: Data) throws -> Any {
545+
let responseJSONObject = try JSONSerialization.jsonObject(with: data)
546+
547+
guard let responseJSON = responseJSONObject as? NSDictionary else {
548+
let userInfo = [NSLocalizedDescriptionKey: "Response was not a dictionary."]
549+
throw FunctionsErrorCode.internal.generatedError(userInfo: userInfo)
550+
}
551+
552+
// `result` is checked for backwards compatibility:
553+
guard let dataJSON = responseJSON["data"] ?? responseJSON["result"] else {
554+
let userInfo = [NSLocalizedDescriptionKey: "Response is missing data field."]
555+
throw FunctionsErrorCode.internal.generatedError(userInfo: userInfo)
556+
}
557+
558+
return dataJSON
565559
}
566560
}

0 commit comments

Comments
 (0)