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

[Auth] Migrate setFields behavior to Response initializer #14023

Merged
merged 3 commits into from
Nov 6, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
88 changes: 48 additions & 40 deletions FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift
Original file line number Diff line number Diff line change
Expand Up @@ -251,66 +251,74 @@ class AuthBackend: AuthBackendProtocol {
}
dictionary = decodedDictionary

var response = T.Response()
let responseResult = Result {
try T.Response(dictionary: dictionary)
}

// At this point we either have an error with successfully decoded
// details in the body, or we have a response which must pass further
// validation before we know it's truly successful. We deal with the
// case where we have an error with successfully decoded error details
// first:
if error != nil {
if let errorDictionary = dictionary["error"] as? [String: AnyHashable] {
if let errorMessage = errorDictionary["message"] as? String {
if let clientError = Self.clientError(
withServerErrorMessage: errorMessage,
errorDictionary: errorDictionary,
response: response,
error: error
) {
throw clientError
switch responseResult {
case let .success(response):
try propogateError(error, dictionary: dictionary, response: response)
// In case returnIDPCredential of a verifyAssertion request is set to
// @YES, the server may return a 200 with a response that may contain a
// server error.
if let verifyAssertionRequest = request as? VerifyAssertionRequest {
if verifyAssertionRequest.returnIDPCredential {
if let errorMessage = dictionary["errorMessage"] as? String {
if let clientError = Self.clientError(
withServerErrorMessage: errorMessage,
errorDictionary: dictionary,
response: response,
error: error
) {
throw clientError
}
}
}
// Not a message we know, return the message directly.
throw AuthErrorUtils.unexpectedErrorResponse(
deserializedResponse: errorDictionary,
underlyingError: error
)
}
// No error message at all, return the decoded response.
return response
case let .failure(failure):
try propogateError(error, dictionary: dictionary, response: nil)
ncooke3 marked this conversation as resolved.
Show resolved Hide resolved
throw AuthErrorUtils
.unexpectedErrorResponse(deserializedResponse: dictionary, underlyingError: error)
.RPCResponseDecodingError(deserializedResponse: dictionary, underlyingError: failure)
}
}

// Finally, we try to populate the response object with the JSON values.
do {
try response.setFields(dictionary: dictionary)
} catch {
throw AuthErrorUtils
.RPCResponseDecodingError(deserializedResponse: dictionary, underlyingError: error)
private func propogateError(_ error: Error?, dictionary: [String: AnyHashable],
response: AuthRPCResponse?) throws {
guard let error else {
return
}
// In case returnIDPCredential of a verifyAssertion request is set to
// @YES, the server may return a 200 with a response that may contain a
// server error.
if let verifyAssertionRequest = request as? VerifyAssertionRequest {
if verifyAssertionRequest.returnIDPCredential {
if let errorMessage = dictionary["errorMessage"] as? String {
if let clientError = Self.clientError(
withServerErrorMessage: errorMessage,
errorDictionary: dictionary,
response: response,
error: error
) {
throw clientError
}

if let errorDictionary = dictionary["error"] as? [String: AnyHashable] {
if let errorMessage = errorDictionary["message"] as? String {
if let clientError = Self.clientError(
withServerErrorMessage: errorMessage,
errorDictionary: errorDictionary,
response: response,
error: error
) {
throw clientError
}
}
// Not a message we know, return the message directly.
throw AuthErrorUtils.unexpectedErrorResponse(
deserializedResponse: errorDictionary,
underlyingError: error
)
}
return response
// No error message at all, return the decoded response.
throw AuthErrorUtils
.unexpectedErrorResponse(deserializedResponse: dictionary, underlyingError: error)
}

private static func clientError(withServerErrorMessage serverErrorMessage: String,
errorDictionary: [String: Any],
response: AuthRPCResponse,
response: AuthRPCResponse?,
error: Error?) -> Error? {
let split = serverErrorMessage.split(separator: ":")
let shortErrorMessage = split.first?.trimmingCharacters(in: .whitespacesAndNewlines)
Expand Down
7 changes: 2 additions & 5 deletions FirebaseAuth/Sources/Swift/Backend/AuthRPCResponse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,11 @@
import Foundation

protocol AuthRPCResponse: Sendable {
/// Bare initializer for a response.
init()

/// Sets the response instance from the decoded JSON response.
/// initializes the response instance from the decoded JSON response.
ncooke3 marked this conversation as resolved.
Show resolved Hide resolved
/// - Parameter dictionary: The dictionary decoded from HTTP JSON response.
/// - Parameter error: An out field for an error which occurred constructing the request.
/// - Returns: Whether the operation was successful or not.
mutating func setFields(dictionary: [String: AnyHashable]) throws
init(dictionary: [String: AnyHashable]) throws

/// This optional method allows response classes to create client errors given a short error
/// message and a detail error message from the server.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ struct CreateAuthURIResponse: AuthRPCResponse {
/// A list of sign-in methods available for the passed identifier.
var signinMethods: [String] = []

mutating func setFields(dictionary: [String: AnyHashable]) throws {
init(dictionary: [String: AnyHashable]) throws {
providerID = dictionary["providerId"] as? String
authURI = dictionary["authUri"] as? String
registered = dictionary["registered"] as? Bool ?? false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@ import Foundation
///
/// See https://developers.google.com/identity/toolkit/web/reference/relyingparty/deleteAccount
struct DeleteAccountResponse: AuthRPCResponse {
mutating func setFields(dictionary: [String: AnyHashable]) throws {}
init(dictionary: [String: AnyHashable]) throws {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ struct EmailLinkSignInResponse: AuthRPCResponse, AuthMFAResponse {
/// Info on which multi-factor authentication providers are enabled.
private(set) var mfaInfo: [AuthProtoMFAEnrollment]?

mutating func setFields(dictionary: [String: AnyHashable]) throws {
init(dictionary: [String: AnyHashable]) throws {
email = dictionary["email"] as? String
idToken = dictionary["idToken"] as? String
isNewUser = dictionary["isNewUser"] as? Bool ?? false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ struct GetAccountInfoResponse: AuthRPCResponse {
/// The requested users' profiles.
var users: [Self.User]?

mutating func setFields(dictionary: [String: AnyHashable]) throws {
init(dictionary: [String: AnyHashable]) throws {
guard let usersData = dictionary["users"] as? [[String: AnyHashable]] else {
throw AuthErrorUtils.unexpectedResponse(deserializedResponse: dictionary)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ private let kOOBCodeKey = "oobCode"
struct GetOOBConfirmationCodeResponse: AuthRPCResponse {
var OOBCode: String?

mutating func setFields(dictionary: [String: AnyHashable]) throws {
init(dictionary: [String: AnyHashable]) throws {
OOBCode = dictionary[kOOBCodeKey] as? String
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ struct GetProjectConfigResponse: AuthRPCResponse {

var authorizedDomains: [String]?

mutating func setFields(dictionary: [String: AnyHashable]) throws {
init(dictionary: [String: AnyHashable]) throws {
projectID = dictionary["projectId"] as? String
if let authorizedDomains = dictionary["authorizedDomains"] as? String,
let data = authorizedDomains.data(using: .utf8) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ struct GetRecaptchaConfigResponse: AuthRPCResponse {
private(set) var recaptchaKey: String?
private(set) var enforcementState: [[String: String]]?

mutating func setFields(dictionary: [String: AnyHashable]) throws {
init(dictionary: [String: AnyHashable]) throws {
recaptchaKey = dictionary["recaptchaKey"] as? String
enforcementState = dictionary["recaptchaEnforcementState"] as? [[String: String]]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ struct FinalizeMFAEnrollmentResponse: AuthRPCResponse {
private(set) var phoneSessionInfo: AuthProtoFinalizeMFAPhoneResponseInfo?
private(set) var totpSessionInfo: AuthProtoFinalizeMFATOTPEnrollmentResponseInfo?

mutating func setFields(dictionary: [String: AnyHashable]) throws {
init(dictionary: [String: AnyHashable]) throws {
idToken = dictionary["idToken"] as? String
refreshToken = dictionary["refreshToken"] as? String

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ struct StartMFAEnrollmentResponse: AuthRPCResponse {
private(set) var phoneSessionInfo: AuthProtoStartMFAPhoneResponseInfo?
private(set) var totpSessionInfo: AuthProtoStartMFATOTPEnrollmentResponseInfo?

mutating func setFields(dictionary: [String: AnyHashable]) throws {
init(dictionary: [String: AnyHashable]) throws {
if let data = dictionary["phoneSessionInfo"] as? [String: AnyHashable] {
phoneSessionInfo = AuthProtoStartMFAPhoneResponseInfo(dictionary: data)
} else if let data = dictionary["totpSessionInfo"] as? [String: AnyHashable] {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ struct FinalizeMFASignInResponse: AuthRPCResponse {
var IDToken: String?
var refreshToken: String?

mutating func setFields(dictionary: [String: AnyHashable]) throws {
init(dictionary: [String: AnyHashable]) throws {
IDToken = dictionary["idToken"] as? String
refreshToken = dictionary["refreshToken"] as? String
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import Foundation
struct StartMFASignInResponse: AuthRPCResponse {
var responseInfo: AuthProtoStartMFAPhoneResponseInfo?

mutating func setFields(dictionary: [String: AnyHashable]) throws {
init(dictionary: [String: AnyHashable]) throws {
if let data = dictionary["phoneResponseInfo"] as? [String: AnyHashable] {
responseInfo = AuthProtoStartMFAPhoneResponseInfo(dictionary: data)
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ struct WithdrawMFAResponse: AuthRPCResponse {
var idToken: String?
var refreshToken: String?

mutating func setFields(dictionary: [String: AnyHashable]) throws {
init(dictionary: [String: AnyHashable]) throws {
idToken = dictionary["idToken"] as? String
refreshToken = dictionary["refreshToken"] as? String
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ struct ResetPasswordResponse: AuthRPCResponse {
/// The type of request as returned by the backend.
var requestType: String?

mutating func setFields(dictionary: [String: AnyHashable]) throws {
init(dictionary: [String: AnyHashable]) throws {
email = dictionary["email"] as? String
requestType = dictionary["requestType"] as? String
verifiedEmail = dictionary["newEmail"] as? String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import Foundation

struct RevokeTokenResponse: AuthRPCResponse {
mutating func setFields(dictionary: [String: AnyHashable]) throws {
init(dictionary: [String: AnyHashable]) throws {
// Nothing to set or throw.
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ struct SecureTokenResponse: AuthRPCResponse {

var expectedKind: String? { nil }

mutating func setFields(dictionary: [String: AnyHashable]) throws {
init(dictionary: [String: AnyHashable]) throws {
refreshToken = dictionary[kRefreshTokenKey] as? String
self.accessToken = dictionary[kAccessTokenKey] as? String
idToken = dictionary[kIDTokenKey] as? String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import Foundation
struct SendVerificationCodeResponse: AuthRPCResponse {
var verificationID: String?

mutating func setFields(dictionary: [String: AnyHashable]) throws {
init(dictionary: [String: AnyHashable]) throws {
verificationID = dictionary["sessionInfo"] as? String
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ struct SetAccountInfoResponse: AuthRPCResponse {
/// The refresh token from Secure Token Service.
var refreshToken: String?

mutating func setFields(dictionary: [String: AnyHashable]) throws {
init(dictionary: [String: AnyHashable]) throws {
email = dictionary["email"] as? String
displayName = dictionary["displayName"] as? String
idToken = dictionary["idToken"] as? String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ struct SignInWithGameCenterResponse: AuthRPCResponse {
var isNewUser: Bool = false
var displayName: String?

mutating func setFields(dictionary: [String: AnyHashable]) throws {
init(dictionary: [String: AnyHashable]) throws {
idToken = dictionary["idToken"] as? String
refreshToken = dictionary["refreshToken"] as? String
localID = dictionary["localId"] as? String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ struct SignUpNewUserResponse: AuthRPCResponse {
/// The refresh token from Secure Token Service.
var refreshToken: String?

mutating func setFields(dictionary: [String: AnyHashable]) throws {
init(dictionary: [String: AnyHashable]) throws {
idToken = dictionary["idToken"] as? String
if let approximateExpirationDate = dictionary["expiresIn"] as? String {
self
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ struct VerifyAssertionResponse: AuthRPCResponse, AuthMFAResponse {

private(set) var mfaInfo: [AuthProtoMFAEnrollment]?

mutating func setFields(dictionary: [String: AnyHashable]) throws {
init(dictionary: [String: AnyHashable]) throws {
federatedID = dictionary["federatedId"] as? String
providerID = dictionary["providerId"] as? String
localID = dictionary["localId"] as? String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ struct VerifyCustomTokenResponse: AuthRPCResponse {
/// Flag indicating that the user signing in is a new user and not a returning user.
var isNewUser: Bool = false

mutating func setFields(dictionary: [String: AnyHashable]) throws {
init(dictionary: [String: AnyHashable]) throws {
idToken = dictionary["idToken"] as? String
if let dateString = dictionary["expiresIn"] as? NSString {
approximateExpirationDate = Date(timeIntervalSinceNow: dateString.doubleValue)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ struct VerifyPasswordResponse: AuthRPCResponse, AuthMFAResponse {

private(set) var mfaInfo: [AuthProtoMFAEnrollment]?

mutating func setFields(dictionary: [String: AnyHashable]) throws {
init(dictionary: [String: AnyHashable]) throws {
localID = dictionary["localId"] as? String
email = dictionary["email"] as? String
displayName = dictionary["displayName"] as? String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ struct VerifyPhoneNumberResponse: AuthRPCResponse {
nil
}

mutating func setFields(dictionary: [String: AnyHashable]) throws {
init(dictionary: [String: AnyHashable]) throws {
idToken = dictionary["idToken"] as? String
refreshToken = dictionary["refreshToken"] as? String
isNewUser = (dictionary["isNewUser"] as? Bool) ?? false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,13 @@
import Foundation

struct VerifyClientResponse: AuthRPCResponse {
init() {}

/// Receipt that the APNS token was successfully validated with APNS.
private(set) var receipt: String?

/// The date after which delivery of the silent push notification is considered to have failed.
private(set) var suggestedTimeOutDate: Date?

mutating func setFields(dictionary: [String: AnyHashable]) throws {
init(dictionary: [String: AnyHashable]) throws {
receipt = dictionary["receipt"] as? String
let suggestedTimeout = dictionary["suggestedTimeout"]
if let string = suggestedTimeout as? String,
Expand Down
4 changes: 2 additions & 2 deletions FirebaseAuth/Tests/Unit/AuthBackendTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -713,7 +713,7 @@ class AuthBackendTests: RPCBaseTests {

private struct FakeResponse: AuthRPCResponse {
var receivedValue: String?
mutating func setFields(dictionary: [String: AnyHashable]) throws {
init(dictionary: [String: AnyHashable]) throws {
receivedValue = dictionary["TestKey"] as? String
}
}
Expand All @@ -739,7 +739,7 @@ class AuthBackendTests: RPCBaseTests {
}

private struct FakeDecodingErrorResponse: AuthRPCResponse {
mutating func setFields(dictionary: [String: AnyHashable]) throws {
init(dictionary: [String: AnyHashable]) throws {
throw NSError(domain: "dummy", code: -1)
}
}
Expand Down
Loading