// // KingfisherError.swift // Kingfisher // // Created by onevcat on 2018/09/26. // // Copyright (c) 2019 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Foundation extension Never {} /// Represents all the errors which can happen in Kingfisher framework. /// Kingfisher related methods always throw a `KingfisherError` or invoke the callback with `KingfisherError` /// as its error type. To handle errors from Kingfisher, you switch over the error to get a reason catalog, /// then switch over the reason to know error detail. public enum KingfisherError: Error { // MARK: Error Reason Types /// Represents the error reason during networking request phase. /// /// - emptyRequest: The request is empty. Code 1001. /// - invalidURL: The URL of request is invalid. Code 1002. /// - taskCancelled: The downloading task is cancelled by user. Code 1003. public enum RequestErrorReason { /// The request is empty. Code 1001. case emptyRequest /// The URL of request is invalid. Code 1002. /// - request: The request is tend to be sent but its URL is invalid. case invalidURL(request: URLRequest) /// The downloading task is cancelled by user. Code 1003. /// - task: The session data task which is cancelled. /// - token: The cancel token which is used for cancelling the task. case taskCancelled(task: SessionDataTask, token: SessionDataTask.CancelToken) } /// Represents the error reason during networking response phase. /// /// - invalidURLResponse: The response is not a valid URL response. Code 2001. /// - invalidHTTPStatusCode: The response contains an invalid HTTP status code. Code 2002. /// - URLSessionError: An error happens in the system URL session. Code 2003. /// - dataModifyingFailed: Data modifying fails on returning a valid data. Code 2004. /// - noURLResponse: The task is done but no URL response found. Code 2005. public enum ResponseErrorReason { /// The response is not a valid URL response. Code 2001. /// - response: The received invalid URL response. /// The response is expected to be an HTTP response, but it is not. case invalidURLResponse(response: URLResponse) /// The response contains an invalid HTTP status code. Code 2002. /// - Note: /// By default, status code 200..<400 is recognized as valid. You can override /// this behavior by conforming to the `ImageDownloaderDelegate`. /// - response: The received response. case invalidHTTPStatusCode(response: HTTPURLResponse) /// An error happens in the system URL session. Code 2003. /// - error: The underlying URLSession error object. case URLSessionError(error: Error) /// Data modifying fails on returning a valid data. Code 2004. /// - task: The failed task. case dataModifyingFailed(task: SessionDataTask) /// The task is done but no URL response found. Code 2005. /// - task: The failed task. case noURLResponse(task: SessionDataTask) } /// Represents the error reason during Kingfisher caching system. /// /// - fileEnumeratorCreationFailed: Cannot create a file enumerator for a certain disk URL. Code 3001. /// - invalidFileEnumeratorContent: Cannot get correct file contents from a file enumerator. Code 3002. /// - invalidURLResource: The file at target URL exists, but its URL resource is unavailable. Code 3003. /// - cannotLoadDataFromDisk: The file at target URL exists, but the data cannot be loaded from it. Code 3004. /// - cannotCreateDirectory: Cannot create a folder at a given path. Code 3005. /// - imageNotExisting: The requested image does not exist in cache. Code 3006. /// - cannotConvertToData: Cannot convert an object to data for storing. Code 3007. /// - cannotSerializeImage: Cannot serialize an image to data for storing. Code 3008. public enum CacheErrorReason { /// Cannot create a file enumerator for a certain disk URL. Code 3001. /// - url: The target disk URL from which the file enumerator should be created. case fileEnumeratorCreationFailed(url: URL) /// Cannot get correct file contents from a file enumerator. Code 3002. /// - url: The target disk URL from which the content of a file enumerator should be got. case invalidFileEnumeratorContent(url: URL) /// The file at target URL exists, but its URL resource is unavailable. Code 3003. /// - error: The underlying error thrown by file manager. /// - key: The key used to getting the resource from cache. /// - url: The disk URL where the target cached file exists. case invalidURLResource(error: Error, key: String, url: URL) /// The file at target URL exists, but the data cannot be loaded from it. Code 3004. /// - url: The disk URL where the target cached file exists. /// - error: The underlying error which describes why this error happens. case cannotLoadDataFromDisk(url: URL, error: Error) /// Cannot create a folder at a given path. Code 3005. /// - path: The disk path where the directory creating operation fails. /// - error: The underlying error which describes why this error happens. case cannotCreateDirectory(path: String, error: Error) /// The requested image does not exist in cache. Code 3006. /// - key: Key of the requested image in cache. case imageNotExisting(key: String) /// Cannot convert an object to data for storing. Code 3007. /// - object: The object which needs be convert to data. case cannotConvertToData(object: Any, error: Error) /// Cannot serialize an image to data for storing. Code 3008. /// - image: The input image needs to be serialized to cache. /// - original: The original image data, if exists. /// - serializer: The `CacheSerializer` used for the image serializing. case cannotSerializeImage(image: Image?, original: Data?, serializer: CacheSerializer) } /// Represents the error reason during image processing phase. /// /// - processingFailed: Image processing fails. There is no valid output image from the processor. Code 4001. public enum ProcessorErrorReason { /// Image processing fails. There is no valid output image from the processor. Code 4001. /// - processor: The `ImageProcessor` used to process the image or its data in `item`. /// - item: The image or its data content. case processingFailed(processor: ImageProcessor, item: ImageProcessItem) } /// Represents the error reason during image setting in a view related class. /// /// - emptySource: The input resource is empty or `nil`. Code 5001. /// - notCurrentSourceTask: The source task is finished, but it is not the one expected now. Code 5002. /// - dataProviderError: An error happens during getting data from an `ImageDataProvider`. Code 5003. public enum ImageSettingErrorReason { /// The input resource is empty or `nil`. Code 5001. case emptySource /// The resource task is finished, but it is not the one expected now. This usually happens when you set another /// resource on the view without cancelling the current on-going one. The previous setting task will fail with /// this `.notCurrentSourceTask` error when a result got, regardless of it being successful or not for that task. /// The result of this original task is contained in the associated value. /// Code 5002. /// - result: The `RetrieveImageResult` if the source task is finished without problem. `nil` if an error /// happens. /// - error: The `Error` if an issue happens during image setting task. `nil` if the task finishes without /// problem. /// - source: The original source value of the taks. case notCurrentSourceTask(result: RetrieveImageResult?, error: Error?, source: Source) /// An error happens during getting data from an `ImageDataProvider`. Code 5003. case dataProviderError(provider: ImageDataProvider, error: Error) } // MARK: Member Cases /// Represents the error reason during networking request phase. case requestError(reason: RequestErrorReason) /// Represents the error reason during networking response phase. case responseError(reason: ResponseErrorReason) /// Represents the error reason during Kingfisher caching system. case cacheError(reason: CacheErrorReason) /// Represents the error reason during image processing phase. case processorError(reason: ProcessorErrorReason) /// Represents the error reason during image setting in a view related class. case imageSettingError(reason: ImageSettingErrorReason) // MARK: Helper Properties & Methods /// Helper property to check whether this error is a `RequestErrorReason.taskCancelled` or not. public var isTaskCancelled: Bool { if case .requestError(reason: .taskCancelled) = self { return true } return false } /// Helper method to check whether this error is a `ResponseErrorReason.invalidHTTPStatusCode` and the /// associated value is a given status code. /// /// - Parameter code: The given status code. /// - Returns: If `self` is a `ResponseErrorReason.invalidHTTPStatusCode` error /// and its status code equals to `code`, `true` is returned. Otherwise, `false`. public func isInvalidResponseStatusCode(_ code: Int) -> Bool { if case .responseError(reason: .invalidHTTPStatusCode(let response)) = self { return response.statusCode == code } return false } public var isInvalidResponseStatusCode: Bool { if case .responseError(reason: .invalidHTTPStatusCode) = self { return true } return false } /// Helper property to check whether this error is a `ImageSettingErrorReason.notCurrentSourceTask` or not. /// When a new image setting task starts while the old one is still running, the new task identifier will be /// set and the old one is overwritten. A `.notCurrentSourceTask` error will be raised when the old task finishes /// to let you know the setting process finishes with a certain result, but the image view or button is not set. public var isNotCurrentTask: Bool { if case .imageSettingError(reason: .notCurrentSourceTask(_, _, _)) = self { return true } return false } } // MARK: - LocalizedError Conforming extension KingfisherError: LocalizedError { /// A localized message describing what error occurred. public var errorDescription: String? { switch self { case .requestError(let reason): return reason.errorDescription case .responseError(let reason): return reason.errorDescription case .cacheError(let reason): return reason.errorDescription case .processorError(let reason): return reason.errorDescription case .imageSettingError(let reason): return reason.errorDescription } } } // MARK: - CustomNSError Conforming extension KingfisherError: CustomNSError { /// The error domain of `KingfisherError`. All errors from Kingfisher is under this domain. public static let domain = "com.onevcat.Kingfisher.Error" /// The error code within the given domain. public var errorCode: Int { switch self { case .requestError(let reason): return reason.errorCode case .responseError(let reason): return reason.errorCode case .cacheError(let reason): return reason.errorCode case .processorError(let reason): return reason.errorCode case .imageSettingError(let reason): return reason.errorCode } } } extension KingfisherError.RequestErrorReason { var errorDescription: String? { switch self { case .emptyRequest: return "The request is empty or `nil`." case .invalidURL(let request): return "The request contains an invalid or empty URL. Request: \(request)." case .taskCancelled(let task, let token): return "The session task was cancelled. Task: \(task), cancel token: \(token)." } } var errorCode: Int { switch self { case .emptyRequest: return 1001 case .invalidURL: return 1002 case .taskCancelled: return 1003 } } } extension KingfisherError.ResponseErrorReason { var errorDescription: String? { switch self { case .invalidURLResponse(let response): return "The URL response is invalid: \(response)" case .invalidHTTPStatusCode(let response): return "The HTTP status code in response is invalid. Code: \(response.statusCode), response: \(response)." case .URLSessionError(let error): return "A URL session error happened. The underlying error: \(error)" case .dataModifyingFailed(let task): return "The data modifying delegate returned `nil` for the downloaded data. Task: \(task)." case .noURLResponse(let task): return "No URL response received. Task: \(task)," } } var errorCode: Int { switch self { case .invalidURLResponse: return 2001 case .invalidHTTPStatusCode: return 2002 case .URLSessionError: return 2003 case .dataModifyingFailed: return 2004 case .noURLResponse: return 2005 } } } extension KingfisherError.CacheErrorReason { var errorDescription: String? { switch self { case .fileEnumeratorCreationFailed(let url): return "Cannot create file enumerator for URL: \(url)." case .invalidFileEnumeratorContent(let url): return "Cannot get contents from the file enumerator at URL: \(url)." case .invalidURLResource(let error, let key, let url): return "Cannot get URL resource values or data for the given URL: \(url). " + "Cache key: \(key). Underlying error: \(error)" case .cannotLoadDataFromDisk(let url, let error): return "Cannot load data from disk at URL: \(url). Underlying error: \(error)" case .cannotCreateDirectory(let path, let error): return "Cannot create directory at given path: Path: \(path). Underlying error: \(error)" case .imageNotExisting(let key): return "The image is not in cache, but you requires it should only be " + "from cache by enabling the `.onlyFromCache` option. Key: \(key)." case .cannotConvertToData(let object, let error): return "Cannot convert the input object to a `Data` object when storing it to disk cache. " + "Object: \(object). Underlying error: \(error)" case .cannotSerializeImage(let image, let originalData, let serializer): return "Cannot serialize an image due to the cache serializer returning `nil`. " + "Image: \(String(describing:image)), original data: \(String(describing: originalData)), serializer: \(serializer)." } } var errorCode: Int { switch self { case .fileEnumeratorCreationFailed: return 3001 case .invalidFileEnumeratorContent: return 3002 case .invalidURLResource: return 3003 case .cannotLoadDataFromDisk: return 3004 case .cannotCreateDirectory: return 3005 case .imageNotExisting: return 3006 case .cannotConvertToData: return 3007 case .cannotSerializeImage: return 3008 } } } extension KingfisherError.ProcessorErrorReason { var errorDescription: String? { switch self { case .processingFailed(let processor, let item): return "Processing image failed. Processor: \(processor). Processing item: \(item)." } } var errorCode: Int { switch self { case .processingFailed: return 4001 } } } extension KingfisherError.ImageSettingErrorReason { var errorDescription: String? { switch self { case .emptySource: return "The input resource is empty." case .notCurrentSourceTask(let result, let error, let resource): if let result = result { return "Retrieving resource succeeded, but this source is " + "not the one currently expected. Result: \(result). Resource: \(resource)." } else if let error = error { return "Retrieving resource failed, and this resource is " + "not the one currently expected. Error: \(error). Resource: \(resource)." } else { return nil } case .dataProviderError(let provider, let error): return "Image data provider fails to provide data. Provider: \(provider), error: \(error)" } } var errorCode: Int { switch self { case .emptySource: return 5001 case .notCurrentSourceTask: return 5002 case .dataProviderError: return 5003 } } }