diff options
| author | Andrej Mihajlov <and@mullvad.net> | 2022-09-26 13:46:17 +0200 |
|---|---|---|
| committer | Andrej Mihajlov <and@mullvad.net> | 2022-09-26 13:46:17 +0200 |
| commit | 5a7cb538c6b71af5117efef273d8ea3845788517 (patch) | |
| tree | 4028c8d42bd18f73c61f55f36a9f2a267dcd8249 /ios/MullvadVPN | |
| parent | 0bff2bee9cf3eae8e55138d7af3cf7edcbea5fa0 (diff) | |
| parent | 324556c1ef5ff6aca3ed331b71de0a30b0567032 (diff) | |
| download | mullvadvpn-5a7cb538c6b71af5117efef273d8ea3845788517.tar.xz mullvadvpn-5a7cb538c6b71af5117efef273d8ea3845788517.zip | |
Merge branch 'remove-chained-error'
Diffstat (limited to 'ios/MullvadVPN')
34 files changed, 235 insertions, 202 deletions
diff --git a/ios/MullvadVPN/AddressCache/AddressCacheStore.swift b/ios/MullvadVPN/AddressCache/AddressCacheStore.swift index 94ea5d5850..57e5a9bed8 100644 --- a/ios/MullvadVPN/AddressCache/AddressCacheStore.swift +++ b/ios/MullvadVPN/AddressCache/AddressCacheStore.swift @@ -121,7 +121,7 @@ extension AddressCache { try writeToDisk() } catch { logger.error( - chainedError: AnyChainedError(error), + error: error, message: "Failed to persist address cache after reading it from bundle." ) } @@ -170,7 +170,7 @@ extension AddressCache { try writeToDisk() } catch { logger.error( - chainedError: AnyChainedError(error), + error: error, message: "Failed to write address cache after selecting next endpoint." ) } @@ -209,7 +209,7 @@ extension AddressCache { try writeToDisk() } catch { logger.error( - chainedError: AnyChainedError(error), + error: error, message: "Failed to write address cache after setting new endpoints." ) } @@ -238,7 +238,7 @@ extension AddressCache { return readResult } catch { logger.error( - chainedError: AnyChainedError(error), + error: error, message: "Failed to read address cache from disk. Fallback to pre-bundled cache." ) @@ -253,7 +253,7 @@ extension AddressCache { return readResult } catch { logger.error( - chainedError: AnyChainedError(error), + error: error, message: "Failed to read address cache from bundle." ) diff --git a/ios/MullvadVPN/AddressCache/AddressCacheTracker.swift b/ios/MullvadVPN/AddressCache/AddressCacheTracker.swift index 69300772ac..00d118de75 100644 --- a/ios/MullvadVPN/AddressCache/AddressCacheTracker.swift +++ b/ios/MullvadVPN/AddressCache/AddressCacheTracker.swift @@ -143,7 +143,7 @@ extension AddressCache { case let .failure(error): logger.error( - chainedError: AnyChainedError(error), + error: error, message: "Failed to update address cache." ) diff --git a/ios/MullvadVPN/AppDelegate.swift b/ios/MullvadVPN/AppDelegate.swift index 74670008a7..10d44fd99b 100644 --- a/ios/MullvadVPN/AppDelegate.swift +++ b/ios/MullvadVPN/AppDelegate.swift @@ -317,7 +317,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { try BGTaskScheduler.shared.submit(request) } catch { logger.error( - chainedError: AnyChainedError(error), + error: error, message: "Could not schedule app refresh task." ) } @@ -341,7 +341,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { try BGTaskScheduler.shared.submit(request) } catch { logger.error( - chainedError: AnyChainedError(error), + error: error, message: "Could not schedule private key rotation task." ) } @@ -363,7 +363,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { try BGTaskScheduler.shared.submit(request) } catch { logger.error( - chainedError: AnyChainedError(error), + error: error, message: "Could not schedule address cache update task." ) } diff --git a/ios/MullvadVPN/AppStorePaymentManager/AppStorePaymentManagerError.swift b/ios/MullvadVPN/AppStorePaymentManager/AppStorePaymentManagerError.swift index ad29ef3929..f88b6255d4 100644 --- a/ios/MullvadVPN/AppStorePaymentManager/AppStorePaymentManagerError.swift +++ b/ios/MullvadVPN/AppStorePaymentManager/AppStorePaymentManagerError.swift @@ -10,7 +10,7 @@ import Foundation extension AppStorePaymentManager { /// An error type emitted by `AppStorePaymentManager`. - enum Error: ChainedError { + enum Error: LocalizedError, WrappingError { /// Failure to find the account token associated with the transaction. case noAccountSet @@ -29,15 +29,30 @@ extension AppStorePaymentManager { var errorDescription: String? { switch self { case .noAccountSet: - return "Account is not set" + return "Account is not set." case .validateAccount: - return "Account validation error" + return "Account validation error." case .storePayment: - return "Store payment error" + return "Store payment error." case .readReceipt: - return "Read recept error" + return "Read recept error." case .sendReceipt: - return "Send receipt error" + return "Send receipt error." + } + } + + var underlyingError: Swift.Error? { + switch self { + case .noAccountSet: + return nil + case let .sendReceipt(error): + return error + case let .validateAccount(error): + return error + case let .readReceipt(error): + return error + case let .storePayment(error): + return error } } } diff --git a/ios/MullvadVPN/AppStorePaymentManager/SendAppStoreReceiptOperation.swift b/ios/MullvadVPN/AppStorePaymentManager/SendAppStoreReceiptOperation.swift index 32295d4350..b99c542c5a 100644 --- a/ios/MullvadVPN/AppStorePaymentManager/SendAppStoreReceiptOperation.swift +++ b/ios/MullvadVPN/AppStorePaymentManager/SendAppStoreReceiptOperation.swift @@ -60,7 +60,7 @@ class SendAppStoreReceiptOperation: ResultOperation< case let .failure(error): self.logger.error( - chainedError: AnyChainedError(error), + error: error, message: "Failed to fetch the AppStore receipt." ) self.finish(completion: .failure(.readReceipt(error))) @@ -87,7 +87,7 @@ class SendAppStoreReceiptOperation: ResultOperation< case let .failure(error): self.logger.error( - chainedError: error, + error: error, message: "Failed to send the AppStore receipt." ) self.finish(completion: .failure(.sendReceipt(error))) diff --git a/ios/MullvadVPN/ChainedError.swift b/ios/MullvadVPN/ChainedError.swift deleted file mode 100644 index 446ed99469..0000000000 --- a/ios/MullvadVPN/ChainedError.swift +++ /dev/null @@ -1,98 +0,0 @@ -// -// ErrorChain.swift -// MullvadVPN -// -// Created by pronebird on 07/05/2020. -// Copyright © 2020 Mullvad VPN AB. All rights reserved. -// - -import Foundation - -/// A protocol describing errors that can be chained together. -protocol ChainedError: LocalizedError { - /// A source error when available. - var source: Error? { get } -} - -/// A protocol providing error a way to override error description when printing error chain. -protocol CustomChainedErrorDescriptionProtocol { - /// A custom error description that overrides `localizedDescription` when printing error chain. - var customErrorDescription: String? { get } -} - -extension ChainedError { - var source: Error? { - let reflection = Mirror(reflecting: self) - - if case .enum = reflection.displayStyle { - for child in reflection.children { - if let associatedError = child.value as? Error { - return associatedError - } - } - } - - return nil - } - - /// Create a string representation of the entire error chain. - /// An extra `message` is added at the start of the chain when given. - func displayChain(message: String? = nil) -> String { - var s: String - - let errorDescription = Self.getErrorDescription(self) - if let message = message { - s = "Error: \(message)\nCaused by: \(errorDescription)" - } else { - s = "Error: \(errorDescription)" - } - - for sourceError in makeChainIterator() { - s.append("\nCaused by: \(Self.getErrorDescription(sourceError))") - } - - return s - } - - private func makeChainIterator() -> AnyIterator<Error> { - var current: Error? = self - return AnyIterator { () -> Error? in - current = (current as? ChainedError)?.source - return current - } - } - - private static func getErrorDescription(_ error: Error) -> String { - let anError = error as? CustomChainedErrorDescriptionProtocol - - return anError?.customErrorDescription ?? error.localizedDescription - } -} - -extension CustomChainedErrorDescriptionProtocol { - var customErrorDescription: String? { - return nil - } -} - -/// A type-erasing container type for any `Error` that makes the wrapped error behave like -/// `ChainedError`. -final class AnyChainedError: ChainedError, CustomChainedErrorDescriptionProtocol { - private let wrappedError: Error - - init(_ error: Error) { - wrappedError = error - } - - var source: Error? { - return (wrappedError as? ChainedError)?.source - } - - var errorDescription: String? { - return wrappedError.localizedDescription - } - - var customErrorDescription: String? { - return (wrappedError as? CustomChainedErrorDescriptionProtocol)?.customErrorDescription - } -} diff --git a/ios/MullvadVPN/CodingErrors+ChainedError.swift b/ios/MullvadVPN/CodingErrors+CustomErrorDescription.swift index 1dfa73f923..25c65d8b82 100644 --- a/ios/MullvadVPN/CodingErrors+ChainedError.swift +++ b/ios/MullvadVPN/CodingErrors+CustomErrorDescription.swift @@ -1,5 +1,5 @@ // -// CodingErrors+ChainedError.swift +// CodingErrors+CustomErrorDescription.swift // MullvadVPN // // Created by pronebird on 17/02/2022. @@ -8,7 +8,7 @@ import Foundation -extension DecodingError: CustomChainedErrorDescriptionProtocol { +extension DecodingError: CustomErrorDescriptionProtocol { var customErrorDescription: String? { switch self { case let .typeMismatch(type, context): @@ -29,7 +29,7 @@ extension DecodingError: CustomChainedErrorDescriptionProtocol { } } -extension EncodingError: CustomChainedErrorDescriptionProtocol { +extension EncodingError: CustomErrorDescriptionProtocol { var customErrorDescription: String? { switch self { case let .invalidValue(_, context): diff --git a/ios/MullvadVPN/ConnectViewController.swift b/ios/MullvadVPN/ConnectViewController.swift index 49a7bdf1bf..e3da0fe159 100644 --- a/ios/MullvadVPN/ConnectViewController.swift +++ b/ios/MullvadVPN/ConnectViewController.swift @@ -536,7 +536,7 @@ class ConnectViewController: UIViewController, MKMapViewDelegate, RootContainmen contentView.mapView.addOverlays(overlays, level: .aboveLabels) } catch { - logger.error(chainedError: AnyChainedError(error), message: "Failed to load geojson.") + logger.error(error: error, message: "Failed to load geojson.") } } } diff --git a/ios/MullvadVPN/CustomErrorDescriptionProtocol.swift b/ios/MullvadVPN/CustomErrorDescriptionProtocol.swift new file mode 100644 index 0000000000..448d848686 --- /dev/null +++ b/ios/MullvadVPN/CustomErrorDescriptionProtocol.swift @@ -0,0 +1,15 @@ +// +// CustomErrorDescription.swift +// MullvadVPN +// +// Created by pronebird on 23/09/2022. +// Copyright © 2022 Mullvad VPN AB. All rights reserved. +// + +import Foundation + +/// A protocol providing error a way to override error description when printing error chain. +protocol CustomErrorDescriptionProtocol { + /// A custom error description that overrides `localizedDescription` when printing error chain. + var customErrorDescription: String? { get } +} diff --git a/ios/MullvadVPN/DeviceManagementViewController.swift b/ios/MullvadVPN/DeviceManagementViewController.swift index 128e1c6ab4..383b7ca822 100644 --- a/ios/MullvadVPN/DeviceManagementViewController.swift +++ b/ios/MullvadVPN/DeviceManagementViewController.swift @@ -248,7 +248,7 @@ class DeviceManagementViewController: UIViewController, RootContainment { case let .failure(error): self.logger.error( - chainedError: AnyChainedError(error), + error: error, message: "Failed to delete device." ) completionHandler(error) diff --git a/ios/MullvadVPN/Error+Chain.swift b/ios/MullvadVPN/Error+Chain.swift new file mode 100644 index 0000000000..8ddd0df219 --- /dev/null +++ b/ios/MullvadVPN/Error+Chain.swift @@ -0,0 +1,44 @@ +// +// Error+Chain.swift +// MullvadVPN +// +// Created by pronebird on 23/09/2022. +// Copyright © 2022 Mullvad VPN AB. All rights reserved. +// + +import Foundation + +extension Error { + /// Returns a flat list of errors by unrolling the underlying error chain. + var underlyingErrorChain: [Error] { + var errors: [Error] = [] + var currentError: Error? = self as Error + + while let underlyingError = currentError?.getUnderlyingError() { + currentError = underlyingError + errors.append(underlyingError) + } + + return errors + } + + func logFormatError() -> String { + let nsError = self as NSError + var message = "" + + let description = (self as? CustomErrorDescriptionProtocol)? + .customErrorDescription ?? localizedDescription + + message += "\(description) (domain = \(nsError.domain), code = \(nsError.code))" + + return message + } + + private func getUnderlyingError() -> Error? { + if let wrappingError = self as? WrappingError { + return wrappingError.underlyingError + } else { + return (self as NSError).userInfo[NSUnderlyingErrorKey] as? Error + } + } +} diff --git a/ios/MullvadVPN/Logging/LogRotation.swift b/ios/MullvadVPN/Logging/LogRotation.swift index ff7671ae80..a4c3be01f5 100644 --- a/ios/MullvadVPN/Logging/LogRotation.swift +++ b/ios/MullvadVPN/Logging/LogRotation.swift @@ -9,7 +9,7 @@ import Foundation enum LogRotation { - enum Error: ChainedError { + enum Error: LocalizedError, WrappingError { case noSourceLogFile case moveSourceLogFile(Swift.Error) @@ -21,30 +21,27 @@ enum LogRotation { return "Failure to move the source log file to backup." } } + + var underlyingError: Swift.Error? { + switch self { + case .noSourceLogFile: + return nil + case let .moveSourceLogFile(error): + return error + } + } } static func rotateLog(logsDirectory: URL, logFileName: String) throws { - let fileManager = FileManager.default let source = logsDirectory.appendingPathComponent(logFileName) let backup = source.deletingPathExtension().appendingPathExtension("old.log") do { - _ = try fileManager.replaceItemAt(backup, withItemAt: source) + _ = try FileManager.default.replaceItemAt(backup, withItemAt: source) } catch { // FileManager returns a very obscure error chain so we need to traverse it to find // the root cause of the error. - var errorCursor: Swift.Error? = error - let cocoaErrorIterator = AnyIterator { () -> CocoaError? in - if let cocoaError = errorCursor as? CocoaError { - errorCursor = cocoaError.underlying - return cocoaError - } else { - errorCursor = nil - return nil - } - } - - while let fileError = cocoaErrorIterator.next() { + for case let fileError as CocoaError in error.underlyingErrorChain { // .fileNoSuchFile is returned when both backup and source log files do not exist // .fileReadNoSuchFile is returned when backup exists but source log file does not if fileError.code == .fileNoSuchFile || fileError.code == .fileReadNoSuchFile, diff --git a/ios/MullvadVPN/Logging/ChainedError+Logger.swift b/ios/MullvadVPN/Logging/Logger+Errors.swift index 0aede67701..0b2811a68e 100644 --- a/ios/MullvadVPN/Logging/ChainedError+Logger.swift +++ b/ios/MullvadVPN/Logging/Logger+Errors.swift @@ -1,5 +1,5 @@ // -// ChainedError+Logger.swift +// Logger+Errors.swift // MullvadVPN // // Created by pronebird on 02/08/2020. @@ -10,8 +10,8 @@ import Foundation import Logging extension Logger { - func error<T: ChainedError>( - chainedError: T, + func error<T: Error>( + error: T, message: @autoclosure () -> String? = nil, metadata: @autoclosure () -> Logger.Metadata? = nil, source: @autoclosure () -> String? = nil, @@ -19,11 +19,25 @@ extension Logger { function: String = #function, line: UInt = #line ) { + var lines = [String]() + var errors = [Error]() + + if let prefixMessage = message() { + lines.append(prefixMessage) + errors.append(error) + } else { + lines.append(error.logFormatError()) + } + + errors.append(contentsOf: error.underlyingErrorChain) + + for error in errors { + lines.append("Caused by: \(error.logFormatError())") + } + log( level: .error, - Message( - stringLiteral: chainedError.displayChain(message: message()) - ), + Message(stringLiteral: lines.joined(separator: "\n")), metadata: metadata(), source: source(), file: file, diff --git a/ios/MullvadVPN/Logging/Logging.swift b/ios/MullvadVPN/Logging/Logging.swift index a0c535853e..1e65249ec8 100644 --- a/ios/MullvadVPN/Logging/Logging.swift +++ b/ios/MullvadVPN/Logging/Logging.swift @@ -70,7 +70,7 @@ func initLoggingSystem(bundleIdentifier: String, metadata: Logger.Metadata? = ni if let logRotationError = logRotationError { Logger(label: "LogRotation").error( - chainedError: AnyChainedError(logRotationError), + error: logRotationError, message: "Failed to rotate log" ) } diff --git a/ios/MullvadVPN/LoginViewController.swift b/ios/MullvadVPN/LoginViewController.swift index 6e363a6b25..b0587e9543 100644 --- a/ios/MullvadVPN/LoginViewController.swift +++ b/ios/MullvadVPN/LoginViewController.swift @@ -236,7 +236,7 @@ class LoginViewController: UIViewController, RootContainment { contentView.accountInputGroup.setLastUsedAccount(accountNumber, animated: false) } catch { logger.error( - chainedError: AnyChainedError(error), + error: error, message: "Failed to update last used account." ) } @@ -423,7 +423,7 @@ extension LoginViewController: AccountInputGroupViewDelegate { return true } catch { logger.error( - chainedError: AnyChainedError(error), + error: error, message: "Failed to remove last used account." ) return false diff --git a/ios/MullvadVPN/NotificationManager.swift b/ios/MullvadVPN/NotificationManager.swift index 63468e70a3..5838b41d5b 100644 --- a/ios/MullvadVPN/NotificationManager.swift +++ b/ios/MullvadVPN/NotificationManager.swift @@ -145,7 +145,7 @@ class NotificationManager: NotificationProviderDelegate { notificationCenter.add(newRequest) { error in if let error = error { self.logger.error( - chainedError: AnyChainedError(error), + error: error, message: "Failed to add notification request with identifier \(newRequest.identifier)." ) } @@ -174,7 +174,7 @@ class NotificationManager: NotificationProviderDelegate { .requestAuthorization(options: authorizationOptions) { granted, error in if let error = error { self.logger.error( - chainedError: AnyChainedError(error), + error: error, message: "Failed to obtain user notifications authorization" ) } diff --git a/ios/MullvadVPN/Notifications/TunnelStatusNotificationProvider.swift b/ios/MullvadVPN/Notifications/TunnelStatusNotificationProvider.swift index d19335972d..59642ccc0c 100644 --- a/ios/MullvadVPN/Notifications/TunnelStatusNotificationProvider.swift +++ b/ios/MullvadVPN/Notifications/TunnelStatusNotificationProvider.swift @@ -124,7 +124,7 @@ class TunnelStatusNotificationProvider: NotificationProvider, InAppNotificationP value: "Failed to start the tunnel: %@.", comment: "" ), - startError.underlyingError.localizedDescription + startError.underlyingError?.localizedDescription ?? "" ) } else if let stopError = error as? StopTunnelError { body = String( @@ -133,7 +133,7 @@ class TunnelStatusNotificationProvider: NotificationProvider, InAppNotificationP value: "Failed to stop the tunnel: %@.", comment: "" ), - stopError.underlyingError.localizedDescription + stopError.underlyingError?.localizedDescription ?? "" ) } else { body = error.localizedDescription diff --git a/ios/MullvadVPN/REST/RESTAccessTokenManager.swift b/ios/MullvadVPN/REST/RESTAccessTokenManager.swift index 7d46b16edd..79e5096fb5 100644 --- a/ios/MullvadVPN/REST/RESTAccessTokenManager.swift +++ b/ios/MullvadVPN/REST/RESTAccessTokenManager.swift @@ -49,7 +49,7 @@ extension REST { case let .failure(error): self.logger.error( - chainedError: error, + error: error, message: "Failed to fetch access token." ) diff --git a/ios/MullvadVPN/REST/RESTError.swift b/ios/MullvadVPN/REST/RESTError.swift index 25e51bd1b9..63a6167d39 100644 --- a/ios/MullvadVPN/REST/RESTError.swift +++ b/ios/MullvadVPN/REST/RESTError.swift @@ -10,7 +10,7 @@ import Foundation extension REST { /// An error type returned by REST API classes. - enum Error: ChainedError { + enum Error: LocalizedError, WrappingError { /// A failure to create URL request. case createURLRequest(Swift.Error) @@ -25,10 +25,10 @@ extension REST { var errorDescription: String? { switch self { - case .createURLRequest: - return "Failure to create URL request." - case .network: - return "Network error." + case let .createURLRequest(error): + return "Failure to create URL request: \(error.localizedDescription)." + case let .network(error): + return "Network error: \(error.localizedDescription)." case let .unhandledResponse(statusCode, serverResponse): var str = "Failure to handle server response: HTTP/\(statusCode)." @@ -41,8 +41,21 @@ extension REST { } return str - case .decodeResponse: - return "Failure to decode URL response data." + case let .decodeResponse(error): + return "Failure to decode URL response data: \(error.localizedDescription)." + } + } + + var underlyingError: Swift.Error? { + switch self { + case let .network(error): + return error + case let .createURLRequest(error): + return error + case let .decodeResponse(error): + return error + case .unhandledResponse: + return nil } } diff --git a/ios/MullvadVPN/REST/RESTNetworkOperation.swift b/ios/MullvadVPN/REST/RESTNetworkOperation.swift index 02b1f5bff2..f1ffe472da 100644 --- a/ios/MullvadVPN/REST/RESTNetworkOperation.swift +++ b/ios/MullvadVPN/REST/RESTNetworkOperation.swift @@ -125,7 +125,7 @@ extension REST { dispatchPrecondition(condition: .onQueue(dispatchQueue)) logger.error( - chainedError: error, + error: error, message: "Failed to request authorization." ) @@ -165,7 +165,7 @@ extension REST { dispatchPrecondition(condition: .onQueue(dispatchQueue)) logger.error( - chainedError: error, + error: error, message: "Failed to create URLRequest." ) @@ -188,7 +188,7 @@ extension REST { } logger.error( - chainedError: AnyChainedError(urlError), + error: urlError, message: "Failed to perform request to \(endpoint)." ) diff --git a/ios/MullvadVPN/RelayCache/RelayCacheTracker.swift b/ios/MullvadVPN/RelayCache/RelayCacheTracker.swift index ec56e2b5bb..9698322559 100644 --- a/ios/MullvadVPN/RelayCache/RelayCacheTracker.swift +++ b/ios/MullvadVPN/RelayCache/RelayCacheTracker.swift @@ -104,7 +104,7 @@ extension RelayCache { ) } catch { logger.error( - chainedError: AnyChainedError(error), + error: error, message: "Failed to read the relay cache during initialization." ) @@ -241,7 +241,7 @@ extension RelayCache { if let error = mappedCompletion.error { logger.error( - chainedError: AnyChainedError(error), + error: error, message: "Failed to update relays." ) } diff --git a/ios/MullvadVPN/SettingsManager/SettingsManager.swift b/ios/MullvadVPN/SettingsManager/SettingsManager.swift index 95af7b2a99..cd40ef67d1 100644 --- a/ios/MullvadVPN/SettingsManager/SettingsManager.swift +++ b/ios/MullvadVPN/SettingsManager/SettingsManager.swift @@ -224,7 +224,7 @@ extension SettingsManager { ) } catch { logger.error( - chainedError: AnyChainedError(error), + error: error, message: "Failed to decode legacy settings." ) return nil @@ -248,7 +248,7 @@ extension SettingsManager { if error != .itemNotFound { logger.error( - chainedError: AnyChainedError(error), + error: error, message: "Failed to list legacy settings." ) } @@ -280,7 +280,7 @@ extension SettingsManager { let error = KeychainError(code: status) logger.error( - chainedError: AnyChainedError(error), + error: error, message: "Failed to remove legacy settings entry \(index)." ) } diff --git a/ios/MullvadVPN/ShortcutsManager.swift b/ios/MullvadVPN/ShortcutsManager.swift index 4a6309429c..1baa525401 100644 --- a/ios/MullvadVPN/ShortcutsManager.swift +++ b/ios/MullvadVPN/ShortcutsManager.swift @@ -38,7 +38,7 @@ final class ShortcutsManager { guard let self = self else { return } if let error = error { self.logger.error( - chainedError: AnyChainedError(error), + error: error, message: "Failed to fetch voice shortcuts." ) return diff --git a/ios/MullvadVPN/SimulatorTunnelProviderHost.swift b/ios/MullvadVPN/SimulatorTunnelProviderHost.swift index 31d28ad84c..c30dda0e39 100644 --- a/ios/MullvadVPN/SimulatorTunnelProviderHost.swift +++ b/ios/MullvadVPN/SimulatorTunnelProviderHost.swift @@ -31,7 +31,7 @@ class SimulatorTunnelProviderHost: SimulatorTunnelProviderDelegate { selectorResult = try tunnelOptions.getSelectorResult() } catch { self.providerLogger.error( - chainedError: AnyChainedError(error), + error: error, message: """ Failed to decode relay selector result passed from the app. \ Will continue by picking new relay. @@ -45,7 +45,7 @@ class SimulatorTunnelProviderHost: SimulatorTunnelProviderDelegate { completionHandler(nil) } catch { self.providerLogger.error( - chainedError: AnyChainedError(error), + error: error, message: "Failed to pick relay." ) completionHandler(error) @@ -72,7 +72,7 @@ class SimulatorTunnelProviderHost: SimulatorTunnelProviderDelegate { completionHandler?(response) } catch { self.providerLogger.error( - chainedError: AnyChainedError(error), + error: error, message: "Failed to handle app message." ) diff --git a/ios/MullvadVPN/TunnelManager/LoadTunnelConfigurationOperation.swift b/ios/MullvadVPN/TunnelManager/LoadTunnelConfigurationOperation.swift index e98aaff5fd..70a3d95d25 100644 --- a/ios/MullvadVPN/TunnelManager/LoadTunnelConfigurationOperation.swift +++ b/ios/MullvadVPN/TunnelManager/LoadTunnelConfigurationOperation.swift @@ -51,7 +51,7 @@ class LoadTunnelConfigurationOperation: ResultOperation<Void, Error> { tunnel.removeFromPreferences { error in if let error = error { self.logger.error( - chainedError: AnyChainedError(error), + error: error, message: "Failed to remove VPN configuration." ) } @@ -78,14 +78,14 @@ class LoadTunnelConfigurationOperation: ResultOperation<Void, Error> { return .success(nil) } else if let error = error as? DecodingError { logger.error( - chainedError: AnyChainedError(error), + error: error, message: "Cannot decode settings. Will attempt to delete them from keychain." ) return Result { try SettingsManager.deleteSettings() } .mapError { error in logger.error( - chainedError: AnyChainedError(error), + error: error, message: "Failed to delete settings from keychain." ) @@ -109,14 +109,14 @@ class LoadTunnelConfigurationOperation: ResultOperation<Void, Error> { return .success(nil) } else if let error = error as? DecodingError { logger.error( - chainedError: AnyChainedError(error), + error: error, message: "Cannot decode device state. Will attempt to delete it from keychain." ) return Result { try SettingsManager.deleteDeviceState() } .mapError { error in logger.error( - chainedError: AnyChainedError(error), + error: error, message: "Failed to delete device state from keychain." ) diff --git a/ios/MullvadVPN/TunnelManager/MigrateSettingsOperation.swift b/ios/MullvadVPN/TunnelManager/MigrateSettingsOperation.swift index 17fc69cff1..adf3735f14 100644 --- a/ios/MullvadVPN/TunnelManager/MigrateSettingsOperation.swift +++ b/ios/MullvadVPN/TunnelManager/MigrateSettingsOperation.swift @@ -55,7 +55,7 @@ class MigrateSettingsOperation: AsyncOperation { try SettingsManager.setLastUsedAccount(storedAccountNumber) } catch { logger.error( - chainedError: AnyChainedError(error), + error: error, message: "Failed to store last used account." ) } @@ -73,7 +73,7 @@ class MigrateSettingsOperation: AsyncOperation { return } catch { logger.error( - chainedError: AnyChainedError(error), + error: error, message: "Failed to read legacy settings from keychain." ) finishMigration() @@ -147,7 +147,7 @@ class MigrateSettingsOperation: AsyncOperation { self.accountData = accountData case let .failure(error): - logger.error(chainedError: error, message: "Failed to fetch accound data.") + logger.error(error: error, message: "Failed to fetch accound data.") case .cancelled: logger.debug("Account data request was cancelled.") @@ -163,7 +163,7 @@ class MigrateSettingsOperation: AsyncOperation { self.devices = devices case let .failure(error): - logger.error(chainedError: error, message: "Failed to fetch devices.") + logger.error(error: error, message: "Failed to fetch devices.") case .cancelled: logger.debug("Device request was cancelled.") @@ -235,7 +235,7 @@ class MigrateSettingsOperation: AsyncOperation { try SettingsManager.writeDeviceState(newDeviceState) } catch { logger.error( - chainedError: AnyChainedError(error), + error: error, message: "Failed to write migrated settings." ) } diff --git a/ios/MullvadVPN/TunnelManager/RotateKeyOperation.swift b/ios/MullvadVPN/TunnelManager/RotateKeyOperation.swift index 5cdf10a872..0029373af7 100644 --- a/ios/MullvadVPN/TunnelManager/RotateKeyOperation.swift +++ b/ios/MullvadVPN/TunnelManager/RotateKeyOperation.swift @@ -107,7 +107,7 @@ class RotateKeyOperation: ResultOperation<Bool, Error> { case let .failure(error): logger.error( - chainedError: AnyChainedError(error), + error: error, message: "Failed to rotate device key." ) finish(completion: .failure(error)) diff --git a/ios/MullvadVPN/TunnelManager/SendTunnelProviderMessageOperation.swift b/ios/MullvadVPN/TunnelManager/SendTunnelProviderMessageOperation.swift index 2652433cee..9fb72e03d9 100644 --- a/ios/MullvadVPN/TunnelManager/SendTunnelProviderMessageOperation.swift +++ b/ios/MullvadVPN/TunnelManager/SendTunnelProviderMessageOperation.swift @@ -235,7 +235,7 @@ extension SendTunnelProviderMessageOperation where Output == Void { } } -enum SendTunnelProviderMessageError: ChainedError { +enum SendTunnelProviderMessageError: LocalizedError, WrappingError { /// Tunnel process is either down or about to go down. case tunnelDown(NEVPNStatus) @@ -251,8 +251,17 @@ enum SendTunnelProviderMessageError: ChainedError { return "Tunnel is either down or about to go down (status: \(status))." case .timeout: return "Send timeout." - case .system: - return "System error." + case let .system(error): + return "System error: \(error.localizedDescription)" + } + } + + var underlyingError: Error? { + switch self { + case let .system(error): + return error + case .timeout, .tunnelDown: + return nil } } } diff --git a/ios/MullvadVPN/TunnelManager/SetAccountOperation.swift b/ios/MullvadVPN/TunnelManager/SetAccountOperation.swift index 812869caaa..5ceb8cdfda 100644 --- a/ios/MullvadVPN/TunnelManager/SetAccountOperation.swift +++ b/ios/MullvadVPN/TunnelManager/SetAccountOperation.swift @@ -204,7 +204,7 @@ class SetAccountOperation: ResultOperation<StoredAccountData?, Error> { let task = self.accountsProxy.createAccount(retryStrategy: .default) { completion in let mappedCompletion = completion.mapError { error -> Error in self.logger.error( - chainedError: AnyChainedError(error), + error: error, message: "Failed to create new account." ) return error @@ -243,7 +243,7 @@ class SetAccountOperation: ResultOperation<StoredAccountData?, Error> { ) { completion in let mappedCompletion = completion.mapError { error -> Error in self.logger.error( - chainedError: AnyChainedError(error), + error: error, message: "Failed to receive account data." ) return error @@ -286,7 +286,7 @@ class SetAccountOperation: ResultOperation<StoredAccountData?, Error> { let mappedCompletion = completion .mapError { error -> Error in self.logger.error( - chainedError: AnyChainedError(error), + error: error, message: "Failed to delete device." ) return error @@ -333,7 +333,7 @@ class SetAccountOperation: ResultOperation<StoredAccountData?, Error> { // Ignore error but log it. if let error = error { self.logger.error( - chainedError: AnyChainedError(error), + error: error, message: "Failed to remove VPN configuration." ) } @@ -362,7 +362,7 @@ class SetAccountOperation: ResultOperation<StoredAccountData?, Error> { try SettingsManager.setLastUsedAccount(storedAccountData.number) } catch { self.logger.error( - chainedError: AnyChainedError(error), + error: error, message: "Failed to store last used account number." ) } @@ -386,7 +386,7 @@ class SetAccountOperation: ResultOperation<StoredAccountData?, Error> { return (privateKey, device) } .mapError { error -> Error in - self.logger.error(chainedError: error, message: "Failed to create device.") + self.logger.error(error: error, message: "Failed to create device.") return error } diff --git a/ios/MullvadVPN/TunnelManager/StartTunnelOperation.swift b/ios/MullvadVPN/TunnelManager/StartTunnelOperation.swift index f11164961e..f1eeee4a21 100644 --- a/ios/MullvadVPN/TunnelManager/StartTunnelOperation.swift +++ b/ios/MullvadVPN/TunnelManager/StartTunnelOperation.swift @@ -97,7 +97,7 @@ class StartTunnelOperation: ResultOperation<Void, Error> { try tunnelOptions.setSelectorResult(selectorResult) } catch { logger.error( - chainedError: AnyChainedError(error), + error: error, message: "Failed to encode the selector result." ) } diff --git a/ios/MullvadVPN/TunnelManager/TunnelManager.swift b/ios/MullvadVPN/TunnelManager/TunnelManager.swift index 1fa465b67b..b107491712 100644 --- a/ios/MullvadVPN/TunnelManager/TunnelManager.swift +++ b/ios/MullvadVPN/TunnelManager/TunnelManager.swift @@ -212,7 +212,7 @@ final class TunnelManager { if case let .failure(error) = completion { self.logger.error( - chainedError: AnyChainedError(error), + error: error, message: "Failed to load configuration." ) } @@ -261,7 +261,7 @@ final class TunnelManager { DispatchQueue.main.async { if let error = completion.error { self.logger.error( - chainedError: AnyChainedError(error), + error: error, message: "Failed to start the tunnel." ) @@ -293,7 +293,7 @@ final class TunnelManager { DispatchQueue.main.async { if let error = completion.error { self.logger.error( - chainedError: AnyChainedError(error), + error: error, message: "Failed to stop the tunnel." ) @@ -651,7 +651,7 @@ final class TunnelManager { try SettingsManager.writeSettings(settings) } catch { logger.error( - chainedError: AnyChainedError(error), + error: error, message: "Failed to write settings." ) } @@ -679,7 +679,7 @@ final class TunnelManager { try SettingsManager.writeDeviceState(deviceState) } catch { logger.error( - chainedError: AnyChainedError(error), + error: error, message: "Failed to write device state." ) } @@ -730,7 +730,7 @@ final class TunnelManager { if let error = completion.error { logger.error( - chainedError: AnyChainedError(error), + error: error, message: "Failed to reconnect the tunnel." ) } diff --git a/ios/MullvadVPN/TunnelManager/TunnelManagerErrors.swift b/ios/MullvadVPN/TunnelManager/TunnelManagerErrors.swift index 8c25b173df..35b804a711 100644 --- a/ios/MullvadVPN/TunnelManager/TunnelManagerErrors.swift +++ b/ios/MullvadVPN/TunnelManager/TunnelManagerErrors.swift @@ -31,7 +31,9 @@ struct InvalidDeviceStateError: LocalizedError { } } -struct StartTunnelError: LocalizedError { +struct StartTunnelError: LocalizedError, WrappingError { + private let _underlyingError: Error + var errorDescription: String? { return NSLocalizedString( "START_TUNNEL_ERROR", @@ -41,13 +43,18 @@ struct StartTunnelError: LocalizedError { ) } - let underlyingError: Error + var underlyingError: Error? { + return _underlyingError + } + init(underlyingError: Error) { - self.underlyingError = underlyingError + _underlyingError = underlyingError } } -struct StopTunnelError: LocalizedError { +struct StopTunnelError: LocalizedError, WrappingError { + private let _underlyingError: Error + var errorDescription: String? { return NSLocalizedString( "STOP_TUNNEL_ERROR", @@ -57,8 +64,11 @@ struct StopTunnelError: LocalizedError { ) } - let underlyingError: Error + var underlyingError: Error? { + return _underlyingError + } + init(underlyingError: Error) { - self.underlyingError = underlyingError + _underlyingError = underlyingError } } diff --git a/ios/MullvadVPN/TunnelManager/UpdateAccountDataOperation.swift b/ios/MullvadVPN/TunnelManager/UpdateAccountDataOperation.swift index d0f62c68e4..b95d629ea5 100644 --- a/ios/MullvadVPN/TunnelManager/UpdateAccountDataOperation.swift +++ b/ios/MullvadVPN/TunnelManager/UpdateAccountDataOperation.swift @@ -52,7 +52,7 @@ class UpdateAccountDataOperation: ResultOperation<Void, Error> { ) { let mappedCompletion = completion.mapError { error -> Error in self.logger.error( - chainedError: error, + error: error, message: "Failed to fetch account expiry." ) return error diff --git a/ios/MullvadVPN/WrappingError.swift b/ios/MullvadVPN/WrappingError.swift new file mode 100644 index 0000000000..ff217d0dae --- /dev/null +++ b/ios/MullvadVPN/WrappingError.swift @@ -0,0 +1,14 @@ +// +// WrappingError.swift +// MullvadVPN +// +// Created by pronebird on 23/09/2022. +// Copyright © 2022 Mullvad VPN AB. All rights reserved. +// + +import Foundation + +/// Protocol describing errors that may contain underlying errors. +protocol WrappingError: Error { + var underlyingError: Error? { get } +} |
