1pub type FrozenResult<T> = Result<T, FrozenError>;
33
34#[derive(Debug, Clone)]
36pub struct FrozenError {
37 pub id: u32,
39
40 pub context: String,
42}
43
44impl FrozenError {
45 #[inline(always)]
59 pub fn new(module: u8, domain: u8, code: ErrCode, errmsg: &str) -> Self {
60 Self { id: error_id(module, domain, code.reason), context: format!("[{}] {}", code.detail, errmsg) }
61 }
62
63 #[inline(always)]
78 pub fn new_raw<E: std::fmt::Display>(module: u8, domain: u8, code: ErrCode, err: E) -> Self {
79 Self { id: error_id(module, domain, code.reason), context: format!("[{}] {}", code.detail, err) }
80 }
81
82 #[inline(always)]
95 pub fn compare_id(&self, err: &FrozenError) -> bool {
96 self.id == err.id
97 }
98}
99
100#[derive(Debug, Clone)]
113pub struct ErrCode {
114 pub reason: u16,
116
117 pub detail: &'static str,
119}
120
121impl ErrCode {
122 #[inline]
137 pub const fn new(reason: u16, detail: &'static str) -> Self {
138 Self { reason, detail }
139 }
140}
141
142#[inline]
143const fn error_id(module: u8, domain: u8, reason: u16) -> u32 {
144 ((module as u32) << 0x18) | ((domain as u32) << 0x10) | (reason as u32)
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150
151 #[inline]
152 const fn from_error_id(eid: u32) -> (u8, u8, u16) {
153 let module = ((eid >> 24) & 0xff) as u8;
154 let domain = ((eid >> 16) & 0xff) as u8;
155 let reason = (eid & 0xffff) as u16;
156
157 (module, domain, reason)
158 }
159
160 #[test]
161 fn ok_context_exact_format() {
162 let err = FrozenError::new(1, 2, ErrCode::new(3, "io"), "failure");
163 assert_eq!(err.context, "[io] failure");
164 }
165
166 #[test]
167 fn ok_empty_message() {
168 let err = FrozenError::new(1, 2, ErrCode::new(3, "io"), "");
169 assert_eq!(err.context, "[io] ");
170 }
171
172 #[test]
173 fn ok_empty_detail() {
174 let err = FrozenError::new(1, 2, ErrCode::new(3, ""), "failure");
175 assert_eq!(err.context, "[] failure");
176 }
177
178 #[test]
179 fn ok_error_id_roundtrip_basic() {
180 let err = FrozenError::new(0x01, 0x02, ErrCode::new(0x0033, "io"), "read failed");
181 let (m, d, r) = from_error_id(err.id);
182
183 assert_eq!(err.id, 0x0102_0033);
184 assert_eq!((m, d, r), (0x01, 0x02, 0x0033));
185 }
186
187 #[test]
188 fn ok_new_and_new_raw_same_id() {
189 let e1 = FrozenError::new(1, 2, ErrCode::new(3, "io"), "fail");
190
191 let io_err = std::io::Error::new(std::io::ErrorKind::Other, "fail");
192 let e2 = FrozenError::new_raw(1, 2, ErrCode::new(3, "io"), io_err);
193
194 assert_eq!(e1.id, e2.id);
195 }
196
197 #[test]
198 fn ok_error_id_roundtrip_edges() {
199 let err = FrozenError::new(0xff, 0x00, ErrCode::new(0xffff, "edge"), "max values");
200 let (m, d, r) = from_error_id(err.id);
201
202 assert_eq!(err.id, 0xff00_ffff);
203 assert_eq!((m, d, r), (0xff, 0x00, 0xffff));
204 }
205
206 #[test]
207 fn ok_error_id_reason_only_changes_low_bits() {
208 let e1 = FrozenError::new(1, 2, ErrCode::new(1, "x"), "a");
209 let e2 = FrozenError::new(1, 2, ErrCode::new(2, "x"), "a");
210
211 assert_eq!(e1.id & 0xffff_0000, e2.id & 0xffff_0000);
212 assert_ne!(e1.id & 0x0000_ffff, e2.id & 0x0000_ffff);
213 }
214
215 #[test]
216 fn ok_context_formatting() {
217 let err = FrozenError::new(1, 1, ErrCode::new(1, "io"), "disk failure");
218 assert_eq!(err.context, "[io] disk failure");
219 }
220
221 #[test]
222 fn ok_new_raw_uses_display() {
223 let io_err = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "access denied");
224 let err = FrozenError::new_raw(1, 2, ErrCode::new(3, "io"), io_err);
225
226 assert!(err.context.contains("[io]"));
227 assert!(err.context.contains("access denied"));
228 }
229
230 #[test]
231 fn ok_compare_same_id_different_context() {
232 let e1 = FrozenError::new(1, 2, ErrCode::new(3, "io"), "a");
233 let e2 = FrozenError::new(1, 2, ErrCode::new(3, "io"), "b");
234
235 assert!(e1.compare_id(&e2));
236 }
237
238 #[test]
239 fn ok_compare_different_id() {
240 let e1 = FrozenError::new(1, 2, ErrCode::new(3, "io"), "a");
241 let e2 = FrozenError::new(1, 2, ErrCode::new(4, "io"), "a");
242
243 assert!(!e1.compare_id(&e2));
244 }
245}