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
//! Common error handling components for the RIOT operating system
//!
//! ## 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;

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 {
    number: 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."
        );
        NumericError { number: -name }
    }

    /// Numeric value of the error
    pub const fn number(&self) -> isize {
        self.number
    }

    /// 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)
    }
}

// Would be nice, but there's no strerror
//
// impl core::fmt::Display for NumericError {
//     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
//         write!(f, "Error {} ({})", self.number(), ...)
//     }
// }

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(NumericError {
                number: self.try_into().unwrap_or(-(riot_sys::EOVERFLOW as isize)),
            })
        }
    }
}

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);