gitbay/Networking/GitbayError.swift
65 lines · 2700 bytes
1import Foundation
2
3/// A failure from either API surface, already mapped off the exit code so
4/// callers switch on meaning rather than on numbers.
5///
6/// exit http case
7/// 2 400 usage a bug in the app
8/// 3 404 notFound an empty state, not an error banner
9/// 4 403 denied the message explains the rule; show it verbatim
10/// 1, 5 500 failure retried once by the client, then surfaced
11/// — 401 unauthorized the token is invalid, expired or revoked
12/// — 429 rateLimited Retry-After was longer than the client will wait
13nonisolated enum GitbayError: LocalizedError, Sendable {
14 case usage(String)
15 case notFound(String)
16 case denied(String)
17 case failure(String)
18 case unauthorized(String)
19 case rateLimited(retryAfter: TimeInterval)
20 /// The command exists but is not read-only, so it cannot be reached
21 /// with GET. Also an app bug: it means we routed a write through `read`.
22 case notReadable(String)
23 case transport(any Error)
24 case decoding(any Error)
25 /// A major protocol version the app does not understand.
26 case protocolMismatch(Int)
27 /// The client refused to send the token somewhere other than the instance.
28 case unexpectedHost(URL)
29
30 /// Text for a person. `denied` and `notFound` come from the server
31 /// verbatim — those messages exist to explain a rule, and rewording
32 /// them loses the explanation.
33 var userFacingMessage: String {
34 switch self {
35 case .denied(let message), .notFound(let message):
36 message
37 case .unauthorized:
38 "Your token is no longer valid. Sign in again."
39 case .rateLimited(let seconds):
40 "Too many requests. Try again in \(Int(seconds.rounded()))s."
41 case .failure(let message):
42 message.isEmpty ? "The server could not complete that." : message
43 case .transport:
44 "Check your connection and try again."
45 // A usage or protocol failure is ours, not the user's. argv never
46 // reaches the screen.
47 case .usage, .notReadable, .decoding, .protocolMismatch, .unexpectedHost:
48 "Something went wrong. Please try again."
49 }
50 }
51
52 var errorDescription: String? { userFacingMessage }
53
54 /// True when the right response is an empty screen rather than a banner.
55 var isEmptyState: Bool {
56 if case .notFound = self { return true }
57 return false
58 }
59
60 /// True when the token needs replacing and the app should sign out.
61 var requiresReauthentication: Bool {
62 if case .unauthorized = self { return true }
63 return false
64 }
65}