/src/grpc-swift/Sources/GRPC/GRPCStatus.swift
Line | Count | Source |
1 | | /* |
2 | | * Copyright 2019, gRPC Authors All rights reserved. |
3 | | * |
4 | | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | | * you may not use this file except in compliance with the License. |
6 | | * You may obtain a copy of the License at |
7 | | * |
8 | | * http://www.apache.org/licenses/LICENSE-2.0 |
9 | | * |
10 | | * Unless required by applicable law or agreed to in writing, software |
11 | | * distributed under the License is distributed on an "AS IS" BASIS, |
12 | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 | | * See the License for the specific language governing permissions and |
14 | | * limitations under the License. |
15 | | */ |
16 | | import NIOCore |
17 | | import NIOHTTP1 |
18 | | import NIOHTTP2 |
19 | | |
20 | | /// Encapsulates the result of a gRPC call. |
21 | | public struct GRPCStatus: Error, Sendable { |
22 | | /// Storage for message/cause. In the happy case ('ok') there will not be a message or cause |
23 | | /// and this will reference a static storage containing nil values. Making it optional makes the |
24 | | /// setters for message and cause a little messy. |
25 | | private var storage: Storage |
26 | | |
27 | | /// The status code of the RPC. |
28 | | public var code: Code |
29 | | |
30 | | /// The status message of the RPC. |
31 | | public var message: String? { |
32 | 3.16M | get { |
33 | 3.16M | return self.storage.message |
34 | 3.16M | } |
35 | 0 | set { |
36 | 0 | if isKnownUniquelyReferenced(&self.storage) { |
37 | 0 | self.storage.message = newValue |
38 | 0 | } else { |
39 | 0 | self.storage = .makeStorage(message: newValue, cause: self.storage.cause) |
40 | 0 | } |
41 | 0 | } |
42 | | } |
43 | | |
44 | | /// The cause of an error (not 'ok') status. This value is never transmitted over the wire and is |
45 | | /// **not** included in equality checks. |
46 | | public var cause: Error? { |
47 | 0 | get { |
48 | 0 | return self.storage.cause |
49 | 0 | } |
50 | 0 | set { |
51 | 0 | if isKnownUniquelyReferenced(&self.storage) { |
52 | 0 | self.storage.cause = newValue |
53 | 0 | } else { |
54 | 0 | self.storage = .makeStorage(message: self.storage.message, cause: newValue) |
55 | 0 | } |
56 | 0 | } |
57 | | } |
58 | | |
59 | | // Backing storage for 'message' and 'cause'. |
60 | | fileprivate final class Storage { |
61 | | // On many happy paths there will be no message or cause, so we'll use this shared reference |
62 | | // instead of allocating a new storage each time. |
63 | | // |
64 | | // Alternatively: `GRPCStatus` could hold a storage optionally however doing so made the code |
65 | | // quite unreadable. |
66 | | private static let none = Storage(message: nil, cause: nil) |
67 | | |
68 | 8.95M | private init(message: String?, cause: Error?) { |
69 | 8.95M | self.message = message |
70 | 8.95M | self.cause = cause |
71 | 8.95M | } |
72 | | |
73 | | fileprivate var message: Optional<String> |
74 | | fileprivate var cause: Optional<Error> |
75 | | |
76 | 27.3M | fileprivate static func makeStorage(message: String?, cause: Error?) -> Storage { |
77 | 27.3M | if message == nil, cause == nil { |
78 | 442 | return Storage.none |
79 | 27.3M | } else { |
80 | 27.3M | return Storage(message: message, cause: cause) |
81 | 27.3M | } |
82 | 27.3M | } |
83 | | } |
84 | | |
85 | | /// Whether the status is '.ok'. |
86 | 0 | public var isOk: Bool { |
87 | 0 | return self.code == .ok |
88 | 0 | } |
89 | | |
90 | 3.63M | public init(code: Code, message: String?) { |
91 | 3.63M | self.init(code: code, message: message, cause: nil) |
92 | 3.63M | } |
93 | | |
94 | 27.3M | public init(code: Code, message: String? = nil, cause: Error? = nil) { |
95 | 27.3M | self.code = code |
96 | 27.3M | self.storage = .makeStorage(message: message, cause: cause) |
97 | 27.3M | } |
98 | | |
99 | | // Frequently used "default" statuses. |
100 | | |
101 | | /// The default status to return for succeeded calls. |
102 | | /// |
103 | | /// - Important: This should *not* be used when checking whether a returned status has an 'ok' |
104 | | /// status code. Use `GRPCStatus.isOk` or check the code directly. |
105 | | public static let ok = GRPCStatus(code: .ok, message: nil) |
106 | | /// "Internal server error" status. |
107 | | public static let processingError = Self.processingError(cause: nil) |
108 | | |
109 | 152k | public static func processingError(cause: Error?) -> GRPCStatus { |
110 | 152k | return GRPCStatus( |
111 | 152k | code: .internalError, |
112 | 152k | message: "unknown error processing request", |
113 | 152k | cause: cause |
114 | 152k | ) |
115 | 152k | } |
116 | | } |
117 | | |
118 | | extension GRPCStatus: Equatable { |
119 | 1.86M | public static func == (lhs: GRPCStatus, rhs: GRPCStatus) -> Bool { |
120 | 1.86M | return lhs.code == rhs.code && lhs.message == rhs.message |
121 | 1.86M | } |
122 | | } |
123 | | |
124 | | extension GRPCStatus: CustomStringConvertible { |
125 | 0 | public var description: String { |
126 | 0 | switch (self.message, self.cause) { |
127 | 0 | case let (.some(message), .some(cause)): |
128 | 0 | return "\(self.code): \(message), cause: \(cause)" |
129 | 0 | case let (.some(message), .none): |
130 | 0 | return "\(self.code): \(message)" |
131 | 0 | case let (.none, .some(cause)): |
132 | 0 | return "\(self.code), cause: \(cause)" |
133 | 0 | case (.none, .none): |
134 | 0 | return "\(self.code)" |
135 | 0 | } |
136 | 0 | } |
137 | | } |
138 | | |
139 | | extension GRPCStatus { |
140 | 0 | internal var testingOnly_storageObjectIdentifier: ObjectIdentifier { |
141 | 0 | return ObjectIdentifier(self.storage) |
142 | 0 | } |
143 | | } |
144 | | |
145 | | extension GRPCStatus { |
146 | | /// Status codes for gRPC operations (replicated from `status_code_enum.h` in the |
147 | | /// [gRPC core library](https://github.com/grpc/grpc)). |
148 | | public struct Code: Hashable, CustomStringConvertible, Sendable { |
149 | | // `rawValue` must be an `Int` for API reasons and we don't need (or want) to store anything so |
150 | | // wide, a `UInt8` is fine. |
151 | | private let _rawValue: UInt8 |
152 | | |
153 | 281k | public var rawValue: Int { |
154 | 281k | return Int(self._rawValue) |
155 | 281k | } |
156 | | |
157 | 0 | public init?(rawValue: Int) { |
158 | 0 | switch rawValue { |
159 | 0 | case 0 ... 16: |
160 | 0 | self._rawValue = UInt8(truncatingIfNeeded: rawValue) |
161 | 0 | default: |
162 | 0 | return nil |
163 | 0 | } |
164 | 0 | } |
165 | | |
166 | 10 | private init(_ code: UInt8) { |
167 | 10 | self._rawValue = code |
168 | 10 | } |
169 | | |
170 | | /// Not an error; returned on success. |
171 | | public static let ok = Code(0) |
172 | | |
173 | | /// The operation was cancelled (typically by the caller). |
174 | | public static let cancelled = Code(1) |
175 | | |
176 | | /// Unknown error. An example of where this error may be returned is if a |
177 | | /// Status value received from another address space belongs to an error-space |
178 | | /// that is not known in this address space. Also errors raised by APIs that |
179 | | /// do not return enough error information may be converted to this error. |
180 | | public static let unknown = Code(2) |
181 | | |
182 | | /// Client specified an invalid argument. Note that this differs from |
183 | | /// FAILED_PRECONDITION. INVALID_ARGUMENT indicates arguments that are |
184 | | /// problematic regardless of the state of the system (e.g., a malformed file |
185 | | /// name). |
186 | | public static let invalidArgument = Code(3) |
187 | | |
188 | | /// Deadline expired before operation could complete. For operations that |
189 | | /// change the state of the system, this error may be returned even if the |
190 | | /// operation has completed successfully. For example, a successful response |
191 | | /// from a server could have been delayed long enough for the deadline to |
192 | | /// expire. |
193 | | public static let deadlineExceeded = Code(4) |
194 | | |
195 | | /// Some requested entity (e.g., file or directory) was not found. |
196 | | public static let notFound = Code(5) |
197 | | |
198 | | /// Some entity that we attempted to create (e.g., file or directory) already |
199 | | /// exists. |
200 | | public static let alreadyExists = Code(6) |
201 | | |
202 | | /// The caller does not have permission to execute the specified operation. |
203 | | /// PERMISSION_DENIED must not be used for rejections caused by exhausting |
204 | | /// some resource (use RESOURCE_EXHAUSTED instead for those errors). |
205 | | /// PERMISSION_DENIED must not be used if the caller can not be identified |
206 | | /// (use UNAUTHENTICATED instead for those errors). |
207 | | public static let permissionDenied = Code(7) |
208 | | |
209 | | /// Some resource has been exhausted, perhaps a per-user quota, or perhaps the |
210 | | /// entire file system is out of space. |
211 | | public static let resourceExhausted = Code(8) |
212 | | |
213 | | /// Operation was rejected because the system is not in a state required for |
214 | | /// the operation's execution. For example, directory to be deleted may be |
215 | | /// non-empty, an rmdir operation is applied to a non-directory, etc. |
216 | | /// |
217 | | /// A litmus test that may help a service implementor in deciding |
218 | | /// between FAILED_PRECONDITION, ABORTED, and UNAVAILABLE: |
219 | | /// (a) Use UNAVAILABLE if the client can retry just the failing call. |
220 | | /// (b) Use ABORTED if the client should retry at a higher-level |
221 | | /// (e.g., restarting a read-modify-write sequence). |
222 | | /// (c) Use FAILED_PRECONDITION if the client should not retry until |
223 | | /// the system state has been explicitly fixed. E.g., if an "rmdir" |
224 | | /// fails because the directory is non-empty, FAILED_PRECONDITION |
225 | | /// should be returned since the client should not retry unless |
226 | | /// they have first fixed up the directory by deleting files from it. |
227 | | /// (d) Use FAILED_PRECONDITION if the client performs conditional |
228 | | /// REST Get/Update/Delete on a resource and the resource on the |
229 | | /// server does not match the condition. E.g., conflicting |
230 | | /// read-modify-write on the same resource. |
231 | | public static let failedPrecondition = Code(9) |
232 | | |
233 | | /// The operation was aborted, typically due to a concurrency issue like |
234 | | /// sequencer check failures, transaction aborts, etc. |
235 | | /// |
236 | | /// See litmus test above for deciding between FAILED_PRECONDITION, ABORTED, |
237 | | /// and UNAVAILABLE. |
238 | | public static let aborted = Code(10) |
239 | | |
240 | | /// Operation was attempted past the valid range. E.g., seeking or reading |
241 | | /// past end of file. |
242 | | /// |
243 | | /// Unlike INVALID_ARGUMENT, this error indicates a problem that may be fixed |
244 | | /// if the system state changes. For example, a 32-bit file system will |
245 | | /// generate INVALID_ARGUMENT if asked to read at an offset that is not in the |
246 | | /// range [0,2^32-1], but it will generate OUT_OF_RANGE if asked to read from |
247 | | /// an offset past the current file size. |
248 | | /// |
249 | | /// There is a fair bit of overlap between FAILED_PRECONDITION and |
250 | | /// OUT_OF_RANGE. We recommend using OUT_OF_RANGE (the more specific error) |
251 | | /// when it applies so that callers who are iterating through a space can |
252 | | /// easily look for an OUT_OF_RANGE error to detect when they are done. |
253 | | public static let outOfRange = Code(11) |
254 | | |
255 | | /// Operation is not implemented or not supported/enabled in this service. |
256 | | public static let unimplemented = Code(12) |
257 | | |
258 | | /// Internal errors. Means some invariants expected by underlying System has |
259 | | /// been broken. If you see one of these errors, Something is very broken. |
260 | | public static let internalError = Code(13) |
261 | | |
262 | | /// The service is currently unavailable. This is a most likely a transient |
263 | | /// condition and may be corrected by retrying with a backoff. |
264 | | /// |
265 | | /// See litmus test above for deciding between FAILED_PRECONDITION, ABORTED, |
266 | | /// and UNAVAILABLE. |
267 | | public static let unavailable = Code(14) |
268 | | |
269 | | /// Unrecoverable data loss or corruption. |
270 | | public static let dataLoss = Code(15) |
271 | | |
272 | | /// The request does not have valid authentication credentials for the |
273 | | /// operation. |
274 | | public static let unauthenticated = Code(16) |
275 | | |
276 | 0 | public var description: String { |
277 | 0 | switch self { |
278 | 0 | case .ok: |
279 | 0 | return "ok (\(self._rawValue))" |
280 | 0 | case .cancelled: |
281 | 0 | return "cancelled (\(self._rawValue))" |
282 | 0 | case .unknown: |
283 | 0 | return "unknown (\(self._rawValue))" |
284 | 0 | case .invalidArgument: |
285 | 0 | return "invalid argument (\(self._rawValue))" |
286 | 0 | case .deadlineExceeded: |
287 | 0 | return "deadline exceeded (\(self._rawValue))" |
288 | 0 | case .notFound: |
289 | 0 | return "not found (\(self._rawValue))" |
290 | 0 | case .alreadyExists: |
291 | 0 | return "already exists (\(self._rawValue))" |
292 | 0 | case .permissionDenied: |
293 | 0 | return "permission denied (\(self._rawValue))" |
294 | 0 | case .resourceExhausted: |
295 | 0 | return "resource exhausted (\(self._rawValue))" |
296 | 0 | case .failedPrecondition: |
297 | 0 | return "failed precondition (\(self._rawValue))" |
298 | 0 | case .aborted: |
299 | 0 | return "aborted (\(self._rawValue))" |
300 | 0 | case .outOfRange: |
301 | 0 | return "out of range (\(self._rawValue))" |
302 | 0 | case .unimplemented: |
303 | 0 | return "unimplemented (\(self._rawValue))" |
304 | 0 | case .internalError: |
305 | 0 | return "internal error (\(self._rawValue))" |
306 | 0 | case .unavailable: |
307 | 0 | return "unavailable (\(self._rawValue))" |
308 | 0 | case .dataLoss: |
309 | 0 | return "data loss (\(self._rawValue))" |
310 | 0 | case .unauthenticated: |
311 | 0 | return "unauthenticated (\(self._rawValue))" |
312 | 0 | default: |
313 | 0 | return String(describing: self._rawValue) |
314 | 0 | } |
315 | 0 | } |
316 | | } |
317 | | } |
318 | | |
319 | | // `GRPCStatus` has CoW semantics so it is inherently `Sendable`. Rather than marking `GRPCStatus` |
320 | | // as `@unchecked Sendable` we only mark `Storage` as such. |
321 | | extension GRPCStatus.Storage: @unchecked Sendable {} |
322 | | |
323 | | /// This protocol serves as a customisation point for error types so that gRPC calls may be |
324 | | /// terminated with an appropriate status. |
325 | | public protocol GRPCStatusTransformable: Error { |
326 | | /// Make a `GRPCStatus` from the underlying error. |
327 | | /// |
328 | | /// - Returns: A `GRPCStatus` representing the underlying error. |
329 | | func makeGRPCStatus() -> GRPCStatus |
330 | | } |
331 | | |
332 | | extension GRPCStatus: GRPCStatusTransformable { |
333 | 0 | public func makeGRPCStatus() -> GRPCStatus { |
334 | 0 | return self |
335 | 0 | } |
336 | | } |
337 | | |
338 | | extension NIOHTTP2Errors.StreamClosed: GRPCStatusTransformable { |
339 | 0 | public func makeGRPCStatus() -> GRPCStatus { |
340 | 0 | return .init(code: .unavailable, message: self.localizedDescription, cause: self) |
341 | 0 | } |
342 | | } |
343 | | |
344 | | extension NIOHTTP2Errors.IOOnClosedConnection: GRPCStatusTransformable { |
345 | 0 | public func makeGRPCStatus() -> GRPCStatus { |
346 | 0 | return .init(code: .unavailable, message: "The connection is closed", cause: self) |
347 | 0 | } |
348 | | } |
349 | | |
350 | | extension ChannelError: GRPCStatusTransformable { |
351 | 0 | public func makeGRPCStatus() -> GRPCStatus { |
352 | 0 | switch self { |
353 | 0 | case .inputClosed, .outputClosed, .ioOnClosedChannel: |
354 | 0 | return .init(code: .unavailable, message: "The connection is closed", cause: self) |
355 | 0 |
|
356 | 0 | default: |
357 | 0 | var processingError = GRPCStatus.processingError |
358 | 0 | processingError.cause = self |
359 | 0 | return processingError |
360 | 0 | } |
361 | 0 | } |
362 | | } |