Skip to content

Commit 59fc403

Browse files
Merge pull request #217 from nextcloud/error
Improvements
2 parents 2777583 + df2f861 commit 59fc403

1 file changed

Lines changed: 158 additions & 15 deletions

File tree

Sources/NextcloudKit/NKError.swift

Lines changed: 158 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import SwiftyJSON
99
import SwiftyXMLParser
1010

1111
typealias OCSPath = Array<String>
12+
1213
protocol DataSubscriptable {
1314
subscript(path: OCSPath) -> Self { get }
1415
}
@@ -34,6 +35,7 @@ extension OCSPath {
3435

3536
public struct NKError: Error, Equatable, Sendable {
3637
static let internalError = -9999
38+
3739
public let errorCode: Int
3840
public let errorDescription: String
3941
public let error: Error
@@ -57,91 +59,180 @@ public struct NKError: Error, Equatable, Sendable {
5759

5860
public static let success = NKError(errorCode: 0, errorDescription: "")
5961

62+
/// Returns a localized user-facing description for a known error code.
63+
///
64+
/// - Parameter code: The HTTP, URL loading, WebDAV, OCS, or internal error code.
65+
/// - Returns: A localized description when the code is known, otherwise `nil`.
6066
public static func getErrorDescription(for code: Int) -> String? {
6167
switch code {
6268
case -9999:
6369
return NSLocalizedString("_internal_server_", value: "Internal error", comment: "")
70+
6471
case -1001:
6572
return NSLocalizedString("_time_out_", value: "Time out", comment: "")
73+
6674
case -1004:
6775
return NSLocalizedString("_server_down_", value: "The server appears to be down", comment: "")
76+
6877
case -1005:
6978
return NSLocalizedString("_not_possible_connect_to_server_", value: "It is not possible to connect to the server at this time", comment: "")
79+
7080
case -1009:
7181
return NSLocalizedString("_not_connected_internet_", value: "Server connection error", comment: "")
82+
7283
case -1011:
7384
return NSLocalizedString("_error_", value: "Generic error", comment: "")
85+
7486
case -1012:
7587
return NSLocalizedString("_not_possible_connect_to_server_", value: "It is not possible to connect to the server at this time", comment: "")
88+
7689
case -1013:
7790
return NSLocalizedString("_user_authentication_required_", value: "User authentication required", comment: "")
91+
7892
case -1200:
7993
return NSLocalizedString("_ssl_connection_error_", value: "Connection SSL error, try again", comment: "")
94+
8095
case -1202:
8196
return NSLocalizedString("_ssl_certificate_untrusted_", value: "The certificate for this server is invalid", comment: "")
82-
case 0: return ""
97+
98+
case 0:
99+
return ""
100+
83101
case 101:
84102
return NSLocalizedString("_forbidden_characters_from_server_", value: "The name contains at least one invalid character", comment: "")
103+
104+
case 200:
105+
return NSLocalizedString("_transfer_stopped_", value: "Transfer stopped", comment: "")
106+
107+
case 207:
108+
return NSLocalizedString("_error_multi_status_", value: "WebDAV multistatus", comment: "")
109+
85110
case 304:
86111
return NSLocalizedString("_error_not_modified_", value: "Resource not modified", comment: "")
112+
87113
case 400:
88114
return NSLocalizedString("_bad_request_", value: "Bad request", comment: "")
115+
89116
case 401:
90117
return NSLocalizedString("_unauthorized_", value: "Unauthorized", comment: "")
118+
91119
case 403:
92120
return NSLocalizedString("_error_not_permission_", value: "You don't have permission to complete the operation", comment: "")
121+
93122
case 404:
94123
return NSLocalizedString("_error_not_found_", value: "The requested resource could not be found", comment: "")
124+
95125
case 405:
96126
return NSLocalizedString("_method_not_allowed_", value: "The requested method is not supported", comment: "")
127+
128+
case 408:
129+
return NSLocalizedString("_request_timeout_", value: "Request timeout", comment: "")
130+
97131
case 409:
98132
return NSLocalizedString("_error_conflict_", value: "The request could not be completed due to a conflict with the current state of the resource", comment: "")
133+
99134
case 412:
100135
return NSLocalizedString("_error_precondition_", value: "The server does not meet one of the preconditions that the requester", comment: "")
136+
101137
case 413:
102138
return NSLocalizedString("_request_entity_too_large_", value: "The file is too large", comment: "")
139+
103140
case 417:
104141
return NSLocalizedString("_expectation_failed_", value: "Expectation failed", comment: "")
142+
105143
case 423:
106144
return NSLocalizedString("_webdav_locked_", value: "WebDAV Locked: Trying to access locked resource", comment: "")
145+
146+
case 429:
147+
return NSLocalizedString("_too_many_requests_", value: "Too many requests", comment: "")
148+
107149
case 500:
108150
return NSLocalizedString("_internal_server_", value: "Internal server error", comment: "")
151+
152+
case 502:
153+
return NSLocalizedString("_bad_gateway_", value: "Bad gateway", comment: "")
154+
109155
case 503:
110156
return NSLocalizedString("_server_maintenance_mode_", value: "Server is currently in maintenance mode", comment: "")
157+
158+
case 504:
159+
return NSLocalizedString("_gateway_timeout_", value: "Gateway timeout", comment: "")
160+
111161
case 507:
112162
return NSLocalizedString("_user_over_quota_", value: "Storage quota is reached", comment: "")
113-
case 200:
114-
return NSLocalizedString("_transfer_stopped_", value: "Transfer stopped", comment: "")
115-
case 207:
116-
return NSLocalizedString("_error_multi_status_", value: "WebDAV multistatus", comment: "")
163+
117164
case NSURLErrorCannotDecodeContentData:
118165
return NSLocalizedString("_invalid_data_format_", value: "Invalid data format", comment: "")
166+
119167
default:
120168
return nil
121169
}
122170
}
123171

172+
/// Returns a clean fallback description for an HTTP status code.
173+
///
174+
/// This method intentionally avoids `HTTPURLResponse.description`, because that value contains
175+
/// the full response dump, including URL and headers, and is not suitable for UI.
176+
///
177+
/// - Parameter statusCode: The HTTP status code.
178+
/// - Returns: A clean fallback description.
179+
private static func httpFallbackDescription(for statusCode: Int) -> String {
180+
let description = HTTPURLResponse.localizedString(forStatusCode: statusCode)
181+
182+
if description.isEmpty {
183+
return NSLocalizedString("_error_", value: "Generic error", comment: "")
184+
}
185+
186+
return description
187+
}
188+
189+
/// Creates an `NKError` from an explicit code and description.
190+
///
191+
/// - Parameters:
192+
/// - errorCode: The error code.
193+
/// - errorDescription: The user-facing error description.
194+
/// - responseData: Optional raw response data associated with the error.
124195
public init(errorCode: Int = 0, errorDescription: String = "", responseData: Data? = nil) {
125196
self.errorCode = errorCode
126197
self.errorDescription = errorDescription
127-
self.error = NSError(domain: NSCocoaErrorDomain, code: self.errorCode, userInfo: [NSLocalizedDescriptionKey: self.errorDescription])
198+
self.error = NSError(
199+
domain: NSCocoaErrorDomain,
200+
code: self.errorCode,
201+
userInfo: [NSLocalizedDescriptionKey: self.errorDescription]
202+
)
128203
self.responseData = responseData
129204
}
130205

206+
/// Creates an `NKError` from a generic Swift `Error`.
207+
///
208+
/// - Parameters:
209+
/// - error: The source error.
210+
/// - responseData: Optional raw response data associated with the error.
131211
public init(error: Error, responseData: Data? = nil) {
132212
self.errorCode = error._code
133213
self.errorDescription = error.localizedDescription
134214
self.error = error
135215
self.responseData = responseData
136216
}
137217

218+
/// Creates an `NKError` from an `NSError`.
219+
///
220+
/// - Parameters:
221+
/// - nsError: The source `NSError`.
222+
/// - responseData: Optional raw response data associated with the error.
138223
public init(nsError: NSError, responseData: Data? = nil) {
139224
self.errorCode = nsError.code
140225
self.errorDescription = nsError.localizedDescription
141226
self.error = nsError
142227
self.responseData = responseData
143228
}
144229

230+
/// Creates an `NKError` from an OCS JSON response.
231+
///
232+
/// - Parameters:
233+
/// - rootJson: The parsed JSON response.
234+
/// - fallbackStatusCode: The fallback HTTP status code used when the OCS status code is missing.
235+
/// - responseData: Optional raw response data associated with the error.
145236
public init(rootJson: JSON, fallbackStatusCode: Int?, responseData: Data? = nil) {
146237
let statuscode = rootJson[.ocsMetaCode].int ?? fallbackStatusCode ?? NSURLErrorCannotDecodeContentData
147238
errorCode = 200..<300 ~= statuscode ? 0 : statuscode
@@ -151,23 +242,54 @@ public struct NKError: Error, Equatable, Sendable {
151242
} else if let metaMsg = rootJson[.ocsMetaMsg].string {
152243
errorDescription = metaMsg
153244
} else {
154-
errorDescription = NKError.getErrorDescription(for: statuscode) ?? ""
245+
errorDescription = NKError.getErrorDescription(for: statuscode) ?? NKError.httpFallbackDescription(for: statuscode)
155246
}
247+
156248
self.responseData = responseData
157-
self.error = NSError(domain: NSCocoaErrorDomain, code: self.errorCode, userInfo: [NSLocalizedDescriptionKey: self.errorDescription])
249+
self.error = NSError(
250+
domain: NSCocoaErrorDomain,
251+
code: self.errorCode,
252+
userInfo: [NSLocalizedDescriptionKey: self.errorDescription]
253+
)
158254
}
159255

256+
/// Creates an `NKError` from an HTTP status code.
257+
///
258+
/// - Parameters:
259+
/// - statusCode: The HTTP status code.
260+
/// - fallbackDescription: A clean fallback description used when the status code is unknown.
261+
/// - responseData: Optional raw response data associated with the error.
160262
public init(statusCode: Int, fallbackDescription: String, responseData: Data? = nil) {
161263
self.errorCode = statusCode
162-
self.errorDescription = "\(statusCode): " + (NKError.getErrorDescription(for: statusCode) ?? fallbackDescription)
163-
self.error = NSError(domain: NSCocoaErrorDomain, code: self.errorCode, userInfo: [NSLocalizedDescriptionKey: self.errorDescription])
264+
265+
let description = NKError.getErrorDescription(for: statusCode) ?? fallbackDescription
266+
self.errorDescription = "\(statusCode): \(description)"
267+
268+
self.error = NSError(
269+
domain: NSCocoaErrorDomain,
270+
code: self.errorCode,
271+
userInfo: [NSLocalizedDescriptionKey: self.errorDescription]
272+
)
273+
164274
self.responseData = responseData
165275
}
166276

277+
/// Creates an `NKError` from an HTTP response.
278+
///
279+
/// - Parameter httpResponse: The source HTTP response.
167280
init(httpResponse: HTTPURLResponse) {
168-
self.init(statusCode: httpResponse.statusCode, fallbackDescription: httpResponse.description)
281+
self.init(
282+
statusCode: httpResponse.statusCode,
283+
fallbackDescription: Self.httpFallbackDescription(for: httpResponse.statusCode)
284+
)
169285
}
170286

287+
/// Creates an `NKError` from an OCS or WebDAV XML response.
288+
///
289+
/// - Parameters:
290+
/// - xmlData: The raw XML response data.
291+
/// - fallbackStatusCode: The fallback HTTP status code used when the OCS status code is missing.
292+
/// - responseData: Optional raw response data associated with the error.
171293
init(xmlData: Data, fallbackStatusCode: Int? = nil, responseData: Data? = nil) {
172294
let xml = XML.parse(xmlData)
173295
let statuscode = xml[.ocsMetaCode].int ?? fallbackStatusCode ?? NSURLErrorCannotDecodeContentData
@@ -180,18 +302,33 @@ public struct NKError: Error, Equatable, Sendable {
180302
} else if let metaMsg = xml[.ocsXMLMsg].text {
181303
errorDescription = metaMsg
182304
} else {
183-
errorDescription = NKError.getErrorDescription(for: statuscode) ?? ""
305+
errorDescription = NKError.getErrorDescription(for: statuscode) ?? NKError.httpFallbackDescription(for: statuscode)
184306
}
307+
185308
self.responseData = responseData
186-
self.error = NSError(domain: NSCocoaErrorDomain, code: self.errorCode, userInfo: [NSLocalizedDescriptionKey: self.errorDescription])
309+
self.error = NSError(
310+
domain: NSCocoaErrorDomain,
311+
code: self.errorCode,
312+
userInfo: [NSLocalizedDescriptionKey: self.errorDescription]
313+
)
187314
}
188315

316+
/// Creates an `NKError` from an Alamofire response and optional Alamofire error.
317+
///
318+
/// - Parameters:
319+
/// - error: The Alamofire error, if available.
320+
/// - afResponse: The Alamofire response.
321+
/// - responseData: Optional raw response data associated with the error.
189322
public init<T: AFResponse>(error: AFError?, afResponse: T, responseData: Data? = nil) {
190323
if let errorCode = afResponse.response?.statusCode {
191324
guard let dataResponse = afResponse as? Alamofire.DataResponse<T.Success, T.Failure>,
192325
let errorData = dataResponse.data
193326
else {
194-
self.init(statusCode: errorCode, fallbackDescription: afResponse.response?.description ?? "", responseData: responseData)
327+
self.init(
328+
statusCode: errorCode,
329+
fallbackDescription: Self.httpFallbackDescription(for: errorCode),
330+
responseData: responseData
331+
)
195332
return
196333
}
197334

@@ -205,14 +342,19 @@ public struct NKError: Error, Equatable, Sendable {
205342
switch error {
206343
case .createUploadableFailed(let error as NSError):
207344
self.init(nsError: error, responseData: responseData)
345+
208346
case .createURLRequestFailed(let error as NSError):
209347
self.init(nsError: error, responseData: responseData)
348+
210349
case .requestAdaptationFailed(let error as NSError):
211350
self.init(nsError: error, responseData: responseData)
351+
212352
case .sessionInvalidated(let error as NSError):
213353
self.init(nsError: error, responseData: responseData)
354+
214355
case .sessionTaskFailed(let error as NSError):
215356
self.init(nsError: error, responseData: responseData)
357+
216358
default:
217359
self.init(error: error, responseData: responseData)
218360
}
@@ -227,8 +369,9 @@ public struct NKError: Error, Equatable, Sendable {
227369

228370
public static func == (lhs: NKError, rhs: NKError?) -> Bool {
229371
if let rhs {
230-
return lhs == rhs;
372+
return lhs == rhs
231373
}
374+
232375
return false
233376
}
234377
}

0 commit comments

Comments
 (0)