1use crate::AsciiStr;
2use core::fmt;
3use core::panic::Location;
4
5#[derive(Debug, Clone)]
13pub struct TraceIter<'a, K, const DEPTH: usize, const REASON_LEN: usize>
14where
15 K: Copy + Clone + core::fmt::Debug + PartialEq + Eq,
16{
17 error: &'a AnErr<K, DEPTH, REASON_LEN>,
18 pos: usize,
19}
20
21impl<'a, K, const DEPTH: usize, const REASON_LEN: usize> Iterator
22 for TraceIter<'a, K, DEPTH, REASON_LEN>
23where
24 K: Copy + Clone + core::fmt::Debug + PartialEq + Eq,
25{
26 type Item = (
27 K,
28 &'static Location<'static>,
29 Option<&'a AsciiStr<REASON_LEN>>,
30 );
31
32 fn next(&mut self) -> Option<Self::Item> {
33 if self.pos >= self.error.len as usize {
34 return None;
35 }
36
37 let idx = (self.error.len as usize) - 1 - self.pos;
38 let kind = self.error.kinds[idx]?;
39 let loc = self.error.locations[idx]?;
40 let reason = self.error.reasons[idx].as_ref();
41
42 self.pos += 1;
43 Some((kind, loc, reason))
44 }
45
46 fn size_hint(&self) -> (usize, Option<usize>) {
47 let remaining = (self.error.len as usize).saturating_sub(self.pos);
48 (remaining, Some(remaining))
49 }
50}
51
52impl<'a, K, const DEPTH: usize, const REASON_LEN: usize> ExactSizeIterator
53 for TraceIter<'a, K, DEPTH, REASON_LEN>
54where
55 K: Copy + Clone + core::fmt::Debug + PartialEq + Eq,
56{
57}
58
59#[derive(Clone, Copy, PartialEq, Eq)]
129#[must_use = "this error should be handled or converted to a different type"]
130pub struct AnErr<K, const DEPTH: usize = 3, const REASON_LEN: usize = 29>
131where
132 K: Copy + Clone + core::fmt::Debug + PartialEq + Eq,
133{
134 pub reasons: [Option<AsciiStr<REASON_LEN>>; DEPTH],
137
138 pub locations: [Option<&'static Location<'static>>; DEPTH],
141
142 pub kinds: [Option<K>; DEPTH],
145
146 pub len: u8,
148}
149
150impl<K, const DEPTH: usize, const REASON_LEN: usize> AnErr<K, DEPTH, REASON_LEN>
151where
152 K: Copy + Clone + core::fmt::Debug + PartialEq + Eq,
153{
154 #[inline]
156 #[track_caller]
157 pub fn new(kind: K) -> Self {
158 let mut kinds = [None; DEPTH];
159 let mut locs = [None; DEPTH];
160 let reasons = [None; DEPTH];
161
162 kinds[0] = Some(kind);
163 locs[0] = Some(Location::caller());
164
165 Self {
166 kinds,
167 locations: locs,
168 reasons,
169 len: 1,
170 }
171 }
172
173 #[inline]
177 #[track_caller]
178 pub fn with_reason(kind: K, reason: AsciiStr<REASON_LEN>) -> Self {
179 let mut kinds = [None; DEPTH];
180 let mut locs = [None; DEPTH];
181 let mut reasons = [None; DEPTH];
182
183 kinds[0] = Some(kind);
184 locs[0] = Some(Location::caller());
185 reasons[0] = if reason.is_empty() {
186 None
187 } else {
188 Some(reason)
189 };
190
191 Self {
192 kinds,
193 locations: locs,
194 reasons,
195 len: 1,
196 }
197 }
198
199 #[inline]
203 #[track_caller]
204 pub fn with_fmt(kind: K, args: core::fmt::Arguments<'_>) -> Self {
205 let mut kinds = [None; DEPTH];
206 let mut locs = [None; DEPTH];
207 let mut reasons = [None; DEPTH];
208
209 kinds[0] = Some(kind);
210 locs[0] = Some(Location::caller());
211 let reason = AsciiStr::from_fmt(args);
212 reasons[0] = if reason.is_empty() {
213 None
214 } else {
215 Some(reason)
216 };
217
218 Self {
219 kinds,
220 locations: locs,
221 reasons,
222 len: 1,
223 }
224 }
225
226 #[inline]
228 pub fn depth(&self) -> u8 {
229 self.len
230 }
231
232 #[inline]
234 pub fn kind(&self) -> Option<K> {
235 if self.len == 0 {
236 None
237 } else {
238 let idx = (self.len as usize) - 1;
239 self.kinds[idx]
240 }
241 }
242
243 #[inline]
248 #[track_caller]
249 pub fn context(&mut self, kind: K, new_reason: AsciiStr<REASON_LEN>) {
250 let idx = self.len as usize;
251 if idx < DEPTH {
252 self.reasons[idx] = if new_reason.is_empty() {
253 None
254 } else {
255 Some(new_reason)
256 };
257 self.push(kind, Location::caller());
258 }
259 }
260
261 #[inline]
266 #[track_caller]
267 pub fn context_fmt(&mut self, kind: K, args: core::fmt::Arguments<'_>) {
268 let idx = self.len as usize;
269 if idx < DEPTH {
270 let reason = AsciiStr::from_fmt(args);
271 self.reasons[idx] = if reason.is_empty() {
272 None
273 } else {
274 Some(reason)
275 };
276 self.push(kind, Location::caller());
277 }
278 }
279
280 pub fn trace(&self) -> TraceIter<'_, K, DEPTH, REASON_LEN> {
286 TraceIter {
287 error: self,
288 pos: 0,
289 }
290 }
291
292 #[inline]
293 fn push(&mut self, kind: K, loc: &'static Location<'static>) {
294 if (self.len as usize) < DEPTH {
295 let idx = self.len as usize;
296 self.kinds[idx] = Some(kind);
297 self.locations[idx] = Some(loc);
298 self.len += 1;
299 }
300 }
301
302 #[cfg(feature = "wire")]
318 pub fn to_wire_bytes<const PATH_LEN: usize>(
319 &self,
320 kind_to_u16: impl Fn(K) -> u16,
321 buf: &mut [u8],
322 ) -> Result<usize, ()> {
323 let needed = Self::wire_size::<PATH_LEN>();
324 if buf.len() < needed {
325 return Err(());
326 }
327
328 let mut offset = 0;
329
330 buf[offset] = 1; offset += 1;
333 buf[offset] = self.len;
334 offset += 1;
335
336 for i in 0..DEPTH {
337 if i < self.len as usize {
338 let kind_val = self.kinds[i].map_or(0, |k| kind_to_u16(k));
340 buf[offset..offset + 2].copy_from_slice(&kind_val.to_le_bytes());
341 offset += 2;
342
343 let reason = self.reasons[i]
345 .as_ref()
346 .unwrap_or_else(|| &AsciiStr::DEFAULT);
347 buf[offset..offset + REASON_LEN].copy_from_slice(&reason.to_wire_bytes());
348 offset += REASON_LEN;
349
350 if let Some(loc) = self.locations[i] {
352 let file = AsciiStr::<PATH_LEN>::from_str_truncate(loc.file());
353 buf[offset..offset + PATH_LEN].copy_from_slice(&file.to_wire_bytes());
354 offset += PATH_LEN;
355
356 buf[offset..offset + 4].copy_from_slice(&loc.line().to_le_bytes());
357 offset += 4;
358 buf[offset..offset + 4].copy_from_slice(&loc.column().to_le_bytes());
359 offset += 4;
360 } else {
361 offset += PATH_LEN + 8; }
363 } else {
364 offset += 2 + REASON_LEN + PATH_LEN + 8;
366 }
367 }
368
369 Ok(needed)
370 }
371
372 #[cfg(feature = "wire")]
374 pub const fn wire_size<const PATH_LEN: usize>() -> usize {
375 2 + DEPTH * (2 + REASON_LEN + PATH_LEN + 8)
376 }
377}
378
379impl<K, const DEPTH: usize, const REASON_LEN: usize> From<K> for AnErr<K, DEPTH, REASON_LEN>
380where
381 K: Copy + Clone + core::fmt::Debug + PartialEq + Eq,
382{
383 #[inline]
385 #[track_caller]
386 fn from(kind: K) -> Self {
387 Self::new(kind)
388 }
389}
390
391impl<K, const DEPTH: usize, const REASON_LEN: usize> core::fmt::Display
392 for AnErr<K, DEPTH, REASON_LEN>
393where
394 K: Copy + Clone + core::fmt::Debug + PartialEq + Eq,
395{
396 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
397 writeln!(f)?;
398 writeln!(f, "--")?;
399 writeln!(f, "Error:")?;
400
401 for (i, (kind, loc, reason_opt)) in self.trace().enumerate() {
402 let num = i + 1;
403
404 write!(f, " {:>2}. {:?}", num, kind)?;
405
406 if let Some(reason) = reason_opt {
407 if let Ok(s) = reason.as_str() {
408 write!(f, ": {}", s)?;
409 } else {
410 write!(f, ": <invalid ascii>")?;
411 }
412 }
413
414 writeln!(f, " @ {}:{}:{}", loc.file(), loc.line(), loc.column())?;
415 }
416
417 Ok(())
418 }
419}
420
421impl<K, const DEPTH: usize, const REASON_LEN: usize> fmt::Debug for AnErr<K, DEPTH, REASON_LEN>
422where
423 K: Copy + Clone + fmt::Debug + PartialEq + Eq,
424{
425 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
429 fmt::Display::fmt(self, f)
430 }
431}
432
433impl<K, const DEPTH: usize, const REASON_LEN: usize> core::error::Error
434 for AnErr<K, DEPTH, REASON_LEN>
435where
436 K: Copy + Clone + core::fmt::Debug + PartialEq + Eq,
437{
438}
439
440#[macro_export]
454macro_rules! an_err {
455 ($kind:expr) => {
457 $crate::AnErr::new($kind)
458 };
459
460 ($kind:expr, $fmt:literal $(, $arg:expr)* => $inner:expr $(,)?) => {{
462 let mut e = $inner;
463 e.context_fmt(
464 $kind,
465 format_args!($fmt $(, $arg)*)
466 );
467 e
468 }};
469
470 ($kind:expr, $fmt:literal $(, $arg:expr)* $(,)?) => {
472 $crate::AnErr::with_fmt($kind, format_args!($fmt $(, $arg)*))
473 };
474}
475
476#[cfg(feature = "wire")]
478#[derive(Debug, Clone, Copy, PartialEq, Eq)]
479pub struct WireLocation<const N: usize> {
480 pub file: AsciiStr<N>,
481 pub line: u32,
482 pub column: u32,
483}
484
485#[cfg(feature = "wire")]
487#[derive(Debug, Clone, Copy, PartialEq, Eq)]
488pub struct WireErr<const DEPTH: usize = 3, const REASON_LEN: usize = 29, const FILE_LEN: usize = 80>
489{
490 pub len: u8,
491 pub kinds: [Option<u16>; DEPTH],
492 pub reasons: [Option<AsciiStr<REASON_LEN>>; DEPTH],
493 pub locations: [Option<WireLocation<FILE_LEN>>; DEPTH],
494}
495
496#[cfg(feature = "wire")]
497impl<const DEPTH: usize, const REASON_LEN: usize, const FILE_LEN: usize>
498 WireErr<DEPTH, REASON_LEN, FILE_LEN>
499{
500 pub const fn wire_size() -> usize {
502 const fn compute_size<const D: usize, const R: usize, const F: usize>() -> usize {
503 2 + D * (2 + R + F + 8)
504 }
505 compute_size::<DEPTH, REASON_LEN, FILE_LEN>()
506 }
507
508 pub fn from_wire_bytes(bytes: &[u8]) -> Option<Self> {
513 if bytes.len() != Self::wire_size() {
514 return None;
515 }
516
517 let mut offset = 0;
518
519 let version = bytes[offset];
521 if version != 1 {
522 return None; }
524 offset += 1;
525
526 let len = bytes[offset];
527 if len == 0 || len as usize > DEPTH {
528 return None;
529 }
530 offset += 1;
531
532 let mut kinds = [None; DEPTH];
533 let mut reasons = [None; DEPTH];
534 let mut locations = [None; DEPTH];
535
536 for i in 0..(len as usize) {
537 let kind_bytes = <[u8; 2]>::try_from(&bytes[offset..offset + 2]).ok()?;
539 kinds[i] = Some(u16::from_le_bytes(kind_bytes));
540 offset += 2;
541
542 let reason_bytes = &bytes[offset..offset + REASON_LEN];
544 reasons[i] = AsciiStr::from_wire_bytes(reason_bytes);
545 offset += REASON_LEN;
546
547 let file_bytes = &bytes[offset..offset + FILE_LEN];
549 let file = AsciiStr::from_wire_bytes(file_bytes)?;
550
551 offset += FILE_LEN;
552
553 let line_bytes = <[u8; 4]>::try_from(&bytes[offset..offset + 4]).ok()?;
554 let line = u32::from_le_bytes(line_bytes);
555 offset += 4;
556
557 let col_bytes = <[u8; 4]>::try_from(&bytes[offset..offset + 4]).ok()?;
558 let column = u32::from_le_bytes(col_bytes);
559 offset += 4;
560
561 locations[i] = Some(WireLocation { file, line, column });
562 }
563
564 Some(WireErr {
567 len,
568 kinds,
569 reasons,
570 locations,
571 })
572 }
573}
574
575#[cfg(feature = "alloc")]
576#[cfg(test)]
577mod tests {
578 use super::*;
579 use alloc::format;
580 use alloc::vec::Vec;
581
582 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
583 #[repr(u8)]
584 enum TestKind {
585 Root,
586 Context1,
587 Context2,
588 Parse,
589 Io,
590 }
591
592 fn r<const N: usize>(s: &str) -> AsciiStr<N> {
594 AsciiStr::from_str_truncate(s)
595 }
596
597 type E3 = AnErr<TestKind, 3, 29>;
600
601 #[test]
602 fn test_new_from_and_basic_properties() {
603 let e1: E3 = AnErr::new(TestKind::Root);
604 let e2: E3 = TestKind::Root.into();
605
606 assert_eq!(e1.depth(), e2.depth());
610 assert_eq!(e1.kind(), e2.kind());
611 assert_eq!(e1.depth(), 1);
612 assert_eq!(e1.kind(), Some(TestKind::Root));
613
614 let mut trace = e1.trace();
615 let (kind, _loc, reason) = trace.next().unwrap();
616 assert_eq!(kind, TestKind::Root);
617 assert!(reason.is_none());
618 assert!(trace.next().is_none());
619 }
620
621 #[test]
622 fn test_with_reason_and_with_fmt() {
623 let e: E3 = AnErr::with_reason(TestKind::Parse, r::<29>("bad token"));
625 assert_eq!(e.depth(), 1);
626
627 let items: Vec<_> = e.trace().collect();
628 assert_eq!(items[0].2.unwrap().as_str().unwrap(), "bad token");
629
630 let e2: E3 = AnErr::with_fmt(
631 TestKind::Io,
632 format_args!("file not found: {}", "config.toml"),
633 );
634 let items2: Vec<_> = e2.trace().collect();
635 assert_eq!(
636 items2[0].2.unwrap().as_str().unwrap(),
637 "file not found: config.toml"
638 );
639 }
640
641 #[test]
642 fn test_an_err_macro_all_forms() {
643 let e1: E3 = an_err!(TestKind::Root);
644 assert_eq!(e1.kind(), Some(TestKind::Root));
645
646 let e2: E3 = an_err!(TestKind::Parse, "unexpected {}", "EOF");
647 assert_eq!(
648 e2.trace().next().unwrap().2.unwrap().as_str().unwrap(),
649 "unexpected EOF"
650 );
651
652 let inner: E3 = an_err!(TestKind::Parse, "bad data");
654 let outer: E3 = an_err!(TestKind::Io, "while reading file" => inner);
655
656 assert_eq!(outer.depth(), 2);
657 let mut t = outer.trace();
658 let (k1, _, r1) = t.next().unwrap();
659 assert_eq!(k1, TestKind::Io);
660 assert_eq!(r1.unwrap().as_str().unwrap(), "while reading file");
661
662 let (k2, _, r2) = t.next().unwrap();
663 assert_eq!(k2, TestKind::Parse);
664 assert_eq!(r2.unwrap().as_str().unwrap(), "bad data");
665 }
666
667 #[test]
668 fn test_context_and_context_fmt() {
669 let mut e: E3 = an_err!(TestKind::Root, "initial");
670 e.context(TestKind::Context1, r::<29>("level 1"));
671 e.context_fmt(TestKind::Context2, format_args!("level {}", 2));
672
673 assert_eq!(e.depth(), 3);
674
675 let trace: Vec<_> = e.trace().collect();
676 assert_eq!(trace[0].0, TestKind::Context2);
678 assert_eq!(trace[1].0, TestKind::Context1);
679 assert_eq!(trace[2].0, TestKind::Root);
680
681 assert_eq!(trace[0].2.unwrap().as_str().unwrap(), "level 2");
682 assert_eq!(trace[1].2.unwrap().as_str().unwrap(), "level 1");
683 assert_eq!(trace[2].2.unwrap().as_str().unwrap(), "initial");
684 }
685
686 #[test]
687 fn test_max_depth_is_no_op() {
688 let mut e: E3 = an_err!(TestKind::Root);
689 for i in 0..10 {
690 e.context(TestKind::Context1, r::<29>(&format!("extra {i}")));
691 }
692 assert_eq!(e.depth(), 3); let trace: Vec<_> = e.trace().collect();
695 assert_eq!(trace.len(), 3);
696 assert_eq!(trace[0].0, TestKind::Context1); }
698
699 #[test]
700 fn test_empty_reason_becomes_none() {
701 let e: E3 = an_err!(TestKind::Parse, "");
702 let (_, _, reason) = e.trace().next().unwrap();
703 assert!(reason.is_none());
704
705 let mut e2: E3 = an_err!(TestKind::Root);
706 e2.context(TestKind::Io, r::<29>("")); let items: Vec<_> = e2.trace().collect();
708 assert!(items[0].2.is_none());
709 }
710
711 #[test]
712 fn test_trace_iter_order_exact_size_and_size_hint() {
713 let e: E3 = an_err!(TestKind::Root, "a" => an_err!(TestKind::Io, "b" => an_err!(TestKind::Parse, "c")));
714
715 let trace = e.trace();
716 assert_eq!(trace.len(), 3); assert_eq!(trace.size_hint(), (3, Some(3)));
718
719 let collected: Vec<_> = trace.collect();
720 assert_eq!(collected.len(), 3);
721 assert_eq!(collected[0].0, TestKind::Root); assert_eq!(collected[1].0, TestKind::Io);
723 assert_eq!(collected[2].0, TestKind::Parse); }
725
726 #[test]
727 fn test_kind_returns_most_recent() {
728 let mut e: E3 = an_err!(TestKind::Parse);
729 e.context(TestKind::Context1, r::<29>("ctx1"));
730 e.context(TestKind::Context2, r::<29>("ctx2"));
731
732 assert_eq!(e.kind(), Some(TestKind::Context2)); }
734
735 #[test]
736 fn test_display() {
737 let inner: E3 = an_err!(TestKind::Parse, "bad syntax");
738 let e: E3 = an_err!(TestKind::Io, "while loading config" => inner);
739
740 let display = format!("{}", e);
741 assert!(display.contains("--"));
742 assert!(display.contains("Error:"));
743 assert!(display.contains("Io"));
744 assert!(display.contains("while loading config"));
745 assert!(display.contains("Parse"));
746 assert!(display.contains("bad syntax"));
747 }
748
749 #[cfg(feature = "wire")]
750 type E4 = AnErr<TestKind, 4, 29>;
751 #[cfg(feature = "wire")]
752 use alloc::vec;
753
754 #[cfg(feature = "wire")]
755 #[test]
756 fn test_wire_roundtrip() {
757 let inner: E4 = an_err!(TestKind::Parse, "unexpected char");
758 let e: E4 = an_err!(TestKind::Io, "while processing file" => inner);
759
760 const FILE_LEN: usize = 64;
761 let wire_size = E4::wire_size::<FILE_LEN>();
762 let mut buf = vec![0u8; wire_size];
763
764 let written = e.to_wire_bytes::<FILE_LEN>(|k| k as u16, &mut buf).unwrap();
766 assert_eq!(written, wire_size);
767
768 let wire_err = WireErr::<4, 29, FILE_LEN>::from_wire_bytes(&buf[..written]).unwrap();
769
770 assert_eq!(wire_err.len, 2);
771
772 assert_eq!(wire_err.kinds[0], Some(TestKind::Parse as u16));
774 assert_eq!(wire_err.kinds[1], Some(TestKind::Io as u16));
775
776 assert_eq!(
777 wire_err.reasons[0].as_ref().unwrap().as_str().unwrap(),
778 "unexpected char"
779 );
780 assert_eq!(
781 wire_err.reasons[1].as_ref().unwrap().as_str().unwrap(),
782 "while processing file"
783 );
784 }
785
786 #[cfg(feature = "wire")]
787 #[test]
788 fn test_wire_invalid_cases() {
789 assert!(WireErr::<3, 29, 64>::from_wire_bytes(&[0u8; 10]).is_none());
791
792 let mut buf = vec![0u8; E4::wire_size::<64>()];
794 buf[0] = 99; assert!(WireErr::<4, 29, 64>::from_wire_bytes(&buf).is_none());
796 }
797}