Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
129 changes: 129 additions & 0 deletions Sources/ErrorKit/BuiltInErrors/DatabaseError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
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.
public var userFriendlyMessage: String {
switch self {
case .connectionFailed:
return String(
localized: "BuiltInErrors.DatabaseError.connectionFailed",
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: "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 { " with ID \($0)" } ?? ""
return String(
localized: "BuiltInErrors.DatabaseError.recordNotFound",
defaultValue: "The \(entity) record\(idMessage) was not found in the database. Verify the details and try again.",
bundle: .module
)
case .generic(let userFriendlyMessage):
return userFriendlyMessage
}
}
}
123 changes: 123 additions & 0 deletions Sources/ErrorKit/BuiltInErrors/FileError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
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.
public var userFriendlyMessage: String {
switch self {
case .fileNotFound(let fileName):
return String(
localized: "BuiltInErrors.FileError.fileNotFound",
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: "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: "Unable to write to the file \(fileName). Ensure you have the necessary permissions and try again.",
bundle: .module
)
case .generic(let userFriendlyMessage):
return userFriendlyMessage
}
}
}
Loading