StorageError.swift 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. // Copyright 2021 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. import Foundation
  15. /// The error domain for codes in the `StorageErrorCode` enum.
  16. public let StorageErrorDomain: String = "FIRStorageErrorDomain"
  17. /**
  18. * Adds wrappers for common Firebase Storage errors (including creating errors from GCS errors).
  19. * For more information on unwrapping GCS errors, see the GCS errors docs:
  20. * https://cloud.google.com/storage/docs/json_api/v1/status-codes
  21. * This is never publicly exposed to end developers (as they will simply see an NSError).
  22. */
  23. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  24. @objc(FIRStorageErrorCode) public enum StorageErrorCode: Int, Swift.Error {
  25. case unknown = -13000
  26. case objectNotFound = -13010
  27. case bucketNotFound = -13011
  28. case projectNotFound = -13012
  29. case quotaExceeded = -13013
  30. case unauthenticated = -13020
  31. case unauthorized = -13021
  32. case retryLimitExceeded = -13030
  33. case nonMatchingChecksum = -13031
  34. case downloadSizeExceeded = -13032
  35. case cancelled = -13040
  36. case invalidArgument = -13050
  37. case bucketMismatch = -13051
  38. case internalError = -13052
  39. case pathError = -13053
  40. /**
  41. * Creates a Firebase Storage error from a specific GCS error and StorageReference.
  42. * @param serverError Server error to wrap and return as a Firebase Storage error.
  43. * @param ref StorageReference which provides context about the request being made.
  44. * @return Returns a Firebase Storage error.
  45. */
  46. static func error(withServerError serverError: NSError, ref: StorageReference) -> NSError {
  47. var errorDictionary = serverError.userInfo
  48. errorDictionary["ResponseErrorDomain"] = serverError.domain
  49. errorDictionary["ResponseErrorCode"] = serverError.code
  50. errorDictionary["bucket"] = ref.path.bucket
  51. errorDictionary[NSUnderlyingErrorKey] = serverError
  52. if let object = ref.path.object {
  53. errorDictionary["object"] = object
  54. }
  55. if let data = (errorDictionary["data"] as? Data) {
  56. errorDictionary["ResponseBody"] = String(data: data, encoding: .utf8)
  57. }
  58. let storageError = switch serverError.code {
  59. case 400: StorageError.unknown(
  60. message: "Unknown 400 error from backend",
  61. serverError: errorDictionary
  62. )
  63. case 401: StorageError.unauthenticated(serverError: errorDictionary)
  64. case 402: StorageError.quotaExceeded(
  65. bucket: ref.path.bucket,
  66. serverError: errorDictionary
  67. )
  68. case 403: StorageError.unauthorized(
  69. bucket: ref.path.bucket,
  70. object: ref.path.object ?? "<object-entity-internal-error>",
  71. serverError: errorDictionary
  72. )
  73. case 404: StorageError.objectNotFound(
  74. object: ref.path.object ?? "<object-entity-internal-error>", serverError: errorDictionary
  75. )
  76. default: StorageError.unknown(
  77. message: "Unexpected \(serverError.code) code from backend", serverError: errorDictionary
  78. )
  79. }
  80. return storageError as NSError
  81. }
  82. /** Creates a Firebase Storage error from an invalid request.
  83. *
  84. * @param request The Data representation of the invalid user request.
  85. * @return Returns the corresponding Firebase Storage error.
  86. */
  87. static func error(withInvalidRequest request: Data?) -> NSError {
  88. var requestString: String
  89. if let request {
  90. requestString = String(data: request, encoding: .utf8) ?? "<unstringable data>"
  91. } else {
  92. requestString = "<nil request returned from server>"
  93. }
  94. let invalidDataString = "Invalid data returned from the server: \(requestString)"
  95. return StorageError.unknown(message: invalidDataString, serverError: [:]) as NSError
  96. }
  97. }
  98. /// Firebase Storage errors
  99. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  100. public enum StorageError: Error, CustomNSError {
  101. case unknown(message: String, serverError: [String: Any])
  102. case objectNotFound(object: String, serverError: [String: Any])
  103. case bucketNotFound(bucket: String)
  104. case projectNotFound(project: String)
  105. case quotaExceeded(bucket: String, serverError: [String: Any])
  106. case unauthenticated(serverError: [String: Any])
  107. case unauthorized(bucket: String, object: String, serverError: [String: Any])
  108. case retryLimitExceeded
  109. case nonMatchingChecksum
  110. case downloadSizeExceeded(total: Int64, maxSize: Int64)
  111. case cancelled
  112. case invalidArgument(message: String)
  113. case internalError(message: String)
  114. case bucketMismatch(message: String)
  115. case pathError(message: String)
  116. // MARK: - CustomNSError
  117. /// Default domain of the error.
  118. public static var errorDomain: String { return StorageErrorDomain }
  119. /// The error code within the given domain.
  120. public var errorCode: Int {
  121. switch self {
  122. case .unknown:
  123. return StorageErrorCode.unknown.rawValue
  124. case .objectNotFound:
  125. return StorageErrorCode.objectNotFound.rawValue
  126. case .bucketNotFound:
  127. return StorageErrorCode.bucketNotFound.rawValue
  128. case .projectNotFound:
  129. return StorageErrorCode.projectNotFound.rawValue
  130. case .quotaExceeded:
  131. return StorageErrorCode.quotaExceeded.rawValue
  132. case .unauthenticated:
  133. return StorageErrorCode.unauthenticated.rawValue
  134. case .unauthorized:
  135. return StorageErrorCode.unauthorized.rawValue
  136. case .retryLimitExceeded:
  137. return StorageErrorCode.retryLimitExceeded.rawValue
  138. case .nonMatchingChecksum:
  139. return StorageErrorCode.nonMatchingChecksum.rawValue
  140. case .downloadSizeExceeded:
  141. return StorageErrorCode.downloadSizeExceeded.rawValue
  142. case .cancelled:
  143. return StorageErrorCode.cancelled.rawValue
  144. case .invalidArgument:
  145. return StorageErrorCode.invalidArgument.rawValue
  146. case .internalError:
  147. return StorageErrorCode.internalError.rawValue
  148. case .bucketMismatch:
  149. return StorageErrorCode.bucketMismatch.rawValue
  150. case .pathError:
  151. return StorageErrorCode.pathError.rawValue
  152. }
  153. }
  154. /// The default user-info dictionary.
  155. public var errorUserInfo: [String: Any] {
  156. switch self {
  157. case let .unknown(message, serverError):
  158. var dictionary = serverError
  159. dictionary[NSLocalizedDescriptionKey] = message
  160. return dictionary
  161. case let .objectNotFound(object, serverError):
  162. var dictionary = serverError
  163. dictionary[NSLocalizedDescriptionKey] = "Object \(object) does not exist."
  164. return dictionary
  165. case let .bucketNotFound(bucket):
  166. return [NSLocalizedDescriptionKey: "Bucket \(bucket) does not exist."]
  167. case let .projectNotFound(project):
  168. return [NSLocalizedDescriptionKey: "Project \(project) does not exist."]
  169. case let .quotaExceeded(bucket, serverError):
  170. var dictionary = serverError
  171. dictionary[NSLocalizedDescriptionKey] =
  172. "Quota for bucket \(bucket) exceeded, please view quota on firebase.google.com."
  173. return dictionary
  174. case let .unauthenticated(serverError):
  175. var dictionary = serverError
  176. dictionary[NSLocalizedDescriptionKey] = "User is not authenticated, please " +
  177. "authenticate using Firebase Authentication and try again."
  178. return dictionary
  179. case let .unauthorized(bucket, object, serverError):
  180. var dictionary = serverError
  181. dictionary[NSLocalizedDescriptionKey] =
  182. "User does not have permission to access gs://\(bucket)/\(object)."
  183. return dictionary
  184. case .retryLimitExceeded:
  185. return [NSLocalizedDescriptionKey: "Max retry time for operation exceeded, please try again."]
  186. case .nonMatchingChecksum:
  187. // TODO: replace with actual checksum strings when we choose to implement.
  188. return [NSLocalizedDescriptionKey: "Uploaded/downloaded object TODO has checksum: TODO " +
  189. "which does not match server checksum: TODO. Please retry the upload/download."]
  190. case let .downloadSizeExceeded(total, maxSize):
  191. var dictionary: [String: Any] = ["totalSize": total, "maxAllowedSize": maxSize]
  192. dictionary[NSLocalizedDescriptionKey] = "Attempted to download object with size of " +
  193. "\(total) bytes, " +
  194. "which exceeds the maximum size of \(maxSize) bytes. " +
  195. "Consider raising the maximum download maxSize, or using StorageReference.write"
  196. return dictionary
  197. case .cancelled:
  198. return [NSLocalizedDescriptionKey: "User cancelled the upload/download."]
  199. case let .invalidArgument(message):
  200. return [NSLocalizedDescriptionKey: message]
  201. case let .internalError(message):
  202. return [NSLocalizedDescriptionKey: message]
  203. case let .bucketMismatch(message):
  204. return [NSLocalizedDescriptionKey: message]
  205. case let .pathError(message):
  206. return [NSLocalizedDescriptionKey: message]
  207. }
  208. }
  209. }