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
//! Helpers for dealing with errno.
//!
//! Most errors in pros-rs are created by reading the last value of ERRNO.
//! This includes the very generic [`PortError`], which is used for most hardware that gets plugged into a port on a V5 Brain.
//!
//! Most of the contents of this file are not public.

/// A result type that makes returning errors easier.
pub type Result<T = ()> = core::result::Result<T, alloc::boxed::Box<dyn core::error::Error>>;

/// Gets the value of errno and sets errno to 0.
pub fn take_errno() -> i32 {
    let err = unsafe { *pros_sys::__errno() };
    if err != 0 {
        unsafe { *pros_sys::__errno() = 0 };
    }
    err
}

/// Generate an implementation of FromErrno for the given type.
///
/// Example:
/// ```ignore
/// map_errno! {
///     GpsError {
///         EAGAIN => Self::StillCalibrating,
///     }
///     inherit PortError;
/// }
/// ```
#[macro_export]
macro_rules! map_errno {
    {
        $err_ty:ty { $($errno:pat => $err:expr),*$(,)? }
        $(inherit $base:ty;)?
    } => {
        impl $crate::error::FromErrno for $err_ty {
            fn from_errno(num: i32) -> Option<Self> {
                #[allow(unused_imports)]
                use pros_sys::error::*;
                $(
                    // if the enum we're inheriting from can handle this errno, return it.
                    if let Some(err) = <$base as $crate::error::FromErrno>::from_errno(num) {
                        return Some(err.into());
                    }
                )?
                match num {
                    $($errno => Some($err),)*
                    // this function should only be called if errno is set
                    0 => panic!("Expected error state in errno, found 0."),
                    _ => None,
                }
            }
        }
    }
}

/// If errno has an error, return early.
#[macro_export]
macro_rules! bail_errno {
    () => {{
        let errno = $crate::error::take_errno();
        if errno != 0 {
            let err = $crate::error::FromErrno::from_errno(errno)
                .unwrap_or_else(|| panic!("Unknown errno code {errno}"));
            return Err(err);
        }
    }};
}

/// Checks if the value is equal to the error state, and if it is,
/// uses the value of errno to create an error and return early.
#[macro_export]
macro_rules! bail_on {
    ($err_state:expr, $val:expr) => {{
        let val = $val;
        #[allow(clippy::cmp_null)]
        if val == $err_state {
            let errno = $crate::error::take_errno();
            let err = $crate::error::FromErrno::from_errno(errno)
                .unwrap_or_else(|| panic!("Unknown errno code {errno}"));
            return Err(err); // where are we using this in a function that doesn't return result?
        }
        val
    }};
}
use snafu::Snafu;

/// A trait for converting an errno value into an error type.
pub trait FromErrno {
    /// Consume the current `errno` and, if it contains a known error, returns Self.
    fn from_errno(num: i32) -> Option<Self>
    where
        Self: Sized;
}

#[derive(Debug, Snafu)]
/// Generic erros that can take place when using ports on the V5 Brain.
pub enum PortError {
    /// The specified port is outside of the allowed range!
    PortOutOfRange,
    /// The specified port couldn't be configured as the specified type.
    PortCannotBeConfigured,
    /// The specified port is already being used or is mismatched.
    AlreadyInUse,
}

map_errno!(PortError {
    ENXIO => Self::PortOutOfRange,
    ENODEV => Self::PortCannotBeConfigured,
    EADDRINUSE => Self::AlreadyInUse,
});