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
use core::{ffi, fmt, num::NonZeroI32, str};

use crate::{esp_err_t, esp_err_to_name, ESP_OK};

/// A wrapped [`esp_err_t`] to check if an error occurred.
///
/// An [`esp_err_t`] is returned from most esp-idf APIs as a status code. If it is equal
/// to [`ESP_OK`] it means **no** error occurred.
#[repr(transparent)]
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct EspError(NonZeroI32);

const _: () = if ESP_OK != 0 {
    panic!("ESP_OK *has* to be 0")
};

impl EspError {
    /// Wrap an [`esp_err_t`], return [`Some`] if `error` is **not** [`ESP_OK`].
    pub const fn from(error: esp_err_t) -> Option<Self> {
        match NonZeroI32::new(error) {
            None => None,
            Some(err) => Some(Self(err)),
        }
    }

    /// Wrap a [`NonZeroI32`]. Since [`ESP_OK`] is 0, this can never fail;
    pub const fn from_non_zero(error: NonZeroI32) -> Self {
        Self(error)
    }

    /// Wrap an [`esp_err_t`], throw a compile time error if `error` is [`ESP_OK`].
    pub const fn from_infallible<const E: esp_err_t>() -> Self {
        // workaround until feature(inline_const) is stabilized: https://github.com/rust-lang/rust/pull/104087
        struct Dummy<const D: esp_err_t>;
        impl<const D: esp_err_t> Dummy<D> {
            pub const ERR: EspError = match EspError::from(D) {
                Some(err) => err,
                None => panic!("ESP_OK can't be an error"),
            };
        }
        Dummy::<E>::ERR
    }

    /// Convert `error` into a [`Result`] with `Ok(value)` if no error occurred.
    ///
    /// If `error` is [`ESP_OK`] return [`Ok`] of `value` otherwise return [`Err`] of
    /// wrapped `error`.
    pub fn check_and_return<T>(error: esp_err_t, value: T) -> Result<T, Self> {
        match NonZeroI32::new(error) {
            None => Ok(value),
            Some(err) => Err(Self(err)),
        }
    }

    /// Convert `error` into a [`Result`] with `Ok(())` if not error occurred..
    ///
    /// If `error` equals to [`ESP_OK`] return [`Ok`], otherwise return [`Err`] with the
    /// wrapped [`esp_err_t`].
    pub fn convert(error: esp_err_t) -> Result<(), Self> {
        EspError::check_and_return(error, ())
    }

    /// Panic with a specific error message of the contained [`esp_err_t`].
    #[track_caller]
    pub fn panic(&self) {
        panic!("ESP-IDF ERROR: {self}");
    }

    /// Get the wrapped [`esp_err_t`].
    pub fn code(&self) -> esp_err_t {
        self.0.get()
    }
}

#[cfg(feature = "std")]
impl std::error::Error for EspError {}

impl fmt::Display for EspError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        unsafe {
            let s = ffi::CStr::from_ptr(esp_err_to_name(self.code()));
            core::fmt::Display::fmt(&str::from_utf8_unchecked(s.to_bytes()), f)
        }
    }
}

impl fmt::Debug for EspError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{} (error code {})", self, self.code())
    }
}

/// Convert an [`esp_err_t`] into a [`Result<(), EspError>`](Result).
///
/// See [`EspError::convert`].
#[macro_export]
macro_rules! esp {
    ($err:expr) => {{
        $crate::EspError::convert($err as $crate::esp_err_t)
    }};
}

/// Convert an [`esp_err_t`] into a [`Result<T, EspError>`](Result).
///
/// See [`EspError::check_and_return`].
#[macro_export]
macro_rules! esp_result {
    ($err:expr, $value:expr) => {{
        $crate::EspError::check_and_return($err as $crate::esp_err_t, $value)
    }};
}

/// Panic with an error-specific message if `err` is not [`ESP_OK`].
///
/// See [`EspError::from`] and [`EspError::panic`].
#[macro_export]
macro_rules! esp_nofail {
    ($err:expr) => {{
        if let ::core::option::Option::Some(error) =
            $crate::EspError::from($err as $crate::esp_err_t)
        {
            error.panic();
        }
    }};
}