1#![cfg_attr(not(test), no_std)]
2#![doc = include_str!("../README.md")]
3
4use core::fmt;
5
6use strum::EnumCount;
7
8mod linux_errno {
9 include!(concat!(env!("OUT_DIR"), "/linux_errno.rs"));
10}
11
12pub use linux_errno::LinuxError;
13
14#[repr(i32)]
20#[non_exhaustive]
21#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, EnumCount)]
22pub enum AxErrorKind {
23 AddrInUse = 1,
25 AlreadyConnected,
27 AlreadyExists,
29 ArgumentListTooLong,
31 BadAddress,
33 BadFileDescriptor,
35 BadState,
37 BrokenPipe,
39 ConnectionRefused,
41 ConnectionReset,
43 CrossesDevices,
45 DirectoryNotEmpty,
47 FilesystemLoop,
50 IllegalBytes,
52 InProgress,
55 Interrupted,
57 InvalidData,
68 InvalidExecutable,
70 InvalidInput,
72 Io,
74 IsADirectory,
76 NameTooLong,
78 NoMemory,
80 NoSuchDevice,
82 NoSuchProcess,
84 NotADirectory,
86 NotASocket,
88 NotATty,
90 NotConnected,
92 NotFound,
94 OperationNotPermitted,
96 OperationNotSupported,
98 OutOfRange,
100 PermissionDenied,
102 ReadOnlyFilesystem,
104 ResourceBusy,
106 StorageFull,
108 TimedOut,
110 TooManyOpenFiles,
112 UnexpectedEof,
115 Unsupported,
117 WouldBlock,
120 WriteZero,
123}
124
125impl AxErrorKind {
126 pub fn as_str(&self) -> &'static str {
128 use AxErrorKind::*;
129 match *self {
130 AddrInUse => "Address in use",
131 AlreadyConnected => "Already connected",
132 AlreadyExists => "Entity already exists",
133 ArgumentListTooLong => "Argument list too long",
134 BadAddress => "Bad address",
135 BadFileDescriptor => "Bad file descriptor",
136 BadState => "Bad internal state",
137 BrokenPipe => "Broken pipe",
138 ConnectionRefused => "Connection refused",
139 ConnectionReset => "Connection reset",
140 CrossesDevices => "Cross-device link or rename",
141 DirectoryNotEmpty => "Directory not empty",
142 FilesystemLoop => "Filesystem loop or indirection limit",
143 IllegalBytes => "Illegal byte sequence",
144 InProgress => "Operation in progress",
145 Interrupted => "Operation interrupted",
146 InvalidData => "Invalid data",
147 InvalidExecutable => "Invalid executable format",
148 InvalidInput => "Invalid input parameter",
149 Io => "I/O error",
150 IsADirectory => "Is a directory",
151 NameTooLong => "Filename too long",
152 NoMemory => "Out of memory",
153 NoSuchDevice => "No such device",
154 NoSuchProcess => "No such process",
155 NotADirectory => "Not a directory",
156 NotASocket => "Not a socket",
157 NotATty => "Inappropriate ioctl for device",
158 NotConnected => "Not connected",
159 NotFound => "Entity not found",
160 OperationNotPermitted => "Operation not permitted",
161 OperationNotSupported => "Operation not supported",
162 OutOfRange => "Result out of range",
163 PermissionDenied => "Permission denied",
164 ReadOnlyFilesystem => "Read-only filesystem",
165 ResourceBusy => "Resource busy",
166 StorageFull => "No storage space",
167 TimedOut => "Timed out",
168 TooManyOpenFiles => "Too many open files",
169 UnexpectedEof => "Unexpected end of file",
170 Unsupported => "Operation not supported",
171 WouldBlock => "Operation would block",
172 WriteZero => "Write zero",
173 }
174 }
175
176 pub const fn code(self) -> i32 {
178 self as i32
179 }
180}
181
182impl TryFrom<i32> for AxErrorKind {
183 type Error = i32;
184
185 #[inline]
186 fn try_from(value: i32) -> Result<Self, Self::Error> {
187 if value > 0 && value <= AxErrorKind::COUNT as i32 {
188 Ok(unsafe { core::mem::transmute::<i32, AxErrorKind>(value) })
189 } else {
190 Err(value)
191 }
192 }
193}
194
195impl fmt::Display for AxErrorKind {
196 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
197 write!(f, "{}", self.as_str())
198 }
199}
200
201impl From<AxErrorKind> for LinuxError {
202 fn from(e: AxErrorKind) -> Self {
203 use AxErrorKind::*;
204 use LinuxError::*;
205 match e {
206 AddrInUse => EADDRINUSE,
207 AlreadyConnected => EISCONN,
208 AlreadyExists => EEXIST,
209 ArgumentListTooLong => E2BIG,
210 BadAddress | BadState => EFAULT,
211 BadFileDescriptor => EBADF,
212 BrokenPipe => EPIPE,
213 ConnectionRefused => ECONNREFUSED,
214 ConnectionReset => ECONNRESET,
215 CrossesDevices => EXDEV,
216 DirectoryNotEmpty => ENOTEMPTY,
217 FilesystemLoop => ELOOP,
218 IllegalBytes => EILSEQ,
219 InProgress => EINPROGRESS,
220 Interrupted => EINTR,
221 InvalidExecutable => ENOEXEC,
222 InvalidInput | InvalidData => EINVAL,
223 Io => EIO,
224 IsADirectory => EISDIR,
225 NameTooLong => ENAMETOOLONG,
226 NoMemory => ENOMEM,
227 NoSuchDevice => ENODEV,
228 NoSuchProcess => ESRCH,
229 NotADirectory => ENOTDIR,
230 NotASocket => ENOTSOCK,
231 NotATty => ENOTTY,
232 NotConnected => ENOTCONN,
233 NotFound => ENOENT,
234 OperationNotPermitted => EPERM,
235 OperationNotSupported => EOPNOTSUPP,
236 OutOfRange => ERANGE,
237 PermissionDenied => EACCES,
238 ReadOnlyFilesystem => EROFS,
239 ResourceBusy => EBUSY,
240 StorageFull => ENOSPC,
241 TimedOut => ETIMEDOUT,
242 TooManyOpenFiles => EMFILE,
243 UnexpectedEof | WriteZero => EIO,
244 Unsupported => ENOSYS,
245 WouldBlock => EAGAIN,
246 }
247 }
248}
249
250impl TryFrom<LinuxError> for AxErrorKind {
251 type Error = LinuxError;
252
253 fn try_from(e: LinuxError) -> Result<Self, Self::Error> {
254 use AxErrorKind::*;
255 use LinuxError::*;
256 Ok(match e {
257 EADDRINUSE => AddrInUse,
258 EISCONN => AlreadyConnected,
259 EEXIST => AlreadyExists,
260 E2BIG => ArgumentListTooLong,
261 EFAULT => BadAddress,
262 EBADF => BadFileDescriptor,
263 EPIPE => BrokenPipe,
264 ECONNREFUSED => ConnectionRefused,
265 ECONNRESET => ConnectionReset,
266 EXDEV => CrossesDevices,
267 ENOTEMPTY => DirectoryNotEmpty,
268 ELOOP => FilesystemLoop,
269 EILSEQ => IllegalBytes,
270 EINPROGRESS => InProgress,
271 EINTR => Interrupted,
272 ENOEXEC => InvalidExecutable,
273 EINVAL => InvalidInput,
274 EIO => Io,
275 EISDIR => IsADirectory,
276 ENAMETOOLONG => NameTooLong,
277 ENOMEM => NoMemory,
278 ENODEV => NoSuchDevice,
279 ESRCH => NoSuchProcess,
280 ENOTDIR => NotADirectory,
281 ENOTSOCK => NotASocket,
282 ENOTTY => NotATty,
283 ENOTCONN => NotConnected,
284 ENOENT => NotFound,
285 EPERM => OperationNotPermitted,
286 EOPNOTSUPP => OperationNotSupported,
287 ERANGE => OutOfRange,
288 EACCES => PermissionDenied,
289 EROFS => ReadOnlyFilesystem,
290 EBUSY => ResourceBusy,
291 ENOSPC => StorageFull,
292 ETIMEDOUT => TimedOut,
293 EMFILE => TooManyOpenFiles,
294 ENOSYS => Unsupported,
295 EAGAIN => WouldBlock,
296 _ => {
297 return Err(e);
298 }
299 })
300 }
301}
302
303#[repr(transparent)]
305#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
306pub struct AxError(i32);
307
308enum AxErrorData {
309 Ax(AxErrorKind),
310 Linux(LinuxError),
311}
312
313impl AxError {
314 const fn new_ax(kind: AxErrorKind) -> Self {
315 AxError(kind.code())
316 }
317
318 const fn new_linux(kind: LinuxError) -> Self {
319 AxError(-kind.code())
320 }
321
322 const fn data(&self) -> AxErrorData {
323 if self.0 < 0 {
324 AxErrorData::Linux(unsafe { core::mem::transmute::<i32, LinuxError>(-self.0) })
325 } else {
326 AxErrorData::Ax(unsafe { core::mem::transmute::<i32, AxErrorKind>(self.0) })
327 }
328 }
329
330 pub const fn code(self) -> i32 {
332 self.0
333 }
334
335 pub fn canonicalize(self) -> Self {
349 AxErrorKind::try_from(self).map_or_else(Into::into, Into::into)
350 }
351}
352
353impl<E: Into<AxErrorKind>> From<E> for AxError {
354 fn from(e: E) -> Self {
355 AxError::new_ax(e.into())
356 }
357}
358
359impl From<LinuxError> for AxError {
360 fn from(e: LinuxError) -> Self {
361 AxError::new_linux(e)
362 }
363}
364
365impl From<AxError> for LinuxError {
366 fn from(e: AxError) -> Self {
367 match e.data() {
368 AxErrorData::Ax(kind) => LinuxError::from(kind),
369 AxErrorData::Linux(kind) => kind,
370 }
371 }
372}
373
374impl TryFrom<AxError> for AxErrorKind {
375 type Error = LinuxError;
376
377 fn try_from(e: AxError) -> Result<Self, Self::Error> {
378 match e.data() {
379 AxErrorData::Ax(kind) => Ok(kind),
380 AxErrorData::Linux(e) => e.try_into(),
381 }
382 }
383}
384
385impl TryFrom<i32> for AxError {
386 type Error = i32;
387
388 fn try_from(value: i32) -> Result<Self, Self::Error> {
389 if AxErrorKind::try_from(value).is_ok() || LinuxError::try_from(-value).is_ok() {
390 Ok(AxError(value))
391 } else {
392 Err(value)
393 }
394 }
395}
396
397impl From<core::fmt::Error> for AxError {
398 fn from(_: core::fmt::Error) -> Self {
399 AxError::new_ax(AxErrorKind::InvalidInput)
400 }
401}
402
403impl fmt::Debug for AxError {
404 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
405 match self.data() {
406 AxErrorData::Ax(kind) => write!(f, "AxErrorKind::{:?}", kind),
407 AxErrorData::Linux(kind) => write!(f, "LinuxError::{:?}", kind),
408 }
409 }
410}
411
412impl fmt::Display for AxError {
413 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
414 match self.data() {
415 AxErrorData::Ax(kind) => write!(f, "{}", kind),
416 AxErrorData::Linux(kind) => write!(f, "{}", kind),
417 }
418 }
419}
420
421macro_rules! axerror_consts {
422 ($($name:ident),*) => {
423 #[allow(non_upper_case_globals)]
424 impl AxError {
425 $(
426 #[doc = concat!("An [`AxError`] with kind [`AxErrorKind::", stringify!($name), "`].")]
427 pub const $name: Self = Self::new_ax(AxErrorKind::$name);
428 )*
429 }
430 };
431}
432
433axerror_consts!(
434 AddrInUse,
435 AlreadyConnected,
436 AlreadyExists,
437 ArgumentListTooLong,
438 BadAddress,
439 BadFileDescriptor,
440 BadState,
441 BrokenPipe,
442 ConnectionRefused,
443 ConnectionReset,
444 CrossesDevices,
445 DirectoryNotEmpty,
446 FilesystemLoop,
447 IllegalBytes,
448 InProgress,
449 Interrupted,
450 InvalidData,
451 InvalidExecutable,
452 InvalidInput,
453 Io,
454 IsADirectory,
455 NameTooLong,
456 NoMemory,
457 NoSuchDevice,
458 NoSuchProcess,
459 NotADirectory,
460 NotASocket,
461 NotATty,
462 NotConnected,
463 NotFound,
464 OperationNotPermitted,
465 OperationNotSupported,
466 OutOfRange,
467 PermissionDenied,
468 ReadOnlyFilesystem,
469 ResourceBusy,
470 StorageFull,
471 TimedOut,
472 TooManyOpenFiles,
473 UnexpectedEof,
474 Unsupported,
475 WouldBlock,
476 WriteZero
477);
478
479pub type AxResult<T = ()> = Result<T, AxError>;
481
482#[macro_export]
501macro_rules! ax_err_type {
502 ($err:ident) => {{
503 use $crate::AxErrorKind::*;
504 let err = $crate::AxError::from($err);
505 $crate::__priv::warn!("[{:?}]", err);
506 err
507 }};
508 ($err:ident, $msg:expr) => {{
509 use $crate::AxErrorKind::*;
510 let err = $crate::AxError::from($err);
511 $crate::__priv::warn!("[{:?}] {}", err, $msg);
512 err
513 }};
514}
515
516#[macro_export]
532macro_rules! ensure {
533 ($predicate:expr, $context_selector:expr $(,)?) => {
534 if !$predicate {
535 return $context_selector;
536 }
537 };
538}
539
540#[macro_export]
562macro_rules! ax_err {
563 ($err:ident) => {
564 Err($crate::ax_err_type!($err))
565 };
566 ($err:ident, $msg:expr) => {
567 Err($crate::ax_err_type!($err, $msg))
568 };
569}
570
571#[macro_export]
574macro_rules! ax_bail {
575 ($($t:tt)*) => {
576 return $crate::ax_err!($($t)*);
577 };
578}
579
580pub type LinuxResult<T = ()> = Result<T, LinuxError>;
582
583impl fmt::Display for LinuxError {
584 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
585 write!(f, "{}", self.as_str())
586 }
587}
588
589#[doc(hidden)]
590pub mod __priv {
591 pub use log::warn;
592}
593
594#[cfg(test)]
595mod tests {
596 use strum::EnumCount;
597
598 use crate::{AxError, AxErrorKind, LinuxError};
599
600 #[test]
601 fn test_try_from() {
602 let max_code = AxErrorKind::COUNT as i32;
603 assert_eq!(max_code, 43);
604 assert_eq!(max_code, AxError::WriteZero.code());
605
606 assert_eq!(AxError::AddrInUse.code(), 1);
607 assert_eq!(Ok(AxError::AddrInUse), AxError::try_from(1));
608 assert_eq!(Ok(AxError::AlreadyConnected), AxError::try_from(2));
609 assert_eq!(Ok(AxError::WriteZero), AxError::try_from(max_code));
610 assert_eq!(Err(max_code + 1), AxError::try_from(max_code + 1));
611 assert_eq!(Err(0), AxError::try_from(0));
612 assert_eq!(Err(i32::MAX), AxError::try_from(i32::MAX));
613 }
614
615 #[test]
616 fn test_conversion() {
617 for i in 1.. {
618 let Ok(err) = LinuxError::try_from(i) else {
619 break;
620 };
621 assert_eq!(err as i32, i);
622 let e = AxError::from(err);
623 assert_eq!(e.code(), -i);
624 assert_eq!(LinuxError::from(e), err);
625 }
626 }
627}