From 388cdf842fd8e6fd12a7bf98055717f0c3ea2a6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cihat=20Gu=CC=88ndu=CC=88z?= Date: Thu, 12 Dec 2024 15:19:50 +0100 Subject: [PATCH 1/3] Add initial set of common built-in error types for developers to throw --- .../BuiltInErrors/DatabaseError.swift | 48 +++++++++++++ .../ErrorKit/BuiltInErrors/FileError.swift | 42 +++++++++++ .../ErrorKit/BuiltInErrors/NetworkError.swift | 72 +++++++++++++++++++ .../BuiltInErrors/OperationError.swift | 43 +++++++++++ .../ErrorKit/BuiltInErrors/ParsingError.swift | 35 +++++++++ .../BuiltInErrors/PermissionError.swift | 44 ++++++++++++ .../ErrorKit/BuiltInErrors/StateError.swift | 42 +++++++++++ .../BuiltInErrors/ValidationError.swift | 49 +++++++++++++ 8 files changed, 375 insertions(+) create mode 100644 Sources/ErrorKit/BuiltInErrors/DatabaseError.swift create mode 100644 Sources/ErrorKit/BuiltInErrors/FileError.swift create mode 100644 Sources/ErrorKit/BuiltInErrors/NetworkError.swift create mode 100644 Sources/ErrorKit/BuiltInErrors/OperationError.swift create mode 100644 Sources/ErrorKit/BuiltInErrors/ParsingError.swift create mode 100644 Sources/ErrorKit/BuiltInErrors/PermissionError.swift create mode 100644 Sources/ErrorKit/BuiltInErrors/StateError.swift create mode 100644 Sources/ErrorKit/BuiltInErrors/ValidationError.swift diff --git a/Sources/ErrorKit/BuiltInErrors/DatabaseError.swift b/Sources/ErrorKit/BuiltInErrors/DatabaseError.swift new file mode 100644 index 0000000..4a33fd6 --- /dev/null +++ b/Sources/ErrorKit/BuiltInErrors/DatabaseError.swift @@ -0,0 +1,48 @@ +import Foundation + +/// Represents errors that occur during database operations. +public enum DatabaseError: Throwable { + /// The database connection failed. + case connectionFailed + + /// The database query failed to execute. + /// - Parameters: + /// - context: A description of the operation or entity being queried. + case operationFailed(context: String) + + /// A requested record was not found in the database. + /// - Parameters: + /// - entity: The name of the entity or record type. + /// - identifier: A unique identifier for the missing entity. + case recordNotFound(entity: String, identifier: String?) + + /// Generic error message if the existing cases don't provide the required details. + case generic(userFriendlyMessage: String) + + /// A user-friendly error message suitable for display to end users. + public var userFriendlyMessage: String { + switch self { + case .connectionFailed: + return String( + localized: "BuiltInErrors.DatabaseError.connectionFailed", + defaultValue: "Failed to connect to the database. Please try again later.", + bundle: .module + ) + case .operationFailed(let context): + return String( + localized: "BuiltInErrors.DatabaseError.operationFailed", + defaultValue: "An error occurred while performing the operation: \(context). Please try again.", + bundle: .module + ) + case .recordNotFound(let entity, let identifier): + let idMessage = identifier.map { " Identifier: \($0)." } ?? "" + return String( + localized: "BuiltInErrors.DatabaseError.recordNotFound", + defaultValue: "The \(entity) record could not be found.\(idMessage) Please check and try again.", + bundle: .module + ) + case .generic(let userFriendlyMessage): + return userFriendlyMessage + } + } +} diff --git a/Sources/ErrorKit/BuiltInErrors/FileError.swift b/Sources/ErrorKit/BuiltInErrors/FileError.swift new file mode 100644 index 0000000..f9a6162 --- /dev/null +++ b/Sources/ErrorKit/BuiltInErrors/FileError.swift @@ -0,0 +1,42 @@ +import Foundation + +/// Represents errors that occur during file operations. +public enum FileError: Throwable { + /// The file could not be found. + case fileNotFound(fileName: String) + + /// There was an issue reading the file. + case readFailed(fileName: String) + + /// There was an issue writing to the file. + case writeFailed(fileName: String) + + /// Generic error message if the existing cases don't provide the required details. + case generic(userFriendlyMessage: String) + + /// A user-friendly error message suitable for display to end users. + public var userFriendlyMessage: String { + switch self { + case .fileNotFound(let fileName): + return String( + localized: "BuiltInErrors.FileError.fileNotFound", + defaultValue: "The file \(fileName) could not be found. Please check the file path.", + bundle: .module + ) + case .readFailed(let fileName): + return String( + localized: "BuiltInErrors.FileError.readError", + defaultValue: "There was an issue reading the file \(fileName). Please try again.", + bundle: .module + ) + case .writeFailed(let fileName): + return String( + localized: "BuiltInErrors.FileError.writeError", + defaultValue: "There was an issue writing to the file \(fileName). Please try again.", + bundle: .module + ) + case .generic(let userFriendlyMessage): + return userFriendlyMessage + } + } +} diff --git a/Sources/ErrorKit/BuiltInErrors/NetworkError.swift b/Sources/ErrorKit/BuiltInErrors/NetworkError.swift new file mode 100644 index 0000000..8e23344 --- /dev/null +++ b/Sources/ErrorKit/BuiltInErrors/NetworkError.swift @@ -0,0 +1,72 @@ +import Foundation + +/// Represents errors that can occur during network operations. +public enum NetworkError: Throwable { + /// No internet connection is available. + /// - Note: This error may occur if the device is in airplane mode or lacks network connectivity. + case noInternet + + /// The request timed out before completion. + case timeout + + /// The server responded with a bad request error. + /// - Parameters: + /// - code: The exact HTTP status code returned by the server. + /// - message: An error message provided by the server in the body. + case badRequest(code: Int, message: String) + + /// The server responded with a general server-side error. + /// - Parameters: + /// - code: The HTTP status code returned by the server. + /// - message: An optional error message provided by the server. + case serverError(code: Int, message: String?) + + /// The response could not be decoded or parsed. + case decodingFailure + + /// Generic error message if the existing cases don't provide the required details. + case generic(userFriendlyMessage: String) + + /// A user-friendly error message suitable for display to end users. + public var userFriendlyMessage: String { + switch self { + case .noInternet: + return String( + localized: "BuiltInErrors.NetworkError.noInternet", + defaultValue: "No internet connection is available. Please check your network settings and try again.", + bundle: .module + ) + case .timeout: + return String( + localized: "BuiltInErrors.NetworkError.timeout", + defaultValue: "The request timed out. Please try again later.", + bundle: .module + ) + case .badRequest(let code, let message): + return String( + localized: "BuiltInErrors.NetworkError.badRequest", + defaultValue: "The request was malformed (\(code)): \(message). Please review and try again.", + bundle: .module + ) + case .serverError(let code, let message): + let defaultMessage = String( + localized: "BuiltInErrors.NetworkError.serverError", + defaultValue: "The server encountered an error (Code: \(code)).", + bundle: .module + ) + if let message = message { + return defaultMessage + " " + message + } else { + return defaultMessage + } + case .decodingFailure: + return String( + localized: "BuiltInErrors.NetworkError.decodingFailure", + defaultValue: "The data received from the server could not be processed. Please try again.", + bundle: .module + ) + case .generic(let userFriendlyMessage): + return userFriendlyMessage + } + } +} diff --git a/Sources/ErrorKit/BuiltInErrors/OperationError.swift b/Sources/ErrorKit/BuiltInErrors/OperationError.swift new file mode 100644 index 0000000..5f88de9 --- /dev/null +++ b/Sources/ErrorKit/BuiltInErrors/OperationError.swift @@ -0,0 +1,43 @@ +import Foundation + +/// Represents errors related to failed or invalid operations. +public enum OperationError: Throwable { + /// The operation could not start due to a dependency failure. + /// - Parameter dependency: A description of the failed dependency. + case dependencyFailed(dependency: String) + + /// The operation was canceled before completion. + case canceled + + /// The operation failed with an unknown reason. + case unknownFailure(description: String) + + /// Generic error message if the existing cases don't provide the required details. + case generic(userFriendlyMessage: String) + + /// A user-friendly error message suitable for display to end users. + public var userFriendlyMessage: String { + switch self { + case .dependencyFailed(let dependency): + return String( + localized: "BuiltInErrors.OperationError.dependencyFailed", + defaultValue: "The operation could not be completed due to a failed dependency: \(dependency).", + bundle: .module + ) + case .canceled: + return String( + localized: "BuiltInErrors.OperationError.canceled", + defaultValue: "The operation was canceled. Please try again if necessary.", + bundle: .module + ) + case .unknownFailure(let description): + return String( + localized: "BuiltInErrors.OperationError.unknownFailure", + defaultValue: "The operation failed due to an unknown reason: \(description). Please try again or contact support.", + bundle: .module + ) + case .generic(let userFriendlyMessage): + return userFriendlyMessage + } + } +} diff --git a/Sources/ErrorKit/BuiltInErrors/ParsingError.swift b/Sources/ErrorKit/BuiltInErrors/ParsingError.swift new file mode 100644 index 0000000..25efff9 --- /dev/null +++ b/Sources/ErrorKit/BuiltInErrors/ParsingError.swift @@ -0,0 +1,35 @@ +import Foundation + +/// Represents errors that occur during parsing of input or data. +public enum ParsingError: Throwable { + /// The input was invalid and could not be parsed. + /// - Parameter input: The invalid input string or description. + case invalidInput(input: String) + + /// A required field was missing in the input. + /// - Parameter field: The name of the missing field. + case missingField(field: String) + + /// Generic error message if the existing cases don't provide the required details. + case generic(userFriendlyMessage: String) + + /// A user-friendly error message suitable for display to end users. + public var userFriendlyMessage: String { + switch self { + case .invalidInput(let input): + return String( + localized: "BuiltInErrors.ParsingError.invalidInput", + defaultValue: "The provided input is invalid: \(input). Please correct it and try again.", + bundle: .module + ) + case .missingField(let field): + return String( + localized: "BuiltInErrors.ParsingError.missingField", + defaultValue: "A required field is missing: \(field). Please review and try again.", + bundle: .module + ) + case .generic(let userFriendlyMessage): + return userFriendlyMessage + } + } +} diff --git a/Sources/ErrorKit/BuiltInErrors/PermissionError.swift b/Sources/ErrorKit/BuiltInErrors/PermissionError.swift new file mode 100644 index 0000000..a1728ca --- /dev/null +++ b/Sources/ErrorKit/BuiltInErrors/PermissionError.swift @@ -0,0 +1,44 @@ +import Foundation + +/// Represents errors related to missing or denied permissions. +public enum PermissionError: Throwable { + /// The user denied the required permission. + /// - Parameter permission: The type of permission that was denied. + case denied(permission: String) + + /// The app lacks a required permission and the user cannot grant it. + /// - Parameter permission: The type of permission required. + case restricted(permission: String) + + /// The app lacks a required permission, and it is unknown whether the user can grant it. + case notDetermined(permission: String) + + /// Generic error message if the existing cases don't provide the required details. + case generic(userFriendlyMessage: String) + + /// A user-friendly error message suitable for display to end users. + public var userFriendlyMessage: String { + switch self { + case .denied(let permission): + return String( + localized: "BuiltInErrors.PermissionError.denied", + defaultValue: "The \(permission) permission was denied. Please enable it in Settings to continue.", + bundle: .module + ) + case .restricted(let permission): + return String( + localized: "BuiltInErrors.PermissionError.restricted", + defaultValue: "The \(permission) permission is restricted. This may be due to parental controls or other system restrictions.", + bundle: .module + ) + case .notDetermined(let permission): + return String( + localized: "BuiltInErrors.PermissionError.notDetermined", + defaultValue: "The \(permission) permission has not been determined. Please try again or check your Settings.", + bundle: .module + ) + case .generic(let userFriendlyMessage): + return userFriendlyMessage + } + } +} diff --git a/Sources/ErrorKit/BuiltInErrors/StateError.swift b/Sources/ErrorKit/BuiltInErrors/StateError.swift new file mode 100644 index 0000000..25050a7 --- /dev/null +++ b/Sources/ErrorKit/BuiltInErrors/StateError.swift @@ -0,0 +1,42 @@ +import Foundation + +/// Represents errors caused by invalid or unexpected states. +public enum StateError: Throwable { + /// The required state was not met to proceed with the operation. + case invalidState(description: String) + + /// The operation cannot proceed because the state has already been finalized. + case alreadyFinalized + + /// A required precondition for the operation was not met. + case preconditionFailed(description: String) + + /// Generic error message if the existing cases don't provide the required details. + case generic(userFriendlyMessage: String) + + /// A user-friendly error message suitable for display to end users. + public var userFriendlyMessage: String { + switch self { + case .invalidState(let description): + return String( + localized: "BuiltInErrors.StateError.invalidState", + defaultValue: "The operation cannot proceed due to an invalid state: \(description).", + bundle: .module + ) + case .alreadyFinalized: + return String( + localized: "BuiltInErrors.StateError.alreadyFinalized", + defaultValue: "The operation cannot be performed because the state is already finalized.", + bundle: .module + ) + case .preconditionFailed(let description): + return String( + localized: "BuiltInErrors.StateError.preconditionFailed", + defaultValue: "A required condition was not met: \(description). Please review and try again.", + bundle: .module + ) + case .generic(let userFriendlyMessage): + return userFriendlyMessage + } + } +} diff --git a/Sources/ErrorKit/BuiltInErrors/ValidationError.swift b/Sources/ErrorKit/BuiltInErrors/ValidationError.swift new file mode 100644 index 0000000..ccec40d --- /dev/null +++ b/Sources/ErrorKit/BuiltInErrors/ValidationError.swift @@ -0,0 +1,49 @@ +import Foundation + +/// Represents errors related to validation failures. +public enum ValidationError: Throwable { + /// The input provided is invalid. + /// - Parameters: + /// - field: The name of the field that caused the error. + case invalidInput(field: String) + + /// A required field is missing. + /// - Parameters: + /// - field: The name of the required fields. + case missingField(field: String) + + /// The input exceeds the maximum allowed length. + /// - Parameters: + /// - field: The name of the field that caused the error. + /// - maxLength: The maximum allowed length for the field. + case inputTooLong(field: String, maxLength: Int) + + /// Generic error message if the existing cases don't provide the required details. + case generic(userFriendlyMessage: String) + + /// A user-friendly error message suitable for display to end users. + public var userFriendlyMessage: String { + switch self { + case .invalidInput(let field): + return String( + localized: "BuiltInErrors.ValidationError.invalidInput", + defaultValue: "The value provided for \(field) is invalid. Please correct it.", + bundle: .module + ) + case .missingField(let field): + return String( + localized: "BuiltInErrors.ValidationError.missingField", + defaultValue: "\(field) is a required field. Please provide a value.", + bundle: .module + ) + case .inputTooLong(let field, let maxLength): + return String( + localized: "BuiltInErrors.ValidationError.inputTooLong", + defaultValue: "\(field) exceeds the maximum allowed length of \(maxLength) characters. Please shorten it.", + bundle: .module + ) + case .generic(let userFriendlyMessage): + return userFriendlyMessage + } + } +} From badc95cd66322f4ae0b84701166a5764dd569c77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cihat=20Gu=CC=88ndu=CC=88z?= Date: Wed, 18 Dec 2024 09:08:58 +0100 Subject: [PATCH 2/3] Introduce a GenericError struct + improve documentation & messages --- .../BuiltInErrors/DatabaseError.swift | 89 +++++++++++- .../ErrorKit/BuiltInErrors/FileError.swift | 87 +++++++++++- .../ErrorKit/BuiltInErrors/GenericError.swift | 59 ++++++++ .../ErrorKit/BuiltInErrors/NetworkError.swift | 132 +++++++++++++++++- .../BuiltInErrors/OperationError.swift | 75 ++++++++-- .../ErrorKit/BuiltInErrors/ParsingError.swift | 66 ++++++++- .../BuiltInErrors/PermissionError.swift | 86 +++++++++++- .../ErrorKit/BuiltInErrors/StateError.swift | 80 ++++++++++- .../BuiltInErrors/ValidationError.swift | 87 +++++++++++- 9 files changed, 725 insertions(+), 36 deletions(-) create mode 100644 Sources/ErrorKit/BuiltInErrors/GenericError.swift diff --git a/Sources/ErrorKit/BuiltInErrors/DatabaseError.swift b/Sources/ErrorKit/BuiltInErrors/DatabaseError.swift index 4a33fd6..0a963a4 100644 --- a/Sources/ErrorKit/BuiltInErrors/DatabaseError.swift +++ b/Sources/ErrorKit/BuiltInErrors/DatabaseError.swift @@ -1,22 +1,103 @@ import Foundation /// Represents errors that occur during database operations. +/// +/// # Examples of Use +/// +/// ## Handling Database Connections +/// ```swift +/// struct DatabaseConnection { +/// func connect() throws(DatabaseError) { +/// guard let socket = openNetworkSocket() else { +/// throw .connectionFailed +/// } +/// // Successful connection logic +/// } +/// } +/// ``` +/// +/// ## Managing Record Operations +/// ```swift +/// struct UserRepository { +/// func findUser(byId id: String) throws(DatabaseError) -> User { +/// guard let user = database.findUser(id: id) else { +/// throw .recordNotFound(entity: "User", identifier: id) +/// } +/// return user +/// } +/// +/// func updateUser(_ user: User) throws(DatabaseError) { +/// guard hasValidPermissions(for: user) else { +/// throw .operationFailed(context: "Updating user profile") +/// } +/// // Update logic +/// } +/// } +/// ``` public enum DatabaseError: Throwable { /// The database connection failed. + /// + /// # Example + /// ```swift + /// struct AuthenticationService { + /// func authenticate() throws(DatabaseError) { + /// guard let connection = attemptDatabaseConnection() else { + /// throw .connectionFailed + /// } + /// // Proceed with authentication + /// } + /// } + /// ``` case connectionFailed /// The database query failed to execute. + /// + /// # Example + /// ```swift + /// struct AnalyticsRepository { + /// func generateReport(for period: DateInterval) throws(DatabaseError) -> Report { + /// guard period.duration <= maximumReportPeriod else { + /// throw .operationFailed(context: "Generating analytics report") + /// } + /// // Report generation logic + /// } + /// } + /// ``` /// - Parameters: /// - context: A description of the operation or entity being queried. case operationFailed(context: String) /// A requested record was not found in the database. + /// + /// # Example + /// ```swift + /// struct ProductInventory { + /// func fetchProduct(sku: String) throws(DatabaseError) -> Product { + /// guard let product = database.findProduct(bySKU: sku) else { + /// throw .recordNotFound(entity: "Product", identifier: sku) + /// } + /// return product + /// } + /// } + /// ``` /// - Parameters: /// - entity: The name of the entity or record type. /// - identifier: A unique identifier for the missing entity. case recordNotFound(entity: String, identifier: String?) /// Generic error message if the existing cases don't provide the required details. + /// + /// # Example + /// ```swift + /// struct DataMigrationService { + /// func migrate() throws(DatabaseError) { + /// guard canPerformMigration() else { + /// throw .generic(userFriendlyMessage: "Migration cannot be performed") + /// } + /// // Migration logic + /// } + /// } + /// ``` case generic(userFriendlyMessage: String) /// A user-friendly error message suitable for display to end users. @@ -25,20 +106,20 @@ public enum DatabaseError: Throwable { case .connectionFailed: return String( localized: "BuiltInErrors.DatabaseError.connectionFailed", - defaultValue: "Failed to connect to the database. Please try again later.", + defaultValue: "Unable to establish a connection to the database. Check your network settings and try again.", bundle: .module ) case .operationFailed(let context): return String( localized: "BuiltInErrors.DatabaseError.operationFailed", - defaultValue: "An error occurred while performing the operation: \(context). Please try again.", + defaultValue: "The database operation for \(context) could not be completed. Please retry the action.", bundle: .module ) case .recordNotFound(let entity, let identifier): - let idMessage = identifier.map { " Identifier: \($0)." } ?? "" + let idMessage = identifier.map { " with ID \($0)" } ?? "" return String( localized: "BuiltInErrors.DatabaseError.recordNotFound", - defaultValue: "The \(entity) record could not be found.\(idMessage) Please check and try again.", + defaultValue: "The \(entity) record\(idMessage) was not found in the database. Verify the details and try again.", bundle: .module ) case .generic(let userFriendlyMessage): diff --git a/Sources/ErrorKit/BuiltInErrors/FileError.swift b/Sources/ErrorKit/BuiltInErrors/FileError.swift index f9a6162..15301d3 100644 --- a/Sources/ErrorKit/BuiltInErrors/FileError.swift +++ b/Sources/ErrorKit/BuiltInErrors/FileError.swift @@ -1,17 +1,98 @@ import Foundation /// Represents errors that occur during file operations. +/// +/// # Examples of Use +/// +/// ## Handling File Retrieval +/// ```swift +/// struct DocumentManager { +/// func loadDocument(named name: String) throws(FileError) -> Document { +/// guard let fileURL = findFile(named: name) else { +/// throw .fileNotFound(fileName: name) +/// } +/// // Document loading logic +/// } +/// } +/// ``` +/// +/// ## Managing File Operations +/// ```swift +/// struct FileProcessor { +/// func processFile(at path: String) throws(FileError) { +/// guard canWrite(to: path) else { +/// throw .writeFailed(fileName: path) +/// } +/// // File processing logic +/// } +/// +/// func readConfiguration() throws(FileError) -> Configuration { +/// guard let data = attemptFileRead() else { +/// throw .readFailed(fileName: "config.json") +/// } +/// // Configuration parsing logic +/// } +/// } +/// ``` public enum FileError: Throwable { /// The file could not be found. + /// + /// # Example + /// ```swift + /// struct AssetManager { + /// func loadImage(named name: String) throws(FileError) -> Image { + /// guard let imagePath = searchForImage(name) else { + /// throw .fileNotFound(fileName: name) + /// } + /// // Image loading logic + /// } + /// } + /// ``` case fileNotFound(fileName: String) /// There was an issue reading the file. + /// + /// # Example + /// ```swift + /// struct LogReader { + /// func readLatestLog() throws(FileError) -> String { + /// guard let logContents = attemptFileRead() else { + /// throw .readFailed(fileName: "application.log") + /// } + /// return logContents + /// } + /// } + /// ``` case readFailed(fileName: String) /// There was an issue writing to the file. + /// + /// # Example + /// ```swift + /// struct DataBackup { + /// func backup(data: Data) throws(FileError) { + /// guard canWriteToBackupLocation() else { + /// throw .writeFailed(fileName: "backup.dat") + /// } + /// // Backup writing logic + /// } + /// } + /// ``` case writeFailed(fileName: String) /// Generic error message if the existing cases don't provide the required details. + /// + /// # Example + /// ```swift + /// struct FileIntegrityChecker { + /// func validateFile() throws(FileError) { + /// guard passes(integrityCheck) else { + /// throw .generic(userFriendlyMessage: "File integrity compromised") + /// } + /// // Validation logic + /// } + /// } + /// ``` case generic(userFriendlyMessage: String) /// A user-friendly error message suitable for display to end users. @@ -20,19 +101,19 @@ public enum FileError: Throwable { case .fileNotFound(let fileName): return String( localized: "BuiltInErrors.FileError.fileNotFound", - defaultValue: "The file \(fileName) could not be found. Please check the file path.", + defaultValue: "The file \(fileName) could not be located. Please verify the file path and try again.", bundle: .module ) case .readFailed(let fileName): return String( localized: "BuiltInErrors.FileError.readError", - defaultValue: "There was an issue reading the file \(fileName). Please try again.", + defaultValue: "An error occurred while attempting to read the file \(fileName). Please check file permissions and try again.", bundle: .module ) case .writeFailed(let fileName): return String( localized: "BuiltInErrors.FileError.writeError", - defaultValue: "There was an issue writing to the file \(fileName). Please try again.", + defaultValue: "Unable to write to the file \(fileName). Ensure you have the necessary permissions and try again.", bundle: .module ) case .generic(let userFriendlyMessage): diff --git a/Sources/ErrorKit/BuiltInErrors/GenericError.swift b/Sources/ErrorKit/BuiltInErrors/GenericError.swift new file mode 100644 index 0000000..6957c63 --- /dev/null +++ b/Sources/ErrorKit/BuiltInErrors/GenericError.swift @@ -0,0 +1,59 @@ +import Foundation + +/// Represents a generic error with a custom user-friendly message. +/// Use this when the built-in error types don't match your specific error case +/// or when you need a simple way to throw custom error messages. +/// +/// # Examples of Use +/// +/// ## Custom Business Logic Validation +/// ```swift +/// struct BusinessRuleValidator { +/// func validateComplexRule(data: BusinessData) throws(GenericError) { +/// guard meetsCustomCriteria(data) else { +/// throw GenericError( +/// userFriendlyMessage: String(localized: "The business data doesn't meet required criteria") +/// ) +/// } +/// // Continue with business logic +/// } +/// } +/// ``` +/// +/// ## Application-Specific Errors +/// ```swift +/// struct CustomProcessor { +/// func processSpecialCase() throws(GenericError) { +/// guard canHandleSpecialCase() else { +/// throw GenericError( +/// userFriendlyMessage: String(localized: "Unable to process this special case") +/// ) +/// } +/// // Special case handling +/// } +/// } +/// ``` +public struct GenericError: Throwable { + /// A user-friendly message describing the error. + public let userFriendlyMessage: String + + /// Creates a new generic error with a custom user-friendly message. + /// + /// # Example + /// ```swift + /// struct CustomValidator { + /// func validateSpecialRequirement() throws(GenericError) { + /// guard meetsRequirement() else { + /// throw GenericError( + /// userFriendlyMessage: String(localized: "The requirement was not met. Please try again.") + /// ) + /// } + /// // Validation logic + /// } + /// } + /// ``` + /// - Parameter userFriendlyMessage: A clear, actionable message that will be shown to the user. + public init(userFriendlyMessage: String) { + self.userFriendlyMessage = userFriendlyMessage + } +} diff --git a/Sources/ErrorKit/BuiltInErrors/NetworkError.swift b/Sources/ErrorKit/BuiltInErrors/NetworkError.swift index 8e23344..436a712 100644 --- a/Sources/ErrorKit/BuiltInErrors/NetworkError.swift +++ b/Sources/ErrorKit/BuiltInErrors/NetworkError.swift @@ -1,30 +1,148 @@ import Foundation /// Represents errors that can occur during network operations. +/// +/// # Examples of Use +/// +/// ## Handling Network Connectivity +/// ```swift +/// struct NetworkService { +/// func fetchData() throws(NetworkError) -> Data { +/// guard isNetworkReachable() else { +/// throw .noInternet +/// } +/// // Network request logic +/// } +/// } +/// ``` +/// +/// ## Managing API Requests +/// ```swift +/// struct APIClient { +/// func makeRequest(to endpoint: URL) throws(NetworkError) -> T { +/// guard let response = performRequest(endpoint) else { +/// throw .timeout +/// } +/// +/// guard response.statusCode == 200 else { +/// throw .badRequest( +/// code: response.statusCode, +/// message: response.errorMessage +/// ) +/// } +/// +/// guard let decodedData = try? JSONDecoder().decode(T.self, from: response.data) else { +/// throw .decodingFailure +/// } +/// +/// return decodedData +/// } +/// } +/// ``` public enum NetworkError: Throwable { /// No internet connection is available. + /// + /// # Example + /// ```swift + /// struct OfflineContentManager { + /// func syncContent() throws(NetworkError) { + /// guard isNetworkAvailable() else { + /// throw .noInternet + /// } + /// // Synchronization logic + /// } + /// } + /// ``` /// - Note: This error may occur if the device is in airplane mode or lacks network connectivity. case noInternet /// The request timed out before completion. + /// + /// # Example + /// ```swift + /// struct ImageDownloader { + /// func download(from url: URL) throws(NetworkError) -> Image { + /// guard let image = performDownloadWithTimeout() else { + /// throw .timeout + /// } + /// return image + /// } + /// } + /// ``` case timeout /// The server responded with a bad request error. + /// + /// # Example + /// ```swift + /// struct UserProfileService { + /// func updateProfile(_ profile: UserProfile) throws(NetworkError) { + /// let response = sendUpdateRequest(profile) + /// guard response.isSuccess else { + /// throw .badRequest( + /// code: response.statusCode, + /// message: response.errorMessage + /// ) + /// } + /// // Update success logic + /// } + /// } + /// ``` /// - Parameters: /// - code: The exact HTTP status code returned by the server. /// - message: An error message provided by the server in the body. case badRequest(code: Int, message: String) /// The server responded with a general server-side error. + /// + /// # Example + /// ```swift + /// struct PaymentService { + /// func processPayment(_ payment: Payment) throws(NetworkError) { + /// let response = submitPayment(payment) + /// guard response.isSuccessful else { + /// throw .serverError( + /// code: response.statusCode, + /// message: response.errorMessage + /// ) + /// } + /// // Payment processing logic + /// } + /// } + /// ``` /// - Parameters: /// - code: The HTTP status code returned by the server. /// - message: An optional error message provided by the server. case serverError(code: Int, message: String?) /// The response could not be decoded or parsed. + /// + /// # Example + /// ```swift + /// struct DataTransformer { + /// func parseResponse(_ data: Data) throws(NetworkError) -> T { + /// guard let parsed = try? JSONDecoder().decode(T.self, from: data) else { + /// throw .decodingFailure + /// } + /// return parsed + /// } + /// } + /// ``` case decodingFailure /// Generic error message if the existing cases don't provide the required details. + /// + /// # Example + /// ```swift + /// struct UnexpectedErrorHandler { + /// func handle(_ error: Error) throws(NetworkError) { + /// guard !isHandledError(error) else { + /// throw .generic(userFriendlyMessage: "An unexpected network error occurred") + /// } + /// // Error handling logic + /// } + /// } + /// ``` case generic(userFriendlyMessage: String) /// A user-friendly error message suitable for display to end users. @@ -33,36 +151,36 @@ public enum NetworkError: Throwable { case .noInternet: return String( localized: "BuiltInErrors.NetworkError.noInternet", - defaultValue: "No internet connection is available. Please check your network settings and try again.", + defaultValue: "Unable to connect to the internet. Please check your network settings and try again.", bundle: .module ) case .timeout: return String( localized: "BuiltInErrors.NetworkError.timeout", - defaultValue: "The request timed out. Please try again later.", + defaultValue: "The network request took too long to complete. Please check your connection and try again.", bundle: .module ) case .badRequest(let code, let message): return String( localized: "BuiltInErrors.NetworkError.badRequest", - defaultValue: "The request was malformed (\(code)): \(message). Please review and try again.", + defaultValue: "There was an issue with the request (Code: \(code)). \(message). Please review and retry.", bundle: .module ) case .serverError(let code, let message): let defaultMessage = String( localized: "BuiltInErrors.NetworkError.serverError", - defaultValue: "The server encountered an error (Code: \(code)).", + defaultValue: "The server encountered an error (Code: \(code)). ", bundle: .module ) if let message = message { - return defaultMessage + " " + message + return defaultMessage + message } else { - return defaultMessage + return defaultMessage + "Please try again later." } case .decodingFailure: return String( localized: "BuiltInErrors.NetworkError.decodingFailure", - defaultValue: "The data received from the server could not be processed. Please try again.", + defaultValue: "Unable to process the server's response. Please try again or contact support if the issue persists.", bundle: .module ) case .generic(let userFriendlyMessage): diff --git a/Sources/ErrorKit/BuiltInErrors/OperationError.swift b/Sources/ErrorKit/BuiltInErrors/OperationError.swift index 5f88de9..cb6aa5a 100644 --- a/Sources/ErrorKit/BuiltInErrors/OperationError.swift +++ b/Sources/ErrorKit/BuiltInErrors/OperationError.swift @@ -1,18 +1,77 @@ import Foundation /// Represents errors related to failed or invalid operations. +/// +/// # Examples of Use +/// +/// ## Handling Operation Dependencies +/// ```swift +/// struct DataProcessingPipeline { +/// func runComplexOperation() throws(OperationError) { +/// guard checkDependencies() else { +/// throw .dependencyFailed(dependency: "Cache Initialization") +/// } +/// // Operation processing logic +/// } +/// } +/// ``` +/// +/// ## Managing Cancelable Operations +/// ```swift +/// struct BackgroundTask { +/// func performLongRunningTask() throws(OperationError) { +/// guard !isCancellationRequested() else { +/// throw .canceled +/// } +/// // Long-running task logic +/// } +/// } +/// ``` public enum OperationError: Throwable { /// The operation could not start due to a dependency failure. + /// + /// # Example + /// ```swift + /// struct DataSynchronizer { + /// func synchronize() throws(OperationError) { + /// guard isNetworkReady() else { + /// throw .dependencyFailed(dependency: "Network Connection") + /// } + /// // Synchronization logic + /// } + /// } + /// ``` /// - Parameter dependency: A description of the failed dependency. case dependencyFailed(dependency: String) /// The operation was canceled before completion. + /// + /// # Example + /// ```swift + /// struct ImageProcessor { + /// func processImage() throws(OperationError) { + /// guard !userRequestedCancel else { + /// throw .canceled + /// } + /// // Image processing logic + /// } + /// } + /// ``` case canceled - /// The operation failed with an unknown reason. - case unknownFailure(description: String) - /// Generic error message if the existing cases don't provide the required details. + /// + /// # Example + /// ```swift + /// struct GenericErrorManager { + /// func handleSpecialCase() throws(OperationError) { + /// guard !isHandledCase() else { + /// throw .generic(userFriendlyMessage: "A unique operation error occurred") + /// } + /// // Special case handling + /// } + /// } + /// ``` case generic(userFriendlyMessage: String) /// A user-friendly error message suitable for display to end users. @@ -21,19 +80,13 @@ public enum OperationError: Throwable { case .dependencyFailed(let dependency): return String( localized: "BuiltInErrors.OperationError.dependencyFailed", - defaultValue: "The operation could not be completed due to a failed dependency: \(dependency).", + defaultValue: "The operation could not be started because a required component failed to initialize: \(dependency). Please restart the application or contact support.", bundle: .module ) case .canceled: return String( localized: "BuiltInErrors.OperationError.canceled", - defaultValue: "The operation was canceled. Please try again if necessary.", - bundle: .module - ) - case .unknownFailure(let description): - return String( - localized: "BuiltInErrors.OperationError.unknownFailure", - defaultValue: "The operation failed due to an unknown reason: \(description). Please try again or contact support.", + defaultValue: "The operation was canceled at your request. You can retry the action if needed.", bundle: .module ) case .generic(let userFriendlyMessage): diff --git a/Sources/ErrorKit/BuiltInErrors/ParsingError.swift b/Sources/ErrorKit/BuiltInErrors/ParsingError.swift index 25efff9..0afb172 100644 --- a/Sources/ErrorKit/BuiltInErrors/ParsingError.swift +++ b/Sources/ErrorKit/BuiltInErrors/ParsingError.swift @@ -1,16 +1,78 @@ import Foundation /// Represents errors that occur during parsing of input or data. +/// +/// # Examples of Use +/// +/// ## Handling Input Validation +/// ```swift +/// struct JSONParser { +/// func parse(rawInput: String) throws(ParsingError) -> ParsedData { +/// guard isValidJSONFormat(rawInput) else { +/// throw .invalidInput(input: rawInput) +/// } +/// // Parsing logic +/// } +/// } +/// ``` +/// +/// ## Managing Structured Data Parsing +/// ```swift +/// struct UserProfileParser { +/// func parseProfile(data: [String: Any]) throws(ParsingError) -> UserProfile { +/// guard let username = data["username"] else { +/// throw .missingField(field: "username") +/// } +/// // Profile parsing logic +/// } +/// } +/// ``` public enum ParsingError: Throwable { /// The input was invalid and could not be parsed. + /// + /// # Example + /// ```swift + /// struct ConfigurationParser { + /// func parseConfig(input: String) throws(ParsingError) -> Configuration { + /// guard isValidConfigurationFormat(input) else { + /// throw .invalidInput(input: input) + /// } + /// // Configuration parsing logic + /// } + /// } + /// ``` /// - Parameter input: The invalid input string or description. case invalidInput(input: String) /// A required field was missing in the input. + /// + /// # Example + /// ```swift + /// struct PaymentProcessor { + /// func validatePaymentDetails(_ details: [String: String]) throws(ParsingError) { + /// guard details["cardNumber"] != nil else { + /// throw .missingField(field: "cardNumber") + /// } + /// // Payment processing logic + /// } + /// } + /// ``` /// - Parameter field: The name of the missing field. case missingField(field: String) /// Generic error message if the existing cases don't provide the required details. + /// + /// # Example + /// ```swift + /// struct UnexpectedParsingHandler { + /// func handleUnknownParsingIssue() throws(ParsingError) { + /// guard !isHandledCase() else { + /// throw .generic(userFriendlyMessage: "An unexpected parsing error occurred") + /// } + /// // Fallback error handling + /// } + /// } + /// ``` case generic(userFriendlyMessage: String) /// A user-friendly error message suitable for display to end users. @@ -19,13 +81,13 @@ public enum ParsingError: Throwable { case .invalidInput(let input): return String( localized: "BuiltInErrors.ParsingError.invalidInput", - defaultValue: "The provided input is invalid: \(input). Please correct it and try again.", + defaultValue: "The provided input could not be processed correctly: \(input). Please review the input and ensure it matches the expected format.", bundle: .module ) case .missingField(let field): return String( localized: "BuiltInErrors.ParsingError.missingField", - defaultValue: "A required field is missing: \(field). Please review and try again.", + defaultValue: "The required information is incomplete. The \(field) field is missing and must be provided to continue.", bundle: .module ) case .generic(let userFriendlyMessage): diff --git a/Sources/ErrorKit/BuiltInErrors/PermissionError.swift b/Sources/ErrorKit/BuiltInErrors/PermissionError.swift index a1728ca..a0fe789 100644 --- a/Sources/ErrorKit/BuiltInErrors/PermissionError.swift +++ b/Sources/ErrorKit/BuiltInErrors/PermissionError.swift @@ -1,19 +1,99 @@ import Foundation /// Represents errors related to missing or denied permissions. +/// +/// # Examples of Use +/// +/// ## Handling Permission Checks +/// ```swift +/// struct LocationService { +/// func requestLocation() throws(PermissionError) { +/// switch checkLocationPermission() { +/// case .denied: +/// throw .denied(permission: "Location") +/// case .restricted: +/// throw .restricted(permission: "Location") +/// case .notDetermined: +/// throw .notDetermined(permission: "Location") +/// case .authorized: +/// // Proceed with location request +/// } +/// } +/// } +/// ``` +/// +/// ## Managing Permission Workflows +/// ```swift +/// struct CameraAccessManager { +/// func verifyAndRequestCameraAccess() throws(PermissionError) { +/// guard canRequestCameraPermission() else { +/// throw .restricted(permission: "Camera") +/// } +/// // Permission request logic +/// } +/// } +/// ``` public enum PermissionError: Throwable { /// The user denied the required permission. + /// + /// # Example + /// ```swift + /// struct PhotoLibraryManager { + /// func accessPhotoLibrary() throws(PermissionError) { + /// guard isPhotoLibraryAccessAllowed() else { + /// throw .denied(permission: "Photo Library") + /// } + /// // Photo library access logic + /// } + /// } + /// ``` /// - Parameter permission: The type of permission that was denied. case denied(permission: String) /// The app lacks a required permission and the user cannot grant it. + /// + /// # Example + /// ```swift + /// struct HealthDataService { + /// func accessHealthData() throws(PermissionError) { + /// guard canRequestHealthPermission() else { + /// throw .restricted(permission: "Health Data") + /// } + /// // Health data access logic + /// } + /// } + /// ``` /// - Parameter permission: The type of permission required. case restricted(permission: String) /// The app lacks a required permission, and it is unknown whether the user can grant it. + /// + /// # Example + /// ```swift + /// struct NotificationManager { + /// func setupNotifications() throws(PermissionError) { + /// guard isNotificationPermissionStatusKnown() else { + /// throw .notDetermined(permission: "Notifications") + /// } + /// // Notification setup logic + /// } + /// } + /// ``` case notDetermined(permission: String) /// Generic error message if the existing cases don't provide the required details. + /// + /// # Example + /// ```swift + /// struct UnexpectedPermissionHandler { + /// func handleSpecialCase() throws(PermissionError) { + /// guard !isHandledCase() else { + /// throw .generic(userFriendlyMessage: "A unique permission error occurred") + /// } + /// // Special case handling + /// } + /// } + /// ``` case generic(userFriendlyMessage: String) /// A user-friendly error message suitable for display to end users. @@ -22,19 +102,19 @@ public enum PermissionError: Throwable { case .denied(let permission): return String( localized: "BuiltInErrors.PermissionError.denied", - defaultValue: "The \(permission) permission was denied. Please enable it in Settings to continue.", + defaultValue: "Access to \(permission) was declined. To use this feature, please enable the permission in your device Settings.", bundle: .module ) case .restricted(let permission): return String( localized: "BuiltInErrors.PermissionError.restricted", - defaultValue: "The \(permission) permission is restricted. This may be due to parental controls or other system restrictions.", + defaultValue: "Access to \(permission) is currently restricted. This may be due to system settings or parental controls.", bundle: .module ) case .notDetermined(let permission): return String( localized: "BuiltInErrors.PermissionError.notDetermined", - defaultValue: "The \(permission) permission has not been determined. Please try again or check your Settings.", + defaultValue: "Permission for \(permission) has not been confirmed. Please review and grant access in your device Settings.", bundle: .module ) case .generic(let userFriendlyMessage): diff --git a/Sources/ErrorKit/BuiltInErrors/StateError.swift b/Sources/ErrorKit/BuiltInErrors/StateError.swift index 25050a7..912d21f 100644 --- a/Sources/ErrorKit/BuiltInErrors/StateError.swift +++ b/Sources/ErrorKit/BuiltInErrors/StateError.swift @@ -1,17 +1,91 @@ import Foundation /// Represents errors caused by invalid or unexpected states. +/// +/// # Examples of Use +/// +/// ## Managing State Transitions +/// ```swift +/// struct OrderProcessor { +/// func processOrder(_ order: Order) throws(StateError) { +/// guard order.status == .pending else { +/// throw .invalidState(description: "Order must be in pending state") +/// } +/// // Order processing logic +/// } +/// } +/// ``` +/// +/// ## Handling Finalized States +/// ```swift +/// struct DocumentManager { +/// func updateDocument(_ doc: Document) throws(StateError) { +/// guard !doc.isFinalized else { +/// throw .alreadyFinalized +/// } +/// // Document update logic +/// } +/// } +/// ``` public enum StateError: Throwable { /// The required state was not met to proceed with the operation. + /// + /// # Example + /// ```swift + /// struct PaymentProcessor { + /// func refundPayment(_ payment: Payment) throws(StateError) { + /// guard payment.status == .completed else { + /// throw .invalidState(description: "Payment must be completed") + /// } + /// // Refund processing logic + /// } + /// } + /// ``` case invalidState(description: String) /// The operation cannot proceed because the state has already been finalized. + /// + /// # Example + /// ```swift + /// struct ContractManager { + /// func modifyContract(_ contract: Contract) throws(StateError) { + /// guard !contract.isFinalized else { + /// throw .alreadyFinalized + /// } + /// // Contract modification logic + /// } + /// } + /// ``` case alreadyFinalized /// A required precondition for the operation was not met. + /// + /// # Example + /// ```swift + /// struct GameEngine { + /// func startNewLevel() throws(StateError) { + /// guard player.hasCompletedTutorial else { + /// throw .preconditionFailed(description: "Tutorial must be completed") + /// } + /// // Level initialization logic + /// } + /// } + /// ``` case preconditionFailed(description: String) /// Generic error message if the existing cases don't provide the required details. + /// + /// # Example + /// ```swift + /// struct StateHandler { + /// func handleUnexpectedState() throws(StateError) { + /// guard isStateValid() else { + /// throw .generic(userFriendlyMessage: "System is in an unexpected state") + /// } + /// // State handling logic + /// } + /// } + /// ``` case generic(userFriendlyMessage: String) /// A user-friendly error message suitable for display to end users. @@ -20,19 +94,19 @@ public enum StateError: Throwable { case .invalidState(let description): return String( localized: "BuiltInErrors.StateError.invalidState", - defaultValue: "The operation cannot proceed due to an invalid state: \(description).", + defaultValue: "The current state prevents this action: \(description). Please ensure all requirements are met and try again.", bundle: .module ) case .alreadyFinalized: return String( localized: "BuiltInErrors.StateError.alreadyFinalized", - defaultValue: "The operation cannot be performed because the state is already finalized.", + defaultValue: "This item has already been finalized and cannot be modified. Please create a new version if changes are needed.", bundle: .module ) case .preconditionFailed(let description): return String( localized: "BuiltInErrors.StateError.preconditionFailed", - defaultValue: "A required condition was not met: \(description). Please review and try again.", + defaultValue: "A required condition was not met: \(description). Please complete all prerequisites before proceeding.", bundle: .module ) case .generic(let userFriendlyMessage): diff --git a/Sources/ErrorKit/BuiltInErrors/ValidationError.swift b/Sources/ErrorKit/BuiltInErrors/ValidationError.swift index ccec40d..4e3d852 100644 --- a/Sources/ErrorKit/BuiltInErrors/ValidationError.swift +++ b/Sources/ErrorKit/BuiltInErrors/ValidationError.swift @@ -1,24 +1,105 @@ import Foundation /// Represents errors related to validation failures. +/// +/// # Examples of Use +/// +/// ## Validating Form Input +/// ```swift +/// struct UserRegistrationValidator { +/// func validateUsername(_ username: String) throws(ValidationError) { +/// guard !username.isEmpty else { +/// throw .missingField(field: "Username") +/// } +/// +/// guard username.count <= 30 else { +/// throw .inputTooLong(field: "Username", maxLength: 30) +/// } +/// +/// guard isValidUsername(username) else { +/// throw .invalidInput(field: "Username") +/// } +/// } +/// } +/// ``` +/// +/// ## Handling Required Fields +/// ```swift +/// struct PaymentFormValidator { +/// func validatePaymentDetails(_ details: [String: String]) throws(ValidationError) { +/// guard let cardNumber = details["cardNumber"], !cardNumber.isEmpty else { +/// throw .missingField(field: "Card Number") +/// } +/// // Additional validation logic +/// } +/// } +/// ``` public enum ValidationError: Throwable { /// The input provided is invalid. + /// + /// # Example + /// ```swift + /// struct EmailValidator { + /// func validateEmail(_ email: String) throws(ValidationError) { + /// guard isValidEmailFormat(email) else { + /// throw .invalidInput(field: "Email Address") + /// } + /// // Additional email validation + /// } + /// } + /// ``` /// - Parameters: /// - field: The name of the field that caused the error. case invalidInput(field: String) /// A required field is missing. + /// + /// # Example + /// ```swift + /// struct ShippingAddressValidator { + /// func validateAddress(_ address: Address) throws(ValidationError) { + /// guard !address.street.isEmpty else { + /// throw .missingField(field: "Street Address") + /// } + /// // Additional address validation + /// } + /// } + /// ``` /// - Parameters: /// - field: The name of the required fields. case missingField(field: String) /// The input exceeds the maximum allowed length. + /// + /// # Example + /// ```swift + /// struct CommentValidator { + /// func validateComment(_ text: String) throws(ValidationError) { + /// guard text.count <= 1000 else { + /// throw .inputTooLong(field: "Comment", maxLength: 1000) + /// } + /// // Additional comment validation + /// } + /// } + /// ``` /// - Parameters: /// - field: The name of the field that caused the error. /// - maxLength: The maximum allowed length for the field. case inputTooLong(field: String, maxLength: Int) /// Generic error message if the existing cases don't provide the required details. + /// + /// # Example + /// ```swift + /// struct CustomValidator { + /// func validateSpecialCase(_ input: String) throws(ValidationError) { + /// guard meetsCustomRequirements(input) else { + /// throw .generic(userFriendlyMessage: "Input does not meet requirements") + /// } + /// // Special validation logic + /// } + /// } + /// ``` case generic(userFriendlyMessage: String) /// A user-friendly error message suitable for display to end users. @@ -27,19 +108,19 @@ public enum ValidationError: Throwable { case .invalidInput(let field): return String( localized: "BuiltInErrors.ValidationError.invalidInput", - defaultValue: "The value provided for \(field) is invalid. Please correct it.", + defaultValue: "The value entered for \(field) is not in the correct format. Please review the requirements and try again.", bundle: .module ) case .missingField(let field): return String( localized: "BuiltInErrors.ValidationError.missingField", - defaultValue: "\(field) is a required field. Please provide a value.", + defaultValue: "Please provide a value for \(field). This information is required to proceed.", bundle: .module ) case .inputTooLong(let field, let maxLength): return String( localized: "BuiltInErrors.ValidationError.inputTooLong", - defaultValue: "\(field) exceeds the maximum allowed length of \(maxLength) characters. Please shorten it.", + defaultValue: "The \(field) field cannot be longer than \(maxLength) characters. Please shorten your input and try again.", bundle: .module ) case .generic(let userFriendlyMessage): From 2296c3b3d9ab3d4ade73fe8de1b2bd5171c2b10e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cihat=20Gu=CC=88ndu=CC=88z?= Date: Wed, 18 Dec 2024 09:09:19 +0100 Subject: [PATCH 3/3] Require Sendable conformance for Throwable + add new README section --- README.md | 84 ++++++ .../ErrorKit/Resources/Localizable.xcstrings | 275 ++++++++++++++++++ Sources/ErrorKit/Throwable.swift | 2 +- 3 files changed, 360 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9b86d29..67f8ce5 100644 --- a/README.md +++ b/README.md @@ -209,3 +209,87 @@ Here, the code leverages the specific error types to implement various kinds of ### Summary By utilizing these typed-throws overloads, you can write more robust and maintainable code. ErrorKit's enhanced user-friendly messages and ability to handle specific errors with code lead to a better developer and user experience. As the library continues to evolve, we encourage the community to contribute additional overloads and error types for common system APIs to further enhance its capabilities. + + +## Built-in Error Types for Common Scenarios + +ErrorKit provides a set of pre-defined error types for common scenarios that developers encounter frequently. These built-in types conform to `Throwable` and can be used with both typed throws (`throws(DatabaseError)`) and classical throws declarations. + +### Why Built-in Types? + +Built-in error types offer several advantages: +- **Quick Start**: Begin with well-structured error handling without defining custom types +- **Consistency**: Use standardized error cases and messages across your codebase +- **Flexibility**: Easily transition to custom error types when you need more specific cases +- **Discoverability**: Clear naming conventions make it easy to find the right error type +- **Localization**: All error messages are pre-localized and user-friendly +- **Ecosystem Impact**: As more Swift packages adopt these standardized error types, apps can implement smarter error handling that works across dependencies. Instead of just showing error messages, apps could provide specific UI or recovery actions for known error types, creating a more cohesive error handling experience throughout the ecosystem. + +### Available Error Types + +ErrorKit includes the following built-in error types: + +- **DatabaseError** (connectionFailed, operationFailed, recordNotFound) +- **FileError** (fileNotFound, readFailed, writeFailed) +- **NetworkError** (noInternet, timeout, badRequest, serverError, decodingFailure) +- **OperationError** (dependencyFailed, canceled, unknownFailure) +- **ParsingError** (invalidInput, missingField, inputTooLong) +- **PermissionError** (denied, restricted, notDetermined) +- **StateError** (invalidState, alreadyFinalized, preconditionFailed) +- **ValidationError** (invalidInput, missingField, inputTooLong) +- **GenericError** (for ad-hoc custom messages) + +All built-in error types include a `generic` case that accepts a custom `userFriendlyMessage`, allowing for quick additions of edge cases without creating new error types. Use the `GenericError` struct when you want to quickly throw a one-off error without having to define your own type if none of the other fit, useful especially during early phases of development. + +### Usage Examples + +```swift +func fetchUserData() throws(DatabaseError) { + guard isConnected else { + throw .connectionFailed + } + // Fetching logic +} + +// Or with classical throws +func processData() throws { + guard isValid else { + throw ValidationError.invalidInput(field: "email") + } + // Processing logic +} + +// Quick error throwing with GenericError +func quickOperation() throws { + guard condition else { + throw GenericError(userFriendlyMessage: String(localized: "The condition X was not fulfilled, please check again.")) + } + // Operation logic +} + +// Using generic case for edge cases +func handleSpecialCase() throws(DatabaseError) { + guard specialCondition else { + throw .generic(userFriendlyMessage: String(localized: "Database is in maintenance mode")) + } + // Special case handling +} +``` + +### Contributing New Error Types + +We need your help! If you find yourself: +- Defining similar error types across projects +- Missing a common error scenario in our built-in types +- Seeing patterns in error handling that could benefit others +- Having ideas for better error messages or new cases + +Please contribute! Submit a pull request to add your error types or cases to ErrorKit. Your contribution helps build a more robust error handling ecosystem for Swift developers. + +When contributing: +- Ensure error cases are generic enough for broad use +- Provide clear, actionable error messages +- Include real-world usage examples in documentation +- Follow the existing naming conventions + +Together, we can build a comprehensive set of error types that cover most common scenarios in Swift development and create a more unified error handling experience across the ecosystem. diff --git a/Sources/ErrorKit/Resources/Localizable.xcstrings b/Sources/ErrorKit/Resources/Localizable.xcstrings index b9c39de..4b2ef21 100644 --- a/Sources/ErrorKit/Resources/Localizable.xcstrings +++ b/Sources/ErrorKit/Resources/Localizable.xcstrings @@ -1,6 +1,281 @@ { "sourceLanguage" : "en", "strings" : { + "BuiltInErrors.DatabaseError.connectionFailed" : { + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Failed to connect to the database. Please try again later." + } + } + } + }, + "BuiltInErrors.DatabaseError.operationFailed" : { + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "An error occurred while performing the operation: %@. Please try again." + } + } + } + }, + "BuiltInErrors.DatabaseError.recordNotFound" : { + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "The %1$@ record could not be found.%2$@ Please check and try again." + } + } + } + }, + "BuiltInErrors.FileError.fileNotFound" : { + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "The file %@ could not be found. Please check the file path." + } + } + } + }, + "BuiltInErrors.FileError.readError" : { + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "There was an issue reading the file %@. Please try again." + } + } + } + }, + "BuiltInErrors.FileError.writeError" : { + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "There was an issue writing to the file %@. Please try again." + } + } + } + }, + "BuiltInErrors.NetworkError.badRequest" : { + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "The request was malformed (%1$lld): %2$@. Please review and try again." + } + } + } + }, + "BuiltInErrors.NetworkError.decodingFailure" : { + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "The data received from the server could not be processed. Please try again." + } + } + } + }, + "BuiltInErrors.NetworkError.noInternet" : { + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "No internet connection is available. Please check your network settings and try again." + } + } + } + }, + "BuiltInErrors.NetworkError.serverError" : { + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "The server encountered an error (Code: %lld)." + } + } + } + }, + "BuiltInErrors.NetworkError.timeout" : { + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "The request timed out. Please try again later." + } + } + } + }, + "BuiltInErrors.OperationError.canceled" : { + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "The operation was canceled. Please try again if necessary." + } + } + } + }, + "BuiltInErrors.OperationError.dependencyFailed" : { + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "The operation could not be completed due to a failed dependency: %@." + } + } + } + }, + "BuiltInErrors.OperationError.unknownFailure" : { + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "The operation failed due to an unknown reason: %@. Please try again or contact support." + } + } + } + }, + "BuiltInErrors.ParsingError.invalidInput" : { + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "The provided input is invalid: %@. Please correct it and try again." + } + } + } + }, + "BuiltInErrors.ParsingError.missingField" : { + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "A required field is missing: %@. Please review and try again." + } + } + } + }, + "BuiltInErrors.PermissionError.denied" : { + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "The %@ permission was denied. Please enable it in Settings to continue." + } + } + } + }, + "BuiltInErrors.PermissionError.notDetermined" : { + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "The %@ permission has not been determined. Please try again or check your Settings." + } + } + } + }, + "BuiltInErrors.PermissionError.restricted" : { + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "The %@ permission is restricted. This may be due to parental controls or other system restrictions." + } + } + } + }, + "BuiltInErrors.StateError.alreadyFinalized" : { + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "The operation cannot be performed because the state is already finalized." + } + } + } + }, + "BuiltInErrors.StateError.invalidState" : { + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "The operation cannot proceed due to an invalid state: %@." + } + } + } + }, + "BuiltInErrors.StateError.preconditionFailed" : { + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "A required condition was not met: %@. Please review and try again." + } + } + } + }, + "BuiltInErrors.ValidationError.inputTooLong" : { + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "%1$@ exceeds the maximum allowed length of %2$lld characters. Please shorten it." + } + } + } + }, + "BuiltInErrors.ValidationError.invalidInput" : { + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "The value provided for %@ is invalid. Please correct it." + } + } + } + }, + "BuiltInErrors.ValidationError.missingField" : { + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "%@ is a required field. Please provide a value." + } + } + } + }, "EnhancedDescriptions.CocoaError.default" : { "extractionState" : "extracted_with_value", "localizations" : { diff --git a/Sources/ErrorKit/Throwable.swift b/Sources/ErrorKit/Throwable.swift index e21f6ef..c12a395 100644 --- a/Sources/ErrorKit/Throwable.swift +++ b/Sources/ErrorKit/Throwable.swift @@ -59,7 +59,7 @@ import Foundation /// Caught error with message: Unable to connect to the server. /// ``` /// -public protocol Throwable: LocalizedError { +public protocol Throwable: LocalizedError, Sendable { /// A human-readable error message describing the error. var userFriendlyMessage: String { get } }