pros_core/
error.rs

1//! Helpers for dealing with errno.
2//!
3//! Most errors in pros-rs are created by reading the last value of ERRNO.
4//! This includes the very generic [`PortError`], which is used for most hardware that gets plugged into a port on a V5 Brain.
5//!
6//! Most of the contents of this file are not public.
7
8/// A result type that makes returning errors easier.
9pub type Result<T = ()> = core::result::Result<T, alloc::boxed::Box<dyn core::error::Error>>;
10
11/// Gets the value of errno and sets errno to 0.
12pub fn take_errno() -> i32 {
13    let err = unsafe { *pros_sys::__errno() };
14    if err != 0 {
15        unsafe { *pros_sys::__errno() = 0 };
16    }
17    err
18}
19
20/// Generate an implementation of FromErrno for the given type.
21///
22/// Example:
23/// ```ignore
24/// map_errno! {
25///     GpsError {
26///         EAGAIN => Self::StillCalibrating,
27///     }
28///     inherit PortError;
29/// }
30/// ```
31#[macro_export]
32macro_rules! map_errno {
33    {
34        $err_ty:ty { $($errno:pat => $err:expr),*$(,)? }
35        $(inherit $base:ty;)?
36    } => {
37        impl $crate::error::FromErrno for $err_ty {
38            fn from_errno(num: i32) -> Option<Self> {
39                #[allow(unused_imports)]
40                use pros_sys::error::*;
41                $(
42                    // if the enum we're inheriting from can handle this errno, return it.
43                    if let Some(err) = <$base as $crate::error::FromErrno>::from_errno(num) {
44                        return Some(err.into());
45                    }
46                )?
47                match num {
48                    $($errno => Some($err),)*
49                    // this function should only be called if errno is set
50                    0 => panic!("Expected error state in errno, found 0."),
51                    _ => None,
52                }
53            }
54        }
55    }
56}
57
58/// If errno has an error, return early.
59#[macro_export]
60macro_rules! bail_errno {
61    () => {{
62        let errno = $crate::error::take_errno();
63        if errno != 0 {
64            let err = $crate::error::FromErrno::from_errno(errno)
65                .unwrap_or_else(|| panic!("Unknown errno code {errno}"));
66            return Err(err);
67        }
68    }};
69}
70
71/// Checks if the value is equal to the error state, and if it is,
72/// uses the value of errno to create an error and return early.
73#[macro_export]
74macro_rules! bail_on {
75    ($err_state:expr, $val:expr) => {{
76        let val = $val;
77        #[allow(clippy::cmp_null)]
78        if val == $err_state {
79            let errno = $crate::error::take_errno();
80            let err = $crate::error::FromErrno::from_errno(errno)
81                .unwrap_or_else(|| panic!("Unknown errno code {errno}"));
82            return Err(err); // where are we using this in a function that doesn't return result?
83        }
84        val
85    }};
86}
87use snafu::Snafu;
88
89/// A trait for converting an errno value into an error type.
90pub trait FromErrno {
91    /// Consume the current `errno` and, if it contains a known error, returns Self.
92    fn from_errno(num: i32) -> Option<Self>
93    where
94        Self: Sized;
95}
96
97#[derive(Debug, Snafu)]
98/// Generic erros that can take place when using ports on the V5 Brain.
99pub enum PortError {
100    /// The specified port is outside of the allowed range!
101    PortOutOfRange,
102    /// The specified port couldn't be configured as the specified type.
103    PortCannotBeConfigured,
104    /// The specified port is already being used or is mismatched.
105    AlreadyInUse,
106}
107
108map_errno!(PortError {
109    ENXIO => Self::PortOutOfRange,
110    ENODEV => Self::PortCannotBeConfigured,
111    EADDRINUSE => Self::AlreadyInUse,
112});