thin_status/status_code.rs
1// Copyright 2026 <https://github.com/ppetr/>
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#[cfg(feature = "use_libc")]
16use libc;
17use std::num::NonZeroI32;
18use strum;
19
20/// Derived from <https://github.com/abseil/abseil-cpp/blob/master/absl/status/status.h>. See the
21/// link for more information.
22///
23/// (Copyright 2019 The Abseil Authors.)
24///
25/// Unlike `absl::StatusCode`, this enum only allows representing non-OK values.
26#[derive(
27 Clone,
28 Copy,
29 Debug,
30 Eq,
31 Hash,
32 Ord,
33 PartialEq,
34 PartialOrd,
35 strum::Display,
36 strum::EnumString,
37 strum::EnumIter,
38 strum::FromRepr,
39 strum::IntoStaticStr,
40 strum::VariantArray,
41)]
42#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
43#[repr(i32)]
44#[non_exhaustive]
45pub enum ErrorCode {
46 /// `Cancelled` (gRPC code "CANCELLED") indicates the operation was cancelled, typically by the
47 /// caller.
48 Cancelled = 1,
49
50 /// `Unknown` (gRPC code "UNKNOWN") indicates an unknown error occurred. In general, more
51 /// specific errors should be raised, if possible. Errors raised by APIs that do not return
52 /// enough error information may be converted to this error.
53 Unknown = 2,
54
55 /// `InvalidArgument` (gRPC code "INVALID_ARGUMENT") indicates the caller specified an invalid
56 /// argument, such as a malformed filename. Note that use of such errors should be narrowly
57 /// limited to indicate the invalid nature of the arguments themselves. Errors with validly
58 /// formed arguments that may cause errors with the state of the receiving system should be
59 /// denoted with `FailedPrecondition` instead.
60 InvalidArgument = 3,
61
62 /// `DeadlineExceeded` (gRPC code "DEADLINE_EXCEEDED") indicates a deadline expired before the
63 /// operation could complete. For operations that may change state within a system, this error
64 /// may be returned even if the operation has completed successfully. For example, a successful
65 /// response from a server could have been delayed long enough for the deadline to expire.
66 DeadlineExceeded = 4,
67
68 /// `NotFound` (gRPC code "NOT_FOUND") indicates some requested entity (such as a file or
69 /// directory) was not found.
70 ///
71 /// `NotFound` is useful if a request should be denied for an entire class of users, such as
72 /// during a gradual feature rollout or undocumented allow list. If a request should be denied
73 /// for specific sets of users, such as through user-based access control, use
74 /// `PermissionDenied` instead.
75 NotFound = 5,
76
77 /// `AlreadyExists` (gRPC code "ALREADY_EXISTS") indicates that the entity a caller attempted to
78 /// create (such as a file or directory) is already present.
79 AlreadyExists = 6,
80
81 /// `PermissionDenied` (gRPC code "PERMISSION_DENIED") indicates that the caller does not have
82 /// permission to execute the specified operation. Note that this error is different than an
83 /// error due to an *un*authenticated user. This error code does not imply the request is valid
84 /// or the requested entity exists or satisfies any other pre-conditions.
85 ///
86 /// `PermissionDenied` must not be used for rejections caused by exhausting some resource.
87 /// Instead, use `ResourceExhausted` for those errors. `PermissionDenied` must not be used if
88 /// the caller cannot be identified. Instead, use `Unauthenticated` for those errors.
89 PermissionDenied = 7,
90
91 /// `ResourceExhausted` (gRPC code "RESOURCE_EXHAUSTED") indicates some resource has been
92 /// exhausted, perhaps a per-user quota, or perhaps the entire file system is out of space.
93 ResourceExhausted = 8,
94
95 /// `FailedPrecondition` (gRPC code "FAILED_PRECONDITION") indicates that the operation was
96 /// rejected because the system is not in a state required for the operation's execution. For
97 /// example, a directory to be deleted may be non-empty, an "rmdir" operation is applied to a
98 /// non-directory, etc.
99 ///
100 /// Some guidelines that may help a service implementer in deciding between
101 /// `FailedPrecondition`, `Aborted`, and `Unavailable`:
102 ///
103 /// (a) Use `Unavailable` if the client can retry just the failing call.
104 /// (b) Use `Aborted` if the client should retry at a higher transaction level (such as when a
105 /// client-specified test-and-set fails, indicating the client should restart a
106 /// read-modify-write sequence).
107 /// (c) Use `FailedPrecondition` if the client should not retry until the system state has
108 /// been explicitly fixed. For example, if a "rmdir" fails because the directory is
109 /// non-empty, `FailedPrecondition` should be returned since the client should not retry
110 /// unless the files are deleted from the directory.
111 FailedPrecondition = 9,
112
113 /// `Aborted` (gRPC code "ABORTED") indicates the operation was aborted, typically due to a
114 /// concurrency issue such as a sequencer check failure or a failed transaction.
115 ///
116 /// See the guidelines above for deciding between `FailedPrecondition`, `Aborted`, and
117 /// `Unavailable`.
118 Aborted = 10,
119
120 /// `OutOfRange` (gRPC code "OUT_OF_RANGE") indicates the operation was attempted past the valid
121 /// range, such as seeking or reading past an end-of-file.
122 ///
123 /// Unlike `InvalidArgument`, this error indicates a problem that may be fixed if the system
124 /// state changes. For example, a 32-bit file system will generate `InvalidArgument` if asked
125 /// to read at an offset that is not in the range [0,2^32-1], but it will generate `OutOfRange`
126 /// if asked to read from an offset past the current file size.
127 ///
128 /// There is a fair bit of overlap between `FailedPrecondition` and `OutOfRange`. We recommend
129 /// using `OutOfRange` (the more specific error) when it applies so that callers who are
130 /// iterating through a space can easily look for an `OutOfRange` error to detect when they are
131 /// done.
132 OutOfRange = 11,
133
134 /// `Unimplemented` (gRPC code "UNIMPLEMENTED") indicates the operation is not implemented or
135 /// supported in this service. In this case, the operation should not be re-attempted.
136 Unimplemented = 12,
137
138 /// `Internal` (gRPC code "INTERNAL") indicates an internal error has occurred and some
139 /// invariants expected by the underlying system have not been satisfied. This error code is
140 /// reserved for serious errors.
141 Internal = 13,
142
143 /// `Unavailable` (gRPC code "UNAVAILABLE") indicates the service is currently unavailable and
144 /// that this is most likely a transient condition. An error such as this can be corrected by
145 /// retrying with a backoff scheme. Note that it is not always safe to retry non-idempotent
146 /// operations.
147 ///
148 /// See the guidelines above for deciding between `FailedPrecondition`, `Aborted`, and
149 /// `Unavailable`.
150 Unavailable = 14,
151
152 /// `DataLoss` (gRPC code "DATA_LOSS") indicates that unrecoverable data loss or corruption has
153 /// occurred. As this error is serious, proper alerting should be attached to errors such as
154 /// this.
155 DataLoss = 15,
156
157 /// `Unauthenticated` (gRPC code "UNAUTHENTICATED") indicates that the request does not have
158 /// valid authentication credentials for the operation. Correct the authentication and try
159 /// again.
160 Unauthenticated = 16,
161}
162
163impl ErrorCode {
164 /// Adapted from `ErrnoToStatusCode` in
165 /// <https://github.com/abseil/abseil-cpp/blob/master/absl/status/status.cc>
166 #[cfg(feature = "use_libc")]
167 pub fn from_errno(errno: i32) -> Option<ErrorCode> {
168 match errno {
169 0 => None,
170 libc::EINVAL | // Invalid argument
171 libc::ENAMETOOLONG | // Filename too long
172 libc::E2BIG | // Argument list too long
173 libc::EDESTADDRREQ | // Destination address required
174 libc::EDOM | // Mathematics argument out of domain of function
175 libc::EFAULT | // Bad address
176 libc::EILSEQ | // Illegal byte sequence
177 libc::ENOPROTOOPT | // Protocol not available
178 libc::ENOTSOCK | // Not a socket
179 libc::ENOTTY | // Inappropriate I/O control operation
180 libc::EPROTOTYPE | // Protocol wrong type for socket
181 libc::ESPIPE => // Invalid seek
182 Some(Self::InvalidArgument),
183 libc::ETIMEDOUT => // Connection timed out
184 Some(Self::DeadlineExceeded),
185 libc::ENODEV | // No such device
186 libc::ENOENT | // No such file or directory
187 libc::ENOMEDIUM | // No medium found
188 libc::ENXIO | // No such device or address
189 libc::ESRCH => // No such process
190 Some(Self::NotFound),
191 libc::EEXIST | // File exists
192 libc::EADDRNOTAVAIL | // Address not available
193 libc::EALREADY | // Connection already in progress
194 libc::ENOTUNIQ => // Name not unique on network
195 Some(Self::AlreadyExists),
196 libc::EPERM | // Operation not permitted
197 libc::EACCES | // Permission denied
198 libc::ENOKEY | // Required key not available
199 libc::EROFS => // Read only file system
200 Some(Self::PermissionDenied),
201 libc::ENOTEMPTY | // Directory not empty
202 libc::EISDIR | // Is a directory
203 libc::ENOTDIR | // Not a directory
204 libc::EADDRINUSE | // Address already in use
205 libc::EBADF | // Invalid file descriptor
206 libc::EBADFD | // File descriptor in bad state
207 libc::EBUSY | // Device or resource busy
208 libc::ECHILD | // No child processes
209 libc::EISCONN | // Socket is connected
210 libc::EISNAM | // Is a named type file
211 libc::ENOTBLK | // Block device required
212 libc::ENOTCONN | // The socket is not connected
213 libc::EPIPE | // Broken pipe
214 libc::ESHUTDOWN | // Cannot send after transport endpoint shutdown
215 libc::ETXTBSY | // Text file busy
216 libc::EUNATCH => // Protocol driver not attached
217 Some(Self::FailedPrecondition),
218 libc::ENOSPC | // No space left on device
219 libc::EDQUOT | // Disk quota exceeded
220 libc::EMFILE | // Too many open files
221 libc::EMLINK | // Too many links
222 libc::ENFILE | // Too many open files in system
223 libc::ENOBUFS | // No buffer space available
224 libc::ENOMEM | // Not enough space
225 libc::EUSERS => // Too many users
226 Some(Self::ResourceExhausted),
227 libc::ECHRNG | // Channel number out of range
228 libc::EFBIG | // File too large
229 libc::EOVERFLOW | // Value too large to be stored in data type
230 libc::ERANGE => // Result too large
231 Some(Self::OutOfRange),
232 libc::ENOPKG | // Package not installed
233 libc::ENOSYS | // Function not implemented
234 libc::ENOTSUP | // Operation not supported
235 libc::EAFNOSUPPORT | // Address family not supported
236 libc::EPFNOSUPPORT | // Protocol family not supported
237 libc::EPROTONOSUPPORT | // Protocol not supported
238 libc::ESOCKTNOSUPPORT | // Socket type not supported
239 libc::EXDEV => // Improper link
240 Some(Self::Unimplemented),
241 libc::EAGAIN | // Resource temporarily unavailable
242 libc::ECOMM | // Communication error on send
243 libc::ECONNREFUSED | // Connection refused
244 libc::ECONNABORTED | // Connection aborted
245 libc::ECONNRESET | // Connection reset
246 libc::EINTR | // Interrupted function call
247 libc::EHOSTDOWN | // Host is down
248 libc::EHOSTUNREACH | // Host is unreachable
249 libc::ENETDOWN | // Network is down
250 libc::ENETRESET | // Connection aborted by network
251 libc::ENETUNREACH | // Network unreachable
252 libc::ENOLCK | // No locks available
253 libc::ENOLINK | // Link has been severed
254 libc::ENONET => // Machine is not on the network
255 Some(Self::Unavailable),
256 libc::EDEADLK | // Resource deadlock avoided
257 libc::ESTALE => // Stale file handle
258 Some(Self::Aborted),
259 libc::ECANCELED => // Operation cancelled
260 Some(Self::Cancelled),
261 _ => Some(Self::Unknown),
262 }
263 }
264
265 /// If `err` contains a `raw_os_error()`, return it converted into an `ErrorCode`.
266 /// Otherwise returns `None`.
267 #[cfg(feature = "use_libc")]
268 pub fn from_raw_os_error(err: &std::io::Error) -> Option<ErrorCode> {
269 err.raw_os_error().and_then(Self::from_errno)
270 }
271}
272
273impl From<ErrorCode> for NonZeroI32 {
274 fn from(code: ErrorCode) -> Self {
275 NonZeroI32::new(code as i32).expect(&format!(
276 "The enum value of an ErrorCode must be nonzero, but got '{:?}'",
277 code
278 ))
279 }
280}
281
282/// If a value matches one of the defined error codes, returns it as an `ErrorCode`, otherwise
283/// returns a `()` error.
284///
285/// For converting from strings, use the `std::str::FromStr` instance.
286impl TryFrom<i32> for ErrorCode {
287 type Error = ();
288
289 fn try_from(code: i32) -> Result<Self, ()> {
290 Self::from_repr(code).ok_or(())
291 }
292}
293
294#[cfg(test)]
295mod thin_status_tests {
296 use super::*;
297
298 #[cfg(feature = "use_libc")]
299 #[test]
300 fn test_cloud_rpc_status_conversions() {
301 assert_eq!(ErrorCode::from_errno(0), None);
302 assert_eq!(
303 ErrorCode::from_errno(libc::ENOENT),
304 Some(ErrorCode::NotFound)
305 );
306 }
307}