StorageError.swift 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  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. @objc(FIRStorageErrorCode) public enum StorageErrorCode: Int, Swift.Error {
  24. case unknown = -13000
  25. case objectNotFound = -13010
  26. case bucketNotFound = -13011
  27. case projectNotFound = -13012
  28. case quotaExceeded = -13013
  29. case unauthenticated = -13020
  30. case unauthorized = -13021
  31. case retryLimitExceeded = -13030
  32. case nonMatchingChecksum = -13031
  33. case downloadSizeExceeded = -13032
  34. case cancelled = -13040
  35. case invalidArgument = -13050
  36. /**
  37. * Creates a Firebase Storage error from a specific GCS error and FIRIMPLStorageReference.
  38. * @param serverError Server error to wrap and return as a Firebase Storage error.
  39. * @param ref StorageReference which provides context about the request being made.
  40. * @return Returns a Firebase Storage error.
  41. */
  42. static func error(withServerError serverError: NSError, ref: StorageReference) -> NSError {
  43. var errorCode: StorageErrorCode
  44. switch serverError.code {
  45. case 400: errorCode = .unknown
  46. case 401: errorCode = .unauthenticated
  47. case 402: errorCode = .quotaExceeded
  48. case 403: errorCode = .unauthorized
  49. case 404: errorCode = .objectNotFound
  50. default: errorCode = .unknown
  51. }
  52. var errorDictionary = serverError.userInfo
  53. errorDictionary["ResponseErrorDomain"] = serverError.domain
  54. errorDictionary["ResponseErrorCode"] = serverError.code
  55. errorDictionary["bucket"] = ref.path.bucket
  56. errorDictionary[NSUnderlyingErrorKey] = serverError
  57. if let object = ref.path.object {
  58. errorDictionary["object"] = object
  59. }
  60. if let data = (errorDictionary["data"] as? Data) {
  61. errorDictionary["ResponseBody"] = String(data: data, encoding: .utf8)
  62. }
  63. return error(withCode: errorCode, infoDictionary: errorDictionary)
  64. }
  65. /** Creates a Firebase Storage error from an invalid request.
  66. *
  67. * @param request The Data representation of the invalid user request.
  68. * @return Returns the corresponding Firebase Storage error.
  69. */
  70. static func error(withInvalidRequest request: Data?) -> NSError {
  71. var requestString: String
  72. if let request = request {
  73. requestString = String(data: request, encoding: .utf8) ?? "<unstringable data>"
  74. } else {
  75. requestString = "<nil request returned from server>"
  76. }
  77. let invalidDataString = "Invalid data returned from the server:\(requestString)"
  78. var localizedFailureKey: String
  79. if #available(OSX 10.13, iOS 11.0, watchOS 4.0, tvOS 11.0, *) {
  80. localizedFailureKey = NSLocalizedFailureErrorKey
  81. } else {
  82. localizedFailureKey = "NSLocalizedFailure"
  83. }
  84. return error(withCode: .unknown, infoDictionary: [localizedFailureKey: invalidDataString])
  85. }
  86. /**
  87. * Creates a Firebase Storage error from a specific FIRStorageErrorCode while adding
  88. * custom info from an optionally provided info dictionary.
  89. */
  90. static func error(withCode code: StorageErrorCode,
  91. infoDictionary: [String: Any]? = nil) -> NSError {
  92. var dictionary = infoDictionary ?? [:]
  93. var errorMessage: String
  94. switch code {
  95. case .objectNotFound:
  96. let object = dictionary["object"] ?? "<object-entity-internal-error>"
  97. errorMessage = "Object \(object) does not exist."
  98. case .bucketNotFound:
  99. let bucket = dictionary["bucket"] ?? "<bucket-entity-internal-error>"
  100. errorMessage = "Bucket \(bucket) does not exist."
  101. case .projectNotFound:
  102. let project = dictionary["project"] ?? "<project-entity-internal-error>"
  103. errorMessage = "Project \(project) does not exist."
  104. case .quotaExceeded:
  105. let bucket = dictionary["bucket"] ?? "<bucket-entity-internal-error>"
  106. errorMessage =
  107. "Quota for bucket \(bucket) exceeded, please view quota on firebase.google.com."
  108. case .downloadSizeExceeded:
  109. let total = "\(dictionary["totalSize"] ?? "unknown")"
  110. let size = "\(dictionary["maxAllowedSize"] ?? "unknown")"
  111. errorMessage = "Attempted to download object with size of \(total) bytes, " +
  112. "which exceeds the maximum size of \(size) bytes. " +
  113. "Consider raising the maximum download size, or using StorageReference.write"
  114. case .unauthenticated:
  115. errorMessage = "User is not authenticated, please authenticate using Firebase " +
  116. "Authentication and try again."
  117. case .unauthorized:
  118. let bucket = dictionary["bucket"] ?? "<bucket-entity-internal-error>"
  119. let object = dictionary["object"] ?? "<object-entity-internal-error>"
  120. errorMessage = "User does not have permission to access gs://\(bucket)/\(object)."
  121. case .retryLimitExceeded:
  122. errorMessage = "Max retry time for operation exceeded, please try again."
  123. case .nonMatchingChecksum:
  124. // TODO: replace with actual checksum strings when we choose to implement.
  125. errorMessage = "Uploaded/downloaded object TODO has checksum: TODO " +
  126. "which does not match server checksum: TODO. Please retry the upload/download."
  127. case .cancelled:
  128. errorMessage = "User cancelled the upload/download."
  129. case .unknown, .invalidArgument: // invalidArgument fell through in the old Objective-C code.
  130. errorMessage = "An unknown error occurred, please check the server response."
  131. }
  132. dictionary[NSLocalizedDescriptionKey] = errorMessage
  133. return NSError(domain: StorageErrorDomain, code: code.rawValue, userInfo: dictionary)
  134. }
  135. }
  136. public enum StorageError: Error {
  137. case unknown(String)
  138. case objectNotFound(String)
  139. case bucketNotFound(String)
  140. case projectNotFound(String)
  141. case quotaExceeded(String)
  142. case unauthenticated
  143. case unauthorized(String, String)
  144. case retryLimitExceeded
  145. case nonMatchingChecksum
  146. case downloadSizeExceeded(Int64, Int64)
  147. case cancelled
  148. case invalidArgument(String)
  149. case internalError(String)
  150. case bucketMismatch(String)
  151. case pathError(String)
  152. static func swiftConvert(objcError: NSError) -> StorageError {
  153. let userInfo = objcError.userInfo
  154. switch objcError.code {
  155. case StorageErrorCode.unknown.rawValue:
  156. return StorageError.unknown(objcError.localizedDescription)
  157. case StorageErrorCode.objectNotFound.rawValue:
  158. guard let object = userInfo["object"] as? String else {
  159. return StorageError
  160. .internalError(
  161. "Failed to decode object not found error: \(objcError.localizedDescription)"
  162. )
  163. }
  164. return StorageError.objectNotFound(object)
  165. case StorageErrorCode.bucketNotFound.rawValue:
  166. guard let bucket = userInfo["bucket"] as? String else {
  167. return StorageError
  168. .internalError(
  169. "Failed to decode bucket not found error: \(objcError.localizedDescription)"
  170. )
  171. }
  172. return StorageError.bucketNotFound(bucket)
  173. case StorageErrorCode.projectNotFound.rawValue:
  174. guard let project = userInfo["project"] as? String else {
  175. return StorageError
  176. .internalError(
  177. "Failed to decode project not found error: \(objcError.localizedDescription)"
  178. )
  179. }
  180. return StorageError.projectNotFound(project)
  181. case StorageErrorCode.quotaExceeded.rawValue:
  182. guard let bucket = userInfo["bucket"] as? String else {
  183. return StorageError
  184. .internalError("Failed to decode quota exceeded error: \(objcError.localizedDescription)")
  185. }
  186. return StorageError.quotaExceeded(bucket)
  187. case StorageErrorCode.unauthenticated.rawValue: return StorageError.unauthenticated
  188. case StorageErrorCode.unauthorized.rawValue:
  189. guard let bucket = userInfo["bucket"] as? String,
  190. let object = userInfo["object"] as? String else {
  191. return StorageError
  192. .internalError(
  193. "Failed to decode unauthorized error: \(objcError.localizedDescription)"
  194. )
  195. }
  196. return StorageError.unauthorized(bucket, object)
  197. case StorageErrorCode.retryLimitExceeded.rawValue: return StorageError.retryLimitExceeded
  198. case StorageErrorCode.nonMatchingChecksum.rawValue: return StorageError
  199. .nonMatchingChecksum
  200. case StorageErrorCode.downloadSizeExceeded.rawValue:
  201. guard let total = userInfo["totalSize"] as? Int64,
  202. let maxSize = userInfo["maxAllowedSize"] as? Int64 else {
  203. return StorageError
  204. .internalError(
  205. "Failed to decode downloadSizeExceeded error: \(objcError.localizedDescription)"
  206. )
  207. }
  208. return StorageError.downloadSizeExceeded(total, maxSize)
  209. case StorageErrorCode.cancelled.rawValue: return StorageError.cancelled
  210. case StorageErrorCode.invalidArgument.rawValue: return StorageError
  211. .invalidArgument(objcError.localizedDescription)
  212. default: return StorageError.internalError("Internal error converting ObjC Error to Swift")
  213. }
  214. }
  215. }