Skip to content
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

Updated Functions Errors #13535

Merged
merged 2 commits into from
Sep 3, 2024
Merged
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
4 changes: 2 additions & 2 deletions FirebaseFunctions/Sources/Functions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -509,7 +509,7 @@ enum FunctionsConstants {
if let error = error as NSError? {
let localError: (any Error)?
if error.domain == kGTMSessionFetcherStatusDomain {
localError = FunctionsErrorForResponse(
localError = FunctionsErrorCode.errorForResponse(
status: error.code,
body: data,
serializer: serializer
Expand All @@ -529,7 +529,7 @@ enum FunctionsConstants {
}

// Case 3: `data` is not `nil` but might specify a custom error -> throws conditionally
if let bodyError = FunctionsErrorForResponse(
if let bodyError = FunctionsErrorCode.errorForResponse(
status: 200,
body: data,
serializer: serializer
Expand Down
137 changes: 63 additions & 74 deletions FirebaseFunctions/Sources/FunctionsError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -101,45 +101,32 @@ public let FunctionsErrorDetailsKey: String = "details"
case unauthenticated = 16
}

/**
* Takes an HTTP status code and returns the corresponding `FIRFunctionsErrorCode` error code.
* This is the standard HTTP status code -> error mapping defined in:
* https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto
* - Parameter status An HTTP status code.
* - Returns: The corresponding error code, or `FIRFunctionsErrorCodeUnknown` if none.
ncooke3 marked this conversation as resolved.
Show resolved Hide resolved
*/
func FunctionsCodeForHTTPStatus(_ status: NSInteger) -> FunctionsErrorCode {
switch status {
case 200:
return .OK
case 400:
return .invalidArgument
case 401:
return .unauthenticated
case 403:
return .permissionDenied
case 404:
return .notFound
case 409:
return .alreadyExists
case 429:
return .resourceExhausted
case 499:
return .cancelled
case 500:
return .internal
case 501:
return .unimplemented
case 503:
return .unavailable
case 504:
return .deadlineExceeded
default:
return .internal
extension FunctionsErrorCode {
/// Takes an HTTP status code and returns the corresponding `FIRFunctionsErrorCode` error code.
///
/// + This is the standard HTTP status code -> error mapping defined in:
/// https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto
///
/// - Parameter status: An HTTP status code.
/// - Returns: A `FunctionsErrorCode`. Falls back to `internal` for unknown status codes.
static func errorCode(forHTTPStatus status: Int) -> Self {
switch status {
case 200: .OK
case 400: .invalidArgument
case 401: .unauthenticated
case 403: .permissionDenied
case 404: .notFound
case 409: .alreadyExists
case 429: .resourceExhausted
case 499: .cancelled
case 500: .internal
case 501: .unimplemented
case 503: .unavailable
case 504: .deadlineExceeded
default: .internal
}
}
}

extension FunctionsErrorCode {
static func errorCode(forName name: String) -> FunctionsErrorCode {
switch name {
case "OK": return .OK
Expand Down Expand Up @@ -207,53 +194,55 @@ extension FunctionsErrorCode {
code: rawValue,
userInfo: userInfo ?? [NSLocalizedDescriptionKey: descriptionForErrorCode])
}
}

func FunctionsErrorForResponse(status: NSInteger,
static func errorForResponse(status: Int,
body: Data?,
serializer: FunctionsSerializer) -> NSError? {
// Start with reasonable defaults from the status code.
var code = FunctionsCodeForHTTPStatus(status)
var description = code.descriptionForErrorCode

var details: AnyObject?
// Start with reasonable defaults from the status code.
var code = FunctionsErrorCode.errorCode(forHTTPStatus: status)
var description = code.descriptionForErrorCode
var details: AnyObject?

// Then look through the body for explicit details.
if let body,
let json = try? JSONSerialization.jsonObject(with: body) as? NSDictionary,
let errorDetails = json["error"] as? NSDictionary {
if let status = errorDetails["status"] as? String {
code = .errorCode(forName: status)

// If the code in the body is invalid, treat the whole response as malformed.
guard code != .internal else {
return code.generatedError(userInfo: nil)
}
}

// Then look through the body for explicit details.
if let body,
let json = try? JSONSerialization.jsonObject(with: body) as? NSDictionary,
let errorDetails = json["error"] as? NSDictionary {
if let status = errorDetails["status"] as? String {
code = FunctionsErrorCode.errorCode(forName: status)
if let message = errorDetails["message"] as? String {
description = message
} else {
description = code.descriptionForErrorCode
}

// If the code in the body is invalid, treat the whole response as malformed.
guard code != .internal else {
return code.generatedError(userInfo: nil)
details = errorDetails["details"] as AnyObject?
// Update `details` only if decoding succeeds;
// otherwise, keep the original object.
if let innerDetails = details,
let decodedDetails = try? serializer.decode(innerDetails) {
details = decodedDetails
}
}

if let message = errorDetails["message"] as? String {
description = message
} else {
description = code.descriptionForErrorCode
if code == .OK {
// Technically, there's an edge case where a developer could explicitly return an error code
// of
// OK, and we will treat it as success, but that seems reasonable.
return nil
}

details = errorDetails["details"] as AnyObject?
if let innerDetails = details {
// Just ignore the details if there an error decoding them.
details = try? serializer.decode(innerDetails)
var userInfo = [String: Any]()
userInfo[NSLocalizedDescriptionKey] = description
if let details {
userInfo[FunctionsErrorDetailsKey] = details
}
return code.generatedError(userInfo: userInfo)
}

if code == .OK {
// Technically, there's an edge case where a developer could explicitly return an error code of
// OK, and we will treat it as success, but that seems reasonable.
return nil
}

var userInfo = [String: Any]()
userInfo[NSLocalizedDescriptionKey] = description
if let details {
userInfo[FunctionsErrorDetailsKey] = details
}
return code.generatedError(userInfo: userInfo)
}
Loading