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 NoSuchDeviceOrAddress,
86 NoSuchProcess,
88 NotADirectory,
90 NotASocket,
92 NotATty,
94 NotConnected,
96 NotFound,
98 OperationNotPermitted,
100 OperationNotSupported,
102 OutOfRange,
104 PermissionDenied,
106 ReadOnlyFilesystem,
108 ResourceBusy,
110 StorageFull,
112 TimedOut,
114 TooManyOpenFiles,
116 UnexpectedEof,
119 Unsupported,
121 WouldBlock,
124 DestAddrRequired,
127 MessageTooLong,
130 WriteZero,
133}
134
135impl AxErrorKind {
136 pub fn as_str(&self) -> &'static str {
138 use AxErrorKind::*;
139 match *self {
140 AddrInUse => "Address in use",
141 AlreadyConnected => "Already connected",
142 AlreadyExists => "Entity already exists",
143 ArgumentListTooLong => "Argument list too long",
144 BadAddress => "Bad address",
145 BadFileDescriptor => "Bad file descriptor",
146 BadState => "Bad internal state",
147 BrokenPipe => "Broken pipe",
148 ConnectionRefused => "Connection refused",
149 ConnectionReset => "Connection reset",
150 CrossesDevices => "Cross-device link or rename",
151 DirectoryNotEmpty => "Directory not empty",
152 FilesystemLoop => "Filesystem loop or indirection limit",
153 IllegalBytes => "Illegal byte sequence",
154 InProgress => "Operation in progress",
155 Interrupted => "Operation interrupted",
156 InvalidData => "Invalid data",
157 InvalidExecutable => "Invalid executable format",
158 InvalidInput => "Invalid input parameter",
159 Io => "I/O error",
160 IsADirectory => "Is a directory",
161 NameTooLong => "Filename too long",
162 NoMemory => "Out of memory",
163 NoSuchDevice => "No such device",
164 NoSuchDeviceOrAddress => "No such device or address",
165 NoSuchProcess => "No such process",
166 NotADirectory => "Not a directory",
167 NotASocket => "Not a socket",
168 NotATty => "Inappropriate ioctl for device",
169 NotConnected => "Not connected",
170 NotFound => "Entity not found",
171 OperationNotPermitted => "Operation not permitted",
172 OperationNotSupported => "Operation not supported",
173 OutOfRange => "Result out of range",
174 PermissionDenied => "Permission denied",
175 ReadOnlyFilesystem => "Read-only filesystem",
176 ResourceBusy => "Resource busy",
177 StorageFull => "No storage space",
178 TimedOut => "Timed out",
179 TooManyOpenFiles => "Too many open files",
180 UnexpectedEof => "Unexpected end of file",
181 Unsupported => "Operation not supported",
182 WouldBlock => "Operation would block",
183 DestAddrRequired => "Destination address required",
184 MessageTooLong => "Message too long",
185 WriteZero => "Write zero",
186 }
187 }
188
189 pub const fn code(self) -> i32 {
191 self as i32
192 }
193}
194
195impl TryFrom<i32> for AxErrorKind {
196 type Error = i32;
197
198 #[inline]
199 fn try_from(value: i32) -> Result<Self, Self::Error> {
200 if value > 0 && value <= AxErrorKind::COUNT as i32 {
201 Ok(unsafe { core::mem::transmute::<i32, AxErrorKind>(value) })
202 } else {
203 Err(value)
204 }
205 }
206}
207
208impl fmt::Display for AxErrorKind {
209 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
210 write!(f, "{}", self.as_str())
211 }
212}
213
214impl From<AxErrorKind> for LinuxError {
215 fn from(e: AxErrorKind) -> Self {
216 use AxErrorKind::*;
217 use LinuxError::*;
218 match e {
219 AddrInUse => EADDRINUSE,
220 AlreadyConnected => EISCONN,
221 AlreadyExists => EEXIST,
222 ArgumentListTooLong => E2BIG,
223 BadAddress | BadState => EFAULT,
224 BadFileDescriptor => EBADF,
225 BrokenPipe => EPIPE,
226 ConnectionRefused => ECONNREFUSED,
227 ConnectionReset => ECONNRESET,
228 CrossesDevices => EXDEV,
229 DirectoryNotEmpty => ENOTEMPTY,
230 FilesystemLoop => ELOOP,
231 IllegalBytes => EILSEQ,
232 InProgress => EINPROGRESS,
233 Interrupted => EINTR,
234 InvalidExecutable => ENOEXEC,
235 InvalidInput | InvalidData => EINVAL,
236 Io => EIO,
237 IsADirectory => EISDIR,
238 NameTooLong => ENAMETOOLONG,
239 NoMemory => ENOMEM,
240 NoSuchDevice => ENODEV,
241 NoSuchDeviceOrAddress => ENXIO,
242 NoSuchProcess => ESRCH,
243 NotADirectory => ENOTDIR,
244 NotASocket => ENOTSOCK,
245 NotATty => ENOTTY,
246 DestAddrRequired => EDESTADDRREQ,
247 MessageTooLong => EMSGSIZE,
248 NotConnected => ENOTCONN,
249 NotFound => ENOENT,
250 OperationNotPermitted => EPERM,
251 OperationNotSupported => EOPNOTSUPP,
252 OutOfRange => ERANGE,
253 PermissionDenied => EACCES,
254 ReadOnlyFilesystem => EROFS,
255 ResourceBusy => EBUSY,
256 StorageFull => ENOSPC,
257 TimedOut => ETIMEDOUT,
258 TooManyOpenFiles => EMFILE,
259 UnexpectedEof | WriteZero => EIO,
260 Unsupported => ENOSYS,
261 WouldBlock => EAGAIN,
262 }
263 }
264}
265
266impl TryFrom<LinuxError> for AxErrorKind {
267 type Error = LinuxError;
268
269 fn try_from(e: LinuxError) -> Result<Self, Self::Error> {
270 use AxErrorKind::*;
271 use LinuxError::*;
272 Ok(match e {
273 EADDRINUSE => AddrInUse,
274 EISCONN => AlreadyConnected,
275 EEXIST => AlreadyExists,
276 E2BIG => ArgumentListTooLong,
277 EFAULT => BadAddress,
278 EBADF => BadFileDescriptor,
279 EPIPE => BrokenPipe,
280 ECONNREFUSED => ConnectionRefused,
281 ECONNRESET => ConnectionReset,
282 EXDEV => CrossesDevices,
283 ENOTEMPTY => DirectoryNotEmpty,
284 ELOOP => FilesystemLoop,
285 EILSEQ => IllegalBytes,
286 EINPROGRESS => InProgress,
287 EINTR => Interrupted,
288 ENOEXEC => InvalidExecutable,
289 EINVAL => InvalidInput,
290 EIO => Io,
291 EISDIR => IsADirectory,
292 ENAMETOOLONG => NameTooLong,
293 ENOMEM => NoMemory,
294 ENODEV => NoSuchDevice,
295 ENXIO => NoSuchDeviceOrAddress,
296 ESRCH => NoSuchProcess,
297 ENOTDIR => NotADirectory,
298 ENOTSOCK => NotASocket,
299 ENOTTY => NotATty,
300 EDESTADDRREQ => DestAddrRequired,
301 EMSGSIZE => MessageTooLong,
302 ENOTCONN => NotConnected,
303 ENOENT => NotFound,
304 EPERM => OperationNotPermitted,
305 EOPNOTSUPP => OperationNotSupported,
306 ERANGE => OutOfRange,
307 EACCES => PermissionDenied,
308 EROFS => ReadOnlyFilesystem,
309 EBUSY => ResourceBusy,
310 ENOSPC => StorageFull,
311 ETIMEDOUT => TimedOut,
312 EMFILE => TooManyOpenFiles,
313 ENOSYS => Unsupported,
314 EAGAIN => WouldBlock,
315 _ => {
316 return Err(e);
317 }
318 })
319 }
320}
321
322#[repr(transparent)]
324#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
325pub struct AxError(i32);
326
327enum AxErrorData {
328 Ax(AxErrorKind),
329 Linux(LinuxError),
330}
331
332impl AxError {
333 const fn new_ax(kind: AxErrorKind) -> Self {
334 AxError(kind.code())
335 }
336
337 const fn new_linux(kind: LinuxError) -> Self {
338 AxError(-kind.code())
339 }
340
341 const fn data(&self) -> AxErrorData {
342 if self.0 < 0 {
343 AxErrorData::Linux(unsafe { core::mem::transmute::<i32, LinuxError>(-self.0) })
344 } else {
345 AxErrorData::Ax(unsafe { core::mem::transmute::<i32, AxErrorKind>(self.0) })
346 }
347 }
348
349 pub const fn code(self) -> i32 {
351 self.0
352 }
353
354 pub fn canonicalize(self) -> Self {
368 AxErrorKind::try_from(self).map_or_else(Into::into, Into::into)
369 }
370}
371
372impl<E: Into<AxErrorKind>> From<E> for AxError {
373 fn from(e: E) -> Self {
374 AxError::new_ax(e.into())
375 }
376}
377
378impl From<LinuxError> for AxError {
379 fn from(e: LinuxError) -> Self {
380 AxError::new_linux(e)
381 }
382}
383
384impl From<AxError> for LinuxError {
385 fn from(e: AxError) -> Self {
386 match e.data() {
387 AxErrorData::Ax(kind) => LinuxError::from(kind),
388 AxErrorData::Linux(kind) => kind,
389 }
390 }
391}
392
393impl TryFrom<AxError> for AxErrorKind {
394 type Error = LinuxError;
395
396 fn try_from(e: AxError) -> Result<Self, Self::Error> {
397 match e.data() {
398 AxErrorData::Ax(kind) => Ok(kind),
399 AxErrorData::Linux(e) => e.try_into(),
400 }
401 }
402}
403
404impl TryFrom<i32> for AxError {
405 type Error = i32;
406
407 fn try_from(value: i32) -> Result<Self, Self::Error> {
408 if AxErrorKind::try_from(value).is_ok() || LinuxError::try_from(-value).is_ok() {
409 Ok(AxError(value))
410 } else {
411 Err(value)
412 }
413 }
414}
415
416impl From<core::fmt::Error> for AxError {
417 fn from(_: core::fmt::Error) -> Self {
418 AxError::new_ax(AxErrorKind::InvalidInput)
419 }
420}
421
422impl fmt::Debug for AxError {
423 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
424 match self.data() {
425 AxErrorData::Ax(kind) => write!(f, "AxErrorKind::{:?}", kind),
426 AxErrorData::Linux(kind) => write!(f, "LinuxError::{:?}", kind),
427 }
428 }
429}
430
431impl fmt::Display for AxError {
432 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
433 match self.data() {
434 AxErrorData::Ax(kind) => write!(f, "{}", kind),
435 AxErrorData::Linux(kind) => write!(f, "{}", kind),
436 }
437 }
438}
439
440macro_rules! axerror_consts {
441 ($($name:ident),*) => {
442 #[allow(non_upper_case_globals)]
443 impl AxError {
444 $(
445 #[doc = concat!("An [`AxError`] with kind [`AxErrorKind::", stringify!($name), "`].")]
446 pub const $name: Self = Self::new_ax(AxErrorKind::$name);
447 )*
448 }
449 };
450}
451
452axerror_consts!(
453 AddrInUse,
454 AlreadyConnected,
455 AlreadyExists,
456 ArgumentListTooLong,
457 BadAddress,
458 BadFileDescriptor,
459 BadState,
460 BrokenPipe,
461 ConnectionRefused,
462 ConnectionReset,
463 CrossesDevices,
464 DirectoryNotEmpty,
465 FilesystemLoop,
466 IllegalBytes,
467 InProgress,
468 Interrupted,
469 InvalidData,
470 InvalidExecutable,
471 InvalidInput,
472 Io,
473 IsADirectory,
474 NameTooLong,
475 NoMemory,
476 NoSuchDevice,
477 NoSuchDeviceOrAddress,
478 NoSuchProcess,
479 NotADirectory,
480 NotASocket,
481 NotATty,
482 NotConnected,
483 NotFound,
484 OperationNotPermitted,
485 OperationNotSupported,
486 OutOfRange,
487 PermissionDenied,
488 ReadOnlyFilesystem,
489 ResourceBusy,
490 StorageFull,
491 TimedOut,
492 TooManyOpenFiles,
493 UnexpectedEof,
494 Unsupported,
495 WouldBlock,
496 DestAddrRequired,
497 MessageTooLong,
498 WriteZero
499);
500
501pub type AxResult<T = ()> = Result<T, AxError>;
503
504#[macro_export]
523macro_rules! ax_err_type {
524 ($err:ident) => {{
525 use $crate::AxErrorKind::*;
526 let err = $crate::AxError::from($err);
527 $crate::__priv::warn!("[{:?}]", err);
528 err
529 }};
530 ($err:ident, $msg:expr) => {{
531 use $crate::AxErrorKind::*;
532 let err = $crate::AxError::from($err);
533 $crate::__priv::warn!("[{:?}] {}", err, $msg);
534 err
535 }};
536}
537
538#[macro_export]
554macro_rules! ensure {
555 ($predicate:expr, $context_selector:expr $(,)?) => {
556 if !$predicate {
557 return $context_selector;
558 }
559 };
560}
561
562#[macro_export]
584macro_rules! ax_err {
585 ($err:ident) => {
586 Err($crate::ax_err_type!($err))
587 };
588 ($err:ident, $msg:expr) => {
589 Err($crate::ax_err_type!($err, $msg))
590 };
591}
592
593#[macro_export]
596macro_rules! ax_bail {
597 ($($t:tt)*) => {
598 return $crate::ax_err!($($t)*);
599 };
600}
601
602pub type LinuxResult<T = ()> = Result<T, LinuxError>;
604
605impl fmt::Display for LinuxError {
606 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
607 write!(f, "{}", self.as_str())
608 }
609}
610
611#[doc(hidden)]
612pub mod __priv {
613 pub use log::warn;
614}
615
616#[cfg(test)]
617mod tests {
618 use strum::EnumCount;
619
620 use crate::{AxError, AxErrorKind, LinuxError};
621
622 #[test]
623 fn test_try_from() {
624 let max_code = AxErrorKind::COUNT as i32;
625 assert_eq!(max_code, 46);
629 assert_eq!(max_code, AxError::WriteZero.code());
630
631 assert_eq!(AxError::AddrInUse.code(), 1);
632 assert_eq!(Ok(AxError::AddrInUse), AxError::try_from(1));
633 assert_eq!(Ok(AxError::AlreadyConnected), AxError::try_from(2));
634 assert_eq!(Ok(AxError::WriteZero), AxError::try_from(max_code));
635 assert_eq!(Err(max_code + 1), AxError::try_from(max_code + 1));
636 assert_eq!(Err(0), AxError::try_from(0));
637 assert_eq!(Err(i32::MAX), AxError::try_from(i32::MAX));
638 }
639
640 #[test]
641 fn test_conversion() {
642 for i in 1.. {
643 let Ok(err) = LinuxError::try_from(i) else {
644 break;
645 };
646 assert_eq!(err as i32, i);
647 let e = AxError::from(err);
648 assert_eq!(e.code(), -i);
649 assert_eq!(LinuxError::from(e), err);
650 }
651 }
652}