Skip to main content

frozen_core/
error.rs

1//! Custom implementations for [`Result<T>`] and [`Err<T>`]
2
3/// Custom result type w/ [`FrozenErr`] as error type
4pub type FrozenRes<T> = Result<T, FrozenErr>;
5
6/// Custom error object used in [`FrozenRes`]
7#[derive(Clone, Eq, PartialEq)]
8pub struct FrozenErr {
9    module: u8,
10    domain: u8,
11    reason: u16,
12    detail: &'static [u8],
13    errmsg: Vec<u8>,
14}
15
16impl FrozenErr {
17    /// Constrcut a new instance from raw id's
18    ///
19    /// ## Example
20    ///
21    /// ```
22    /// use frozen_core::error::FrozenErr;
23    ///
24    /// let err = FrozenErr::new(1, 2, 0x0033, b"io", b"failed".to_vec());
25    /// assert_eq!(err.cmp(0x0033), true);
26    /// ```
27    #[inline]
28    pub fn new(module: u8, domain: u8, reason: u16, detail: &'static [u8], errmsg: Vec<u8>) -> Self {
29        Self {
30            module,
31            domain,
32            reason,
33            detail,
34            errmsg,
35        }
36    }
37
38    /// Compare [`Self::reason`] w/ given `reason` id
39    ///
40    /// ## Example
41    ///
42    /// ```
43    /// use frozen_core::error::FrozenErr;
44    ///
45    /// let err = FrozenErr::new(1, 2, 42, b"test", Vec::new());
46    /// assert!(err.cmp(42));
47    /// assert!(!err.cmp(7));
48    /// ```
49    #[inline]
50    pub fn cmp(&self, reason: u16) -> bool {
51        self.reason == reason
52    }
53
54    /// Construct an `error_id` for [`FrozenErr`]
55    ///
56    /// ## Structure
57    ///
58    /// `| module:8 | domain:8 | reason:16 |`
59    ///
60    /// ## Example
61    ///
62    /// ```
63    /// use frozen_core::error::FrozenErr;
64    ///
65    /// let err = FrozenErr::new(0x01, 0x02, 0x0033, b"io", Vec::new());
66    /// let eid = err.error_id();
67    ///
68    /// assert_eq!(eid, 0x0102_0033);
69    /// ```
70    #[inline]
71    pub const fn error_id(&self) -> u32 {
72        ((self.module as u32) << 24) | ((self.domain as u32) << 16) | (self.reason as u32)
73    }
74}
75
76impl core::fmt::Display for FrozenErr {
77    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
78        let detail = core::str::from_utf8(&self.errmsg).unwrap_or("<non-utf8>");
79        let errmsg = core::str::from_utf8(&self.errmsg).unwrap_or("<non-utf8>");
80
81        #[cfg(test)]
82        return write!(
83            f,
84            "[m={}, d={}, c={}] ({detail}) {errmsg}",
85            self.module, self.domain, self.reason
86        );
87
88        #[cfg(not(test))]
89        write!(f, "[{}] ({detail}) {errmsg}", self.error_id())
90    }
91}
92
93impl core::fmt::Debug for FrozenErr {
94    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
95        write!(f, "\n----------\n")?;
96        core::fmt::Display::fmt(self, f)?;
97        write!(f, "\n----------\n")
98    }
99}
100
101/// Default `module_id` for testing
102///
103/// It's only available inside test module across tests, and is never made available outside,
104/// even when `cfg(test)` holds true, rust makes this possible for us ;)
105#[cfg(test)]
106pub const TEST_MID: u8 = 0x00;
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111
112    #[inline]
113    const fn from_error_id(eid: u32) -> (u8, u8, u16) {
114        let module = ((eid >> 24) & 0xff) as u8;
115        let domain = ((eid >> 16) & 0xff) as u8;
116        let reason = (eid & 0xffff) as u16;
117        (module, domain, reason)
118    }
119
120    #[test]
121    fn ok_error_id_roundtrip_basic() {
122        let err = FrozenErr::new(0x01, 0x02, 0x0033, b"io", Vec::new());
123        let eid = err.error_id();
124
125        assert_eq!(eid, 0x0102_0033);
126
127        let (m, d, r) = from_error_id(eid);
128        assert_eq!(m, 0x01);
129        assert_eq!(d, 0x02);
130        assert_eq!(r, 0x0033);
131    }
132
133    #[test]
134    fn ok_error_id_roundtrip_edges() {
135        let err = FrozenErr::new(0xff, 0x00, 0xffff, b"edge", Vec::new());
136        let eid = err.error_id();
137
138        assert_eq!(eid, 0xff00_ffff);
139
140        let (m, d, r) = from_error_id(eid);
141        assert_eq!(m, 0xff);
142        assert_eq!(d, 0x00);
143        assert_eq!(r, 0xffff);
144    }
145
146    #[test]
147    fn ok_error_id_reason_only_changes_low_bits() {
148        let e1 = FrozenErr::new(1, 2, 1, b"", Vec::new()).error_id();
149        let e2 = FrozenErr::new(1, 2, 2, b"", Vec::new()).error_id();
150
151        assert_eq!(e1 & 0xffff_0000, e2 & 0xffff_0000);
152        assert_ne!(e1 & 0x0000_ffff, e2 & 0x0000_ffff);
153    }
154}