KingfisherError.swift 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589
  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 that can occur in the Kingfisher framework.
  34. ///
  35. /// Kingfisher-related methods always throw a ``KingfisherError`` or invoke the callback with ``KingfisherError``
  36. /// as its error type. To handle errors from Kingfisher, you switch over the error to get a reason catalog,
  37. /// then switch over the reason to understand the error details.
  38. ///
  39. public enum KingfisherError: Error {
  40. // MARK: Error Reason Types
  41. /// Represents the error reasons during the networking request phase.
  42. public enum RequestErrorReason: Sendable {
  43. /// The request is empty.
  44. ///
  45. /// Error Code: 1001
  46. case emptyRequest
  47. /// The URL of the request is invalid.
  48. ///
  49. /// - Parameter request: The request is intended to be sent, but its URL is invalid.
  50. ///
  51. /// Error Code: 1002
  52. case invalidURL(request: URLRequest)
  53. /// The downloading task is canceled by the user.
  54. ///
  55. /// - Parameters:
  56. /// - task: The session data task which is canceled.
  57. /// - token: The cancel token which is used for canceling the task.
  58. ///
  59. /// Error Code: 1003
  60. case taskCancelled(task: SessionDataTask, token: SessionDataTask.CancelToken)
  61. }
  62. /// Represents the error reason during networking response phase.
  63. public enum ResponseErrorReason: Sendable {
  64. /// The response is not a valid URL response.
  65. ///
  66. /// - Parameters:
  67. /// - response: The received invalid URL response.
  68. /// The response is expected to be an HTTP response, but it is not.
  69. ///
  70. /// Error Code: 2001
  71. case invalidURLResponse(response: URLResponse)
  72. /// The response contains an invalid HTTP status code.
  73. ///
  74. /// - Parameters:
  75. /// - response: The received response.
  76. ///
  77. /// Error Code: 2002
  78. ///
  79. /// - Note: By default, status code 200..<400 is recognized as valid. You can override
  80. /// this behavior by conforming to the `ImageDownloaderDelegate`.
  81. case invalidHTTPStatusCode(response: HTTPURLResponse)
  82. /// An error happens in the system URL session.
  83. ///
  84. /// - Parameters:
  85. /// - error: The underlying URLSession error object.
  86. ///
  87. /// Error Code: 2003
  88. case URLSessionError(error: any Error)
  89. /// Data modifying fails on returning a valid data.
  90. ///
  91. /// - Parameters:
  92. /// - task: The failed task.
  93. ///
  94. /// Error Code: 2004
  95. case dataModifyingFailed(task: SessionDataTask)
  96. /// The task is done but no URL response found.
  97. ///
  98. /// - Parameters:
  99. /// - task: The failed task.
  100. ///
  101. /// Error Code: 2005
  102. case noURLResponse(task: SessionDataTask)
  103. /// The task is cancelled by ``ImageDownloaderDelegate`` due to the `.cancel` response disposition is
  104. /// specified by the delegate method.
  105. ///
  106. /// - Parameters:
  107. /// - task: The cancelled task.
  108. ///
  109. /// Error Code: 2006
  110. case cancelledByDelegate(response: URLResponse)
  111. }
  112. /// Represents the error reason during Kingfisher caching.
  113. public enum CacheErrorReason: @unchecked Sendable {
  114. /// Cannot create a file enumerator for a certain disk URL.
  115. ///
  116. /// - Parameters:
  117. /// - url: The target disk URL from which the file enumerator should be created.
  118. ///
  119. /// Error Code: 3001
  120. case fileEnumeratorCreationFailed(url: URL)
  121. /// Cannot get correct file contents from a file enumerator.
  122. ///
  123. /// - Parameters:
  124. /// - url: The target disk URL from which the content of a file enumerator should be obtained.
  125. ///
  126. /// Error Code: 3002
  127. case invalidFileEnumeratorContent(url: URL)
  128. /// The file at the target URL exists, but its URL resource is unavailable.
  129. ///
  130. /// - Parameters:
  131. /// - error: The underlying error thrown by the file manager.
  132. /// - key: The key used to retrieve the resource from cache.
  133. /// - url: The disk URL where the target cached file exists.
  134. ///
  135. /// Error Code: 3003
  136. case invalidURLResource(error: any Error, key: String, url: URL)
  137. /// The file at the target URL exists, but the data cannot be loaded from it.
  138. ///
  139. /// - Parameters:
  140. /// - url: The disk URL where the target cached file exists.
  141. /// - error: The underlying error that describes why this error occurs.
  142. ///
  143. /// Error Code: 3004
  144. case cannotLoadDataFromDisk(url: URL, error: any Error)
  145. /// Cannot create a folder at a given path.
  146. ///
  147. /// - Parameters:
  148. /// - path: The disk path where the directory creation operation fails.
  149. /// - error: The underlying error that describes why this error occurs.
  150. ///
  151. /// Error Code: 3005
  152. case cannotCreateDirectory(path: String, error: any Error)
  153. /// The requested image does not exist in the cache.
  154. ///
  155. /// - Parameters:
  156. /// - key: The key of the requested image in the cache.
  157. ///
  158. /// Error Code: 3006
  159. case imageNotExisting(key: String)
  160. /// Unable to convert an object to data for storage.
  161. ///
  162. /// - Parameters:
  163. /// - object: The object that needs to be converted to data.
  164. ///
  165. /// Error Code: 3007
  166. case cannotConvertToData(object: Any, error: any Error)
  167. /// Unable to serialize an image to data for storage.
  168. ///
  169. /// - Parameters:
  170. /// - image: The input image that needs to be serialized to cache.
  171. /// - original: The original image data, if it exists.
  172. /// - serializer: The ``CacheSerializer`` used for the image serialization.
  173. ///
  174. /// Error Code: 3008
  175. case cannotSerializeImage(image: KFCrossPlatformImage?, original: Data?, serializer: any CacheSerializer)
  176. /// Unable to create the cache file at a specified `fileURL` under a given `key`.
  177. ///
  178. /// - Parameters:
  179. /// - fileURL: The URL where the cache file should be created.
  180. /// - key: The cache key used for the cache. When caching a file through ``KingfisherManager`` and Kingfisher's
  181. /// extension method, it is the resolved cache key based on your input ``Source`` and the image
  182. /// processors.
  183. /// - data: The data to be cached.
  184. /// - error: The underlying error originally thrown by Foundation when attempting to write the `data` to the disk file at
  185. /// `fileURL`.
  186. ///
  187. /// Error Code: 3009
  188. case cannotCreateCacheFile(fileURL: URL, key: String, data: Data, error: any Error)
  189. /// Unable to set file attributes for a cached file.
  190. ///
  191. /// - Parameters:
  192. /// - filePath: The path of the target cache file.
  193. /// - attributes: The file attributes to be set for the target file.
  194. /// - error: The underlying error originally thrown by the Foundation framework when attempting to set the specified
  195. /// `attributes` for the disk file at `filePath`.
  196. ///
  197. /// Error Code: 3010
  198. case cannotSetCacheFileAttribute(filePath: String, attributes: [FileAttributeKey : Any], error: any Error)
  199. /// The disk storage for caching is not ready.
  200. ///
  201. /// - Parameters:
  202. /// - cacheURL: The intended URL that should be the storage folder.
  203. ///
  204. /// This issue typically arises due to an extreme lack of space on the disk storage. Kingfisher fails to create
  205. /// the cache folder under these circumstances, rendering the disk storage unusable. In such cases, it is
  206. /// recommended to prompt the user to free up storage space and restart the app to restore functionality.
  207. ///
  208. /// Error Code: 3011
  209. case diskStorageIsNotReady(cacheURL: URL)
  210. }
  211. /// Represents the error reason during image processing phase.
  212. public enum ProcessorErrorReason: Sendable {
  213. /// Image processing has failed, and there is no valid output image generated by the processor.
  214. ///
  215. /// - Parameters:
  216. /// - processor: The `ImageProcessor` responsible for processing the image or its data in `item`.
  217. /// - item: The image or its data content.
  218. ///
  219. /// Error Code: 4001
  220. case processingFailed(processor: any ImageProcessor, item: ImageProcessItem)
  221. }
  222. /// Represents the error reason during image setting in a view related class.
  223. public enum ImageSettingErrorReason: Sendable {
  224. /// The input resource is empty or `nil`.
  225. ///
  226. /// Error Code: 5001
  227. case emptySource
  228. /// The resource task is completed, but it is not the one that was expected. This typically occurs when you set
  229. /// another resource on the view without canceling the current ongoing task. The previous task will fail with the
  230. /// `.notCurrentSourceTask` error when a result is obtained, regardless of whether it was successful or not for
  231. /// that task.
  232. ///
  233. /// - Parameters:
  234. /// - result: The `RetrieveImageResult` if the source task is completed without any issues. `nil` if an error occurred.
  235. /// - error: The `Error` if there was a problem during the image setting task. `nil` if the task completed successfully.
  236. /// - source: The original source value of the task.
  237. ///
  238. /// Error Code: 5002
  239. case notCurrentSourceTask(result: RetrieveImageResult?, error: (any Error)?, source: Source)
  240. /// An error occurs while retrieving data from an `ImageDataProvider`.
  241. ///
  242. /// - Parameters:
  243. /// - provider: The ``ImageDataProvider`` that encountered the error.
  244. /// - error: The underlying error that describes why this error occurred.
  245. ///
  246. /// Error Code: 5003
  247. case dataProviderError(provider: any ImageDataProvider, error: any Error)
  248. /// No more alternative ``Source`` can be used in current loading process. It means that the
  249. /// ``KingfisherOptionsInfoItem/alternativeSources(_:)`` are set and Kingfisher tried to recovery from the original error, but still
  250. /// fails for all the given alternative sources. The associated value holds all the errors encountered during
  251. /// the load process, including the original source loading error and all the alternative sources errors.
  252. /// Code 5004.
  253. /// No more alternative `Source` can be used in the current loading process.
  254. ///
  255. /// - Parameters:
  256. /// - error : A ``PropagationError`` contains more information about the source and error.
  257. ///
  258. /// This means that the ``KingfisherOptionsInfoItem/alternativeSources(_:)`` option is set, and Kingfisher attempted to recover from the original error,
  259. /// but still failed for all the provided alternative sources. The associated value holds all the errors encountered during
  260. /// the loading process, including the original source loading error and all the alternative sources errors.
  261. ///
  262. /// Error Code: 5004
  263. case alternativeSourcesExhausted([PropagationError])
  264. }
  265. // MARK: Member Cases
  266. /// Represents the error reasons that can occur during the networking request phase.
  267. case requestError(reason: RequestErrorReason)
  268. /// Represents the error reason that can occur during networking response phase.
  269. case responseError(reason: ResponseErrorReason)
  270. /// Represents the error reason that can occur during Kingfisher caching phase.
  271. case cacheError(reason: CacheErrorReason)
  272. /// Represents the error reason that can occur during image processing phase.
  273. case processorError(reason: ProcessorErrorReason)
  274. /// Represents the error reason that can occur during image setting in a view related class.
  275. case imageSettingError(reason: ImageSettingErrorReason)
  276. // MARK: Helper Properties & Methods
  277. /// A helper property to determine if this error is of type `RequestErrorReason.taskCancelled`.
  278. public var isTaskCancelled: Bool {
  279. if case .requestError(reason: .taskCancelled) = self {
  280. return true
  281. }
  282. return false
  283. }
  284. /// Helper method to check whether this error is a ``ResponseErrorReason/invalidHTTPStatusCode(response:)``
  285. /// and the associated value is a given status code.
  286. ///
  287. /// - Parameter code: The given status code.
  288. /// - Returns: If `self` is a `ResponseErrorReason.invalidHTTPStatusCode` error
  289. /// and its status code equals to `code`, `true` is returned. Otherwise, `false`.
  290. ///
  291. /// A helper method for checking HTTP status code.
  292. ///
  293. /// Use this helper method to determine whether this error corresponds to a
  294. /// ``ResponseErrorReason/invalidHTTPStatusCode(response:)`` with a specific status code.
  295. ///
  296. /// - Parameter code: The desired HTTP status code for comparison.
  297. /// - Returns: `true` if the error is of type ``ResponseErrorReason/invalidHTTPStatusCode(response:)`` and its
  298. /// status code matches the provided `code`, otherwise `false`.
  299. public func isInvalidResponseStatusCode(_ code: Int) -> Bool {
  300. if case .responseError(reason: .invalidHTTPStatusCode(let response)) = self {
  301. return response.statusCode == code
  302. }
  303. return false
  304. }
  305. /// A helper method for checking the error is type of ``ResponseErrorReason/invalidHTTPStatusCode(response:)``.
  306. public var isInvalidResponseStatusCode: Bool {
  307. if case .responseError(reason: .invalidHTTPStatusCode) = self {
  308. return true
  309. }
  310. return false
  311. }
  312. /// A helper property that indicates whether this error is of type
  313. /// ``ImageSettingErrorReason/notCurrentSourceTask(result:error:source:)`` or not.
  314. ///
  315. /// This property is used to check if a new image setting task starts while the old one is still running.
  316. /// In such a scenario, the identifier of the new task will overwrite the identifier of the old task.
  317. ///
  318. /// When the old task finishes, a ``ImageSettingErrorReason/notCurrentSourceTask(result:error:source:)`` error will
  319. /// be raised to notify you that the setting process has completed with a certain result, but the image view or
  320. /// button has not been updated.
  321. ///
  322. /// - Returns: `true` if the error is of type ``ImageSettingErrorReason/notCurrentSourceTask(result:error:source:)``,
  323. /// `false` otherwise.
  324. public var isNotCurrentTask: Bool {
  325. if case .imageSettingError(reason: .notCurrentSourceTask(_, _, _)) = self {
  326. return true
  327. }
  328. return false
  329. }
  330. var isLowDataModeConstrained: Bool {
  331. if #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *),
  332. case .responseError(reason: .URLSessionError(let sessionError)) = self,
  333. let urlError = sessionError as? URLError,
  334. urlError.networkUnavailableReason == .constrained
  335. {
  336. return true
  337. }
  338. return false
  339. }
  340. }
  341. // MARK: - LocalizedError Conforming
  342. extension KingfisherError: LocalizedError {
  343. /// Provides a localized message describing the error that occurred.
  344. ///
  345. /// Use this property to obtain a human-readable description of the error for display to the user.
  346. public var errorDescription: String? {
  347. switch self {
  348. case .requestError(let reason): return reason.errorDescription
  349. case .responseError(let reason): return reason.errorDescription
  350. case .cacheError(let reason): return reason.errorDescription
  351. case .processorError(let reason): return reason.errorDescription
  352. case .imageSettingError(let reason): return reason.errorDescription
  353. }
  354. }
  355. }
  356. // MARK: - CustomNSError Conforming
  357. extension KingfisherError: CustomNSError {
  358. /// The error domain for ``KingfisherError``. All errors generated by Kingfisher are categorized under this domain.
  359. ///
  360. /// When handling errors from the Kingfisher library, you can use this domain to identify and distinguish them
  361. /// from other types of errors in your application.
  362. ///
  363. /// - Note: The error domain is a string identifier associated with each error.
  364. public static let domain = "com.onevcat.Kingfisher.Error"
  365. /// Represents the error code within the specified error domain.
  366. ///
  367. /// Use this property to retrieve the specific error code associated with a ``KingfisherError``. The error code
  368. /// provides additional context and information about the error, allowing you to handle and respond to different
  369. /// error scenarios.
  370. ///
  371. /// - Note: Error codes are numerical values associated with each error within a domain. Check the error code in the
  372. /// API reference of each error reason for the detail.
  373. ///
  374. /// - Returns: The error code as an integer.
  375. public var errorCode: Int {
  376. switch self {
  377. case .requestError(let reason): return reason.errorCode
  378. case .responseError(let reason): return reason.errorCode
  379. case .cacheError(let reason): return reason.errorCode
  380. case .processorError(let reason): return reason.errorCode
  381. case .imageSettingError(let reason): return reason.errorCode
  382. }
  383. }
  384. }
  385. extension KingfisherError.RequestErrorReason {
  386. var errorDescription: String? {
  387. switch self {
  388. case .emptyRequest:
  389. return "The request is empty or `nil`."
  390. case .invalidURL(let request):
  391. return "The request contains an invalid or empty URL. Request: \(request)."
  392. case .taskCancelled(let task, let token):
  393. return "The session task was cancelled. Task: \(task), cancel token: \(token)."
  394. }
  395. }
  396. var errorCode: Int {
  397. switch self {
  398. case .emptyRequest: return 1001
  399. case .invalidURL: return 1002
  400. case .taskCancelled: return 1003
  401. }
  402. }
  403. }
  404. extension KingfisherError.ResponseErrorReason {
  405. var errorDescription: String? {
  406. switch self {
  407. case .invalidURLResponse(let response):
  408. return "The URL response is invalid: \(response)"
  409. case .invalidHTTPStatusCode(let response):
  410. return "The HTTP status code in response is invalid. Code: \(response.statusCode), response: \(response)."
  411. case .URLSessionError(let error):
  412. return "A URL session error happened. The underlying error: \(error)"
  413. case .dataModifyingFailed(let task):
  414. return "The data modifying delegate returned `nil` for the downloaded data. Task: \(task)."
  415. case .noURLResponse(let task):
  416. return "No URL response received. Task: \(task)."
  417. case .cancelledByDelegate(let response):
  418. return "The downloading task is cancelled by the downloader delegate. Response: \(response)."
  419. }
  420. }
  421. var errorCode: Int {
  422. switch self {
  423. case .invalidURLResponse: return 2001
  424. case .invalidHTTPStatusCode: return 2002
  425. case .URLSessionError: return 2003
  426. case .dataModifyingFailed: return 2004
  427. case .noURLResponse: return 2005
  428. case .cancelledByDelegate: return 2006
  429. }
  430. }
  431. }
  432. extension KingfisherError.CacheErrorReason {
  433. var errorDescription: String? {
  434. switch self {
  435. case .fileEnumeratorCreationFailed(let url):
  436. return "Cannot create file enumerator for URL: \(url)."
  437. case .invalidFileEnumeratorContent(let url):
  438. return "Cannot get contents from the file enumerator at URL: \(url)."
  439. case .invalidURLResource(let error, let key, let url):
  440. return "Cannot get URL resource values or data for the given URL: \(url). " +
  441. "Cache key: \(key). Underlying error: \(error)"
  442. case .cannotLoadDataFromDisk(let url, let error):
  443. return "Cannot load data from disk at URL: \(url). Underlying error: \(error)"
  444. case .cannotCreateDirectory(let path, let error):
  445. return "Cannot create directory at given path: Path: \(path). Underlying error: \(error)"
  446. case .imageNotExisting(let key):
  447. return "The image is not in cache, but you requires it should only be " +
  448. "from cache by enabling the `.onlyFromCache` option. Key: \(key)."
  449. case .cannotConvertToData(let object, let error):
  450. return "Cannot convert the input object to a `Data` object when storing it to disk cache. " +
  451. "Object: \(object). Underlying error: \(error)"
  452. case .cannotSerializeImage(let image, let originalData, let serializer):
  453. return "Cannot serialize an image due to the cache serializer returning `nil`. " +
  454. "Image: \(String(describing:image)), original data: \(String(describing: originalData)), " +
  455. "serializer: \(serializer)."
  456. case .cannotCreateCacheFile(let fileURL, let key, let data, let error):
  457. return "Cannot create cache file at url: \(fileURL), key: \(key), data length: \(data.count). " +
  458. "Underlying foundation error: \(error)."
  459. case .cannotSetCacheFileAttribute(let filePath, let attributes, let error):
  460. return "Cannot set file attribute for the cache file at path: \(filePath), attributes: \(attributes)." +
  461. "Underlying foundation error: \(error)."
  462. case .diskStorageIsNotReady(let cacheURL):
  463. return "The disk storage is not ready to use yet at URL: '\(cacheURL)'. " +
  464. "This is usually caused by extremely lack of disk space. Ask users to free up some space and restart the app."
  465. }
  466. }
  467. var errorCode: Int {
  468. switch self {
  469. case .fileEnumeratorCreationFailed: return 3001
  470. case .invalidFileEnumeratorContent: return 3002
  471. case .invalidURLResource: return 3003
  472. case .cannotLoadDataFromDisk: return 3004
  473. case .cannotCreateDirectory: return 3005
  474. case .imageNotExisting: return 3006
  475. case .cannotConvertToData: return 3007
  476. case .cannotSerializeImage: return 3008
  477. case .cannotCreateCacheFile: return 3009
  478. case .cannotSetCacheFileAttribute: return 3010
  479. case .diskStorageIsNotReady: return 3011
  480. }
  481. }
  482. }
  483. extension KingfisherError.ProcessorErrorReason {
  484. var errorDescription: String? {
  485. switch self {
  486. case .processingFailed(let processor, let item):
  487. return "Processing image failed. Processor: \(processor). Processing item: \(item)."
  488. }
  489. }
  490. var errorCode: Int {
  491. switch self {
  492. case .processingFailed: return 4001
  493. }
  494. }
  495. }
  496. extension KingfisherError.ImageSettingErrorReason {
  497. var errorDescription: String? {
  498. switch self {
  499. case .emptySource:
  500. return "The input resource is empty."
  501. case .notCurrentSourceTask(let result, let error, let resource):
  502. if let result = result {
  503. return "Retrieving resource succeeded, but this source is " +
  504. "not the one currently expected. Result: \(result). Resource: \(resource)."
  505. } else if let error = error {
  506. return "Retrieving resource failed, and this resource is " +
  507. "not the one currently expected. Error: \(error). Resource: \(resource)."
  508. } else {
  509. return nil
  510. }
  511. case .dataProviderError(let provider, let error):
  512. return "Image data provider fails to provide data. Provider: \(provider), error: \(error)"
  513. case .alternativeSourcesExhausted(let errors):
  514. return "Image setting from alternative sources failed: \(errors)"
  515. }
  516. }
  517. var errorCode: Int {
  518. switch self {
  519. case .emptySource: return 5001
  520. case .notCurrentSourceTask: return 5002
  521. case .dataProviderError: return 5003
  522. case .alternativeSourcesExhausted: return 5004
  523. }
  524. }
  525. }