Skip to content

Commit

Permalink
Cleanup error code types
Browse files Browse the repository at this point in the history
Changes:
- Allow consumers to cast `Error` instances received to `AuthErrorCode`
  or `FirestoreErrorCode`, corresponding to the API used.
- Allow consumers to write code like `AuthErrorCode.userDisabled`.
- Allow consumers to write code like `AuthErrorCode(.userDisabled)`.
- Make sure `{Auth,Firestore}ErrorCode` conform to `RawRepresentable`
  with a `RawValue` of type `Int`.
- `FirebaseError` just becomes a protocol, and `resultAndError` becomes
  a generic function that takes an `ErrorType.Type` parameter. This is
  necessary since the underlying C++ API does not have any type
  information for the error codes produced (they are just integers). At
  the Swift layer we want to generate the right concrete error types.

This is all intended for better conformance to how these types are
reflected to Swift from Obj C.
  • Loading branch information
darinf committed Apr 6, 2024
1 parent 25dac03 commit 8be5bc7
Show file tree
Hide file tree
Showing 9 changed files with 206 additions and 153 deletions.
8 changes: 4 additions & 4 deletions Sources/FirebaseAuth/FirebaseAuth+Swift.swift
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ public final class Auth {
private func fetchSignInMethodsImpl(forEmail email: String, completion: @escaping ([String]?, Error?) -> Void) {
let future = swift_firebase.swift_cxx_shims.firebase.auth.auth_fetch_providers_for_email(impl, email)
future.setCompletion({
let (result, error) = future.resultAndError
let (result, error) = future.resultAndError(as: AuthErrorCode.self)
var providers: [String]?
if let result {
providers = result.providers.map(String.init)
Expand Down Expand Up @@ -139,7 +139,7 @@ public final class Auth {
private func signInImpl(withEmail email: String, password: String, completion: @escaping (AuthDataResult?, Error?) -> Void) {
let future = swift_firebase.swift_cxx_shims.firebase.auth.auth_sign_in_with_email_and_password(impl, email, password)
future.setCompletion({
let (result, error) = future.resultAndError
let (result, error) = future.resultAndError(as: AuthErrorCode.self)
var data: AuthDataResult?
if let result {
data = .init(result)
Expand Down Expand Up @@ -197,7 +197,7 @@ public final class Auth {
private func createUserImpl(withEmail email: String, password: String, completion: @escaping (AuthDataResult?, Error?) -> Void) {
let future = swift_firebase.swift_cxx_shims.firebase.auth.auth_create_user_with_email_and_password(impl, email, password)
future.setCompletion({
let (result, error) = future.resultAndError
let (result, error) = future.resultAndError(as: AuthErrorCode.self)
var data: AuthDataResult?
if let result {
data = .init(result)
Expand Down Expand Up @@ -250,7 +250,7 @@ public final class Auth {
private func sendPasswordResetImpl(withEmail email: String, completion: @escaping (Error?) -> Void) {
let future = swift_firebase.swift_cxx_shims.firebase.auth.auth_send_password_reset_email(impl, email)
future.setCompletion({
let (_, error) = future.resultAndError
let (_, error) = future.resultAndError(as: AuthErrorCode.self)
completion(error)
})
}
Expand Down
228 changes: 130 additions & 98 deletions Sources/FirebaseAuth/FirebaseAuthError.swift
Original file line number Diff line number Diff line change
@@ -1,117 +1,149 @@
// SPDX-License-Identifier: BSD-3-Clause

import Foundation
@_exported
import firebase
@_spi(FirebaseInternal)
import FirebaseCore

public enum AuthErrorCode: Int {
case invalidCustomToken = 2
case customTokenMismatch = 3
case invalidCredential = 4
case userDisabled = 5
case accountExistsWithDifferentCredential = 6
case operationNotAllowed = 7
case emailAlreadyInUse = 8
case requiresRecentLogin = 9
case credentialAlreadyInUse = 10
case invalidEmail = 11
case wrongPassword = 12
case tooManyRequests = 13
case userNotFound = 14
case providerAlreadyLinked = 15
case noSuchProvider = 16
case invalidUserToken = 17
case userTokenExpired = 18
case networkError = 19
case invalidAPIKey = 20
case appNotAuthorized = 21
case userMismatch = 22
case weakPassword = 23
case noSignedInUser = 24
case apiNotAvailable = 25
case expiredActionCode = 26
case invalidActionCode = 27
case invalidMessagePayload = 28
case invalidPhoneNumber = 29
case missingPhoneNumber = 30
case invalidRecipientEmail = 31
case invalidSender = 32
case invalidVerificationCode = 33
case invalidVerificationID = 34
case missingVerificationCode = 35
case missingVerificationID = 36
case missingEmail = 37
case missingPassword = 38
case quotaExceeded = 39
case retryPhoneAuth = 40
case sessionExpired = 41
case appNotVerified = 42
case appVerificationUserInteractionFailure = 43
case captchaCheckFailed = 44
case invalidAppCredential = 45
case missingAppCredential = 46
case invalidClientID = 47
case invalidContinueURI = 48
case missingContinueURI = 49
case keychainError = 50
case missingAppToken = 51
case missingIosBundleID = 52
case notificationNotForwarded = 53
case unauthorizedDomain = 54
case webContextAlreadyPresented = 55
case webContextCancelled = 56
case dynamicLinkNotActivated = 57
case cancelled = 58
case invalidProviderID = 59
case webInternalError = 60
public struct AuthErrorCode: RawRepresentable, FirebaseError {
public typealias RawValue = Int

public let rawValue: Int
public let localizedDescription: String

public init(rawValue: Int) {
self.rawValue = rawValue
localizedDescription = "\(rawValue)"
}

@_spi(FirebaseInternal)
public init(code: Int32, message: String) {
self.rawValue = Int(code)
localizedDescription = message
}

private init(_ error: firebase.auth.AuthError) {
self.init(rawValue: Int(error.rawValue))
}
}

extension AuthErrorCode {
public static let none: Self = .init(firebase.auth.kAuthErrorNone)
public static let unimplemented: Self = .init(firebase.auth.kAuthErrorUnimplemented)
public static let invalidCustomToken: Self = .init(firebase.auth.kAuthErrorInvalidCustomToken)
public static let customTokenMismatch: Self = .init(firebase.auth.kAuthErrorCustomTokenMismatch)
public static let invalidCredential: Self = .init(firebase.auth.kAuthErrorInvalidCredential)
public static let userDisabled: Self = .init(firebase.auth.kAuthErrorUserDisabled)
public static let accountExistsWithDifferentCredential: Self = .init(firebase.auth.kAuthErrorAccountExistsWithDifferentCredentials)
public static let operationNotAllowed: Self = .init(firebase.auth.kAuthErrorOperationNotAllowed)
public static let emailAlreadyInUse: Self = .init(firebase.auth.kAuthErrorEmailAlreadyInUse)
public static let requiresRecentLogin: Self = .init(firebase.auth.kAuthErrorRequiresRecentLogin)
public static let credentialAlreadyInUse: Self = .init(firebase.auth.kAuthErrorCredentialAlreadyInUse)
public static let invalidEmail: Self = .init(firebase.auth.kAuthErrorInvalidEmail)
public static let wrongPassword: Self = .init(firebase.auth.kAuthErrorWrongPassword)
public static let tooManyRequests: Self = .init(firebase.auth.kAuthErrorTooManyRequests)
public static let userNotFound: Self = .init(firebase.auth.kAuthErrorUserNotFound)
public static let providerAlreadyLinked: Self = .init(firebase.auth.kAuthErrorProviderAlreadyLinked)
public static let noSuchProvider: Self = .init(firebase.auth.kAuthErrorNoSuchProvider)
public static let invalidUserToken: Self = .init(firebase.auth.kAuthErrorInvalidUserToken)
public static let userTokenExpired: Self = .init(firebase.auth.kAuthErrorUserTokenExpired)
public static let networkError: Self = .init(firebase.auth.kAuthErrorNetworkRequestFailed)
public static let invalidAPIKey: Self = .init(firebase.auth.kAuthErrorInvalidApiKey)
public static let appNotAuthorized: Self = .init(firebase.auth.kAuthErrorAppNotAuthorized)
public static let userMismatch: Self = .init(firebase.auth.kAuthErrorUserMismatch)
public static let weakPassword: Self = .init(firebase.auth.kAuthErrorWeakPassword)
public static let noSignedInUser: Self = .init(firebase.auth.kAuthErrorNoSignedInUser)
public static let apiNotAvailable: Self = .init(firebase.auth.kAuthErrorApiNotAvailable)
public static let expiredActionCode: Self = .init(firebase.auth.kAuthErrorExpiredActionCode)
public static let invalidActionCode: Self = .init(firebase.auth.kAuthErrorInvalidActionCode)
public static let invalidMessagePayload: Self = .init(firebase.auth.kAuthErrorInvalidMessagePayload)
public static let invalidPhoneNumber: Self = .init(firebase.auth.kAuthErrorInvalidPhoneNumber)
public static let missingPhoneNumber: Self = .init(firebase.auth.kAuthErrorMissingPhoneNumber)
public static let invalidRecipientEmail: Self = .init(firebase.auth.kAuthErrorInvalidRecipientEmail)
public static let invalidSender: Self = .init(firebase.auth.kAuthErrorInvalidSender)
public static let invalidVerificationCode: Self = .init(firebase.auth.kAuthErrorInvalidVerificationCode)
public static let invalidVerificationID: Self = .init(firebase.auth.kAuthErrorInvalidVerificationId)
public static let missingVerificationCode: Self = .init(firebase.auth.kAuthErrorMissingVerificationCode)
public static let missingVerificationID: Self = .init(firebase.auth.kAuthErrorMissingVerificationId)
public static let missingEmail: Self = .init(firebase.auth.kAuthErrorMissingEmail)
public static let missingPassword: Self = .init(firebase.auth.kAuthErrorMissingPassword)
public static let quotaExceeded: Self = .init(firebase.auth.kAuthErrorQuotaExceeded)
public static let retryPhoneAuth: Self = .init(firebase.auth.kAuthErrorRetryPhoneAuth)
public static let sessionExpired: Self = .init(firebase.auth.kAuthErrorSessionExpired)
public static let appNotVerified: Self = .init(firebase.auth.kAuthErrorAppNotVerified)
public static let appVerificationUserInteractionFailure: Self = .init(firebase.auth.kAuthErrorAppVerificationFailed)
public static let captchaCheckFailed: Self = .init(firebase.auth.kAuthErrorCaptchaCheckFailed)
public static let invalidAppCredential: Self = .init(firebase.auth.kAuthErrorInvalidAppCredential)
public static let missingAppCredential: Self = .init(firebase.auth.kAuthErrorMissingAppCredential)
public static let invalidClientID: Self = .init(firebase.auth.kAuthErrorInvalidClientId)
public static let invalidContinueURI: Self = .init(firebase.auth.kAuthErrorInvalidContinueUri)
public static let missingContinueURI: Self = .init(firebase.auth.kAuthErrorMissingContinueUri)
public static let keychainError: Self = .init(firebase.auth.kAuthErrorKeychainError)
public static let missingAppToken: Self = .init(firebase.auth.kAuthErrorMissingAppToken)
public static let missingIosBundleID: Self = .init(firebase.auth.kAuthErrorMissingIosBundleId)
public static let notificationNotForwarded: Self = .init(firebase.auth.kAuthErrorNotificationNotForwarded)
public static let unauthorizedDomain: Self = .init(firebase.auth.kAuthErrorUnauthorizedDomain)
public static let webContextAlreadyPresented: Self = .init(firebase.auth.kAuthErrorWebContextAlreadyPresented)
public static let webContextCancelled: Self = .init(firebase.auth.kAuthErrorWebContextCancelled)
public static let dynamicLinkNotActivated: Self = .init(firebase.auth.kAuthErrorDynamicLinkNotActivated)
public static let cancelled: Self = .init(firebase.auth.kAuthErrorCancelled)
public static let invalidProviderID: Self = .init(firebase.auth.kAuthErrorInvalidProviderId)
public static let webInternalError: Self = .init(firebase.auth.kAuthErrorWebInternalError)
// There's a typo in the Firebase error, carrying it over here.
case webStorateUnsupported = 61
case tenantIDMismatch = 62
case unsupportedTenantOperation = 63
case invalidDynamicLinkDomain = 64
case rejectedCredential = 65
case phoneNumberNotFound = 66
case invalidTenantID = 67
case missingClientIdentifier = 68
case missingMultiFactorSession = 69
case missingMultiFactorInfo = 70
case invalidMultiFactorSession = 71
case multiFactorInfoNotFound = 72
case adminRestrictedOperation = 73
case unverifiedEmail = 74
case secondFactorAlreadyEnrolled = 75
case maximumSecondFactorCountExceeded = 76
case unsupportedFirstFactor = 77
case emailChangeNeedsVerification = 78
public static let webStorateUnsupported: Self = .init(firebase.auth.kAuthErrorWebStorateUnsupported)
public static let tenantIDMismatch: Self = .init(firebase.auth.kAuthErrorTenantIdMismatch)
public static let unsupportedTenantOperation: Self = .init(firebase.auth.kAuthErrorUnsupportedTenantOperation)
public static let invalidDynamicLinkDomain: Self = .init(firebase.auth.kAuthErrorInvalidLinkDomain)
public static let rejectedCredential: Self = .init(firebase.auth.kAuthErrorRejectedCredential)
public static let phoneNumberNotFound: Self = .init(firebase.auth.kAuthErrorPhoneNumberNotFound)
public static let invalidTenantID: Self = .init(firebase.auth.kAuthErrorInvalidTenantId)
public static let missingClientIdentifier: Self = .init(firebase.auth.kAuthErrorMissingClientIdentifier)
public static let missingMultiFactorSession: Self = .init(firebase.auth.kAuthErrorMissingMultiFactorSession)
public static let missingMultiFactorInfo: Self = .init(firebase.auth.kAuthErrorMissingMultiFactorInfo)
public static let invalidMultiFactorSession: Self = .init(firebase.auth.kAuthErrorInvalidMultiFactorSession)
public static let multiFactorInfoNotFound: Self = .init(firebase.auth.kAuthErrorMultiFactorInfoNotFound)
public static let adminRestrictedOperation: Self = .init(firebase.auth.kAuthErrorAdminRestrictedOperation)
public static let unverifiedEmail: Self = .init(firebase.auth.kAuthErrorUnverifiedEmail)
public static let secondFactorAlreadyEnrolled: Self = .init(firebase.auth.kAuthErrorSecondFactorAlreadyEnrolled)
public static let maximumSecondFactorCountExceeded: Self = .init(firebase.auth.kAuthErrorMaximumSecondFactorCountExceeded)
public static let unsupportedFirstFactor: Self = .init(firebase.auth.kAuthErrorUnsupportedFirstFactor)
public static let emailChangeNeedsVerification: Self = .init(firebase.auth.kAuthErrorEmailChangeNeedsVerification)
#if INTERNAL_EXPERIMENTAL
case invalidEventHandler = 79
case federatedProviderAlreadyInUse = 80
case invalidAuthenticatedUserData = 81
case federatedSignInUserInteractionFailure = 82
case missingOrInvalidNonce = 83
case userCancelled = 84
case unsupportedPassthroughOperation = 85
case tokenRefreshUnavailable = 86
public static let invalidEventHandler: Self = .init(firebase.auth.kAuthErrorInvalidEventHandler)
public static let federatedProviderAlreadyInUse: Self = .init(firebase.auth.kAuthErrorFederatedProviderAlreadyInUse)
public static let invalidAuthenticatedUserData: Self = .init(firebase.auth.kAuthErrorInvalidAuthenticatedUserData)
public static let federatedSignInUserInteractionFailure: Self = .init(firebase.auth.kAuthErrorFederatedSignInUserInteractionFailure)
public static let missingOrInvalidNonce: Self = .init(firebase.auth.kAuthErrorMissingOrInvalidNonce)
public static let userCancelled: Self = .init(firebase.auth.kAuthErrorUserCancelled)
public static let unsupportedPassthroughOperation: Self = .init(firebase.auth.kAuthErrorUnsupportedPassthroughOperation)
public static let tokenRefreshUnavailable: Self = .init(firebase.auth.kAuthErrorTokenRefreshUnavailable)
#endif

// Errors that are not represented in the C++ SDK, but are
// present in the reference API.
case missingAndroidPackageName = 17037
case webNetworkRequestFailed = 17061
case webSignInUserInteractionFailure = 17063
case localPlayerNotAuthenticated = 17066
case nullUser = 17067
case gameKitNotLinked = 17076
case secondFactorRequired = 17078
case blockingCloudFunctionError = 17105
case internalError = 17999
case malformedJWT = 18000
public static let missingAndroidPackageName: Self = .init(rawValue: 17037)
public static let webNetworkRequestFailed: Self = .init(rawValue: 17061)
public static let webSignInUserInteractionFailure: Self = .init(rawValue: 17063)
public static let localPlayerNotAuthenticated: Self = .init(rawValue: 17066)
public static let nullUser: Self = .init(rawValue: 17067)
public static let gameKitNotLinked: Self = .init(rawValue: 17076)
public static let secondFactorRequired: Self = .init(rawValue: 17078)
public static let blockingCloudFunctionError: Self = .init(rawValue: 17105)
public static let internalError: Self = .init(rawValue: 17999)
public static let malformedJWT: Self = .init(rawValue: 18000)
}

extension AuthErrorCode: Error {}
extension AuthErrorCode: Equatable {}

extension AuthErrorCode {
// This allows us to re-expose self as a code similarly
// to what the Firebase SDK does when it creates the
// underlying NSErrors on iOS/macOS.
public var code: AuthErrorCode {
return self
}

public init(_ code: AuthErrorCode) {
self.init(rawValue: code.rawValue)
}
}
8 changes: 4 additions & 4 deletions Sources/FirebaseAuth/FirebaseUser+Swift.swift
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public final class User {
private func reloadImpl(completion: @escaping (Error?) -> Void) {
let future = swift_firebase.swift_cxx_shims.firebase.auth.user_reload(impl)
future.setCompletion({
let (_, error) = future.resultAndError
let (_, error) = future.resultAndError(as: AuthErrorCode.self)
completion(error)
})
}
Expand Down Expand Up @@ -128,7 +128,7 @@ public final class User {
public func reauthenticateImpl(with credential: Credential, completion: @escaping (AuthResult?, Error?) -> Void) {
let future = swift_firebase.swift_cxx_shims.firebase.auth.user_reauthenticate_and_retrieve_data(impl, credential)
future.setCompletion({
let (result, error) = future.resultAndError
let (result, error) = future.resultAndError(as: AuthErrorCode.self)
completion(result, error)
})
}
Expand Down Expand Up @@ -178,7 +178,7 @@ public final class User {
private func idTokenForcingRefreshImpl(_ forceRefresh: Bool, completion: @escaping (String?, Error?) -> Void) {
let future = swift_firebase.swift_cxx_shims.firebase.auth.user_get_token(impl, forceRefresh)
future.setCompletion({
let (result, error) = future.resultAndError
let (result, error) = future.resultAndError(as: AuthErrorCode.self)
let stringResult: String?
if let result {
stringResult = String(result)
Expand Down Expand Up @@ -225,7 +225,7 @@ public final class User {
public func sendEmailVerificationImpl(completion: @escaping (Error?) -> Void) {
let future = swift_firebase.swift_cxx_shims.firebase.auth.user_send_email_verification(impl)
future.setCompletion({
let (_, error) = future.resultAndError
let (_, error) = future.resultAndError(as: AuthErrorCode.self)
completion(error)
})
}
Expand Down
11 changes: 2 additions & 9 deletions Sources/FirebaseCore/FirebaseError.swift
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
// SPDX-License-Identifier: BSD-3-Clause

public struct FirebaseError: Error {
public let code: CInt
public let message: String

@_spi(FirebaseInternal)
public init(code: CInt, message: String) {
self.code = code
self.message = message
}
public protocol FirebaseError: Error {
init(code: Int32, message: String)
}
4 changes: 2 additions & 2 deletions Sources/FirebaseCore/FutureProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@ public extension FutureProtocol {
return String(cString: errorMessageUnsafe)
}

var resultAndError: (ResultType?, Error?) {
func resultAndError<ErrorType: FirebaseError>(as errorType: ErrorType.Type) -> (ResultType?, ErrorType?) {
let error = error()
guard error == 0 else {
return (nil, FirebaseError(code: error, message: errorMessage!))
return (nil, ErrorType(code: error, message: errorMessage!))
}
return (result, nil)
}
Expand Down
6 changes: 3 additions & 3 deletions Sources/FirebaseFirestore/DocumentReference+Swift.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ extension DocumentReference {
public func getDocument(source: FirestoreSource = .default, completion: @escaping (DocumentSnapshot?, Error?) -> Void) {
let future = swift_firebase.swift_cxx_shims.firebase.firestore.document_get(self, source)
future.setCompletion({
let (snapshot, error) = future.resultAndError
let (snapshot, error) = future.resultAndError(as: FirestoreErrorCode.self)
DispatchQueue.main.async {
completion(snapshot, error)
}
Expand All @@ -42,7 +42,7 @@ extension DocumentReference {
try await withCheckedThrowingContinuation { continuation in
let future = swift_firebase.swift_cxx_shims.firebase.firestore.document_get(self, source)
future.setCompletion({
let (snapshot, error) = future.resultAndError
let (snapshot, error) = future.resultAndError(as: FirestoreErrorCode.self)
if let error {
continuation.resume(throwing: error)
} else {
Expand Down Expand Up @@ -99,7 +99,7 @@ extension DocumentReference {
let options = merge ? firebase.firestore.SetOptions.Merge() : firebase.firestore.SetOptions()
let future = swift_firebase.swift_cxx_shims.firebase.firestore.document_set_data(self, converted, options)
future.setCompletion({
let (_, error) = future.resultAndError
let (_, error) = future.resultAndError(as: FirestoreErrorCode.self)
completion(error)
})
}
Expand Down
Loading

0 comments on commit 8be5bc7

Please sign in to comment.