Skip to content

Commit 0551596

Browse files
authored
improve string interpolation of errors (#6482)
motivation: better error messages changes: * audit usage of "\(error)" and replace them with "\(error.interpolationDescription)" * add ADPI to pass an optional underlying error to observability's debug, info and warn calls such that it automatically interpolates the error correctly * update call sites rdar://108418554
1 parent 7a8f7f6 commit 0551596

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+389
-196
lines changed

Sources/Basics/AuthorizationProvider.swift

Lines changed: 58 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,13 @@ public protocol AuthorizationProvider {
2424
}
2525

2626
public protocol AuthorizationWriter {
27-
func addOrUpdate(for url: URL, user: String, password: String, persist: Bool, callback: @escaping (Result<Void, Error>) -> Void)
27+
func addOrUpdate(
28+
for url: URL,
29+
user: String,
30+
password: String,
31+
persist: Bool,
32+
callback: @escaping (Result<Void, Error>) -> Void
33+
)
2834

2935
func remove(for url: URL, callback: @escaping (Result<Void, Error>) -> Void)
3036
}
@@ -36,9 +42,9 @@ public enum AuthorizationProviderError: Error {
3642
case other(String)
3743
}
3844

39-
public extension AuthorizationProvider {
45+
extension AuthorizationProvider {
4046
@Sendable
41-
func httpAuthorizationHeader(for url: URL) -> String? {
47+
public func httpAuthorizationHeader(for url: URL) -> String? {
4248
guard let (user, password) = self.authentication(for: url) else {
4349
return nil
4450
}
@@ -50,8 +56,8 @@ public extension AuthorizationProvider {
5056
}
5157
}
5258

53-
private extension URL {
54-
var authenticationID: String? {
59+
extension URL {
60+
fileprivate var authenticationID: String? {
5561
guard let host = host?.lowercased() else {
5662
return nil
5763
}
@@ -75,7 +81,13 @@ public class NetrcAuthorizationProvider: AuthorizationProvider, AuthorizationWri
7581
_ = try Self.load(fileSystem: fileSystem, path: path)
7682
}
7783

78-
public func addOrUpdate(for url: URL, user: String, password: String, persist: Bool = true, callback: @escaping (Result<Void, Error>) -> Void) {
84+
public func addOrUpdate(
85+
for url: URL,
86+
user: String,
87+
password: String,
88+
persist: Bool = true,
89+
callback: @escaping (Result<Void, Error>) -> Void
90+
) {
7991
guard let machine = url.authenticationID else {
8092
return callback(.failure(AuthorizationProviderError.invalidURLHost))
8193
}
@@ -87,7 +99,9 @@ public class NetrcAuthorizationProvider: AuthorizationProvider, AuthorizationWri
8799

88100
// Same entry already exists, no need to add or update
89101
let netrc = try? Self.load(fileSystem: self.fileSystem, path: self.path)
90-
guard netrc?.machines.first(where: { $0.name.lowercased() == machine && $0.login == user && $0.password == password }) == nil else {
102+
guard netrc?.machines
103+
.first(where: { $0.name.lowercased() == machine && $0.login == user && $0.password == password }) == nil
104+
else {
91105
return callback(.success(()))
92106
}
93107

@@ -108,12 +122,18 @@ public class NetrcAuthorizationProvider: AuthorizationProvider, AuthorizationWri
108122

109123
callback(.success(()))
110124
} catch {
111-
callback(.failure(AuthorizationProviderError.other("Failed to update netrc file at \(self.path): \(error)")))
125+
callback(.failure(
126+
AuthorizationProviderError
127+
.other("Failed to update netrc file at \(self.path): \(error.interpolationDescription)")
128+
))
112129
}
113130
}
114131

115132
public func remove(for url: URL, callback: @escaping (Result<Void, Error>) -> Void) {
116-
callback(.failure(AuthorizationProviderError.other("User must edit netrc file at \(self.path) manually to remove entries")))
133+
callback(.failure(
134+
AuthorizationProviderError
135+
.other("User must edit netrc file at \(self.path) manually to remove entries")
136+
))
117137
}
118138

119139
public func authentication(for url: URL) -> (user: String, password: String)? {
@@ -124,7 +144,9 @@ public class NetrcAuthorizationProvider: AuthorizationProvider, AuthorizationWri
124144
}
125145

126146
private func machine(for url: URL) -> Basics.Netrc.Machine? {
127-
if let machine = url.authenticationID, let existing = self.machines.first(where: { $0.name.lowercased() == machine }) {
147+
if let machine = url.authenticationID,
148+
let existing = self.machines.first(where: { $0.name.lowercased() == machine })
149+
{
128150
return existing
129151
}
130152
if let existing = self.machines.first(where: { $0.isDefault }) {
@@ -164,7 +186,13 @@ public class KeychainAuthorizationProvider: AuthorizationProvider, Authorization
164186
self.observabilityScope = observabilityScope
165187
}
166188

167-
public func addOrUpdate(for url: URL, user: String, password: String, persist: Bool = true, callback: @escaping (Result<Void, Error>) -> Void) {
189+
public func addOrUpdate(
190+
for url: URL,
191+
user: String,
192+
password: String,
193+
persist: Bool = true,
194+
callback: @escaping (Result<Void, Error>) -> Void
195+
) {
168196
guard let server = url.authenticationID else {
169197
return callback(.failure(AuthorizationProviderError.invalidURLHost))
170198
}
@@ -214,12 +242,14 @@ public class KeychainAuthorizationProvider: AuthorizationProvider, Authorization
214242
}
215243

216244
do {
217-
guard let existingItem = try self.search(server: server, protocol: self.protocol(for: url)) as? [String: Any],
218-
let passwordData = existingItem[kSecValueData as String] as? Data,
219-
let password = String(data: passwordData, encoding: String.Encoding.utf8),
220-
let account = existingItem[kSecAttrAccount as String] as? String
245+
guard let existingItem = try self
246+
.search(server: server, protocol: self.protocol(for: url)) as? [String: Any],
247+
let passwordData = existingItem[kSecValueData as String] as? Data,
248+
let password = String(data: passwordData, encoding: String.Encoding.utf8),
249+
let account = existingItem[kSecAttrAccount as String] as? String
221250
else {
222-
throw AuthorizationProviderError.other("Failed to extract credentials for server \(server) from keychain")
251+
throw AuthorizationProviderError
252+
.other("Failed to extract credentials for server \(server) from keychain")
223253
}
224254
return (user: account, password: password)
225255
} catch {
@@ -229,7 +259,10 @@ public class KeychainAuthorizationProvider: AuthorizationProvider, Authorization
229259
case AuthorizationProviderError.other(let detail):
230260
self.observabilityScope.emit(error: detail)
231261
default:
232-
self.observabilityScope.emit(error: "Failed to find credentials for server \(server) in keychain: \(error)")
262+
self.observabilityScope.emit(
263+
error: "Failed to find credentials for server \(server) in keychain",
264+
underlyingError: error
265+
)
233266
}
234267
return nil
235268
}
@@ -244,7 +277,8 @@ public class KeychainAuthorizationProvider: AuthorizationProvider, Authorization
244277

245278
let status = SecItemAdd(query as CFDictionary, nil)
246279
guard status == errSecSuccess else {
247-
throw AuthorizationProviderError.other("Failed to save credentials for server \(server) to keychain: status \(status)")
280+
throw AuthorizationProviderError
281+
.other("Failed to save credentials for server \(server) to keychain: status \(status)")
248282
}
249283
}
250284

@@ -260,7 +294,8 @@ public class KeychainAuthorizationProvider: AuthorizationProvider, Authorization
260294
return false
261295
}
262296
guard status == errSecSuccess else {
263-
throw AuthorizationProviderError.other("Failed to update credentials for server \(server) in keychain: status \(status)")
297+
throw AuthorizationProviderError
298+
.other("Failed to update credentials for server \(server) in keychain: status \(status)")
264299
}
265300
return true
266301
}
@@ -271,7 +306,8 @@ public class KeychainAuthorizationProvider: AuthorizationProvider, Authorization
271306
kSecAttrProtocol as String: `protocol`]
272307
let status = SecItemDelete(query as CFDictionary)
273308
guard status == errSecSuccess else {
274-
throw AuthorizationProviderError.other("Failed to delete credentials for server \(server) from keychain: status \(status)")
309+
throw AuthorizationProviderError
310+
.other("Failed to delete credentials for server \(server) from keychain: status \(status)")
275311
}
276312
}
277313

@@ -290,7 +326,8 @@ public class KeychainAuthorizationProvider: AuthorizationProvider, Authorization
290326
throw AuthorizationProviderError.notFound
291327
}
292328
guard status == errSecSuccess else {
293-
throw AuthorizationProviderError.other("Failed to find credentials for server \(server) in keychain: status \(status)")
329+
throw AuthorizationProviderError
330+
.other("Failed to find credentials for server \(server) in keychain: status \(status)")
294331
}
295332

296333
return item

Sources/Basics/Cancellator.swift

Lines changed: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,11 @@ public final class Cancellator: Cancellable {
2525

2626
private let observabilityScope: ObservabilityScope?
2727
private let registry = ThreadSafeKeyValueStore<String, (name: String, handler: CancellationHandler)>()
28-
private let cancelationQueue = DispatchQueue(label: "org.swift.swiftpm.cancellator", qos: .userInteractive, attributes: .concurrent)
28+
private let cancelationQueue = DispatchQueue(
29+
label: "org.swift.swiftpm.cancellator",
30+
qos: .userInteractive,
31+
attributes: .concurrent
32+
)
2933
private let cancelling = ThreadSafeBox<Bool>(false)
3034

3135
private static let signalHandlerLock = NSLock()
@@ -44,60 +48,60 @@ public final class Cancellator: Cancellable {
4448
public func installSignalHandlers() {
4549
Self.signalHandlerLock.withLock {
4650
precondition(!Self.isSignalHandlerInstalled)
47-
48-
#if os(Windows)
51+
52+
#if os(Windows)
4953
// Closures passed to `SetConsoleCtrlHandler` can't capture context, working around that with a global.
5054
Self.shared = self
51-
55+
5256
// set shutdown handler to terminate sub-processes, etc
5357
_ = SetConsoleCtrlHandler({ _ in
5458
// Terminate all processes on receiving an interrupt signal.
5559
try? Cancellator.shared?.cancel(deadline: .now() + .seconds(30))
56-
60+
5761
// Reset the handler.
5862
_ = SetConsoleCtrlHandler(nil, false)
59-
63+
6064
// Exit as if by signal()
6165
TerminateProcess(GetCurrentProcess(), 3)
62-
66+
6367
return true
6468
}, true)
65-
#else
69+
#else
6670
// trap SIGINT to terminate sub-processes, etc
6771
signal(SIGINT, SIG_IGN)
6872
let interruptSignalSource = DispatchSource.makeSignalSource(signal: SIGINT)
6973
interruptSignalSource.setEventHandler { [weak self] in
7074
// cancel the trap?
7175
interruptSignalSource.cancel()
72-
76+
7377
// Terminate all processes on receiving an interrupt signal.
7478
try? self?.cancel(deadline: .now() + .seconds(30))
75-
76-
#if canImport(Darwin) || os(OpenBSD)
79+
80+
#if canImport(Darwin) || os(OpenBSD)
7781
// Install the default signal handler.
7882
var action = sigaction()
7983
action.__sigaction_u.__sa_handler = SIG_DFL
8084
sigaction(SIGINT, &action, nil)
8185
kill(getpid(), SIGINT)
82-
#elseif os(Android)
86+
#elseif os(Android)
8387
// Install the default signal handler.
8488
var action = sigaction()
8589
action.sa_handler = SIG_DFL
8690
sigaction(SIGINT, &action, nil)
8791
kill(getpid(), SIGINT)
88-
#else
92+
#else
8993
var action = sigaction()
9094
action.__sigaction_handler = unsafeBitCast(
9195
SIG_DFL,
9296
to: sigaction.__Unnamed_union___sigaction_handler.self
9397
)
9498
sigaction(SIGINT, &action, nil)
9599
kill(getpid(), SIGINT)
96-
#endif
100+
#endif
97101
}
98102
interruptSignalSource.resume()
99-
#endif
100-
103+
#endif
104+
101105
Self.isSignalHandlerInstalled = true
102106
}
103107
}
@@ -138,20 +142,21 @@ public final class Cancellator: Cancellable {
138142
self.registry[key] = nil
139143
}
140144

141-
public func cancel(deadline: DispatchTime) throws -> Void {
145+
public func cancel(deadline: DispatchTime) throws {
142146
self._cancel(deadline: deadline)
143147
}
144148

145149
// marked internal for testing
146150
@discardableResult
147-
internal func _cancel(deadline: DispatchTime? = .none)-> Int {
151+
internal func _cancel(deadline: DispatchTime? = .none) -> Int {
148152
self.cancelling.put(true)
149153

150-
self.observabilityScope?.emit(info: "starting cancellation cycle with \(self.registry.count) cancellation handlers registered")
154+
self.observabilityScope?
155+
.emit(info: "starting cancellation cycle with \(self.registry.count) cancellation handlers registered")
151156

152157
let deadline = deadline ?? .now() + .seconds(30)
153158
// deadline for individual handlers set slightly before overall deadline
154-
let delta: DispatchTimeInterval = .nanoseconds(abs(deadline.distance(to: .now()).nanoseconds() ?? 0) / 5)
159+
let delta: DispatchTimeInterval = .nanoseconds(abs(deadline.distance(to: .now()).nanoseconds() ?? 0) / 5)
155160
let handlersDeadline = deadline - delta
156161

157162
let cancellationHandlers = self.registry.get()
@@ -164,13 +169,19 @@ public final class Cancellator: Cancellable {
164169
try handler(handlersDeadline)
165170
cancelled.append(name)
166171
} catch {
167-
self.observabilityScope?.emit(warning: "failed cancelling '\(name)': \(error)")
172+
self.observabilityScope?.emit(
173+
warning: "failed cancelling '\(name)'",
174+
underlyingError: error
175+
)
168176
}
169177
}
170178
}
171179

172180
if case .timedOut = group.wait(timeout: deadline) {
173-
self.observabilityScope?.emit(warning: "timeout waiting for cancellation with \(cancellationHandlers.count - cancelled.count) cancellation handlers remaining")
181+
self.observabilityScope?
182+
.emit(
183+
warning: "timeout waiting for cancellation with \(cancellationHandlers.count - cancelled.count) cancellation handlers remaining"
184+
)
174185
} else {
175186
self.observabilityScope?.emit(info: "cancellation cycle completed successfully")
176187
}
@@ -197,7 +208,8 @@ public struct CancellationError: Error, CustomStringConvertible {
197208
}
198209

199210
static func failedToRegisterProcess(_ process: TSCBasic.Process) -> Self {
200-
Self(description: """
211+
Self(
212+
description: """
201213
failed to register a cancellation handler for this process invocation `\(
202214
process.arguments.joined(separator: " ")
203215
)`

Sources/Basics/Errors.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,11 @@ public struct InternalError: Error {
3030
extension Error {
3131
public var interpolationDescription: String {
3232
switch self {
33+
// special case because `LocalizedError` conversion will hide the underlying error
34+
case let _error as DecodingError:
35+
return "\(_error)"
3336
case let _error as LocalizedError:
34-
var description = _error.localizedDescription
37+
var description = _error.errorDescription ?? _error.localizedDescription
3538
if let recoverySuggestion = _error.recoverySuggestion {
3639
description += ". \(recoverySuggestion)"
3740
}
@@ -45,7 +48,6 @@ extension Error {
4548
description += ". \(localizedRecoverySuggestion)"
4649
}
4750
return description
48-
4951
default:
5052
return "\(self)"
5153
}

0 commit comments

Comments
 (0)