Skip to content

Commit a5edc71

Browse files
authored
Merge pull request #13 from FlineDev/wip/system-overloads
Rename Throwable property + introduce typed-throw overloads to first common APIs
2 parents 65bf4dc + a94aa07 commit a5edc71

File tree

11 files changed

+1013
-93
lines changed

11 files changed

+1013
-93
lines changed

README.md

Lines changed: 68 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ The correct approach is to conform to `LocalizedError`, which defines the follow
6161
- `recoverySuggestion: String?`
6262
- `helpAnchor: String?`
6363

64-
However, since all of these properties are optional, you won’t get any compiler errors if you forget to implement them. Worse, only `errorDescription` affects `localizedDescription`. Fields like `failureReason` and `recoverySuggestion` are ignored, while `helpAnchor` is rarely used today.
64+
However, since all of these properties are optional, you won’t get any compiler errors if you forget to implement them. Worse, only `errorDescription` affects `localizedDescription`. Fields like `failureReason` and `recoverySuggestion` are ignored, while `helpAnchor` is rarely used nowadays.
6565

6666
This makes `LocalizedError` both confusing and error-prone.
6767

@@ -71,11 +71,11 @@ To address these issues, **ErrorKit** introduces the `Throwable` protocol:
7171

7272
```swift
7373
public protocol Throwable: LocalizedError {
74-
var localizedDescription: String { get }
74+
var userFriendlyMessage: String { get }
7575
}
7676
```
7777

78-
This protocol is simple and clear. It’s named `Throwable` to align with Swift’s `throw` keyword and follows Swift’s convention of using the `able` suffix (like `Codable` and `Identifiable`). Most importantly, it requires the `localizedDescription` property, ensuring your errors behave exactly as expected.
78+
This protocol is simple and clear. It’s named `Throwable` to align with Swift’s `throw` keyword and follows Swift’s convention of using the `able` suffix (like `Codable` and `Identifiable`). Most importantly, it requires the `userFriendlyMessage` property, ensuring your errors behave exactly as expected.
7979

8080
Here’s how you use it:
8181

@@ -84,7 +84,7 @@ enum NetworkError: Throwable {
8484
case noConnectionToServer
8585
case parsingFailed
8686

87-
var localizedDescription: String {
87+
var userFriendlyMessage: String {
8888
switch self {
8989
case .noConnectionToServer: "Unable to connect to the server."
9090
case .parsingFailed: "Data parsing failed."
@@ -110,24 +110,24 @@ This approach eliminates boilerplate code while keeping the error definitions co
110110

111111
### Summary
112112

113-
> Conform your custom error types to `Throwable` instead of `Error` or `LocalizedError`. The `Throwable` protocol requires only `localizedDescription: String`, ensuring your error messages are exactly what you expect – no surprises.
113+
> Conform your custom error types to `Throwable` instead of `Error` or `LocalizedError`. The `Throwable` protocol requires only `userFriendlyMessage: String`, ensuring your error messages are exactly what you expect – no surprises.
114114
115115

116-
## Enhanced Error Descriptions with `enhancedDescription(for:)`
116+
## Enhanced Error Descriptions with `userFriendlyMessage(for:)`
117117

118-
ErrorKit goes beyond simplifying error handling — it enhances the clarity of error messages by providing improved, localized descriptions. With the `ErrorKit.enhancedDescription(for:)` function, developers can deliver clear, user-friendly error messages tailored to their audience.
118+
ErrorKit goes beyond simplifying error handling — it enhances the clarity of error messages by providing improved, localized descriptions. With the `ErrorKit.userFriendlyMessage(for:)` function, developers can deliver clear, user-friendly error messages tailored to their audience.
119119

120120
### How It Works
121121

122-
The `enhancedDescription(for:)` function analyzes the provided `Error` and returns an enhanced, localized message. It draws on a community-maintained collection of descriptions to ensure the messages are accurate, helpful, and continuously evolving.
122+
The `userFriendlyMessage(for:)` function analyzes the provided `Error` and returns an enhanced, localized message. It draws on a community-maintained collection of descriptions to ensure the messages are accurate, helpful, and continuously evolving.
123123

124124
### Supported Error Domains
125125

126126
ErrorKit supports errors from various domains such as `Foundation`, `CoreData`, `MapKit`, and more. These domains are continuously updated, providing coverage for the most common error types in Swift development.
127127

128128
### Usage Example
129129

130-
Here’s how to use `enhancedDescription(for:)` to handle errors gracefully:
130+
Here’s how to use `userFriendlyMessage(for:)` to handle errors gracefully:
131131

132132
```swift
133133
do {
@@ -136,12 +136,12 @@ do {
136136
let _ = try Data(contentsOf: url)
137137
} catch {
138138
// Print or show the enhanced error message to a user
139-
print(ErrorKit.enhancedDescription(for: error))
139+
print(ErrorKit.userFriendlyMessage(for: error))
140140
// Example output: "You are not connected to the Internet. Please check your connection."
141141
}
142142
```
143143

144-
### Why Use `enhancedDescription(for:)`?
144+
### Why Use `userFriendlyMessage(for:)`?
145145

146146
- **Localization**: Error messages are localized to ~40 languages to provide a better user experience.
147147
- **Clarity**: Returns clear and concise error messages, avoiding cryptic system-generated descriptions.
@@ -152,3 +152,60 @@ do {
152152
Found a bug or missing description? We welcome your contributions! Submit a pull request (PR), and we’ll gladly review and merge it to enhance the library further.
153153

154154
> **Note:** The enhanced error descriptions are constantly evolving, and we’re committed to making them as accurate and helpful as possible.
155+
156+
## Overloads of Common System Functions with Typed Throws
157+
158+
ErrorKit introduces typed-throws overloads for common system APIs like `FileManager` and `URLSession`, providing more granular error handling and improved code clarity. These overloads allow you to handle specific error scenarios with tailored responses, making your code more robust and easier to maintain.
159+
160+
To streamline discovery, ErrorKit uses the same API names prefixed with `throwable`. These functions throw specific errors that conform to `Throwable`, allowing for clear and informative error messages.
161+
162+
**Enhanced User-Friendly Error Messages:**
163+
164+
One of the key advantages of ErrorKit's typed throws is the improved `localizedDescription` property. This property provides user-friendly error messages that are tailored to the specific error type. This eliminates the need for manual error message construction and ensures a consistent and informative user experience.
165+
166+
**Example: Creating a Directory**
167+
168+
```swift
169+
do {
170+
try FileManager.default.throwableCreateDirectory(at: URL(string: "file:///path/to/directory")!)
171+
} catch {
172+
switch error {
173+
case FileManagerError.noWritePermission:
174+
// Request write permission from the user intead of showing error message
175+
default:
176+
// Common error cases have a more descriptive message
177+
showErrorDialog(error.localizedDescription)
178+
}
179+
}
180+
```
181+
182+
The code demonstrates how to handle errors for specific error cases with an improved UX rather than just showing an error message to the user, which can still be the fallback. And the error cases are easy to discover thanks to the typed enum error.
183+
184+
**Example: Handling network request errors**
185+
186+
```swift
187+
do {
188+
let (data, response) = try await URLSession.shared.throwableData(from: URL(string: "https://api.example.com/data")!)
189+
// Process the data and response
190+
} catch {
191+
// Error is of type `URLSessionError`
192+
print(error.localizedDescription)
193+
194+
switch error {
195+
case .timeout, .requestTimeout, .tooManyRequests:
196+
// Automatically retry the request with a backoff strategy
197+
case .noNetwork:
198+
// Show an SF Symbol indicating the user is offline plus a retry button
199+
case .unauthorized:
200+
// Redirect the user to your login-flow (e.g. because token expired)
201+
default:
202+
// Fall back to showing error message
203+
}
204+
}
205+
```
206+
207+
Here, the code leverages the specific error types to implement various kinds of custom logic. This demonstrates the power of typed throws in providing fine-grained control over error handling.
208+
209+
### Summary
210+
211+
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.

Sources/ErrorKit/CommonErrors/ErrorKit+CoreData.swift renamed to Sources/ErrorKit/EnhancedDescriptions/ErrorKit+CoreData.swift

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import CoreData
33
#endif
44

55
extension ErrorKit {
6-
static func enhancedCoreDataDescription(for error: Error) -> String? {
6+
static func userFriendlyCoreDataMessage(for error: Error) -> String? {
77
#if canImport(CoreData)
88
let nsError = error as NSError
99

@@ -12,43 +12,43 @@ extension ErrorKit {
1212

1313
case NSPersistentStoreSaveError:
1414
return String(
15-
localized: "CommonErrors.CoreData.NSPersistentStoreSaveError",
15+
localized: "EnhancedDescriptions.CoreData.NSPersistentStoreSaveError",
1616
defaultValue: "Failed to save the data. Please try again.",
1717
bundle: .module
1818
)
1919
case NSValidationMultipleErrorsError:
2020
return String(
21-
localized: "CommonErrors.CoreData.NSValidationMultipleErrorsError",
21+
localized: "EnhancedDescriptions.CoreData.NSValidationMultipleErrorsError",
2222
defaultValue: "Multiple validation errors occurred while saving.",
2323
bundle: .module
2424
)
2525
case NSValidationMissingMandatoryPropertyError:
2626
return String(
27-
localized: "CommonErrors.CoreData.NSValidationMissingMandatoryPropertyError",
27+
localized: "EnhancedDescriptions.CoreData.NSValidationMissingMandatoryPropertyError",
2828
defaultValue: "A mandatory property is missing. Please fill all required fields.",
2929
bundle: .module
3030
)
3131
case NSValidationRelationshipLacksMinimumCountError:
3232
return String(
33-
localized: "CommonErrors.CoreData.NSValidationRelationshipLacksMinimumCountError",
33+
localized: "EnhancedDescriptions.CoreData.NSValidationRelationshipLacksMinimumCountError",
3434
defaultValue: "A relationship is missing required related objects.",
3535
bundle: .module
3636
)
3737
case NSPersistentStoreIncompatibleVersionHashError:
3838
return String(
39-
localized: "CommonErrors.CoreData.NSPersistentStoreIncompatibleVersionHashError",
39+
localized: "EnhancedDescriptions.CoreData.NSPersistentStoreIncompatibleVersionHashError",
4040
defaultValue: "The data store is incompatible with the current model version.",
4141
bundle: .module
4242
)
4343
case NSPersistentStoreOpenError:
4444
return String(
45-
localized: "CommonErrors.CoreData.NSPersistentStoreOpenError",
45+
localized: "EnhancedDescriptions.CoreData.NSPersistentStoreOpenError",
4646
defaultValue: "Unable to open the persistent store. Please check your storage or permissions.",
4747
bundle: .module
4848
)
4949
case NSManagedObjectValidationError:
5050
return String(
51-
localized: "CommonErrors.CoreData.NSManagedObjectValidationError",
51+
localized: "EnhancedDescriptions.CoreData.NSManagedObjectValidationError",
5252
defaultValue: "An object validation error occurred.",
5353
bundle: .module
5454
)

Sources/ErrorKit/CommonErrors/ErrorKit+Foundation.swift renamed to Sources/ErrorKit/EnhancedDescriptions/ErrorKit+Foundation.swift

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,39 +4,39 @@ import FoundationNetworking
44
#endif
55

66
extension ErrorKit {
7-
static func enhancedFoundationDescription(for error: Error) -> String? {
7+
static func userFriendlyFoundationMessage(for error: Error) -> String? {
88
switch error {
99

1010
// URLError: Networking errors
1111
case let urlError as URLError:
1212
switch urlError.code {
1313
case .notConnectedToInternet:
1414
return String(
15-
localized: "CommonErrors.URLError.notConnectedToInternet",
15+
localized: "EnhancedDescriptions.URLError.notConnectedToInternet",
1616
defaultValue: "You are not connected to the Internet. Please check your connection.",
1717
bundle: .module
1818
)
1919
case .timedOut:
2020
return String(
21-
localized: "CommonErrors.URLError.timedOut",
21+
localized: "EnhancedDescriptions.URLError.timedOut",
2222
defaultValue: "The request timed out. Please try again later.",
2323
bundle: .module
2424
)
2525
case .cannotFindHost:
2626
return String(
27-
localized: "CommonErrors.URLError.cannotFindHost",
27+
localized: "EnhancedDescriptions.URLError.cannotFindHost",
2828
defaultValue: "Unable to find the server. Please check the URL or your network.",
2929
bundle: .module
3030
)
3131
case .networkConnectionLost:
3232
return String(
33-
localized: "CommonErrors.URLError.networkConnectionLost",
33+
localized: "EnhancedDescriptions.URLError.networkConnectionLost",
3434
defaultValue: "The network connection was lost. Please try again.",
3535
bundle: .module
3636
)
3737
default:
3838
return String(
39-
localized: "CommonErrors.URLError.default",
39+
localized: "EnhancedDescriptions.URLError.default",
4040
defaultValue: "A network error occurred: \(urlError.localizedDescription)",
4141
bundle: .module
4242
)
@@ -47,25 +47,25 @@ extension ErrorKit {
4747
switch cocoaError.code {
4848
case .fileNoSuchFile:
4949
return String(
50-
localized: "CommonErrors.CocoaError.fileNoSuchFile",
50+
localized: "EnhancedDescriptions.CocoaError.fileNoSuchFile",
5151
defaultValue: "The file could not be found.",
5252
bundle: .module
5353
)
5454
case .fileReadNoPermission:
5555
return String(
56-
localized: "CommonErrors.CocoaError.fileReadNoPermission",
56+
localized: "EnhancedDescriptions.CocoaError.fileReadNoPermission",
5757
defaultValue: "You do not have permission to read this file.",
5858
bundle: .module
5959
)
6060
case .fileWriteOutOfSpace:
6161
return String(
62-
localized: "CommonErrors.CocoaError.fileWriteOutOfSpace",
62+
localized: "EnhancedDescriptions.CocoaError.fileWriteOutOfSpace",
6363
defaultValue: "There is not enough disk space to complete the operation.",
6464
bundle: .module
6565
)
6666
default:
6767
return String(
68-
localized: "CommonErrors.CocoaError.default",
68+
localized: "EnhancedDescriptions.CocoaError.default",
6969
defaultValue: "A file system error occurred: \(cocoaError.localizedDescription)",
7070
bundle: .module
7171
)
@@ -76,25 +76,25 @@ extension ErrorKit {
7676
switch posixError.code {
7777
case .ENOSPC:
7878
return String(
79-
localized: "CommonErrors.POSIXError.ENOSPC",
79+
localized: "EnhancedDescriptions.POSIXError.ENOSPC",
8080
defaultValue: "There is no space left on the device.",
8181
bundle: .module
8282
)
8383
case .EACCES:
8484
return String(
85-
localized: "CommonErrors.POSIXError.EACCES",
85+
localized: "EnhancedDescriptions.POSIXError.EACCES",
8686
defaultValue: "Permission denied. Please check your file permissions.",
8787
bundle: .module
8888
)
8989
case .EBADF:
9090
return String(
91-
localized: "CommonErrors.POSIXError.EBADF",
91+
localized: "EnhancedDescriptions.POSIXError.EBADF",
9292
defaultValue: "Bad file descriptor. The file may be closed or invalid.",
9393
bundle: .module
9494
)
9595
default:
9696
return String(
97-
localized: "CommonErrors.POSIXError.default",
97+
localized: "EnhancedDescriptions.POSIXError.default",
9898
defaultValue: "A system error occurred: \(posixError.localizedDescription)",
9999
bundle: .module
100100
)

Sources/ErrorKit/CommonErrors/ErrorKit+MapKit.swift renamed to Sources/ErrorKit/EnhancedDescriptions/ErrorKit+MapKit.swift

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,43 +3,43 @@ import MapKit
33
#endif
44

55
extension ErrorKit {
6-
static func enhancedMapKitDescription(for error: Error) -> String? {
6+
static func userFriendlyMapKitMessage(for error: Error) -> String? {
77
#if canImport(MapKit)
88
if let mkError = error as? MKError {
99
switch mkError.code {
1010
case .unknown:
1111
return String(
12-
localized: "CommonErrors.MKError.unknown",
12+
localized: "EnhancedDescriptions.MKError.unknown",
1313
defaultValue: "An unknown error occurred in MapKit.",
1414
bundle: .module
1515
)
1616
case .serverFailure:
1717
return String(
18-
localized: "CommonErrors.MKError.serverFailure",
18+
localized: "EnhancedDescriptions.MKError.serverFailure",
1919
defaultValue: "The MapKit server returned an error. Please try again later.",
2020
bundle: .module
2121
)
2222
case .loadingThrottled:
2323
return String(
24-
localized: "CommonErrors.MKError.loadingThrottled",
24+
localized: "EnhancedDescriptions.MKError.loadingThrottled",
2525
defaultValue: "Map loading is being throttled. Please wait a moment and try again.",
2626
bundle: .module
2727
)
2828
case .placemarkNotFound:
2929
return String(
30-
localized: "CommonErrors.MKError.placemarkNotFound",
30+
localized: "EnhancedDescriptions.MKError.placemarkNotFound",
3131
defaultValue: "The requested placemark could not be found. Please check the location details.",
3232
bundle: .module
3333
)
3434
case .directionsNotFound:
3535
return String(
36-
localized: "CommonErrors.MKError.directionsNotFound",
36+
localized: "EnhancedDescriptions.MKError.directionsNotFound",
3737
defaultValue: "No directions could be found for the specified route.",
3838
bundle: .module
3939
)
4040
default:
4141
return String(
42-
localized: "CommonErrors.MKError.default",
42+
localized: "EnhancedDescriptions.MKError.default",
4343
defaultValue: "A MapKit error occurred: \(mkError.localizedDescription)",
4444
bundle: .module
4545
)

0 commit comments

Comments
 (0)