Explorar el Código

[v11] Storage Error Handling Consistency (#13114)

Co-authored-by: Nick Cooke <36927374+ncooke3@users.noreply.github.com>
Paul Beusterien hace 1 año
padre
commit
337d1efd54

+ 6 - 0
FirebaseStorage/CHANGELOG.md

@@ -1,3 +1,9 @@
+# 11.0.0
+- [fixed] Updated error handling to support both Swift error enum handling and NSError error
+  handling. Some of the Swift enums have additional parameters which may be a **breaking** change.
+  There are additional NSError's for completeness, but nothing related to NSError handling is
+  breaking. (#13071, #10889, #13114)
+
 # 10.24.0
 - [fixed] `putFile` and `putFileAsync` now work in app extensions. A background session
    configuration is not used when uploading from an app extension (#12579).

+ 6 - 3
FirebaseStorage/Sources/AsyncAwait.swift

@@ -66,7 +66,8 @@ public extension StorageReference {
       }
       uploadTask.observe(.failure) { snapshot in
         continuation.resume(with: .failure(
-          snapshot.error ?? StorageError.internalError("Internal Storage Error in putDataAsync")
+          snapshot.error ?? StorageError
+            .internalError(message: "Internal Storage Error in putDataAsync")
         ))
       }
     }
@@ -103,7 +104,8 @@ public extension StorageReference {
       }
       uploadTask.observe(.failure) { snapshot in
         continuation.resume(with: .failure(
-          snapshot.error ?? StorageError.internalError("Internal Storage Error in putFileAsync")
+          snapshot.error ?? StorageError
+            .internalError(message: "Internal Storage Error in putFileAsync")
         ))
       }
     }
@@ -137,7 +139,8 @@ public extension StorageReference {
       }
       downloadTask.observe(.failure) { snapshot in
         continuation.resume(with: .failure(
-          snapshot.error ?? StorageError.internalError("Internal Storage Error in writeAsync")
+          snapshot.error ?? StorageError
+            .internalError(message: "Internal Storage Error in writeAsync")
         ))
       }
     }

+ 4 - 4
FirebaseStorage/Sources/Internal/StorageGetDownloadURLTask.swift

@@ -74,10 +74,10 @@ class StorageGetDownloadURLTask: StorageTask, StorageTaskManagement {
              .jsonObject(with: data) as? [String: Any] {
             downloadURL = self.downloadURLFromMetadataDictionary(responseDictionary)
             if downloadURL == nil {
-              self.error = NSError(domain: StorageErrorDomain,
-                                   code: StorageErrorCode.unknown.rawValue,
-                                   userInfo: [NSLocalizedDescriptionKey:
-                                     "Failed to retrieve a download URL."])
+              self.error = StorageError.unknown(
+                message: "Failed to retrieve a download URL.",
+                serverError: [:]
+              ) as NSError
             }
           } else {
             self.error = StorageErrorCode.error(withInvalidRequest: data)

+ 1 - 6
FirebaseStorage/Sources/Internal/StorageTokenAuthorizer.swift

@@ -45,12 +45,7 @@ class StorageTokenAuthorizer: NSObject, GTMSessionFetcherAuthorizer {
           var errorDictionary = error.userInfo
           errorDictionary["ResponseErrorDomain"] = error.domain
           errorDictionary["ResponseErrorCode"] = error.code
-          errorDictionary[NSLocalizedDescriptionKey] =
-            "User is not authenticated, please authenticate" +
-            " using Firebase Authentication and try again."
-          tokenError = NSError(domain: "FIRStorageErrorDomain",
-                               code: StorageErrorCode.unauthenticated.rawValue,
-                               userInfo: errorDictionary)
+          tokenError = StorageError.unauthenticated(serverError: errorDictionary) as NSError
         } else if let token {
           let firebaseToken = "Firebase \(token)"
           request?.setValue(firebaseToken, forHTTPHeaderField: "Authorization")

+ 4 - 2
FirebaseStorage/Sources/Result.swift

@@ -29,9 +29,11 @@ private func getResultCallback<T>(completion: @escaping (Result<T, Error>) -> Vo
     if let value {
       completion(.success(value))
     } else if let error {
-      completion(.failure(StorageError.swiftConvert(objcError: error as NSError)))
+      completion(.failure(error))
     } else {
-      completion(.failure(StorageError.internalError("Internal failure in getResultCallback")))
+      completion(.failure(StorageError.internalError(
+        message: "Internal failure in getResultCallback"
+      )))
     }
   }
 }

+ 3 - 3
FirebaseStorage/Sources/Storage.swift

@@ -193,9 +193,9 @@ import FirebaseCore
     do {
       path = try StoragePath.path(string: url.absoluteString)
     } catch let StoragePathError.storagePathError(message) {
-      throw StorageError.pathError(message)
+      throw StorageError.pathError(message: message)
     } catch {
-      throw StorageError.pathError("Internal error finding StoragePath: \(error)")
+      throw StorageError.pathError(message: "Internal error finding StoragePath: \(error)")
     }
 
     // If no default bucket exists (empty string), accept anything.
@@ -205,7 +205,7 @@ import FirebaseCore
     // If there exists a default bucket, throw if provided a different bucket.
     if path.bucket != storageBucket {
       throw StorageError
-        .bucketMismatch("Provided bucket: `\(path.bucket)` does not match the Storage " +
+        .bucketMismatch(message: "Provided bucket: `\(path.bucket)` does not match the Storage " +
           "bucket of the current instance: `\(storageBucket)`")
     }
     return StorageReference(storage: self, path: path)

+ 1 - 2
FirebaseStorage/Sources/StorageDownloadTask.swift

@@ -67,8 +67,7 @@ open class StorageDownloadTask: StorageObservableTask, StorageTaskManagement {
    * Cancels a task.
    */
   @objc open func cancel() {
-    let error = StorageErrorCode.error(withCode: .cancelled)
-    cancel(withError: error)
+    cancel(withError: StorageError.cancelled as NSError)
   }
 
   /**

+ 130 - 135
FirebaseStorage/Sources/StorageError.swift

@@ -36,24 +36,17 @@ public let StorageErrorDomain: String = "FIRStorageErrorDomain"
   case downloadSizeExceeded = -13032
   case cancelled = -13040
   case invalidArgument = -13050
+  case bucketMismatch = -13051
+  case internalError = -13052
+  case pathError = -13053
 
   /**
-   * Creates a Firebase Storage error from a specific GCS error and FIRIMPLStorageReference.
+   * Creates a Firebase Storage error from a specific GCS error and StorageReference.
    * @param serverError Server error to wrap and return as a Firebase Storage error.
    * @param ref StorageReference which provides context about the request being made.
    * @return Returns a Firebase Storage error.
    */
   static func error(withServerError serverError: NSError, ref: StorageReference) -> NSError {
-    var errorCode: StorageErrorCode
-    switch serverError.code {
-    case 400: errorCode = .unknown
-    case 401: errorCode = .unauthenticated
-    case 402: errorCode = .quotaExceeded
-    case 403: errorCode = .unauthorized
-    case 404: errorCode = .objectNotFound
-    default: errorCode = .unknown
-    }
-
     var errorDictionary = serverError.userInfo
     errorDictionary["ResponseErrorDomain"] = serverError.domain
     errorDictionary["ResponseErrorCode"] = serverError.code
@@ -66,7 +59,29 @@ public let StorageErrorDomain: String = "FIRStorageErrorDomain"
     if let data = (errorDictionary["data"] as? Data) {
       errorDictionary["ResponseBody"] = String(data: data, encoding: .utf8)
     }
-    return error(withCode: errorCode, infoDictionary: errorDictionary)
+    let storageError = switch serverError.code {
+    case 400: StorageError.unknown(
+        message: "Unknown 400 error from backend",
+        serverError: errorDictionary
+      )
+    case 401: StorageError.unauthenticated(serverError: errorDictionary)
+    case 402: StorageError.quotaExceeded(
+        bucket: ref.path.bucket,
+        serverError: errorDictionary
+      )
+    case 403: StorageError.unauthorized(
+        bucket: ref.path.bucket,
+        object: ref.path.object ?? "<object-entity-internal-error>",
+        serverError: errorDictionary
+      )
+    case 404: StorageError.objectNotFound(
+        object: ref.path.object ?? "<object-entity-internal-error>", serverError: errorDictionary
+      )
+    default: StorageError.unknown(
+        message: "Unexpected \(serverError.code) code from backend", serverError: errorDictionary
+      )
+    }
+    return storageError as NSError
   }
 
   /** Creates a Firebase Storage error from an invalid request.
@@ -81,143 +96,123 @@ public let StorageErrorDomain: String = "FIRStorageErrorDomain"
     } else {
       requestString = "<nil request returned from server>"
     }
-    let invalidDataString = "Invalid data returned from the server:\(requestString)"
-    return error(
-      withCode: .unknown,
-      infoDictionary: [NSLocalizedFailureErrorKey: invalidDataString]
-    )
+    let invalidDataString = "Invalid data returned from the server: \(requestString)"
+    return StorageError.unknown(message: invalidDataString, serverError: [:]) as NSError
   }
+}
 
-  /**
-   * Creates a Firebase Storage error from a specific FIRStorageErrorCode while adding
-   * custom info from an optionally provided info dictionary.
-   */
-  static func error(withCode code: StorageErrorCode,
-                    infoDictionary: [String: Any]? = nil) -> NSError {
-    var dictionary = infoDictionary ?? [:]
-    var errorMessage: String
-    switch code {
+/// Firebase Storage errors
+public enum StorageError: Error, CustomNSError {
+  case unknown(message: String, serverError: [String: Any])
+  case objectNotFound(object: String, serverError: [String: Any])
+  case bucketNotFound(bucket: String)
+  case projectNotFound(project: String)
+  case quotaExceeded(bucket: String, serverError: [String: Any])
+  case unauthenticated(serverError: [String: Any])
+  case unauthorized(bucket: String, object: String, serverError: [String: Any])
+  case retryLimitExceeded
+  case nonMatchingChecksum
+  case downloadSizeExceeded(total: Int64, maxSize: Int64)
+  case cancelled
+  case invalidArgument(message: String)
+  case internalError(message: String)
+  case bucketMismatch(message: String)
+  case pathError(message: String)
+
+  // MARK: - CustomNSError
+
+  /// Default domain of the error.
+  public static var errorDomain: String { return StorageErrorDomain }
+
+  /// The error code within the given domain.
+  public var errorCode: Int {
+    switch self {
+    case .unknown:
+      return StorageErrorCode.unknown.rawValue
     case .objectNotFound:
-      let object = dictionary["object"] ?? "<object-entity-internal-error>"
-      errorMessage = "Object \(object) does not exist."
+      return StorageErrorCode.objectNotFound.rawValue
     case .bucketNotFound:
-      let bucket = dictionary["bucket"] ?? "<bucket-entity-internal-error>"
-      errorMessage = "Bucket \(bucket) does not exist."
+      return StorageErrorCode.bucketNotFound.rawValue
     case .projectNotFound:
-      let project = dictionary["project"] ?? "<project-entity-internal-error>"
-      errorMessage = "Project \(project) does not exist."
+      return StorageErrorCode.projectNotFound.rawValue
     case .quotaExceeded:
-      let bucket = dictionary["bucket"] ?? "<bucket-entity-internal-error>"
-      errorMessage =
-        "Quota for bucket \(bucket) exceeded, please view quota on firebase.google.com."
-    case .downloadSizeExceeded:
-      let total = "\(dictionary["totalSize"] ?? "unknown")"
-      let size = "\(dictionary["maxAllowedSize"] ?? "unknown")"
-      errorMessage = "Attempted to download object with size of \(total) bytes, " +
-        "which exceeds the maximum size of \(size) bytes. " +
-        "Consider raising the maximum download size, or using StorageReference.write"
+      return StorageErrorCode.quotaExceeded.rawValue
     case .unauthenticated:
-      errorMessage = "User is not authenticated, please authenticate using Firebase " +
-        "Authentication and try again."
+      return StorageErrorCode.unauthenticated.rawValue
     case .unauthorized:
-      let bucket = dictionary["bucket"] ?? "<bucket-entity-internal-error>"
-      let object = dictionary["object"] ?? "<object-entity-internal-error>"
-      errorMessage = "User does not have permission to access gs://\(bucket)/\(object)."
+      return StorageErrorCode.unauthorized.rawValue
     case .retryLimitExceeded:
-      errorMessage = "Max retry time for operation exceeded, please try again."
+      return StorageErrorCode.retryLimitExceeded.rawValue
     case .nonMatchingChecksum:
-      // TODO: replace with actual checksum strings when we choose to implement.
-      errorMessage = "Uploaded/downloaded object TODO has checksum: TODO " +
-        "which does not match server checksum: TODO. Please retry the upload/download."
+      return StorageErrorCode.nonMatchingChecksum.rawValue
+    case .downloadSizeExceeded:
+      return StorageErrorCode.downloadSizeExceeded.rawValue
     case .cancelled:
-      errorMessage = "User cancelled the upload/download."
-    case .unknown, .invalidArgument: // invalidArgument fell through in the old Objective-C code.
-      errorMessage = "An unknown error occurred, please check the server response."
+      return StorageErrorCode.cancelled.rawValue
+    case .invalidArgument:
+      return StorageErrorCode.invalidArgument.rawValue
+    case .internalError:
+      return StorageErrorCode.internalError.rawValue
+    case .bucketMismatch:
+      return StorageErrorCode.bucketMismatch.rawValue
+    case .pathError:
+      return StorageErrorCode.pathError.rawValue
     }
-    dictionary[NSLocalizedDescriptionKey] = errorMessage
-    return NSError(domain: StorageErrorDomain, code: code.rawValue, userInfo: dictionary)
   }
-}
-
-public enum StorageError: Error {
-  case unknown(String)
-  case objectNotFound(String)
-  case bucketNotFound(String)
-  case projectNotFound(String)
-  case quotaExceeded(String)
-  case unauthenticated
-  case unauthorized(String, String)
-  case retryLimitExceeded
-  case nonMatchingChecksum
-  case downloadSizeExceeded(Int64, Int64)
-  case cancelled
-  case invalidArgument(String)
-  case internalError(String)
-  case bucketMismatch(String)
-  case pathError(String)
 
-  static func swiftConvert(objcError: NSError) -> StorageError {
-    let userInfo = objcError.userInfo
-
-    switch objcError.code {
-    case StorageErrorCode.unknown.rawValue:
-      return StorageError.unknown(objcError.localizedDescription)
-    case StorageErrorCode.objectNotFound.rawValue:
-      guard let object = userInfo["object"] as? String else {
-        return StorageError
-          .internalError(
-            "Failed to decode object not found error: \(objcError.localizedDescription)"
-          )
-      }
-      return StorageError.objectNotFound(object)
-    case StorageErrorCode.bucketNotFound.rawValue:
-      guard let bucket = userInfo["bucket"] as? String else {
-        return StorageError
-          .internalError(
-            "Failed to decode bucket not found error: \(objcError.localizedDescription)"
-          )
-      }
-      return StorageError.bucketNotFound(bucket)
-    case StorageErrorCode.projectNotFound.rawValue:
-      guard let project = userInfo["project"] as? String else {
-        return StorageError
-          .internalError(
-            "Failed to decode project not found error: \(objcError.localizedDescription)"
-          )
-      }
-      return StorageError.projectNotFound(project)
-    case StorageErrorCode.quotaExceeded.rawValue:
-      guard let bucket = userInfo["bucket"] as? String else {
-        return StorageError
-          .internalError("Failed to decode quota exceeded error: \(objcError.localizedDescription)")
-      }
-      return StorageError.quotaExceeded(bucket)
-    case StorageErrorCode.unauthenticated.rawValue: return StorageError.unauthenticated
-    case StorageErrorCode.unauthorized.rawValue:
-      guard let bucket = userInfo["bucket"] as? String,
-            let object = userInfo["object"] as? String else {
-        return StorageError
-          .internalError(
-            "Failed to decode unauthorized error: \(objcError.localizedDescription)"
-          )
-      }
-      return StorageError.unauthorized(bucket, object)
-    case StorageErrorCode.retryLimitExceeded.rawValue: return StorageError.retryLimitExceeded
-    case StorageErrorCode.nonMatchingChecksum.rawValue: return StorageError
-      .nonMatchingChecksum
-    case StorageErrorCode.downloadSizeExceeded.rawValue:
-      guard let total = userInfo["totalSize"] as? Int64,
-            let maxSize = userInfo["maxAllowedSize"] as? Int64 else {
-        return StorageError
-          .internalError(
-            "Failed to decode downloadSizeExceeded error: \(objcError.localizedDescription)"
-          )
-      }
-      return StorageError.downloadSizeExceeded(total, maxSize)
-    case StorageErrorCode.cancelled.rawValue: return StorageError.cancelled
-    case StorageErrorCode.invalidArgument.rawValue: return StorageError
-      .invalidArgument(objcError.localizedDescription)
-    default: return StorageError.internalError("Internal error converting ObjC Error to Swift")
+  /// The default user-info dictionary.
+  public var errorUserInfo: [String: Any] {
+    switch self {
+    case let .unknown(message, serverError):
+      var dictionary = serverError
+      dictionary[NSLocalizedDescriptionKey] = message
+      return dictionary
+    case let .objectNotFound(object, serverError):
+      var dictionary = serverError
+      dictionary[NSLocalizedDescriptionKey] = "Object \(object) does not exist."
+      return dictionary
+    case let .bucketNotFound(bucket):
+      return [NSLocalizedDescriptionKey: "Bucket \(bucket) does not exist."]
+    case let .projectNotFound(project):
+      return [NSLocalizedDescriptionKey: "Project \(project) does not exist."]
+    case let .quotaExceeded(bucket, serverError):
+      var dictionary = serverError
+      dictionary[NSLocalizedDescriptionKey] =
+        "Quota for bucket \(bucket) exceeded, please view quota on firebase.google.com."
+      return dictionary
+    case let .unauthenticated(serverError):
+      var dictionary = serverError
+      dictionary[NSLocalizedDescriptionKey] = "User is not authenticated, please " +
+        "authenticate using Firebase Authentication and try again."
+      return dictionary
+    case let .unauthorized(bucket, object, serverError):
+      var dictionary = serverError
+      dictionary[NSLocalizedDescriptionKey] =
+        "User does not have permission to access gs://\(bucket)/\(object)."
+      return dictionary
+    case .retryLimitExceeded:
+      return [NSLocalizedDescriptionKey: "Max retry time for operation exceeded, please try again."]
+    case .nonMatchingChecksum:
+      // TODO: replace with actual checksum strings when we choose to implement.
+      return [NSLocalizedDescriptionKey: "Uploaded/downloaded object TODO has checksum: TODO " +
+        "which does not match server checksum: TODO. Please retry the upload/download."]
+    case let .downloadSizeExceeded(total, maxSize):
+      var dictionary: [String: Any] = ["totalSize": total, "maxAllowedSize": maxSize]
+      dictionary[NSLocalizedDescriptionKey] = "Attempted to download object with size of " +
+        "\(total) bytes, " +
+        "which exceeds the maximum size of \(maxSize) bytes. " +
+        "Consider raising the maximum download maxSize, or using StorageReference.write"
+      return dictionary
+    case .cancelled:
+      return [NSLocalizedDescriptionKey: "User cancelled the upload/download."]
+    case let .invalidArgument(message):
+      return [NSLocalizedDescriptionKey: message]
+    case let .internalError(message):
+      return [NSLocalizedDescriptionKey: message]
+    case let .bucketMismatch(message):
+      return [NSLocalizedDescriptionKey: message]
+    case let .pathError(message):
+      return [NSLocalizedDescriptionKey: message]
     }
   }
 }

+ 10 - 15
FirebaseStorage/Sources/StorageReference.swift

@@ -399,11 +399,9 @@ import Foundation
   open func list(maxResults: Int64,
                  completion: @escaping ((_: StorageListResult?, _: Error?) -> Void)) {
     if maxResults <= 0 || maxResults > 1000 {
-      completion(nil,
-                 NSError(domain: StorageErrorDomain,
-                         code: StorageErrorCode.invalidArgument.rawValue,
-                         userInfo: [NSLocalizedDescriptionKey:
-                           "Argument 'maxResults' must be between 1 and 1000 inclusive."]))
+      completion(nil, StorageError.invalidArgument(
+        message: "Argument 'maxResults' must be between 1 and 1000 inclusive."
+      ))
     } else {
       let fetcherService = storage.fetcherServiceForApp
       let task = StorageListTask(reference: self,
@@ -437,11 +435,9 @@ import Foundation
                  pageToken: String,
                  completion: @escaping ((_: StorageListResult?, _: Error?) -> Void)) {
     if maxResults <= 0 || maxResults > 1000 {
-      completion(nil,
-                 NSError(domain: StorageErrorDomain,
-                         code: StorageErrorCode.invalidArgument.rawValue,
-                         userInfo: [NSLocalizedDescriptionKey:
-                           "Argument 'maxResults' must be between 1 and 1000 inclusive."]))
+      completion(nil, StorageError.invalidArgument(
+        message: "Argument 'maxResults' must be between 1 and 1000 inclusive."
+      ))
     } else {
       let fetcherService = storage.fetcherServiceForApp
       let task = StorageListTask(reference: self,
@@ -584,11 +580,10 @@ import Foundation
   /// For maxSize API, return an error if the size is exceeded.
   private func checkSizeOverflow(task: StorageTask, maxSize: Int64) -> NSError? {
     if task.progress.totalUnitCount > maxSize || task.progress.completedUnitCount > maxSize {
-      return StorageErrorCode.error(withCode: .downloadSizeExceeded,
-                                    infoDictionary: [
-                                      "totalSize": task.progress.totalUnitCount,
-                                      "maxAllowedSize": maxSize,
-                                    ])
+      return StorageError.downloadSizeExceeded(
+        total: task.progress.totalUnitCount,
+        maxSize: maxSize
+      ) as NSError
     }
     return nil
   }

+ 1 - 1
FirebaseStorage/Sources/StorageTaskSnapshot.swift

@@ -68,7 +68,7 @@ import Foundation
        reference: StorageReference,
        progress: Progress,
        metadata: StorageMetadata? = nil,
-       error: NSError? = nil) {
+       error: Error? = nil) {
     self.task = task
     self.reference = reference
     self.progress = progress

+ 4 - 8
FirebaseStorage/Sources/StorageUploadTask.swift

@@ -237,14 +237,10 @@ import Foundation
        isFile == true {
       return nil
     }
-    let userInfo = [NSLocalizedDescriptionKey:
-      "File at URL: \(fileURL?.absoluteString ?? "") is not reachable."
-      + " Ensure file URL is not a directory, symbolic link, or invalid url."]
-    return NSError(
-      domain: StorageErrorDomain,
-      code: StorageErrorCode.unknown.rawValue,
-      userInfo: userInfo
-    )
+    return StorageError.unknown(message: "File at URL: \(fileURL?.absoluteString ?? "") is " +
+      "not reachable. Ensure file URL is not " +
+      "a directory, symbolic link, or invalid url.",
+      serverError: [:]) as NSError
   }
 
   func finishTaskWithStatus(status: StorageTaskStatus, snapshot: StorageTaskSnapshot) {

+ 22 - 4
FirebaseStorage/Tests/Integration/StorageAsyncAwait.swift

@@ -118,11 +118,29 @@ class StorageAsyncAwait: StorageIntegrationCommon {
     do {
       _ = try await ref.putDataAsync(data)
       XCTFail("Unexpected success from unauthorized putData")
-    } catch let StorageError.unauthorized(bucket, object) {
+    } catch let StorageError.unauthorized(bucket, object, _) {
       XCTAssertEqual(bucket, "ios-opensource-samples.appspot.com")
       XCTAssertEqual(object, objectLocation)
     } catch {
-      XCTFail("error failed to convert to StorageError.unauthorized")
+      XCTFail("error failed to convert to StorageError.unauthorized  \(error)")
+    }
+  }
+
+  func testSimplePutDataUnauthorizedWithNSError() async throws {
+    let objectLocation = "ios/private/secretfile.txt"
+    let ref = storage.reference(withPath: objectLocation)
+    let data = try XCTUnwrap("Hello Swift World".data(using: .utf8), "Data construction failed")
+    do {
+      _ = try await ref.putDataAsync(data)
+      XCTFail("Unexpected success from unauthorized putData")
+    } catch {
+      let e = error as NSError
+      XCTAssertEqual(e.domain, StorageErrorDomain)
+      XCTAssertEqual(e.code, StorageErrorCode.unauthorized.rawValue)
+      XCTAssertEqual(e.localizedDescription, "User does not have permission to access" +
+        " gs://ios-opensource-samples.appspot.com/ios/private/secretfile.txt.")
+      XCTAssertEqual(e.userInfo["ResponseErrorCode"] as? Int, 403)
+      print(e)
     }
   }
 
@@ -136,13 +154,13 @@ class StorageAsyncAwait: StorageIntegrationCommon {
     do {
       _ = try await ref.putFileAsync(from: fileURL)
       XCTFail("Unexpected success from putFile of a directory")
-    } catch let StorageError.unknown(reason) {
+    } catch let StorageError.unknown(reason, _) {
       XCTAssertTrue(reason.starts(with: "File at URL:"))
       XCTAssertTrue(reason.hasSuffix(
         "is not reachable. Ensure file URL is not a directory, symbolic link, or invalid url."
       ))
     } catch {
-      XCTFail("error failed to convert to StorageError.unknown")
+      XCTFail("error failed to convert to StorageError.unknown \(error)")
     }
   }
 

+ 1 - 1
FirebaseStorage/Tests/Integration/StorageIntegration.swift

@@ -201,7 +201,7 @@ class StorageResultTests: StorageIntegrationCommon {
         XCTFail("Unexpected success from unauthorized putData")
       case let .failure(error as StorageError):
         switch error {
-        case let .unauthorized(bucket, object):
+        case let .unauthorized(bucket, object, serverError):
           XCTAssertEqual(bucket, "ios-opensource-samples.appspot.com")
           XCTAssertEqual(object, file)
           expectation.fulfill()

+ 3 - 0
FirebaseStorage/Tests/Unit/StorageAPITests.swift

@@ -158,6 +158,9 @@ final class StorageAPITests: XCTestCase {
     case .downloadSizeExceeded: return code
     case .cancelled: return code
     case .invalidArgument: return code
+    case .bucketMismatch: return code
+    case .internalError: return code
+    case .pathError: return code
     @unknown default:
       fatalError()
     }

+ 3 - 1
FirebaseStorage/Tests/Unit/StorageGetMetadataTests.swift

@@ -181,7 +181,9 @@ class StorageGetMetadataTests: StorageTestHelpers {
       fetcherService: fetcherService!.self,
       queue: dispatchQueue!.self
     ) { metadata, error in
-      XCTAssertEqual((error as? NSError)!.code, StorageErrorCode.unknown.rawValue)
+      XCTAssertNil(metadata)
+      let nsError = try! XCTUnwrap(error as? NSError)
+      XCTAssertEqual(nsError.code, StorageErrorCode.unknown.rawValue)
       expectation.fulfill()
     }
     task.enqueue()

+ 5 - 4
FirebaseStorage/Tests/Unit/StorageReferenceTests.swift

@@ -72,7 +72,7 @@ class StorageReferenceTests: XCTestCase {
     XCTAssertThrowsError(try storage!.reference(for: url), "This was supposed to fail.") { error in
       XCTAssertEqual(
         "\(error)",
-        "bucketMismatch(\"Provided bucket: `bcket` does not match the Storage " +
+        "bucketMismatch(message: \"Provided bucket: `bcket` does not match the Storage " +
           "bucket of the current instance: `bucket`\")"
       )
     }
@@ -95,8 +95,9 @@ class StorageReferenceTests: XCTestCase {
   func testBadBucketScheme2() throws {
     let url = try XCTUnwrap(URL(string: "htttp://bucket/"))
     XCTAssertThrowsError(try storage!.reference(for: url), "This was supposed to fail.") { error in
-      XCTAssertEqual("\(error)", "pathError(\"Internal error: URL scheme must be one of gs://, " +
-        "http://, or https://\")")
+      XCTAssertEqual("\(error)",
+                     "pathError(message: \"Internal error: URL scheme must be one of gs://, " +
+                       "http://, or https://\")")
     }
   }
 
@@ -219,7 +220,7 @@ class StorageReferenceTests: XCTestCase {
         XCTFail("Unexpected success.", file: #file, line: #line)
       case let .failure(error):
         switch error {
-        case let StorageError.unknown(message):
+        case let StorageError.unknown(message, serverError):
           let expectedDescription = "File at URL: \(dummyFileURL.absoluteString) " +
             "is not reachable. Ensure file URL is not a directory, symbolic link, or invalid url."
           XCTAssertEqual(expectedDescription, message)

+ 2 - 2
FirebaseStorage/Tests/Unit/StorageTests.swift

@@ -45,7 +45,7 @@ class StorageTests: XCTestCase {
     XCTAssertThrowsError(try storage.reference(for: url), "This was supposed to fail.") { error in
       XCTAssertEqual(
         "\(error)",
-        "bucketMismatch(\"Provided bucket: `benwu-test2.storage.firebase.com` does not match the " +
+        "bucketMismatch(message: \"Provided bucket: `benwu-test2.storage.firebase.com` does not match the " +
           "Storage bucket of the current instance: `benwu-test1.storage.firebase.com`\")"
       )
     }
@@ -126,7 +126,7 @@ class StorageTests: XCTestCase {
     XCTAssertThrowsError(try storage.reference(for: url), "This was supposed to fail.") { error in
       XCTAssertEqual(
         "\(error)",
-        "bucketMismatch(\"Provided bucket: `bucket` does not match the " +
+        "bucketMismatch(message: \"Provided bucket: `bucket` does not match the " +
           "Storage bucket of the current instance: `notMyBucket`\")"
       )
     }