#![cfg_attr(not(test), no_std)]
#![doc = include_str!("../README.md")]
use core::fmt;
use strum::EnumCount;
mod linux_errno {
include!(concat!(env!("OUT_DIR"), "/linux_errno.rs"));
}
pub use linux_errno::LinuxError;
#[repr(i32)]
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, EnumCount)]
pub enum AxErrorKind {
AddrInUse = 1,
AlreadyConnected,
AlreadyExists,
ArgumentListTooLong,
BadAddress,
BadFileDescriptor,
BadState,
BrokenPipe,
ConnectionRefused,
ConnectionReset,
CrossesDevices,
DirectoryNotEmpty,
FilesystemLoop,
IllegalBytes,
InProgress,
Interrupted,
InvalidData,
InvalidExecutable,
InvalidInput,
Io,
IsADirectory,
NameTooLong,
NoMemory,
NoSuchDevice,
NoSuchProcess,
NotADirectory,
NotASocket,
NotATty,
NotConnected,
NotFound,
OperationNotPermitted,
OperationNotSupported,
OutOfRange,
PermissionDenied,
ReadOnlyFilesystem,
ResourceBusy,
StorageFull,
TimedOut,
TooManyOpenFiles,
UnexpectedEof,
Unsupported,
WouldBlock,
WriteZero,
}
impl AxErrorKind {
pub fn as_str(&self) -> &'static str {
use AxErrorKind::*;
match *self {
AddrInUse => "Address in use",
AlreadyConnected => "Already connected",
AlreadyExists => "Entity already exists",
ArgumentListTooLong => "Argument list too long",
BadAddress => "Bad address",
BadFileDescriptor => "Bad file descriptor",
BadState => "Bad internal state",
BrokenPipe => "Broken pipe",
ConnectionRefused => "Connection refused",
ConnectionReset => "Connection reset",
CrossesDevices => "Cross-device link or rename",
DirectoryNotEmpty => "Directory not empty",
FilesystemLoop => "Filesystem loop or indirection limit",
IllegalBytes => "Illegal byte sequence",
InProgress => "Operation in progress",
Interrupted => "Operation interrupted",
InvalidData => "Invalid data",
InvalidExecutable => "Invalid executable format",
InvalidInput => "Invalid input parameter",
Io => "I/O error",
IsADirectory => "Is a directory",
NameTooLong => "Filename too long",
NoMemory => "Out of memory",
NoSuchDevice => "No such device",
NoSuchProcess => "No such process",
NotADirectory => "Not a directory",
NotASocket => "Not a socket",
NotATty => "Inappropriate ioctl for device",
NotConnected => "Not connected",
NotFound => "Entity not found",
OperationNotPermitted => "Operation not permitted",
OperationNotSupported => "Operation not supported",
OutOfRange => "Result out of range",
PermissionDenied => "Permission denied",
ReadOnlyFilesystem => "Read-only filesystem",
ResourceBusy => "Resource busy",
StorageFull => "No storage space",
TimedOut => "Timed out",
TooManyOpenFiles => "Too many open files",
UnexpectedEof => "Unexpected end of file",
Unsupported => "Operation not supported",
WouldBlock => "Operation would block",
WriteZero => "Write zero",
}
}
pub const fn code(self) -> i32 {
self as i32
}
}
impl TryFrom<i32> for AxErrorKind {
type Error = i32;
#[inline]
fn try_from(value: i32) -> Result<Self, Self::Error> {
if value > 0 && value <= AxErrorKind::COUNT as i32 {
Ok(unsafe { core::mem::transmute::<i32, AxErrorKind>(value) })
} else {
Err(value)
}
}
}
impl fmt::Display for AxErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl From<AxErrorKind> for LinuxError {
fn from(e: AxErrorKind) -> Self {
use AxErrorKind::*;
use LinuxError::*;
match e {
AddrInUse => EADDRINUSE,
AlreadyConnected => EISCONN,
AlreadyExists => EEXIST,
ArgumentListTooLong => E2BIG,
BadAddress | BadState => EFAULT,
BadFileDescriptor => EBADF,
BrokenPipe => EPIPE,
ConnectionRefused => ECONNREFUSED,
ConnectionReset => ECONNRESET,
CrossesDevices => EXDEV,
DirectoryNotEmpty => ENOTEMPTY,
FilesystemLoop => ELOOP,
IllegalBytes => EILSEQ,
InProgress => EINPROGRESS,
Interrupted => EINTR,
InvalidExecutable => ENOEXEC,
InvalidInput | InvalidData => EINVAL,
Io => EIO,
IsADirectory => EISDIR,
NameTooLong => ENAMETOOLONG,
NoMemory => ENOMEM,
NoSuchDevice => ENODEV,
NoSuchProcess => ESRCH,
NotADirectory => ENOTDIR,
NotASocket => ENOTSOCK,
NotATty => ENOTTY,
NotConnected => ENOTCONN,
NotFound => ENOENT,
OperationNotPermitted => EPERM,
OperationNotSupported => EOPNOTSUPP,
OutOfRange => ERANGE,
PermissionDenied => EACCES,
ReadOnlyFilesystem => EROFS,
ResourceBusy => EBUSY,
StorageFull => ENOSPC,
TimedOut => ETIMEDOUT,
TooManyOpenFiles => EMFILE,
UnexpectedEof | WriteZero => EIO,
Unsupported => ENOSYS,
WouldBlock => EAGAIN,
}
}
}
impl TryFrom<LinuxError> for AxErrorKind {
type Error = LinuxError;
fn try_from(e: LinuxError) -> Result<Self, Self::Error> {
use AxErrorKind::*;
use LinuxError::*;
Ok(match e {
EADDRINUSE => AddrInUse,
EISCONN => AlreadyConnected,
EEXIST => AlreadyExists,
E2BIG => ArgumentListTooLong,
EFAULT => BadAddress,
EBADF => BadFileDescriptor,
EPIPE => BrokenPipe,
ECONNREFUSED => ConnectionRefused,
ECONNRESET => ConnectionReset,
EXDEV => CrossesDevices,
ENOTEMPTY => DirectoryNotEmpty,
ELOOP => FilesystemLoop,
EILSEQ => IllegalBytes,
EINPROGRESS => InProgress,
EINTR => Interrupted,
ENOEXEC => InvalidExecutable,
EINVAL => InvalidInput,
EIO => Io,
EISDIR => IsADirectory,
ENAMETOOLONG => NameTooLong,
ENOMEM => NoMemory,
ENODEV => NoSuchDevice,
ESRCH => NoSuchProcess,
ENOTDIR => NotADirectory,
ENOTSOCK => NotASocket,
ENOTTY => NotATty,
ENOTCONN => NotConnected,
ENOENT => NotFound,
EPERM => OperationNotPermitted,
EOPNOTSUPP => OperationNotSupported,
ERANGE => OutOfRange,
EACCES => PermissionDenied,
EROFS => ReadOnlyFilesystem,
EBUSY => ResourceBusy,
ENOSPC => StorageFull,
ETIMEDOUT => TimedOut,
EMFILE => TooManyOpenFiles,
ENOSYS => Unsupported,
EAGAIN => WouldBlock,
_ => {
return Err(e);
}
})
}
}
#[repr(transparent)]
#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct AxError(i32);
enum AxErrorData {
Ax(AxErrorKind),
Linux(LinuxError),
}
impl AxError {
const fn new_ax(kind: AxErrorKind) -> Self {
AxError(kind.code())
}
const fn new_linux(kind: LinuxError) -> Self {
AxError(-kind.code())
}
const fn data(&self) -> AxErrorData {
if self.0 < 0 {
AxErrorData::Linux(unsafe { core::mem::transmute::<i32, LinuxError>(-self.0) })
} else {
AxErrorData::Ax(unsafe { core::mem::transmute::<i32, AxErrorKind>(self.0) })
}
}
pub const fn code(self) -> i32 {
self.0
}
pub fn canonicalize(self) -> Self {
AxErrorKind::try_from(self).map_or_else(Into::into, Into::into)
}
}
impl<E: Into<AxErrorKind>> From<E> for AxError {
fn from(e: E) -> Self {
AxError::new_ax(e.into())
}
}
impl From<LinuxError> for AxError {
fn from(e: LinuxError) -> Self {
AxError::new_linux(e)
}
}
impl From<AxError> for LinuxError {
fn from(e: AxError) -> Self {
match e.data() {
AxErrorData::Ax(kind) => LinuxError::from(kind),
AxErrorData::Linux(kind) => kind,
}
}
}
impl TryFrom<AxError> for AxErrorKind {
type Error = LinuxError;
fn try_from(e: AxError) -> Result<Self, Self::Error> {
match e.data() {
AxErrorData::Ax(kind) => Ok(kind),
AxErrorData::Linux(e) => e.try_into(),
}
}
}
impl TryFrom<i32> for AxError {
type Error = i32;
fn try_from(value: i32) -> Result<Self, Self::Error> {
if AxErrorKind::try_from(value).is_ok() || LinuxError::try_from(-value).is_ok() {
Ok(AxError(value))
} else {
Err(value)
}
}
}
impl fmt::Debug for AxError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.data() {
AxErrorData::Ax(kind) => write!(f, "AxErrorKind::{:?}", kind),
AxErrorData::Linux(kind) => write!(f, "LinuxError::{:?}", kind),
}
}
}
impl fmt::Display for AxError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.data() {
AxErrorData::Ax(kind) => write!(f, "{}", kind),
AxErrorData::Linux(kind) => write!(f, "{}", kind),
}
}
}
macro_rules! axerror_consts {
($($name:ident),*) => {
#[allow(non_upper_case_globals)]
impl AxError {
$(
#[doc = concat!("An [`AxError`] with kind [`AxErrorKind::", stringify!($name), "`].")]
pub const $name: Self = Self::new_ax(AxErrorKind::$name);
)*
}
};
}
axerror_consts!(
AddrInUse,
AlreadyConnected,
AlreadyExists,
ArgumentListTooLong,
BadAddress,
BadFileDescriptor,
BadState,
BrokenPipe,
ConnectionRefused,
ConnectionReset,
CrossesDevices,
DirectoryNotEmpty,
FilesystemLoop,
IllegalBytes,
InProgress,
Interrupted,
InvalidData,
InvalidExecutable,
InvalidInput,
Io,
IsADirectory,
NameTooLong,
NoMemory,
NoSuchDevice,
NoSuchProcess,
NotADirectory,
NotASocket,
NotATty,
NotConnected,
NotFound,
OperationNotPermitted,
OperationNotSupported,
OutOfRange,
PermissionDenied,
ReadOnlyFilesystem,
ResourceBusy,
StorageFull,
TimedOut,
TooManyOpenFiles,
UnexpectedEof,
Unsupported,
WouldBlock,
WriteZero
);
pub type AxResult<T = ()> = Result<T, AxError>;
#[macro_export]
macro_rules! ax_err_type {
($err:ident) => {{
use $crate::AxErrorKind::*;
let err = $crate::AxError::from($err);
$crate::__priv::warn!("[{:?}]", err);
err
}};
($err:ident, $msg:expr) => {{
use $crate::AxErrorKind::*;
let err = $crate::AxError::from($err);
$crate::__priv::warn!("[{:?}] {}", err, $msg);
err
}};
}
#[macro_export]
macro_rules! ensure {
($predicate:expr, $context_selector:expr $(,)?) => {
if !$predicate {
return $context_selector;
}
};
}
#[macro_export]
macro_rules! ax_err {
($err:ident) => {
Err($crate::ax_err_type!($err))
};
($err:ident, $msg:expr) => {
Err($crate::ax_err_type!($err, $msg))
};
}
#[macro_export]
macro_rules! ax_bail {
($($t:tt)*) => {
return $crate::ax_err!($($t)*);
};
}
pub type LinuxResult<T = ()> = Result<T, LinuxError>;
impl fmt::Display for LinuxError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[doc(hidden)]
pub mod __priv {
pub use log::warn;
}
#[cfg(test)]
mod tests {
use strum::EnumCount;
use crate::{AxError, AxErrorKind, LinuxError};
#[test]
fn test_try_from() {
let max_code = AxErrorKind::COUNT as i32;
assert_eq!(max_code, 43);
assert_eq!(max_code, AxError::WriteZero.code());
assert_eq!(AxError::AddrInUse.code(), 1);
assert_eq!(Ok(AxError::AddrInUse), AxError::try_from(1));
assert_eq!(Ok(AxError::AlreadyConnected), AxError::try_from(2));
assert_eq!(Ok(AxError::WriteZero), AxError::try_from(max_code));
assert_eq!(Err(max_code + 1), AxError::try_from(max_code + 1));
assert_eq!(Err(0), AxError::try_from(0));
assert_eq!(Err(i32::MAX), AxError::try_from(i32::MAX));
}
#[test]
fn test_conversion() {
for i in 1.. {
let Ok(err) = LinuxError::try_from(i) else {
break;
};
assert_eq!(err as i32, i);
let e = AxError::from(err);
assert_eq!(e.code(), -i);
assert_eq!(LinuxError::from(e), err);
}
}
}