diff --git a/Mail/Views/Bottom sheets/RestoreEmailsView.swift b/Mail/Views/Bottom sheets/RestoreEmailsView.swift
index 0a7ff7de9..71764957c 100644
--- a/Mail/Views/Bottom sheets/RestoreEmailsView.swift
+++ b/Mail/Views/Bottom sheets/RestoreEmailsView.swift
@@ -76,7 +76,7 @@ struct RestoreEmailsView: View {
Task {
await tryOrDisplayError {
try await mailboxManager.apiFetcher.restoreBackup(mailbox: mailboxManager.mailbox, date: selectedDate)
- await IKSnackBar.showSnackBar(message: MailResourcesStrings.Localizable.snackbarSuccessfulRestoration)
+ IKSnackBar.showSnackBar(message: MailResourcesStrings.Localizable.snackbarRestorationLaunched)
}
}
}
diff --git a/Mail/Views/Onboarding/OnboardingView.swift b/Mail/Views/Onboarding/OnboardingView.swift
index 3918ccbe8..405f75975 100644
--- a/Mail/Views/Onboarding/OnboardingView.swift
+++ b/Mail/Views/Onboarding/OnboardingView.swift
@@ -18,8 +18,8 @@
import AuthenticationServices
import InfomaniakCoreUI
-import InfomaniakDI
import InfomaniakCreateAccount
+import InfomaniakDI
import InfomaniakLogin
import Lottie
import MailCore
@@ -71,7 +71,7 @@ struct Slide: Identifiable {
class LoginHandler: InfomaniakLoginDelegate, ObservableObject {
@LazyInjectService var loginService: InfomaniakLoginable
@LazyInjectService var matomo: MatomoUtils
-
+
@Published var isLoading = false
@Published var isPresentingErrorAlert = false
var sceneDelegate: SceneDelegate?
@@ -129,7 +129,7 @@ class LoginHandler: InfomaniakLoginDelegate, ObservableObject {
_ = try await AccountManager.instance.createAndSetCurrentAccount(code: code, codeVerifier: verifier)
sceneDelegate?.showMainView()
UIApplication.shared.registerForRemoteNotifications()
- } catch MailError.noMailbox {
+ } catch let error as MailError where error == MailError.noMailbox {
sceneDelegate?.showNoMailboxView()
} catch {
if let previousAccount = previousAccount {
diff --git a/Mail/Views/SplitView.swift b/Mail/Views/SplitView.swift
index b1c2beed8..b29ecf6b3 100644
--- a/Mail/Views/SplitView.swift
+++ b/Mail/Views/SplitView.swift
@@ -108,7 +108,7 @@ struct SplitView: View {
if let tappedNotificationThread = tappedNotificationMessage?.originalThread {
navigationStore.threadPath = [tappedNotificationThread]
} else {
- IKSnackBar.showSnackBar(message: MailError.messageNotFound.errorDescription ?? "")
+ IKSnackBar.showSnackBar(message: MailError.messageNotFound.errorDescription)
}
}
.onAppear {
diff --git a/MailCore/API/ApiError.swift b/MailCore/API/ApiError.swift
deleted file mode 100644
index 21ebe495a..000000000
--- a/MailCore/API/ApiError.swift
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
- Infomaniak Mail - iOS App
- Copyright (C) 2022 Infomaniak Network SA
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see .
- */
-
-import Foundation
-
-enum ApiErrorCode: String {
- // General
- case notAuthorized = "not_authorized"
-
- // Folder
- case folderUnableToCreate = "folder__unable_to_create"
- case folderUnableToUpdate = "folder__unable_to_update"
- case folderUnableToDelete = "folder__unable_to_delete"
- case folderUnableToFlush = "folder__unable_to_flush"
- case protectedFolder = "folder__protected_folder"
- case folderUnableToMoveInSub = "folder__unable_to_move_folder_in_its_sub_folders"
- case destinationFolderAlreadyExists = "folder__destination_folder_already_exists"
- case rootDestinationNotExists = "folder__root_destination_not_exists"
- case folderAlreadyExists = "folder__destination_already_exists"
- case folderNotExists = "folder__not_exists"
-
- // Mail
- case moveDestinationNotFound = "mail__move_destination_folder_not_found"
- case cannotConnectToIMAPServer = "mail__cannot_connect_to_server"
- case IMAPAuthFailed = "mail__imap_authentication_failed"
- case IMAPUnableToParseResponse = "mail__imap_unable_to_parse_response"
- case IMAPConnectionTimedOut = "mail__imap_connection_timedout"
- case cannotConnectToSMTPServer = "mail__cannot_connect_to_smtp_server"
- case SMTPAuthFailed = "mail__smtp_authentication_failed"
- case messageNotFound = "mail__message_not_found"
- case messageAttachmentNotFound = "mail__message_attachment_not_found"
- case unableToUndoMoveAction = "mail__unable_to_undo_move_action"
- case unableToMoveEmails = "mail__unable_to_move_emails"
-
- // Draft
- case draftAttachmentNotFound = "draft__attachment_not_found"
- case draftNotFound = "draft__not_found"
- case draftMessageNotFound = "draft__message_not_found"
- case draftTooManyRecipients = "draft__to_many_recipients"
- case draftMaxAttachmentsSizeReached = "draft__max_attachments_size_reached"
- case draftNeedAtLeastOneRecipient = "draft__need_at_least_one_recipient_to_be_sent"
- case draftAlreadyScheduledOrSent = "draft__cannot_modify_scheduled_or_already_sent_message"
- case draftCannotCancelNonScheduledMessage = "draft__cannot_cancel_non_scheduled_message"
- case draftCannotForwardMoreThanOneMessageInline = "draft__cannot_forward_more_than_one_message_inline"
- case draftCannotMoveScheduledMessage = "draft__cannot_move_scheduled_message"
-
- // Send
- case sendFromRefused = "send__server_refused_from"
- case sendRecipientRefused = "send__server_refused_all_recipients"
- case sendLimitExceeded = "send__server_rate_limit_exceeded"
- case sendUnknownError = "send__server_unknown_error"
- case sendDailyLimitReached = "send__server_daily_limit_reached"
- case sendSpamRejected = "send__spam_rejected"
- case sendSenderMismatch = "send__sender_mismatch"
-
- // Attachment
- case attachmentNotValid = "attachment__not_valid"
- case attachmentNotFound = "attachment__not_found"
- case attachmentCannotRender = "attachment__cannot_render"
- case attachmentRenderError = "attachment__error_while_render"
- case attachmentMissingFilenameOrMimeType = "attachment__missing_filename_or_mimetype"
- case attachmentUploadIncorrect = "attachment__incorrect_disposition"
- case attachmentUploadContentIdNotValid = "attachment__content_id_not_valid"
- case attachmentAddFromDriveFailed = "attachment__add_attachment_from_drive_fail"
- case attachmentStoreToDriveFailed = "attachment__store_to_drive_fail"
-
- // Message
- case messageUidIsNotValid = "message__uid_is_not_valid"
-
- var localizedDescription: String {
- switch self {
- case .notAuthorized:
- return "Not authorized"
- case .folderUnableToCreate:
- return "Unable to create folder"
- case .folderUnableToUpdate:
- return "Unable to update folder"
- case .folderUnableToDelete:
- return "Unable to delete folder"
- case .folderUnableToFlush:
- return "Unable to flush folder"
- case .protectedFolder:
- return "Protected folder"
- case .folderUnableToMoveInSub:
- return "Unable to move folder in its sub folders"
- case .destinationFolderAlreadyExists:
- return "Destination folder already exists"
- case .rootDestinationNotExists:
- return "Root destination does not exist"
- case .folderAlreadyExists:
- return "Destination already exists"
- case .folderNotExists:
- return "Folder does not exist"
- case .moveDestinationNotFound:
- return "Move destination folder not found"
- case .cannotConnectToIMAPServer:
- return "Cannot connect to IMAP server"
- case .IMAPAuthFailed:
- return "IMAP authentication failed"
- case .IMAPUnableToParseResponse:
- return "Unable to parse IMAP response"
- case .IMAPConnectionTimedOut:
- return "IMAP connection timed out"
- case .cannotConnectToSMTPServer:
- return "Cannot connect to SMTP server"
- case .SMTPAuthFailed:
- return "SMTP authentication failed"
- case .messageNotFound:
- return "Message not found"
- case .messageAttachmentNotFound:
- return "Message attachment not found"
- case .unableToUndoMoveAction:
- return "Unable to undo move action"
- case .unableToMoveEmails:
- return "Unable to move emails"
- case .draftAttachmentNotFound:
- return "Attachment not found"
- case .draftNotFound:
- return "Draft not found"
- case .draftMessageNotFound:
- return "Message not found"
- case .draftTooManyRecipients:
- return "Too many recipients"
- case .draftMaxAttachmentsSizeReached:
- return "Max attachments size reached"
- case .draftNeedAtLeastOneRecipient:
- return "Draft needs at least one recipient to be sent"
- case .draftAlreadyScheduledOrSent:
- return "Cannot modify scheduled or already sent message"
- case .draftCannotCancelNonScheduledMessage:
- return "Cannot cancel non scheduled message"
- case .draftCannotForwardMoreThanOneMessageInline:
- return "Cannot forward more than one message inline"
- case .draftCannotMoveScheduledMessage:
- return "Cannot move scheduled message"
- case .sendFromRefused:
- return "Server refused from"
- case .sendRecipientRefused:
- return "Server refused all recipients"
- case .sendLimitExceeded:
- return "Rate limit exceeded"
- case .sendUnknownError:
- return "Unknown server error"
- case .sendDailyLimitReached:
- return "Daily limit reached"
- case .sendSpamRejected:
- return "Spam rejected"
- case .sendSenderMismatch:
- return "Sender mismatch"
- case .attachmentNotValid:
- return "Attachment not valid"
- case .attachmentNotFound:
- return "Attachment not found"
- case .attachmentCannotRender:
- return "Attachment cannot render"
- case .attachmentRenderError:
- return "Attachment render error"
- case .attachmentMissingFilenameOrMimeType:
- return "Attachment missing filename or mime type"
- case .attachmentUploadIncorrect:
- return "Attachment incorrect disposition"
- case .attachmentUploadContentIdNotValid:
- return "Attachment content ID not valid"
- case .attachmentAddFromDriveFailed:
- return "Add attachment from drive failed"
- case .attachmentStoreToDriveFailed:
- return "Store attachment to drive failed"
- case .messageUidIsNotValid:
- return "Message UID is not valid"
- }
- }
-}
diff --git a/MailCore/API/MailApiError.swift b/MailCore/API/MailApiError.swift
new file mode 100644
index 000000000..4d3c39b02
--- /dev/null
+++ b/MailCore/API/MailApiError.swift
@@ -0,0 +1,117 @@
+/*
+ Infomaniak Mail - iOS App
+ Copyright (C) 2022 Infomaniak Network SA
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+ */
+
+import Foundation
+import MailResources
+
+class MailApiError: MailError {
+ static let allErrors: [MailApiError] = [
+ // General
+ MailApiError(code: "not_authorized"),
+
+ // Folder
+ MailApiError(code: "folder__unable_to_create"),
+ MailApiError(code: "folder__unable_to_update"),
+ MailApiError(code: "folder__unable_to_delete"),
+ MailApiError(code: "folder__unable_to_flush"),
+ MailApiError(code: "folder__protected_folder"),
+ MailApiError(code: "folder__unable_to_move_folder_in_its_sub_folders"),
+ MailApiError(code: "folder__destination_folder_already_exists"),
+ MailApiError(code: "folder__root_destination_not_exists"),
+ MailApiError(
+ code: "folder__destination_already_exists",
+ localizedDescription: MailResourcesStrings.Localizable.errorNewFolderAlreadyExists,
+ shouldDisplay: true
+ ),
+ MailApiError(code: "folder__not_exists",
+ localizedDescription: MailResourcesStrings.Localizable.errorFolderNotFound,
+ shouldDisplay: true),
+
+ // Mail
+ MailApiError(code: "mail__move_destination_folder_not_found"),
+ MailApiError(code: "mail__cannot_connect_to_server"),
+ MailApiError(code: "mail__imap_authentication_failed"),
+ MailApiError(code: "mail__imap_unable_to_parse_response"),
+ MailApiError(code: "mail__imap_connection_timedout"),
+ MailApiError(code: "mail__cannot_connect_to_smtp_server"),
+ MailApiError(code: "mail__smtp_authentication_failed"),
+ MailApiError(code: "mail__message_not_found"),
+ MailApiError(code: "mail__message_attachment_not_found"),
+ MailApiError(code: "mail__unable_to_undo_move_action"),
+ MailApiError(code: "mail__unable_to_move_emails"),
+
+ // Draft
+ MailApiError(code: "draft__attachment_not_found"),
+ MailApiError(code: "draft__not_found"),
+ MailApiError(code: "draft__message_not_found"),
+ MailApiError(
+ code: "draft__to_many_recipients",
+ localizedDescription: MailResourcesStrings.Localizable.tooManyRecipients,
+ shouldDisplay: true
+ ),
+ MailApiError(code: "draft__max_attachments_size_reached"),
+ MailApiError(
+ code: "draft__need_at_least_one_recipient_to_be_sent",
+ localizedDescription: MailResourcesStrings.Localizable.errorAtLeastOneRecipient,
+ shouldDisplay: true
+ ),
+ MailApiError(
+ code: "draft__cannot_modify_scheduled_or_already_sent_message",
+ localizedDescription: MailResourcesStrings.Localizable.errorEditScheduledMessage,
+ shouldDisplay: true
+ ),
+ MailApiError(code: "draft__cannot_cancel_non_scheduled_message"),
+ MailApiError(code: "draft__cannot_forward_more_than_one_message_inline"),
+ MailApiError(code: "draft__cannot_move_scheduled_message"),
+
+ // Send
+ MailApiError(code: "send__server_refused_from"),
+ MailApiError(code: "send__server_refused_all_recipients",
+ localizedDescription: MailResourcesStrings.Localizable.errorRefusedRecipients,
+ shouldDisplay: true),
+ MailApiError(code: "send__server_rate_limit_exceeded",
+ localizedDescription: MailResourcesStrings.Localizable.errorSendLimitExceeded,
+ shouldDisplay: true),
+ MailApiError(code: "send__server_unknown_error"),
+ MailApiError(code: "send__server_daily_limit_reached"),
+ MailApiError(code: "send__spam_rejected"),
+ MailApiError(code: "send__sender_mismatch"),
+
+ // Attachment
+ MailApiError(code: "attachment__not_valid"),
+ MailApiError(code: "attachment__not_found"),
+ MailApiError(code: "attachment__cannot_render"),
+ MailApiError(code: "attachment__error_while_render"),
+ MailApiError(code: "attachment__missing_filename_or_mimetype"),
+ MailApiError(code: "attachment__incorrect_disposition"),
+ MailApiError(code: "attachment__content_id_not_valid"),
+ MailApiError(code: "attachment__add_attachment_from_drive_fail"),
+ MailApiError(code: "attachment__store_to_drive_fail"),
+
+ // Message
+ MailApiError(code: "message__uid_is_not_valid")
+ ]
+
+ static func mailApiErrorFromCode(_ code: String) -> MailApiError? {
+ return allErrors.first { $0.code == code }
+ }
+
+ static func mailApiErrorWithFallback(apiErrorCode: String) -> MailError {
+ return mailApiErrorFromCode(apiErrorCode) ?? MailError.unknownError
+ }
+}
diff --git a/MailCore/API/MailApiFetcher.swift b/MailCore/API/MailApiFetcher.swift
index 5f34c6cbc..82943822c 100644
--- a/MailCore/API/MailApiFetcher.swift
+++ b/MailCore/API/MailApiFetcher.swift
@@ -43,9 +43,9 @@ public class MailApiFetcher: ApiFetcher {
do {
return try await super.perform(request: request)
} catch InfomaniakError.apiError(let apiError) {
- throw MailError.apiError(apiError)
+ throw MailApiError.mailApiErrorWithFallback(apiErrorCode: apiError.code)
} catch InfomaniakError.serverError(statusCode: let statusCode) {
- throw MailError.serverError(statusCode: statusCode)
+ throw MailServerError(httpStatus: statusCode)
} catch {
if let afError = error.asAFError {
if case .responseSerializationFailed(let reason) = afError,
diff --git a/MailCore/API/MailError.swift b/MailCore/API/MailError.swift
index 4e5941a2f..3bbb61364 100644
--- a/MailCore/API/MailError.swift
+++ b/MailCore/API/MailError.swift
@@ -23,64 +23,65 @@ import MailResources
extension ApiError: CustomStringConvertible {}
-struct AFErrorWithContext: LocalizedError {
+class AFErrorWithContext: MailError {
let request: DataRequest
let afError: AFError
- public var errorDescription: String? {
- return MailError.unknownError.errorDescription
+ init(request: DataRequest, afError: AFError) {
+ self.request = request
+ self.afError = afError
+ super.init(code: "afErrorWithContext", shouldDisplay: false)
}
}
-public enum MailError: LocalizedError {
- case apiError(ApiError)
- case serverError(statusCode: Int)
- case noToken
- case resourceError
- case unknownError
- case unknownToken
- case noMailbox
- case messageNotFound
- case folderNotFound
- case addressBookNotFound
- case contactNotFound
- case attachmentsSizeLimitReached
+public class MailError: LocalizedError {
+ public let code: String
+ public let errorDescription: String
+ public let shouldDisplay: Bool
- public var errorDescription: String? {
- switch self {
- case .apiError(let apiError):
- if let code = ApiErrorCode(rawValue: apiError.code) {
- return code.localizedDescription
- }
- return apiError.description
- case .noToken:
- return "No API token"
- case .resourceError:
- return "Resource error"
- case .unknownError:
- return "Unknown error"
- case .serverError:
- return "Server error"
- case .unknownToken:
- return "Unknown token"
- case .noMailbox:
- return "No Mailbox"
- case .folderNotFound:
- return "Folder not found"
- case .addressBookNotFound:
- return "Address Book not found"
- case .contactNotFound:
- return "Contact not found"
- case .messageNotFound:
- return "Message not found"
- case .attachmentsSizeLimitReached:
- return MailResourcesStrings.Localizable.attachmentFileLimitReached
- }
+ init(code: String,
+ localizedDescription: String = MailResourcesStrings.Localizable.errorUnknown,
+ shouldDisplay: Bool = false) {
+ self.code = code
+ errorDescription = localizedDescription
+ self.shouldDisplay = shouldDisplay
}
+
+ public static let unknownError = MailError(code: "unknownError", shouldDisplay: true)
+ public static let noToken = MailError(code: "noToken", shouldDisplay: true)
+ public static let resourceError = MailError(code: "resourceError", shouldDisplay: true)
+ public static let unknownToken = MailError(code: "unknownToken", shouldDisplay: true)
+ public static let noMailbox = MailError(code: "noMailbox")
+ public static let folderNotFound = MailError(code: "folderNotFound",
+ localizedDescription: MailResourcesStrings.Localizable.errorFolderNotFound,
+ shouldDisplay: true)
+ public static let addressBookNotFound = MailError(code: "addressBookNotFound", shouldDisplay: true)
+ public static let contactNotFound = MailError(code: "contactNotFound", shouldDisplay: true)
+ public static let messageNotFound = MailError(code: "messageNotFound",
+ localizedDescription: MailResourcesStrings.Localizable.errorMessageNotFound,
+ shouldDisplay: true)
+ public static let attachmentsSizeLimitReached = MailError(code: "attachmentsSizeLimitReached",
+ localizedDescription: MailResourcesStrings.Localizable
+ .attachmentFileLimitReached,
+ shouldDisplay: true)
}
extension MailError: Identifiable {
public var id: String {
- return errorDescription ?? UUID().uuidString
+ return code
+ }
+}
+
+extension MailError: Equatable {
+ public static func == (lhs: MailError, rhs: MailError) -> Bool {
+ return lhs.code == rhs.code
+ }
+}
+
+public class MailServerError: MailError {
+ let httpStatus: Int
+ init(httpStatus: Int) {
+ self.httpStatus = httpStatus
+ super.init(code: "serverError")
}
}
diff --git a/MailCore/Utils/Error+Extension.swift b/MailCore/Utils/Error+Extension.swift
index 22a0e7d5b..d69dc43df 100644
--- a/MailCore/Utils/Error+Extension.swift
+++ b/MailCore/Utils/Error+Extension.swift
@@ -16,19 +16,16 @@
along with this program. If not, see .
*/
+import CocoaLumberjackSwift
import Foundation
import InfomaniakCoreUI
+import Sentry
public func tryOrDisplayError(_ body: () throws -> Void) {
do {
try body()
} catch {
- if error.shouldDisplay {
- Task.detached {
- await IKSnackBar.showSnackBar(message: error.localizedDescription)
- }
- }
- print("Error: \(error)")
+ displayErrorIfNeeded(error: error)
}
}
@@ -36,12 +33,30 @@ public func tryOrDisplayError(_ body: () async throws -> Void) async {
do {
try await body()
} catch {
+ displayErrorIfNeeded(error: error)
+ }
+}
+
+private func displayErrorIfNeeded(error: Error) {
+ if let error = error as? MailError {
if error.shouldDisplay {
Task.detached {
- await IKSnackBar.showSnackBar(message: error.localizedDescription)
+ await IKSnackBar.showSnackBar(message: error.errorDescription)
}
+ } else {
+ SentrySDK.capture(message: "Encountered error that we didn't display to the user") { scope in
+ scope.setContext(
+ value: ["Code": error.code, "Raw": error],
+ key: "Error"
+ )
+ }
+ }
+ DDLogError("MailError: \(error)")
+ } else if error.shouldDisplay {
+ Task.detached {
+ await IKSnackBar.showSnackBar(message: error.localizedDescription)
}
- print("Error: \(error)")
+ DDLogError("Error: \(error)")
}
}
@@ -50,7 +65,7 @@ public extension Error {
switch asAFError {
case .explicitlyCancelled:
return false
- case let .sessionTaskFailed(error):
+ case .sessionTaskFailed(let error):
return (error as NSError).code != NSURLErrorNotConnectedToInternet
default:
return true
diff --git a/MailResources/Localizable/de.lproj/Localizable.strings b/MailResources/Localizable/de.lproj/Localizable.strings
index 9071dd613..b2216640d 100644
Binary files a/MailResources/Localizable/de.lproj/Localizable.strings and b/MailResources/Localizable/de.lproj/Localizable.strings differ
diff --git a/MailResources/Localizable/en.lproj/Localizable.strings b/MailResources/Localizable/en.lproj/Localizable.strings
index 20561d2c4..0d40821d4 100644
Binary files a/MailResources/Localizable/en.lproj/Localizable.strings and b/MailResources/Localizable/en.lproj/Localizable.strings differ
diff --git a/MailResources/Localizable/es.lproj/Localizable.strings b/MailResources/Localizable/es.lproj/Localizable.strings
index 20be060ac..79943c364 100644
Binary files a/MailResources/Localizable/es.lproj/Localizable.strings and b/MailResources/Localizable/es.lproj/Localizable.strings differ
diff --git a/MailResources/Localizable/fr.lproj/Localizable.strings b/MailResources/Localizable/fr.lproj/Localizable.strings
index aae046dda..23d30659d 100644
Binary files a/MailResources/Localizable/fr.lproj/Localizable.strings and b/MailResources/Localizable/fr.lproj/Localizable.strings differ
diff --git a/MailResources/Localizable/it.lproj/Localizable.strings b/MailResources/Localizable/it.lproj/Localizable.strings
index 71a9f077d..bd1ebe7c8 100644
Binary files a/MailResources/Localizable/it.lproj/Localizable.strings and b/MailResources/Localizable/it.lproj/Localizable.strings differ