StorageError.swift 9.7 KB

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