Hello Swift community!
I'm excited to introduce ErrorKit, a library I've been developing over the past 8 months that addresses several longstanding challenges with error handling in Swift.
The Problem
Many Swift developers have experienced the frustration of writing custom error messages only to see them ignored when caught:
enum NetworkError: Error {
case noConnectionToServer
var localizedDescription: String {
"No connection to the server."
}
}
// Later, when caught and printed:
// "The operation couldn't be completed. (YourPackage.NetworkError error 0.)"
This issue stems from Swift's Error
protocol's bridging to NSError
, which doesn't respect your custom properties as you might expect.
The Solution: Throwable Protocol
ErrorKit introduces the Throwable
protocol that integrates correctly with Swift's error system:
enum NetworkError: Throwable {
case noConnectionToServer
var userFriendlyMessage: String {
String(localized: "Unable to connect to the server.")
}
}
// Now when caught and printed:
// "Unable to connect to the server."
For rapid development, you can use string raw values:
enum NetworkError: String, Throwable {
case noConnectionToServer = "Unable to connect to the server."
case parsingFailed = "Data parsing failed."
}
But That's Just The Beginning
ErrorKit offers a complete suite of error handling improvements:
Enhanced Error Descriptions
Get improved, user-friendly messages for ANY error, including mapping cryptic system errors to more understandable messages for users:
do {
let _ = try Data(contentsOf: url)
} catch {
// Maps NSURLErrorDomain errors to clearer messages
print(ErrorKit.userFriendlyMessage(for: error))
// "You are not connected to the Internet. Please check your connection."
// Instead of "The operation couldn't be completed. (NSURLErrorDomain error -1009.)"
}
Error Chain Debugging
Trace how errors propagate through your application:
Logger().error("\(ErrorKit.errorChainDescription(for: error))")
// ProfileError
// └─ DatabaseError
// └─ FileError.notFound(path: "/Users/data.db")
// └─ userFriendlyMessage: "Could not find database file."
Built-in Error Types
Use standardized error types for common scenarios:
func fetchData() throws(NetworkError) -> Data {
guard isNetworkReachable() else {
throw .noInternet
}
// ...
guard response.statusCode == 200 else {
throw .serverError(
code: response.statusCode,
message: response.errorMessage
)
}
// ...
}
Includes ready-to-use types like NetworkError
, DatabaseError
, FileError
, ValidationError
, PermissionError
, and more.
Swift 6 Typed Throws Support
The Catching
protocol solves nested errors with typed throws:
enum ProfileError: Throwable, Catching {
case validationFailed(field: String)
case caught(Error) // Single case handles all nested errors
var userFriendlyMessage: String { /* ... */ }
}
func loadProfile(id: String) throws(ProfileError) -> UserProfile {
// First perform validation and throw specific error
guard id.isValidFormat else {
throw ProfileError.validationFailed(field: "id")
}
// Automatically wrap any database or file errors
let userData = try ProfileError.catch {
let user = try database.loadUser(id)
let settings = try fileSystem.readUserSettings(user.settingsPath)
return UserProfile(user: user, settings: settings)
}
// use the returned data here
}
User Feedback with Error Logs
Simplify gathering diagnostic information:
Button("Report a Problem") {
showMailComposer = true
}
.mailComposer(
isPresented: $showMailComposer,
recipient: "support@yourapp.com",
subject: "Bug Report",
messageBody: "Please describe what happened:",
attachments: [
try? ErrorKit.logAttachment(ofLast: .minutes(30))
]
)
Gradual Adoption
Each feature can be adopted independently. Start with the Throwable
protocol, then gradually incorporate other features as needed.
Documentation
The examples above are just the tip of the iceberg! The library is extensively documented with detailed guides, API references, and practical examples for each feature.
Full documentation is available on Swift Package Index.
I also started a series of articles with more details on the problems ErrorKit solves:
- Swift Error Handling Done Right: Overcoming the Objective-C Error Legacy
- Unlocking the Real Power of Swift 6's Typed Throws with Error Chains
I welcome your feedback and contributions to make error handling in Swift more intuitive and powerful for everyone!
Find the GitHub repo here: