KingfisherError.swift 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474
  1. //
  2. // KingfisherError.swift
  3. // Kingfisher
  4. //
  5. // Created by onevcat on 2018/09/26.
  6. //
  7. // Copyright (c) 2019 Wei Wang <onevcat@gmail.com>
  8. //
  9. // Permission is hereby granted, free of charge, to any person obtaining a copy
  10. // of this software and associated documentation files (the "Software"), to deal
  11. // in the Software without restriction, including without limitation the rights
  12. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  13. // copies of the Software, and to permit persons to whom the Software is
  14. // furnished to do so, subject to the following conditions:
  15. //
  16. // The above copyright notice and this permission notice shall be included in
  17. // all copies or substantial portions of the Software.
  18. //
  19. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  20. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  21. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  22. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  23. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  24. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  25. // THE SOFTWARE.
  26. import Foundation
  27. #if os(macOS)
  28. import AppKit
  29. #else
  30. import UIKit
  31. #endif
  32. extension Never {}
  33. /// Represents all the errors which can happen in Kingfisher framework.
  34. /// Kingfisher related methods always throw a `KingfisherError` or invoke the callback with `KingfisherError`
  35. /// as its error type. To handle errors from Kingfisher, you switch over the error to get a reason catalog,
  36. /// then switch over the reason to know error detail.
  37. public enum KingfisherError: Error {
  38. // MARK: Error Reason Types
  39. /// Represents the error reason during networking request phase.
  40. ///
  41. /// - emptyRequest: The request is empty. Code 1001.
  42. /// - invalidURL: The URL of request is invalid. Code 1002.
  43. /// - taskCancelled: The downloading task is cancelled by user. Code 1003.
  44. public enum RequestErrorReason {
  45. /// The request is empty. Code 1001.
  46. case emptyRequest
  47. /// The URL of request is invalid. Code 1002.
  48. /// - request: The request is tend to be sent but its URL is invalid.
  49. case invalidURL(request: URLRequest)
  50. /// The downloading task is cancelled by user. Code 1003.
  51. /// - task: The session data task which is cancelled.
  52. /// - token: The cancel token which is used for cancelling the task.
  53. case taskCancelled(task: SessionDataTask, token: SessionDataTask.CancelToken)
  54. }
  55. /// Represents the error reason during networking response phase.
  56. ///
  57. /// - invalidURLResponse: The response is not a valid URL response. Code 2001.
  58. /// - invalidHTTPStatusCode: The response contains an invalid HTTP status code. Code 2002.
  59. /// - URLSessionError: An error happens in the system URL session. Code 2003.
  60. /// - dataModifyingFailed: Data modifying fails on returning a valid data. Code 2004.
  61. /// - noURLResponse: The task is done but no URL response found. Code 2005.
  62. public enum ResponseErrorReason {
  63. /// The response is not a valid URL response. Code 2001.
  64. /// - response: The received invalid URL response.
  65. /// The response is expected to be an HTTP response, but it is not.
  66. case invalidURLResponse(response: URLResponse)
  67. /// The response contains an invalid HTTP status code. Code 2002.
  68. /// - Note:
  69. /// By default, status code 200..<400 is recognized as valid. You can override
  70. /// this behavior by conforming to the `ImageDownloaderDelegate`.
  71. /// - response: The received response.
  72. case invalidHTTPStatusCode(response: HTTPURLResponse)
  73. /// An error happens in the system URL session. Code 2003.
  74. /// - error: The underlying URLSession error object.
  75. case URLSessionError(error: Error)
  76. /// Data modifying fails on returning a valid data. Code 2004.
  77. /// - task: The failed task.
  78. case dataModifyingFailed(task: SessionDataTask)
  79. /// The task is done but no URL response found. Code 2005.
  80. /// - task: The failed task.
  81. case noURLResponse(task: SessionDataTask)
  82. /// The task is cancelled by `ImageDownloaderDelegate` due to the `.cancel` response disposition is
  83. /// specified by the delegate method. Code 2006.
  84. case cancelledByDelegate(response: URLResponse)
  85. }
  86. /// Represents the error reason during Kingfisher caching system.
  87. ///
  88. /// - fileEnumeratorCreationFailed: Cannot create a file enumerator for a certain disk URL. Code 3001.
  89. /// - invalidFileEnumeratorContent: Cannot get correct file contents from a file enumerator. Code 3002.
  90. /// - invalidURLResource: The file at target URL exists, but its URL resource is unavailable. Code 3003.
  91. /// - cannotLoadDataFromDisk: The file at target URL exists, but the data cannot be loaded from it. Code 3004.
  92. /// - cannotCreateDirectory: Cannot create a folder at a given path. Code 3005.
  93. /// - imageNotExisting: The requested image does not exist in cache. Code 3006.
  94. /// - cannotConvertToData: Cannot convert an object to data for storing. Code 3007.
  95. /// - cannotSerializeImage: Cannot serialize an image to data for storing. Code 3008.
  96. /// - cannotCreateCacheFile: Cannot create the cache file at a certain fileURL under a key. Code 3009.
  97. /// - cannotSetCacheFileAttribute: Cannot set file attributes to a cached file. Code 3010.
  98. public enum CacheErrorReason {
  99. /// Cannot create a file enumerator for a certain disk URL. Code 3001.
  100. /// - url: The target disk URL from which the file enumerator should be created.
  101. case fileEnumeratorCreationFailed(url: URL)
  102. /// Cannot get correct file contents from a file enumerator. Code 3002.
  103. /// - url: The target disk URL from which the content of a file enumerator should be got.
  104. case invalidFileEnumeratorContent(url: URL)
  105. /// The file at target URL exists, but its URL resource is unavailable. Code 3003.
  106. /// - error: The underlying error thrown by file manager.
  107. /// - key: The key used to getting the resource from cache.
  108. /// - url: The disk URL where the target cached file exists.
  109. case invalidURLResource(error: Error, key: String, url: URL)
  110. /// The file at target URL exists, but the data cannot be loaded from it. Code 3004.
  111. /// - url: The disk URL where the target cached file exists.
  112. /// - error: The underlying error which describes why this error happens.
  113. case cannotLoadDataFromDisk(url: URL, error: Error)
  114. /// Cannot create a folder at a given path. Code 3005.
  115. /// - path: The disk path where the directory creating operation fails.
  116. /// - error: The underlying error which describes why this error happens.
  117. case cannotCreateDirectory(path: String, error: Error)
  118. /// The requested image does not exist in cache. Code 3006.
  119. /// - key: Key of the requested image in cache.
  120. case imageNotExisting(key: String)
  121. /// Cannot convert an object to data for storing. Code 3007.
  122. /// - object: The object which needs be convert to data.
  123. case cannotConvertToData(object: Any, error: Error)
  124. /// Cannot serialize an image to data for storing. Code 3008.
  125. /// - image: The input image needs to be serialized to cache.
  126. /// - original: The original image data, if exists.
  127. /// - serializer: The `CacheSerializer` used for the image serializing.
  128. case cannotSerializeImage(image: KFCrossPlatformImage?, original: Data?, serializer: CacheSerializer)
  129. /// Cannot create the cache file at a certain fileURL under a key. Code 3009.
  130. /// - fileURL: The url where the cache file should be created.
  131. /// - key: The cache key used for the cache. When caching a file through `KingfisherManager` and Kingfisher's
  132. /// extension method, it is the resolved cache key based on your input `Source` and the image processors.
  133. /// - data: The data to be cached.
  134. /// - error: The underlying error originally thrown by Foundation when writing the `data` to the disk file at
  135. /// `fileURL`.
  136. case cannotCreateCacheFile(fileURL: URL, key: String, data: Data, error: Error)
  137. /// Cannot set file attributes to a cached file. Code 3010.
  138. /// - filePath: The path of target cache file.
  139. /// - attributes: The file attribute to be set to the target file.
  140. /// - error: The underlying error originally thrown by Foundation when setting the `attributes` to the disk
  141. /// file at `filePath`.
  142. case cannotSetCacheFileAttribute(filePath: String, attributes: [FileAttributeKey : Any], error: Error)
  143. /// The disk storage of cache is not ready. Code 3011.
  144. ///
  145. /// This is usually due to extremely lack of space on disk storage, and
  146. /// Kingfisher failed even when creating the cache folder. The disk storage will be in unusable state. Normally,
  147. /// ask user to free some spaces and restart the app to make the disk storage work again.
  148. /// - cacheURL: The intended URL which should be the storage folder.
  149. case diskStorageIsNotReady(cacheURL: URL)
  150. }
  151. /// Represents the error reason during image processing phase.
  152. ///
  153. /// - processingFailed: Image processing fails. There is no valid output image from the processor. Code 4001.
  154. public enum ProcessorErrorReason {
  155. /// Image processing fails. There is no valid output image from the processor. Code 4001.
  156. /// - processor: The `ImageProcessor` used to process the image or its data in `item`.
  157. /// - item: The image or its data content.
  158. case processingFailed(processor: ImageProcessor, item: ImageProcessItem)
  159. }
  160. /// Represents the error reason during image setting in a view related class.
  161. ///
  162. /// - emptySource: The input resource is empty or `nil`. Code 5001.
  163. /// - notCurrentSourceTask: The source task is finished, but it is not the one expected now. Code 5002.
  164. /// - dataProviderError: An error happens during getting data from an `ImageDataProvider`. Code 5003.
  165. public enum ImageSettingErrorReason {
  166. /// The input resource is empty or `nil`. Code 5001.
  167. case emptySource
  168. /// The resource task is finished, but it is not the one expected now. This usually happens when you set another
  169. /// resource on the view without cancelling the current on-going one. The previous setting task will fail with
  170. /// this `.notCurrentSourceTask` error when a result got, regardless of it being successful or not for that task.
  171. /// The result of this original task is contained in the associated value.
  172. /// Code 5002.
  173. /// - result: The `RetrieveImageResult` if the source task is finished without problem. `nil` if an error
  174. /// happens.
  175. /// - error: The `Error` if an issue happens during image setting task. `nil` if the task finishes without
  176. /// problem.
  177. /// - source: The original source value of the task.
  178. case notCurrentSourceTask(result: RetrieveImageResult?, error: Error?, source: Source)
  179. /// An error happens during getting data from an `ImageDataProvider`. Code 5003.
  180. case dataProviderError(provider: ImageDataProvider, error: Error)
  181. /// No more alternative `Source` can be used in current loading process. It means that the
  182. /// `.alternativeSources` are used and Kingfisher tried to recovery from the original error, but still
  183. /// fails for all the given alternative sources. The associated value holds all the errors encountered during
  184. /// the load process, including the original source loading error and all the alternative sources errors.
  185. /// Code 5004.
  186. case alternativeSourcesExhausted([PropagationError])
  187. }
  188. // MARK: Member Cases
  189. /// Represents the error reason during networking request phase.
  190. case requestError(reason: RequestErrorReason)
  191. /// Represents the error reason during networking response phase.
  192. case responseError(reason: ResponseErrorReason)
  193. /// Represents the error reason during Kingfisher caching system.
  194. case cacheError(reason: CacheErrorReason)
  195. /// Represents the error reason during image processing phase.
  196. case processorError(reason: ProcessorErrorReason)
  197. /// Represents the error reason during image setting in a view related class.
  198. case imageSettingError(reason: ImageSettingErrorReason)
  199. // MARK: Helper Properties & Methods
  200. /// Helper property to check whether this error is a `RequestErrorReason.taskCancelled` or not.
  201. public var isTaskCancelled: Bool {
  202. if case .requestError(reason: .taskCancelled) = self {
  203. return true
  204. }
  205. return false
  206. }
  207. /// Helper method to check whether this error is a `ResponseErrorReason.invalidHTTPStatusCode` and the
  208. /// associated value is a given status code.
  209. ///
  210. /// - Parameter code: The given status code.
  211. /// - Returns: If `self` is a `ResponseErrorReason.invalidHTTPStatusCode` error
  212. /// and its status code equals to `code`, `true` is returned. Otherwise, `false`.
  213. public func isInvalidResponseStatusCode(_ code: Int) -> Bool {
  214. if case .responseError(reason: .invalidHTTPStatusCode(let response)) = self {
  215. return response.statusCode == code
  216. }
  217. return false
  218. }
  219. public var isInvalidResponseStatusCode: Bool {
  220. if case .responseError(reason: .invalidHTTPStatusCode) = self {
  221. return true
  222. }
  223. return false
  224. }
  225. /// Helper property to check whether this error is a `ImageSettingErrorReason.notCurrentSourceTask` or not.
  226. /// When a new image setting task starts while the old one is still running, the new task identifier will be
  227. /// set and the old one is overwritten. A `.notCurrentSourceTask` error will be raised when the old task finishes
  228. /// to let you know the setting process finishes with a certain result, but the image view or button is not set.
  229. public var isNotCurrentTask: Bool {
  230. if case .imageSettingError(reason: .notCurrentSourceTask(_, _, _)) = self {
  231. return true
  232. }
  233. return false
  234. }
  235. var isLowDataModeConstrained: Bool {
  236. if #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *),
  237. case .responseError(reason: .URLSessionError(let sessionError)) = self,
  238. let urlError = sessionError as? URLError,
  239. urlError.networkUnavailableReason == .constrained
  240. {
  241. return true
  242. }
  243. return false
  244. }
  245. }
  246. // MARK: - LocalizedError Conforming
  247. extension KingfisherError: LocalizedError {
  248. /// A localized message describing what error occurred.
  249. public var errorDescription: String? {
  250. switch self {
  251. case .requestError(let reason): return reason.errorDescription
  252. case .responseError(let reason): return reason.errorDescription
  253. case .cacheError(let reason): return reason.errorDescription
  254. case .processorError(let reason): return reason.errorDescription
  255. case .imageSettingError(let reason): return reason.errorDescription
  256. }
  257. }
  258. }
  259. // MARK: - CustomNSError Conforming
  260. extension KingfisherError: CustomNSError {
  261. /// The error domain of `KingfisherError`. All errors from Kingfisher is under this domain.
  262. public static let domain = "com.onevcat.Kingfisher.Error"
  263. /// The error code within the given domain.
  264. public var errorCode: Int {
  265. switch self {
  266. case .requestError(let reason): return reason.errorCode
  267. case .responseError(let reason): return reason.errorCode
  268. case .cacheError(let reason): return reason.errorCode
  269. case .processorError(let reason): return reason.errorCode
  270. case .imageSettingError(let reason): return reason.errorCode
  271. }
  272. }
  273. }
  274. extension KingfisherError.RequestErrorReason {
  275. var errorDescription: String? {
  276. switch self {
  277. case .emptyRequest:
  278. return "The request is empty or `nil`."
  279. case .invalidURL(let request):
  280. return "The request contains an invalid or empty URL. Request: \(request)."
  281. case .taskCancelled(let task, let token):
  282. return "The session task was cancelled. Task: \(task), cancel token: \(token)."
  283. }
  284. }
  285. var errorCode: Int {
  286. switch self {
  287. case .emptyRequest: return 1001
  288. case .invalidURL: return 1002
  289. case .taskCancelled: return 1003
  290. }
  291. }
  292. }
  293. extension KingfisherError.ResponseErrorReason {
  294. var errorDescription: String? {
  295. switch self {
  296. case .invalidURLResponse(let response):
  297. return "The URL response is invalid: \(response)"
  298. case .invalidHTTPStatusCode(let response):
  299. return "The HTTP status code in response is invalid. Code: \(response.statusCode), response: \(response)."
  300. case .URLSessionError(let error):
  301. return "A URL session error happened. The underlying error: \(error)"
  302. case .dataModifyingFailed(let task):
  303. return "The data modifying delegate returned `nil` for the downloaded data. Task: \(task)."
  304. case .noURLResponse(let task):
  305. return "No URL response received. Task: \(task)."
  306. case .cancelledByDelegate(let response):
  307. return "The downloading task is cancelled by the downloader delegate. Response: \(response)."
  308. }
  309. }
  310. var errorCode: Int {
  311. switch self {
  312. case .invalidURLResponse: return 2001
  313. case .invalidHTTPStatusCode: return 2002
  314. case .URLSessionError: return 2003
  315. case .dataModifyingFailed: return 2004
  316. case .noURLResponse: return 2005
  317. case .cancelledByDelegate: return 2006
  318. }
  319. }
  320. }
  321. extension KingfisherError.CacheErrorReason {
  322. var errorDescription: String? {
  323. switch self {
  324. case .fileEnumeratorCreationFailed(let url):
  325. return "Cannot create file enumerator for URL: \(url)."
  326. case .invalidFileEnumeratorContent(let url):
  327. return "Cannot get contents from the file enumerator at URL: \(url)."
  328. case .invalidURLResource(let error, let key, let url):
  329. return "Cannot get URL resource values or data for the given URL: \(url). " +
  330. "Cache key: \(key). Underlying error: \(error)"
  331. case .cannotLoadDataFromDisk(let url, let error):
  332. return "Cannot load data from disk at URL: \(url). Underlying error: \(error)"
  333. case .cannotCreateDirectory(let path, let error):
  334. return "Cannot create directory at given path: Path: \(path). Underlying error: \(error)"
  335. case .imageNotExisting(let key):
  336. return "The image is not in cache, but you requires it should only be " +
  337. "from cache by enabling the `.onlyFromCache` option. Key: \(key)."
  338. case .cannotConvertToData(let object, let error):
  339. return "Cannot convert the input object to a `Data` object when storing it to disk cache. " +
  340. "Object: \(object). Underlying error: \(error)"
  341. case .cannotSerializeImage(let image, let originalData, let serializer):
  342. return "Cannot serialize an image due to the cache serializer returning `nil`. " +
  343. "Image: \(String(describing:image)), original data: \(String(describing: originalData)), " +
  344. "serializer: \(serializer)."
  345. case .cannotCreateCacheFile(let fileURL, let key, let data, let error):
  346. return "Cannot create cache file at url: \(fileURL), key: \(key), data length: \(data.count). " +
  347. "Underlying foundation error: \(error)."
  348. case .cannotSetCacheFileAttribute(let filePath, let attributes, let error):
  349. return "Cannot set file attribute for the cache file at path: \(filePath), attributes: \(attributes)." +
  350. "Underlying foundation error: \(error)."
  351. case .diskStorageIsNotReady(let cacheURL):
  352. return "The disk storage is not ready to use yet at URL: '\(cacheURL)'. " +
  353. "This is usually caused by extremely lack of disk space. Ask users to free up some space and restart the app."
  354. }
  355. }
  356. var errorCode: Int {
  357. switch self {
  358. case .fileEnumeratorCreationFailed: return 3001
  359. case .invalidFileEnumeratorContent: return 3002
  360. case .invalidURLResource: return 3003
  361. case .cannotLoadDataFromDisk: return 3004
  362. case .cannotCreateDirectory: return 3005
  363. case .imageNotExisting: return 3006
  364. case .cannotConvertToData: return 3007
  365. case .cannotSerializeImage: return 3008
  366. case .cannotCreateCacheFile: return 3009
  367. case .cannotSetCacheFileAttribute: return 3010
  368. case .diskStorageIsNotReady: return 3011
  369. }
  370. }
  371. }
  372. extension KingfisherError.ProcessorErrorReason {
  373. var errorDescription: String? {
  374. switch self {
  375. case .processingFailed(let processor, let item):
  376. return "Processing image failed. Processor: \(processor). Processing item: \(item)."
  377. }
  378. }
  379. var errorCode: Int {
  380. switch self {
  381. case .processingFailed: return 4001
  382. }
  383. }
  384. }
  385. extension KingfisherError.ImageSettingErrorReason {
  386. var errorDescription: String? {
  387. switch self {
  388. case .emptySource:
  389. return "The input resource is empty."
  390. case .notCurrentSourceTask(let result, let error, let resource):
  391. if let result = result {
  392. return "Retrieving resource succeeded, but this source is " +
  393. "not the one currently expected. Result: \(result). Resource: \(resource)."
  394. } else if let error = error {
  395. return "Retrieving resource failed, and this resource is " +
  396. "not the one currently expected. Error: \(error). Resource: \(resource)."
  397. } else {
  398. return nil
  399. }
  400. case .dataProviderError(let provider, let error):
  401. return "Image data provider fails to provide data. Provider: \(provider), error: \(error)"
  402. case .alternativeSourcesExhausted(let errors):
  403. return "Image setting from alternaive sources failed: \(errors)"
  404. }
  405. }
  406. var errorCode: Int {
  407. switch self {
  408. case .emptySource: return 5001
  409. case .notCurrentSourceTask: return 5002
  410. case .dataProviderError: return 5003
  411. case .alternativeSourcesExhausted: return 5004
  412. }
  413. }
  414. }