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 fmt::Debug for AxError {
398 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
399 match self.data() {
400 AxErrorData::Ax(kind) => write!(f, "AxErrorKind::{:?}", kind),
401 AxErrorData::Linux(kind) => write!(f, "LinuxError::{:?}", kind),
402 }
403 }
404}
405
406impl fmt::Display for AxError {
407 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
408 match self.data() {
409 AxErrorData::Ax(kind) => write!(f, "{}", kind),
410 AxErrorData::Linux(kind) => write!(f, "{}", kind),
411 }
412 }
413}
414
415macro_rules! axerror_consts {
416 ($($name:ident),*) => {
417 #[allow(non_upper_case_globals)]
418 impl AxError {
419 $(
420 #[doc = concat!("An [`AxError`] with kind [`AxErrorKind::", stringify!($name), "`].")]
421 pub const $name: Self = Self::new_ax(AxErrorKind::$name);
422 )*
423 }
424 };
425}
426
427axerror_consts!(
428 AddrInUse,
429 AlreadyConnected,
430 AlreadyExists,
431 ArgumentListTooLong,
432 BadAddress,
433 BadFileDescriptor,
434 BadState,
435 BrokenPipe,
436 ConnectionRefused,
437 ConnectionReset,
438 CrossesDevices,
439 DirectoryNotEmpty,
440 FilesystemLoop,
441 IllegalBytes,
442 InProgress,
443 Interrupted,
444 InvalidData,
445 InvalidExecutable,
446 InvalidInput,
447 Io,
448 IsADirectory,
449 NameTooLong,
450 NoMemory,
451 NoSuchDevice,
452 NoSuchProcess,
453 NotADirectory,
454 NotASocket,
455 NotATty,
456 NotConnected,
457 NotFound,
458 OperationNotPermitted,
459 OperationNotSupported,
460 OutOfRange,
461 PermissionDenied,
462 ReadOnlyFilesystem,
463 ResourceBusy,
464 StorageFull,
465 TimedOut,
466 TooManyOpenFiles,
467 UnexpectedEof,
468 Unsupported,
469 WouldBlock,
470 WriteZero
471);
472
473pub type AxResult<T = ()> = Result<T, AxError>;
475
476#[macro_export]
495macro_rules! ax_err_type {
496 ($err:ident) => {{
497 use $crate::AxErrorKind::*;
498 let err = $crate::AxError::from($err);
499 $crate::__priv::warn!("[{:?}]", err);
500 err
501 }};
502 ($err:ident, $msg:expr) => {{
503 use $crate::AxErrorKind::*;
504 let err = $crate::AxError::from($err);
505 $crate::__priv::warn!("[{:?}] {}", err, $msg);
506 err
507 }};
508}
509
510#[macro_export]
526macro_rules! ensure {
527 ($predicate:expr, $context_selector:expr $(,)?) => {
528 if !$predicate {
529 return $context_selector;
530 }
531 };
532}
533
534#[macro_export]
556macro_rules! ax_err {
557 ($err:ident) => {
558 Err($crate::ax_err_type!($err))
559 };
560 ($err:ident, $msg:expr) => {
561 Err($crate::ax_err_type!($err, $msg))
562 };
563}
564
565#[macro_export]
568macro_rules! ax_bail {
569 ($($t:tt)*) => {
570 return $crate::ax_err!($($t)*);
571 };
572}
573
574pub type LinuxResult<T = ()> = Result<T, LinuxError>;
576
577impl fmt::Display for LinuxError {
578 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
579 write!(f, "{}", self.as_str())
580 }
581}
582
583#[doc(hidden)]
584pub mod __priv {
585 pub use log::warn;
586}
587
588#[cfg(test)]
589mod tests {
590 use strum::EnumCount;
591
592 use crate::{AxError, AxErrorKind, LinuxError};
593
594 #[test]
595 fn test_try_from() {
596 let max_code = AxErrorKind::COUNT as i32;
597 assert_eq!(max_code, 43);
598 assert_eq!(max_code, AxError::WriteZero.code());
599
600 assert_eq!(AxError::AddrInUse.code(), 1);
601 assert_eq!(Ok(AxError::AddrInUse), AxError::try_from(1));
602 assert_eq!(Ok(AxError::AlreadyConnected), AxError::try_from(2));
603 assert_eq!(Ok(AxError::WriteZero), AxError::try_from(max_code));
604 assert_eq!(Err(max_code + 1), AxError::try_from(max_code + 1));
605 assert_eq!(Err(0), AxError::try_from(0));
606 assert_eq!(Err(i32::MAX), AxError::try_from(i32::MAX));
607 }
608
609 #[test]
610 fn test_conversion() {
611 for i in 1.. {
612 let Ok(err) = LinuxError::try_from(i) else {
613 break;
614 };
615 assert_eq!(err as i32, i);
616 let e = AxError::from(err);
617 assert_eq!(e.code(), -i);
618 assert_eq!(LinuxError::from(e), err);
619 }
620 }
621}