From 8be5bc73b75bd4f8aa3bc44d06e661d8d95ad76d Mon Sep 17 00:00:00 2001 From: Darin Fisher Date: Fri, 5 Apr 2024 17:07:45 -0700 Subject: [PATCH] Cleanup error code types 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. --- Sources/FirebaseAuth/FirebaseAuth+Swift.swift | 8 +- Sources/FirebaseAuth/FirebaseAuthError.swift | 228 ++++++++++-------- Sources/FirebaseAuth/FirebaseUser+Swift.swift | 8 +- Sources/FirebaseCore/FirebaseError.swift | 11 +- Sources/FirebaseCore/FutureProtocol.swift | 4 +- .../DocumentReference+Swift.swift | 6 +- .../FirestoreErrorCode.swift | 86 ++++--- Sources/FirebaseFirestore/Query+Swift.swift | 4 +- .../FirebaseFirestore/WriteBatch+Swift.swift | 4 +- 9 files changed, 206 insertions(+), 153 deletions(-) diff --git a/Sources/FirebaseAuth/FirebaseAuth+Swift.swift b/Sources/FirebaseAuth/FirebaseAuth+Swift.swift index c20658f..a196399 100644 --- a/Sources/FirebaseAuth/FirebaseAuth+Swift.swift +++ b/Sources/FirebaseAuth/FirebaseAuth+Swift.swift @@ -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) @@ -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) @@ -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) @@ -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) }) } diff --git a/Sources/FirebaseAuth/FirebaseAuthError.swift b/Sources/FirebaseAuth/FirebaseAuthError.swift index 6c28969..bceadab 100644 --- a/Sources/FirebaseAuth/FirebaseAuthError.swift +++ b/Sources/FirebaseAuth/FirebaseAuthError.swift @@ -1,112 +1,140 @@ // 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 @@ -114,4 +142,8 @@ extension AuthErrorCode { public var code: AuthErrorCode { return self } + + public init(_ code: AuthErrorCode) { + self.init(rawValue: code.rawValue) + } } diff --git a/Sources/FirebaseAuth/FirebaseUser+Swift.swift b/Sources/FirebaseAuth/FirebaseUser+Swift.swift index 7b75d80..1102f85 100644 --- a/Sources/FirebaseAuth/FirebaseUser+Swift.swift +++ b/Sources/FirebaseAuth/FirebaseUser+Swift.swift @@ -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) }) } @@ -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) }) } @@ -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) @@ -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) }) } diff --git a/Sources/FirebaseCore/FirebaseError.swift b/Sources/FirebaseCore/FirebaseError.swift index 4a47c47..c93fa8a 100644 --- a/Sources/FirebaseCore/FirebaseError.swift +++ b/Sources/FirebaseCore/FirebaseError.swift @@ -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) } diff --git a/Sources/FirebaseCore/FutureProtocol.swift b/Sources/FirebaseCore/FutureProtocol.swift index 4562662..e3b8b1f 100644 --- a/Sources/FirebaseCore/FutureProtocol.swift +++ b/Sources/FirebaseCore/FutureProtocol.swift @@ -47,10 +47,10 @@ public extension FutureProtocol { return String(cString: errorMessageUnsafe) } - var resultAndError: (ResultType?, Error?) { + func resultAndError(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) } diff --git a/Sources/FirebaseFirestore/DocumentReference+Swift.swift b/Sources/FirebaseFirestore/DocumentReference+Swift.swift index 0db1a4c..04a4299 100644 --- a/Sources/FirebaseFirestore/DocumentReference+Swift.swift +++ b/Sources/FirebaseFirestore/DocumentReference+Swift.swift @@ -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) } @@ -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 { @@ -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) }) } diff --git a/Sources/FirebaseFirestore/FirestoreErrorCode.swift b/Sources/FirebaseFirestore/FirestoreErrorCode.swift index fd9e4d3..ba1cd3c 100644 --- a/Sources/FirebaseFirestore/FirestoreErrorCode.swift +++ b/Sources/FirebaseFirestore/FirestoreErrorCode.swift @@ -1,14 +1,35 @@ // SPDX-License-Identifier: BSD-3-Clause -public struct FirestoreErrorCode: Error { - public typealias Code = firebase.firestore.Error +@_exported +import firebase +@_spi(FirebaseInternal) +import FirebaseCore - public let code: Code +public struct FirestoreErrorCode: 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.firestore.Error) { + self.init(rawValue: Int(error.rawValue)) + } +} + +extension FirestoreErrorCode { init(_ error: firebase.firestore.Error, errorMessage: String?) { - code = error - localizedDescription = errorMessage ?? "\(code.rawValue)" + self.init(code: error.rawValue, message: errorMessage ?? "\(error.rawValue)") } init?(_ error: firebase.firestore.Error?, errorMessage: UnsafePointer?) { @@ -19,33 +40,40 @@ public struct FirestoreErrorCode: Error { } self.init(actualError, errorMessage: errorMessageString) } +} - public init(_ code: Code) { - self.init(code, errorMessage: nil) - } +extension FirestoreErrorCode { + public static let ok: Self = .init(firebase.firestore.kErrorOk) + public static let none: Self = .init(firebase.firestore.kErrorNone) + public static let cancelled: Self = .init(firebase.firestore.kErrorCancelled) + public static let unknown: Self = .init(firebase.firestore.kErrorUnknown) + public static let invalidArgument: Self = .init(firebase.firestore.kErrorInvalidArgument) + public static let deadlineExceeded: Self = .init(firebase.firestore.kErrorDeadlineExceeded) + public static let notFound: Self = .init(firebase.firestore.kErrorNotFound) + public static let alreadyExists: Self = .init(firebase.firestore.kErrorAlreadyExists) + public static let permissionDenied: Self = .init(firebase.firestore.kErrorPermissionDenied) + public static let resourceExhausted: Self = .init(firebase.firestore.kErrorResourceExhausted) + public static let failedPrecondition: Self = .init(firebase.firestore.kErrorFailedPrecondition) + public static let aborted: Self = .init(firebase.firestore.kErrorAborted) + public static let outOfRange: Self = .init(firebase.firestore.kErrorOutOfRange) + public static let unimplemented: Self = .init(firebase.firestore.kErrorUnimplemented) + public static let `internal`: Self = .init(firebase.firestore.kErrorInternal) + public static let unavailable: Self = .init(firebase.firestore.kErrorUnavailable) + public static let dataLoss: Self = .init(firebase.firestore.kErrorDataLoss) + public static let unauthenticated: Self = .init(firebase.firestore.kErrorUnauthenticated) } extension FirestoreErrorCode: Equatable {} -// Unfortunately `Error` is not defined as a `enum class` so we need to add -// these wrappers. -extension FirestoreErrorCode.Code { - public static var ok: Self { firebase.firestore.kErrorOk } - public static var none: Self { firebase.firestore.kErrorNone } - public static var cancelled: Self { firebase.firestore.kErrorCancelled } - public static var unknown: Self { firebase.firestore.kErrorUnknown } - public static var invalidArgument: Self { firebase.firestore.kErrorInvalidArgument } - public static var deadlineExceeded: Self { firebase.firestore.kErrorDeadlineExceeded } - public static var notFound: Self { firebase.firestore.kErrorNotFound } - public static var alreadyExists: Self { firebase.firestore.kErrorAlreadyExists } - public static var permissionDenied: Self { firebase.firestore.kErrorPermissionDenied } - public static var resourceExhausted: Self { firebase.firestore.kErrorResourceExhausted } - public static var failedPrecondition: Self { firebase.firestore.kErrorFailedPrecondition } - public static var aborted: Self { firebase.firestore.kErrorAborted } - public static var outOfRange: Self { firebase.firestore.kErrorOutOfRange } - public static var unimplemented: Self { firebase.firestore.kErrorUnimplemented } - public static var `internal`: Self { firebase.firestore.kErrorInternal } - public static var unavailable: Self { firebase.firestore.kErrorUnavailable } - public static var dataLoss: Self { firebase.firestore.kErrorDataLoss } - public static var unauthenticated: Self { firebase.firestore.kErrorUnauthenticated } +extension FirestoreErrorCode { + // 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: FirestoreErrorCode { + return self + } + + public init(_ code: FirestoreErrorCode) { + self.init(rawValue: code.rawValue) + } } diff --git a/Sources/FirebaseFirestore/Query+Swift.swift b/Sources/FirebaseFirestore/Query+Swift.swift index cac438a..ddd1ee5 100644 --- a/Sources/FirebaseFirestore/Query+Swift.swift +++ b/Sources/FirebaseFirestore/Query+Swift.swift @@ -32,7 +32,7 @@ extension QueryProtocol { public func getDocuments(source: FirestoreSource = .default, completion: @escaping (QuerySnapshot?, Error?) -> Void) { let future = swift_firebase.swift_cxx_shims.firebase.firestore.query_get(_asQuery, source) future.setCompletion({ - let (snapshot, error) = future.resultAndError + let (snapshot, error) = future.resultAndError(as: FirestoreErrorCode.self) DispatchQueue.main.async { completion(snapshot, error) } @@ -43,7 +43,7 @@ extension QueryProtocol { try await withCheckedThrowingContinuation { continuation in let future = swift_firebase.swift_cxx_shims.firebase.firestore.query_get(_asQuery, source) future.setCompletion({ - let (snapshot, error) = future.resultAndError + let (snapshot, error) = future.resultAndError(as: FirestoreErrorCode.self) if let error { continuation.resume(throwing: error) } else { diff --git a/Sources/FirebaseFirestore/WriteBatch+Swift.swift b/Sources/FirebaseFirestore/WriteBatch+Swift.swift index 4f43379..5dccc5b 100644 --- a/Sources/FirebaseFirestore/WriteBatch+Swift.swift +++ b/Sources/FirebaseFirestore/WriteBatch+Swift.swift @@ -44,7 +44,7 @@ extension WriteBatch { public mutating func commit(completion: @escaping ((Error?) -> Void) = { _ in }) { let future = swift_firebase.swift_cxx_shims.firebase.firestore.write_batch_commit(self) future.setCompletion({ - let (_, error) = future.resultAndError + let (_, error) = future.resultAndError(as: FirestoreErrorCode.self) DispatchQueue.main.async { completion(error) } @@ -55,7 +55,7 @@ extension WriteBatch { try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in let future = swift_firebase.swift_cxx_shims.firebase.firestore.write_batch_commit(self) future.setCompletion({ - let (_, error) = future.resultAndError + let (_, error) = future.resultAndError(as: FirestoreErrorCode.self) if let error { continuation.resume(throwing: error) } else {