Răsfoiți Sursa

Swift Error Improvements (#9512)

* Firebase Messaging enum

* Add tests for App Check errors

* App Distribution Error

* Installations error improvements

* Remote Config errors

* Firestore error handling

* Formatting

* Silly import error

* Consistency for App Check tests.

* Typo fix
Ryan Wilson 4 ani în urmă
părinte
comite
2258c07ecb

+ 2 - 0
FirebaseAppCheck/Tests/Unit/Swift/AppCheckAPITests.swift

@@ -178,6 +178,8 @@ final class AppCheckAPITests {
               Task {
                 do {
                   _ = try await deviceCheckProvider.getToken()
+                } catch AppCheckErrorCode.unsupported {
+                  // ...
                 } catch {
                   // ...
                 }

+ 11 - 11
FirebaseAppDistribution/Sources/Public/FirebaseAppDistribution/FIRAppDistribution.h

@@ -72,25 +72,25 @@ FOUNDATION_EXPORT NSString *const FIRAppDistributionErrorDomain
 
 /// The key for finding error details in the `NSError`'s `userInfo`.
 FOUNDATION_EXPORT NSString *const FIRAppDistributionErrorDetailsKey
-    NS_SWIFT_NAME(FunctionsErrorDetailsKey);
+    NS_SWIFT_NAME(AppDistributionErrorDetailsKey);
 // clang-format on
 
 /**
  * Error codes representing sign in or version check failure reasons.
  */
-typedef NS_ENUM(NSUInteger, FIRAppDistributionError) {
-  /// Returned when an unknown error occurred.
-  FIRAppDistributionErrorUnknown = 0,
+typedef NS_ERROR_ENUM(FIRAppDistributionErrorDomain, FIRAppDistributionError){
+    /// Returned when an unknown error occurred.
+    FIRAppDistributionErrorUnknown = 0,
 
-  /// Returned when App Distribution failed to authenticate the user.
-  FIRAppDistributionErrorAuthenticationFailure = 1,
+    /// Returned when App Distribution failed to authenticate the user.
+    FIRAppDistributionErrorAuthenticationFailure = 1,
 
-  /// Returned when sign-in was cancelled.
-  FIRAppDistributionErrorAuthenticationCancelled = 2,
+    /// Returned when sign-in was cancelled.
+    FIRAppDistributionErrorAuthenticationCancelled = 2,
 
-  /// Returned when the network was unavailable to make requests or
-  /// the request timed out.
-  FIRAppDistributionErrorNetworkFailure = 3,
+    /// Returned when the network was unavailable to make requests or
+    /// the request timed out.
+    FIRAppDistributionErrorNetworkFailure = 3,
 
 } NS_SWIFT_NAME(AppDistributionError);
 

+ 10 - 9
FirebaseInstallations/Source/Library/Public/FirebaseInstallations/FIRInstallationsErrors.h

@@ -18,17 +18,18 @@
 
 extern NSString *const kFirebaseInstallationsErrorDomain NS_SWIFT_NAME(InstallationsErrorDomain);
 
-typedef NS_ENUM(NSUInteger, FIRInstallationsErrorCode) {
-  /** Unknown error. See `userInfo` for details. */
-  FIRInstallationsErrorCodeUnknown = 0,
+typedef NS_ERROR_ENUM(kFirebaseInstallationsErrorDomain, FIRInstallationsErrorCode){
+    /** Unknown error. See `userInfo` for details. */
+    FIRInstallationsErrorCodeUnknown = 0,
 
-  /** Keychain error. See `userInfo` for details. */
-  FIRInstallationsErrorCodeKeychain = 1,
+    /** Keychain error. See `userInfo` for details. */
+    FIRInstallationsErrorCodeKeychain = 1,
 
-  /** Server unreachable. A network error or server is unavailable. See `userInfo` for details. */
-  FIRInstallationsErrorCodeServerUnreachable = 2,
+    /** Server unreachable. A network error or server is unavailable. See `userInfo` for details. */
+    FIRInstallationsErrorCodeServerUnreachable = 2,
 
-  /** FirebaseApp configuration issues e.g. invalid GMP-App-ID, etc. See `userInfo` for details. */
-  FIRInstallationsErrorCodeInvalidConfiguration = 3,
+    /** FirebaseApp configuration issues e.g. invalid GMP-App-ID, etc. See `userInfo` for details.
+     */
+    FIRInstallationsErrorCodeInvalidConfiguration = 3,
 
 } NS_SWIFT_NAME(InstallationsErrorCode);

+ 22 - 0
FirebaseInstallations/Source/Tests/Unit/Swift/InstallationsAPITests.swift

@@ -121,6 +121,12 @@ final class InstallationsAPITests {
         Task {
           do {
             _ = try await Installations.installations().delete()
+          } catch let error as NSError
+            where error.domain == InstallationsErrorDomain && error.code == InstallationsErrorCode
+            .unknown.rawValue {
+            // Above is the old way to handle errors.
+          } catch InstallationsErrorCode.unknown {
+            // Above is the new way to handle errors.
           } catch {
             // ...
           }
@@ -141,6 +147,7 @@ final class InstallationsAPITests {
 
     Installations.installations().authToken { _, error in
       if let error = error {
+        // Old error handling.
         switch (error as NSError).code {
         case Int(InstallationsErrorCode.unknown.rawValue):
           break
@@ -153,6 +160,21 @@ final class InstallationsAPITests {
         default:
           break
         }
+
+        // New error handling.
+        switch error {
+        case InstallationsErrorCode.unknown:
+          break
+        case InstallationsErrorCode.keychain:
+          break
+        case InstallationsErrorCode.serverUnreachable:
+          break
+        case InstallationsErrorCode.invalidConfiguration:
+          break
+
+        default:
+          break
+        }
       }
     }
     func globalStringSymbols() {

+ 3 - 1
FirebaseMessaging/Sources/FIRMessaging.m

@@ -63,6 +63,8 @@ NSString *const kFIRMessagingUserDefaultsKeyAutoInitEnabled =
 NSString *const kFIRMessagingPlistAutoInitEnabled =
     @"FirebaseMessagingAutoInitEnabled";  // Auto Init Enabled key stored in Info.plist
 
+NSString *const FIRMessagingErrorDomain = @"com.google.fcm";
+
 const BOOL FIRMessagingIsAPNSSyncMessage(NSDictionary *message) {
   if ([message[kFIRMessagingMessageViaAPNSRootKey] isKindOfClass:[NSDictionary class]]) {
     NSDictionary *aps = message[kFIRMessagingMessageViaAPNSRootKey];
@@ -208,7 +210,7 @@ BOOL FIRMessagingIsContextManagerMessage(NSDictionary *message) {
   if (!GCMSenderID.length) {
     FIRMessagingLoggerError(kFIRMessagingMessageCodeFIRApp000,
                             @"Firebase not set up correctly, nil or empty senderID.");
-    [NSException raise:kFIRMessagingDomain
+    [NSException raise:FIRMessagingErrorDomain
                 format:@"Could not configure Firebase Messaging. GCMSenderID must not be nil or "
                        @"empty."];
   }

+ 0 - 2
FirebaseMessaging/Sources/NSError+FIRMessaging.h

@@ -18,8 +18,6 @@
 
 NS_ASSUME_NONNULL_BEGIN
 
-FOUNDATION_EXPORT NSString *const kFIRMessagingDomain;
-
 // FIRMessaging Internal Error Code
 typedef NS_ENUM(NSUInteger, FIRMessagingErrorCode) {
   kFIRMessagingErrorCodeUnknown = 0,

+ 2 - 3
FirebaseMessaging/Sources/NSError+FIRMessaging.m

@@ -15,8 +15,7 @@
  */
 
 #import "FirebaseMessaging/Sources/NSError+FIRMessaging.h"
-
-NSString *const kFIRMessagingDomain = @"com.google.fcm";
+#import "FirebaseMessaging/Sources/Public/FirebaseMessaging/FIRMessaging.h"
 
 @implementation NSError (FIRMessaging)
 
@@ -24,7 +23,7 @@ NSString *const kFIRMessagingDomain = @"com.google.fcm";
                       failureReason:(NSString *)failureReason {
   NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
   userInfo[NSLocalizedFailureReasonErrorKey] = failureReason;
-  return [NSError errorWithDomain:kFIRMessagingDomain code:errorCode userInfo:userInfo];
+  return [NSError errorWithDomain:FIRMessagingErrorDomain code:errorCode userInfo:userInfo];
 }
 
 @end

+ 21 - 17
FirebaseMessaging/Sources/Public/FirebaseMessaging/FIRMessaging.h

@@ -68,33 +68,37 @@ FOUNDATION_EXPORT const NSNotificationName FIRMessagingRegistrationTokenRefreshe
     NS_SWIFT_NAME(MessagingRegistrationTokenRefreshed);
 // clang-format on
 
+/**
+ * The domain used for all errors in Messaging.
+ */
+FOUNDATION_EXPORT NSString *const FIRMessagingErrorDomain NS_SWIFT_NAME(MessagingErrorDomain);
 /**
  *  @enum FIRMessagingError
  */
-typedef NS_ENUM(NSUInteger, FIRMessagingError) {
-  /// Unknown error.
-  FIRMessagingErrorUnknown = 0,
+typedef NS_ERROR_ENUM(FIRMessagingErrorDomain, FIRMessagingError){
+    /// Unknown error.
+    FIRMessagingErrorUnknown = 0,
 
-  /// FIRMessaging couldn't validate request from this client.
-  FIRMessagingErrorAuthentication = 1,
+    /// FIRMessaging couldn't validate request from this client.
+    FIRMessagingErrorAuthentication = 1,
 
-  /// InstanceID service cannot be accessed.
-  FIRMessagingErrorNoAccess = 2,
+    /// InstanceID service cannot be accessed.
+    FIRMessagingErrorNoAccess = 2,
 
-  /// Request to InstanceID backend timed out.
-  FIRMessagingErrorTimeout = 3,
+    /// Request to InstanceID backend timed out.
+    FIRMessagingErrorTimeout = 3,
 
-  /// No network available to reach the servers.
-  FIRMessagingErrorNetwork = 4,
+    /// No network available to reach the servers.
+    FIRMessagingErrorNetwork = 4,
 
-  /// Another similar operation in progress, bailing this one.
-  FIRMessagingErrorOperationInProgress = 5,
+    /// Another similar operation in progress, bailing this one.
+    FIRMessagingErrorOperationInProgress = 5,
 
-  /// Some parameters of the request were invalid.
-  FIRMessagingErrorInvalidRequest = 7,
+    /// Some parameters of the request were invalid.
+    FIRMessagingErrorInvalidRequest = 7,
 
-  /// Topic name is invalid for subscription/unsubscription.
-  FIRMessagingErrorInvalidTopicName = 8,
+    /// Topic name is invalid for subscription/unsubscription.
+    FIRMessagingErrorInvalidTopicName = 8,
 
 } NS_SWIFT_NAME(MessagingError);
 

+ 21 - 2
FirebaseMessaging/Tests/UnitTestsSwift/FIRMessagingAPITest.swift

@@ -55,8 +55,9 @@ func apis() {
   }
 
   // Use random to eliminate the warning that we're evaluating to a constant.
-  let messagingError: MessagingError = Bool.random() ? .unknown : .authentication
-  switch messagingError {
+  let messagingError: MessagingError = Bool
+    .random() ? MessagingError(.unknown) : MessagingError(.authentication)
+  switch messagingError.code {
   case .unknown: ()
   case .authentication: ()
   case .noAccess: ()
@@ -92,6 +93,18 @@ func apis() {
   let topic = "cat_video"
   messaging.subscribe(toTopic: topic)
   messaging.unsubscribe(fromTopic: topic)
+  messaging.unsubscribe(fromTopic: topic, completion: { error in
+    if let error = error {
+      switch error {
+      // Handle errors in the new format.
+      case MessagingError.timeout:
+        ()
+      default:
+        ()
+      }
+    }
+  })
+
   messaging.unsubscribe(fromTopic: topic) { _ in
   }
 
@@ -128,5 +141,11 @@ func apiAsync() async throws {
     try await messaging.deleteFCMToken(forSenderID: "fakeSenderID")
 
     try await messaging.deleteData()
+
+    // Test new handling of errors
+    do {
+      try await messaging.unsubscribe(fromTopic: topic)
+    } catch MessagingError.timeout {
+    } catch {}
   #endif
 }

+ 7 - 7
FirebaseRemoteConfig/Sources/Public/FirebaseRemoteConfig/FIRRemoteConfig.h

@@ -56,13 +56,13 @@ typedef NS_ENUM(NSInteger, FIRRemoteConfigFetchAndActivateStatus) {
 /// Remote Config error domain that handles errors when fetching data from the service.
 extern NSString *const _Nonnull FIRRemoteConfigErrorDomain NS_SWIFT_NAME(RemoteConfigErrorDomain);
 /// Firebase Remote Config service fetch error.
-typedef NS_ENUM(NSInteger, FIRRemoteConfigError) {
-  /// Unknown or no error.
-  FIRRemoteConfigErrorUnknown = 8001,
-  /// Frequency of fetch requests exceeds throttled limit.
-  FIRRemoteConfigErrorThrottled = 8002,
-  /// Internal error that covers all internal HTTP errors.
-  FIRRemoteConfigErrorInternalError = 8003,
+typedef NS_ERROR_ENUM(FIRRemoteConfigErrorDomain, FIRRemoteConfigError){
+    /// Unknown or no error.
+    FIRRemoteConfigErrorUnknown = 8001,
+    /// Frequency of fetch requests exceeds throttled limit.
+    FIRRemoteConfigErrorThrottled = 8002,
+    /// Internal error that covers all internal HTTP errors.
+    FIRRemoteConfigErrorInternalError = 8003,
 } NS_SWIFT_NAME(RemoteConfigError);
 
 /// Enumerated value that indicates the source of Remote Config data. Data can come from

+ 77 - 77
Firestore/Source/Public/FirebaseFirestore/FIRFirestoreErrors.h

@@ -22,82 +22,82 @@ NS_ASSUME_NONNULL_BEGIN
 FOUNDATION_EXPORT NSString *const FIRFirestoreErrorDomain NS_SWIFT_NAME(FirestoreErrorDomain);
 
 /** Error codes used by Cloud Firestore. */
-typedef NS_ENUM(NSInteger, FIRFirestoreErrorCode) {
-  /**
-   * The operation completed successfully. NSError objects will never have a code with this value.
-   */
-  FIRFirestoreErrorCodeOK = 0,
-
-  /** The operation was cancelled (typically by the caller). */
-  FIRFirestoreErrorCodeCancelled = 1,
-
-  /** Unknown error or an error from a different error domain. */
-  FIRFirestoreErrorCodeUnknown = 2,
-
-  /**
-   * Client specified an invalid argument. Note that this differs from FailedPrecondition.
-   * InvalidArgument indicates arguments that are problematic regardless of the state of the
-   * system (e.g., an invalid field name).
-   */
-  FIRFirestoreErrorCodeInvalidArgument = 3,
-
-  /**
-   * Deadline expired before operation could complete. For operations that change the state of the
-   * system, this error may be returned even if the operation has completed successfully. For
-   * example, a successful response from a server could have been delayed long enough for the
-   * deadline to expire.
-   */
-  FIRFirestoreErrorCodeDeadlineExceeded = 4,
-
-  /** Some requested document was not found. */
-  FIRFirestoreErrorCodeNotFound = 5,
-
-  /** Some document that we attempted to create already exists. */
-  FIRFirestoreErrorCodeAlreadyExists = 6,
-
-  /** The caller does not have permission to execute the specified operation. */
-  FIRFirestoreErrorCodePermissionDenied = 7,
-
-  /**
-   * Some resource has been exhausted, perhaps a per-user quota, or perhaps the entire file system
-   * is out of space.
-   */
-  FIRFirestoreErrorCodeResourceExhausted = 8,
-
-  /**
-   * Operation was rejected because the system is not in a state required for the operation's
-   * execution.
-   */
-  FIRFirestoreErrorCodeFailedPrecondition = 9,
-
-  /**
-   * The operation was aborted, typically due to a concurrency issue like transaction aborts, etc.
-   */
-  FIRFirestoreErrorCodeAborted = 10,
-
-  /** Operation was attempted past the valid range. */
-  FIRFirestoreErrorCodeOutOfRange = 11,
-
-  /** Operation is not implemented or not supported/enabled. */
-  FIRFirestoreErrorCodeUnimplemented = 12,
-
-  /**
-   * Internal errors. Means some invariants expected by underlying system has been broken. If you
-   * see one of these errors, something is very broken.
-   */
-  FIRFirestoreErrorCodeInternal = 13,
-
-  /**
-   * The service is currently unavailable. This is a most likely a transient condition and may be
-   * corrected by retrying with a backoff.
-   */
-  FIRFirestoreErrorCodeUnavailable = 14,
-
-  /** Unrecoverable data loss or corruption. */
-  FIRFirestoreErrorCodeDataLoss = 15,
-
-  /** The request does not have valid authentication credentials for the operation. */
-  FIRFirestoreErrorCodeUnauthenticated = 16
-} NS_SWIFT_NAME(FirestoreErrorCode);
+typedef NS_ERROR_ENUM(FIRFirestoreErrorDomain, FIRFirestoreErrorCode){
+    /**
+     * The operation completed successfully. NSError objects will never have a code with this
+     * value.
+     */
+    FIRFirestoreErrorCodeOK = 0,
+
+    /** The operation was cancelled (typically by the caller). */
+    FIRFirestoreErrorCodeCancelled = 1,
+
+    /** Unknown error or an error from a different error domain. */
+    FIRFirestoreErrorCodeUnknown = 2,
+
+    /**
+     * Client specified an invalid argument. Note that this differs from FailedPrecondition.
+     * InvalidArgument indicates arguments that are problematic regardless of the state of the
+     * system (e.g., an invalid field name).
+     */
+    FIRFirestoreErrorCodeInvalidArgument = 3,
+
+    /**
+     * Deadline expired before operation could complete. For operations that change the state of the
+     * system, this error may be returned even if the operation has completed successfully. For
+     * example, a successful response from a server could have been delayed long enough for the
+     * deadline to expire.
+     */
+    FIRFirestoreErrorCodeDeadlineExceeded = 4,
+
+    /** Some requested document was not found. */
+    FIRFirestoreErrorCodeNotFound = 5,
+
+    /** Some document that we attempted to create already exists. */
+    FIRFirestoreErrorCodeAlreadyExists = 6,
+
+    /** The caller does not have permission to execute the specified operation. */
+    FIRFirestoreErrorCodePermissionDenied = 7,
+
+    /**
+     * Some resource has been exhausted, perhaps a per-user quota, or perhaps the entire file system
+     * is out of space.
+     */
+    FIRFirestoreErrorCodeResourceExhausted = 8,
+
+    /**
+     * Operation was rejected because the system is not in a state required for the operation's
+     * execution.
+     */
+    FIRFirestoreErrorCodeFailedPrecondition = 9,
+
+    /**
+     * The operation was aborted, typically due to a concurrency issue like transaction aborts, etc.
+     */
+    FIRFirestoreErrorCodeAborted = 10,
+
+    /** Operation was attempted past the valid range. */
+    FIRFirestoreErrorCodeOutOfRange = 11,
+
+    /** Operation is not implemented or not supported/enabled. */
+    FIRFirestoreErrorCodeUnimplemented = 12,
+
+    /**
+     * Internal errors. Means some invariants expected by underlying system has been broken. If you
+     * see one of these errors, something is very broken.
+     */
+    FIRFirestoreErrorCodeInternal = 13,
+
+    /**
+     * The service is currently unavailable. This is a most likely a transient condition and may be
+     * corrected by retrying with a backoff.
+     */
+    FIRFirestoreErrorCodeUnavailable = 14,
+
+    /** Unrecoverable data loss or corruption. */
+    FIRFirestoreErrorCodeDataLoss = 15,
+
+    /** The request does not have valid authentication credentials for the operation. */
+    FIRFirestoreErrorCodeUnauthenticated = 16} NS_SWIFT_NAME(FirestoreErrorCode);
 
 NS_ASSUME_NONNULL_END

+ 29 - 9
Firestore/Swift/Tests/API/BasicCompileTests.swift

@@ -253,21 +253,41 @@ func readDocument(at docRef: DocumentReference) {
       if let foo = document["foo"] {
         print("Field: \(foo)")
       }
-    } else {
-      // TODO(mikelehen): There may be a better way to do this, but it at least demonstrates
-      // the swift error domain / enum codes are renamed appropriately.
-      if let errorCode = error.flatMap({
-        ($0._domain == FirestoreErrorDomain) ? FirestoreErrorCode(rawValue: $0._code) : nil
-      }) {
+    } else if let error = error {
+      // New way to handle errors.
+      switch error {
+      case FirestoreErrorCode.unavailable:
+        print("Can't read document due to being offline!")
+      default:
+        print("Failed to read.")
+      }
+
+      // Old way to handle errors.
+      let nsError = error as NSError
+      guard nsError.domain == FirestoreErrorDomain else {
+        print("Unknown error!")
+        return
+      }
+
+      // Option 1: try to initialize the error code enum.
+      if let errorCode = FirestoreErrorCode.Code(rawValue: nsError.code) {
         switch errorCode {
         case .unavailable:
           print("Can't read document due to being offline!")
-        case _:
+        default:
           print("Failed to read.")
         }
-      } else {
-        print("Unknown error!")
       }
+
+      // Option 2: switch on the code and compare agianst raw values.
+      switch nsError.code {
+      case FirestoreErrorCode.unavailable.rawValue:
+        print("Can't read document due to being offline!")
+      default:
+        print("Failed to read.")
+      }
+    } else {
+      print("No error or document. Thanks, ObjC.")
     }
   }
 }