riot_wrappers/error.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
//! Common error handling components for the RIOT operating system
//!
//! Most fallible operations in the wrappers produce a [NumericError], which is a slightly more
//! precise wrapper around a negative integer. The [NegativeErrorExt::negative_to_error()] trait
//! method can be used to produce such errors when creating wrappers around C functions.
//!
//! ## Constants
//!
//! Several commonly used errors are provided as constants rather than requiring the use of
//! [NumericError::from_constant] for easier use. That list is not created comprehensively but
//! populated on demand. (Copying the full list would needlessly limit RIOT's ability to slim down
//! the list).
use core::convert::TryInto;
use core::ffi::CStr;
use core::num::NonZero;
pub trait NegativeErrorExt {
type Out;
/// Convert to a Result that is successful if the input value is zero or positive, or a
/// NumericError if it is negative
fn negative_to_error(self) -> Result<Self::Out, NumericError>;
}
/// An error that is expressed as a negative number
///
/// Ideally, that constraint should be expressed in the type system to allow the compiler to
/// represent `Result<positive_usize, NumericError>` as just the isize it originally was. For the
/// time being, this works well enough, and performance evaluation can later be done against a
/// manually implemented newtype around isize that'd be used to represent the Result.
#[derive(Debug, PartialEq, Eq)]
pub struct NumericError {
// The NonZero doesn't cover the full desired range, but at least Result<(), NumericError> can
// be lean.
number: NonZero<isize>,
}
impl NumericError {
/// Construct a NumericError from a [riot_sys] constant
///
/// As error constants are in their unsigned positive form, this flips the argument's sign into
/// the negative range.
///
/// ```
/// # #![no_std]
/// # #![feature(start)]
/// # #[start]
/// # fn main(_argc: isize, _argv: *const *const u8) -> isize {
/// # use riot_wrappers::error::NumericError;
/// # use riot_wrappers::stdio::println;
/// let err = NumericError::from_constant(riot_sys::ENOTSUP as _);
/// println!("{:?}", err); // NumericError { number: -61 }
/// # 0
/// # }
/// ```
///
/// ## Panics
///
/// In debug mode, this ensures that the given error is greater than zero.
pub const fn from_constant(name: isize) -> Self {
debug_assert!(
name > 0,
"Error names are expected to be positive for conversion into negative error numbers."
);
// Can be an `.unwrap()` once feature(const_trait_impl) is stabilized
let number = match NonZero::new(name) {
Some(n) => n,
_ => panic!("Error names are expected to be positive for conversion into negative error numbers.")
};
NumericError { number }
}
/// Numeric value of the error
pub const fn number(&self) -> isize {
self.number.get()
}
/// Convert the error into an [nb::Error] that is [nb::Error::WouldBlock] if the error is
/// `-EAGAIN`, and an actual error otherwise.
pub fn again_is_wouldblock(self) -> nb::Error<Self> {
if self == Self::from_constant(riot_sys::EAGAIN as _) {
return nb::Error::WouldBlock;
}
nb::Error::Other(self)
}
fn string(&self) -> Option<&'static CStr> {
#[cfg(all(riot_module_tiny_strerror, not(riot_module_tiny_strerror_minimal)))]
// unsafe: According to C API
// number cast: Disagreements on the numeric error size
// string cast: Disagreements on the signedness of char
return Some(unsafe {
CStr::from_ptr(riot_sys::tiny_strerror(-self.number.get() as _) as _)
});
#[cfg(not(all(riot_module_tiny_strerror, not(riot_module_tiny_strerror_minimal))))]
return None;
}
}
impl core::fmt::Display for NumericError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
if let Some(s) = self.string() {
write!(f, "Error {} ({})", self.number(), s.to_str().unwrap())?;
} else {
write!(f, "Error {}", self.number())?;
}
Ok(())
}
}
impl core::error::Error for NumericError {}
impl<T> NegativeErrorExt for T
where
T: num_traits::Zero + core::cmp::PartialOrd + TryInto<isize>,
{
type Out = T;
fn negative_to_error(self) -> Result<Self::Out, NumericError> {
if self >= Self::zero() {
Ok(self)
} else {
Err(self
.try_into()
.ok()
.and_then(NonZero::new)
.map(|number| NumericError { number })
.unwrap_or(EOVERFLOW))
}
}
}
macro_rules! E {
($e:ident) => {
#[doc = concat!("The predefined error ", stringify!($e))]
pub const $e: NumericError = NumericError::from_constant(riot_sys::$e as _);
};
}
// See module level comment
E!(EAGAIN);
E!(EINVAL);
E!(ENOMEM);
E!(ENOSPC);
E!(EOVERFLOW);