1use crate::LiteStr;
2use core::fmt;
3use core::fmt::Write;
4use core::panic::Location;
5
6#[derive(Debug, Clone)]
14pub struct TraceIter<'a, K, const DEPTH: usize, const REASON_LEN: usize>
15where
16 K: Copy + Clone + core::fmt::Debug + PartialEq + Eq,
17{
18 error: &'a AnErr<K, DEPTH, REASON_LEN>,
19 pos: usize,
20}
21
22impl<'a, K, const DEPTH: usize, const REASON_LEN: usize> Iterator
23 for TraceIter<'a, K, DEPTH, REASON_LEN>
24where
25 K: Copy + Clone + core::fmt::Debug + PartialEq + Eq,
26{
27 type Item = (
28 K,
29 &'static Location<'static>,
30 Option<&'a LiteStr<REASON_LEN>>,
31 );
32
33 fn next(&mut self) -> Option<Self::Item> {
34 if self.pos >= self.error.len as usize {
35 return None;
36 }
37
38 let idx = (self.error.len as usize) - 1 - self.pos;
39 let kind = self.error.kinds[idx]?;
40 let loc = self.error.locations[idx]?;
41 let reason = self.error.reasons[idx].as_ref();
42
43 self.pos += 1;
44 Some((kind, loc, reason))
45 }
46
47 fn size_hint(&self) -> (usize, Option<usize>) {
48 let remaining = (self.error.len as usize).saturating_sub(self.pos);
49 (remaining, Some(remaining))
50 }
51}
52
53impl<'a, K, const DEPTH: usize, const REASON_LEN: usize> ExactSizeIterator
54 for TraceIter<'a, K, DEPTH, REASON_LEN>
55where
56 K: Copy + Clone + core::fmt::Debug + PartialEq + Eq,
57{
58}
59
60#[derive(Clone, Copy, PartialEq, Eq)]
201#[must_use = "this error should be handled or converted to a different type e.g `pub type DtErr = AnErr<MyError, 2, 49>;`"]
202pub struct AnErr<K, const DEPTH: usize = 3, const REASON_LEN: usize = 29>
203where
204 K: Copy + Clone + core::fmt::Debug + PartialEq + Eq,
205{
206 pub reasons: [Option<LiteStr<REASON_LEN>>; DEPTH],
209
210 pub locations: [Option<&'static Location<'static>>; DEPTH],
213
214 pub kinds: [Option<K>; DEPTH],
217
218 pub len: u8,
220}
221
222impl<K, const DEPTH: usize, const REASON_LEN: usize> AnErr<K, DEPTH, REASON_LEN>
223where
224 K: Copy + Clone + core::fmt::Debug + PartialEq + Eq,
225{
226 #[inline]
228 #[track_caller]
229 pub fn new(kind: K) -> Self {
230 let mut kinds = [None; DEPTH];
231 let mut locs = [None; DEPTH];
232 let reasons = [None; DEPTH];
233
234 kinds[0] = Some(kind);
235 locs[0] = Some(Location::caller());
236
237 Self {
238 kinds,
239 locations: locs,
240 reasons,
241 len: 1,
242 }
243 }
244
245 #[inline]
249 #[track_caller]
250 pub fn with_reason(kind: K, reason: LiteStr<REASON_LEN>) -> Self {
251 let mut kinds = [None; DEPTH];
252 let mut locs = [None; DEPTH];
253 let mut reasons = [None; DEPTH];
254
255 kinds[0] = Some(kind);
256 locs[0] = Some(Location::caller());
257 reasons[0] = if reason.as_bytes().is_empty() {
258 None
259 } else {
260 Some(reason)
261 };
262
263 Self {
264 kinds,
265 locations: locs,
266 reasons,
267 len: 1,
268 }
269 }
270
271 #[inline]
275 #[track_caller]
276 pub fn with_fmt(kind: K, args: core::fmt::Arguments<'_>) -> Self {
277 let mut kinds = [None; DEPTH];
278 let mut locs = [None; DEPTH];
279 let mut reasons = [None; DEPTH];
280
281 kinds[0] = Some(kind);
282 locs[0] = Some(Location::caller());
283 let mut reason = LiteStr::<REASON_LEN>::default();
284 let _ = write!(&mut reason, "{}", args);
285 reasons[0] = if reason.as_bytes().is_empty() {
286 None
287 } else {
288 Some(reason)
289 };
290
291 Self {
292 kinds,
293 locations: locs,
294 reasons,
295 len: 1,
296 }
297 }
298
299 #[inline]
301 pub fn depth(&self) -> u8 {
302 self.len
303 }
304
305 #[inline]
307 pub fn kind(&self) -> Option<K> {
308 if self.len == 0 {
309 None
310 } else {
311 let idx = (self.len as usize) - 1;
312 self.kinds[idx]
313 }
314 }
315
316 #[inline]
321 #[track_caller]
322 pub fn context(&mut self, kind: K, new_reason: LiteStr<REASON_LEN>) {
323 let idx = self.len as usize;
324 if idx < DEPTH {
325 self.reasons[idx] = if new_reason.as_bytes().is_empty() {
326 None
327 } else {
328 Some(new_reason)
329 };
330 self.push(kind, Location::caller());
331 }
332 }
333
334 #[inline]
339 #[track_caller]
340 pub fn context_fmt(&mut self, kind: K, args: core::fmt::Arguments<'_>) {
341 let idx = self.len as usize;
342 if idx < DEPTH {
343 let mut reason = LiteStr::<REASON_LEN>::default();
344 let _ = write!(&mut reason, "{}", args);
345
346 self.reasons[idx] = if reason.as_bytes().is_empty() {
347 None
348 } else {
349 Some(reason)
350 };
351 self.push(kind, Location::caller());
352 }
353 }
354
355 pub fn trace(&self) -> TraceIter<'_, K, DEPTH, REASON_LEN> {
361 TraceIter {
362 error: self,
363 pos: 0,
364 }
365 }
366
367 #[inline]
368 fn push(&mut self, kind: K, loc: &'static Location<'static>) {
369 if (self.len as usize) < DEPTH {
370 let idx = self.len as usize;
371 self.kinds[idx] = Some(kind);
372 self.locations[idx] = Some(loc);
373 self.len += 1;
374 }
375 }
376
377 #[inline]
384 pub fn get(
385 &self,
386 index: usize,
387 ) -> Option<(K, &'static Location<'static>, Option<&LiteStr<REASON_LEN>>)> {
388 let depth = self.len as usize;
389 if index >= depth {
390 return None;
391 }
392 let arr_idx = depth - 1 - index; Some((
394 self.kinds[arr_idx]?,
395 self.locations[arr_idx]?,
396 self.reasons[arr_idx].as_ref(),
397 ))
398 }
399
400 #[inline]
402 pub fn location(&self) -> Option<&'static Location<'static>> {
403 self.get(0).map(|(_, loc, _)| loc)
404 }
405
406 #[inline]
408 pub fn reason(&self) -> Option<&LiteStr<REASON_LEN>> {
409 self.get(0).and_then(|(_, _, r)| r)
410 }
411
412 #[inline]
414 pub fn root_kind(&self) -> Option<K> {
415 (self.len > 0).then(|| self.kinds[0]).flatten()
416 }
417
418 #[inline]
420 pub fn root_location(&self) -> Option<&'static Location<'static>> {
421 (self.len > 0).then(|| self.locations[0]).flatten()
422 }
423
424 #[inline]
426 pub fn root_reason(&self) -> Option<&LiteStr<REASON_LEN>> {
427 (self.len > 0).then(|| self.reasons[0].as_ref()).flatten()
428 }
429}
430
431impl<K, const DEPTH: usize, const REASON_LEN: usize> From<K> for AnErr<K, DEPTH, REASON_LEN>
432where
433 K: Copy + Clone + core::fmt::Debug + PartialEq + Eq,
434{
435 #[inline]
437 #[track_caller]
438 fn from(kind: K) -> Self {
439 Self::new(kind)
440 }
441}
442
443impl<K, const DEPTH: usize, const REASON_LEN: usize> core::fmt::Display
444 for AnErr<K, DEPTH, REASON_LEN>
445where
446 K: Copy + Clone + core::fmt::Debug + PartialEq + Eq,
447{
448 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
449 writeln!(f)?;
450 writeln!(f, "--")?;
451 writeln!(f, "Error:")?;
452
453 for (i, (kind, loc, reason_opt)) in self.trace().enumerate() {
454 let num = i + 1;
455
456 write!(f, " {:>2}. {:?}", num, kind)?;
457
458 if let Some(reason) = reason_opt {
459 write!(f, ": {}", reason.as_str())?;
460 }
461
462 writeln!(f, " @ {}:{}:{}", loc.file(), loc.line(), loc.column())?;
463 }
464
465 Ok(())
466 }
467}
468
469impl<K, const DEPTH: usize, const REASON_LEN: usize> fmt::Debug for AnErr<K, DEPTH, REASON_LEN>
470where
471 K: Copy + Clone + fmt::Debug + PartialEq + Eq,
472{
473 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
477 fmt::Display::fmt(self, f)
478 }
479}
480
481impl<K, const DEPTH: usize, const REASON_LEN: usize> core::error::Error
482 for AnErr<K, DEPTH, REASON_LEN>
483where
484 K: Copy + Clone + core::fmt::Debug + PartialEq + Eq,
485{
486}
487
488#[macro_export]
502macro_rules! an_err {
503 ($kind:expr) => {
505 $crate::AnErr::new($kind)
506 };
507
508 ($kind:expr, $fmt:literal $(, $arg:expr)* => $inner:expr $(,)?) => {{
510 let mut e = $inner;
511 e.context_fmt(
512 $kind,
513 format_args!($fmt $(, $arg)*)
514 );
515 e
516 }};
517
518 ($kind:expr, $fmt:literal $(, $arg:expr)* $(,)?) => {
520 $crate::AnErr::with_fmt($kind, format_args!($fmt $(, $arg)*))
521 };
522}
523
524#[cfg(feature = "wire")]
525impl<K, const DEPTH: usize, const REASON_LEN: usize> AnErr<K, DEPTH, REASON_LEN>
526where
527 K: Copy + Clone + core::fmt::Debug + PartialEq + Eq,
528{
529 pub fn to_wire_bytes<const PATH_LEN: usize>(
552 &self,
553 kind_to_u16: impl Fn(K) -> u16,
554 buf: &mut [u8],
555 ) -> Result<usize, ()> {
556 let needed = Self::wire_size::<PATH_LEN>();
557 if buf.len() < needed {
558 return Err(());
559 }
560
561 let mut offset = 0;
562
563 buf[offset] = 1; offset += 1;
566 buf[offset] = self.len;
567 offset += 1;
568
569 for i in 0..DEPTH {
570 if i < self.len as usize {
571 let kind_val = self.kinds[i].map_or(0, &kind_to_u16);
573 buf[offset..offset + 2].copy_from_slice(&kind_val.to_le_bytes());
574 offset += 2;
575
576 let defaultx = LiteStr::default();
578 let reason = self.reasons[i].as_ref().unwrap_or(&defaultx);
579 buf[offset..offset + REASON_LEN].copy_from_slice(&reason.bytes);
580 offset += REASON_LEN;
581
582 if let Some(loc) = self.locations[i] {
584 let file = LiteStr::<PATH_LEN>::new(loc.file());
585 buf[offset..offset + PATH_LEN].copy_from_slice(&file.bytes);
586 offset += PATH_LEN;
587
588 buf[offset..offset + 4].copy_from_slice(&loc.line().to_le_bytes());
589 offset += 4;
590 buf[offset..offset + 4].copy_from_slice(&loc.column().to_le_bytes());
591 offset += 4;
592 } else {
593 offset += PATH_LEN + 8; }
595 } else {
596 offset += 2 + REASON_LEN + PATH_LEN + 8;
598 }
599 }
600
601 Ok(needed)
602 }
603
604 pub const fn wire_size<const PATH_LEN: usize>() -> usize {
606 2 + DEPTH * (2 + REASON_LEN + PATH_LEN + 8)
607 }
608}
609
610#[cfg(feature = "wire")]
612#[derive(Debug, Clone, Copy, PartialEq, Eq)]
613pub struct WireLocation<const N: usize> {
614 pub file: LiteStr<N>,
615 pub line: u32,
616 pub column: u32,
617}
618
619#[cfg(feature = "wire")]
621#[derive(Debug, Clone, Copy, PartialEq, Eq)]
622pub struct WireErr<const DEPTH: usize = 3, const REASON_LEN: usize = 29, const FILE_LEN: usize = 80>
623{
624 pub len: u8,
625 pub kinds: [Option<u16>; DEPTH],
626 pub reasons: [Option<LiteStr<REASON_LEN>>; DEPTH],
627 pub locations: [Option<WireLocation<FILE_LEN>>; DEPTH],
628}
629
630#[cfg(feature = "wire")]
631impl<const DEPTH: usize, const REASON_LEN: usize, const FILE_LEN: usize>
632 WireErr<DEPTH, REASON_LEN, FILE_LEN>
633{
634 pub const fn wire_size() -> usize {
636 const fn compute_size<const D: usize, const R: usize, const F: usize>() -> usize {
637 2 + D * (2 + R + F + 8)
638 }
639 compute_size::<DEPTH, REASON_LEN, FILE_LEN>()
640 }
641
642 pub fn from_wire_bytes(bytes: &[u8]) -> Option<Self> {
647 if bytes.len() != Self::wire_size() {
648 return None;
649 }
650
651 let mut offset = 0;
652
653 let version = bytes[offset];
655 if version != 1 {
656 return None; }
658 offset += 1;
659
660 let len = bytes[offset];
661 if len == 0 || len as usize > DEPTH {
662 return None;
663 }
664 offset += 1;
665
666 let mut kinds = [None; DEPTH];
667 let mut reasons = [None; DEPTH];
668 let mut locations = [None; DEPTH];
669
670 for i in 0..(len as usize) {
671 let kind_bytes = <[u8; 2]>::try_from(&bytes[offset..offset + 2]).ok()?;
673 kinds[i] = Some(u16::from_le_bytes(kind_bytes));
674 offset += 2;
675
676 let reason_bytes = &bytes[offset..offset + REASON_LEN];
678 reasons[i] = Some(LiteStr::from_bytes(reason_bytes));
679 offset += REASON_LEN;
680
681 let file_bytes = &bytes[offset..offset + FILE_LEN];
683 let file = LiteStr::from_bytes(file_bytes);
684
685 offset += FILE_LEN;
686
687 let line_bytes = <[u8; 4]>::try_from(&bytes[offset..offset + 4]).ok()?;
688 let line = u32::from_le_bytes(line_bytes);
689 offset += 4;
690
691 let col_bytes = <[u8; 4]>::try_from(&bytes[offset..offset + 4]).ok()?;
692 let column = u32::from_le_bytes(col_bytes);
693 offset += 4;
694
695 locations[i] = Some(WireLocation { file, line, column });
696 }
697
698 Some(WireErr {
701 len,
702 kinds,
703 reasons,
704 locations,
705 })
706 }
707}
708
709#[cfg(feature = "alloc")]
710#[cfg(test)]
711mod tests {
712 use super::*;
713 use alloc::format;
714 use alloc::vec::Vec;
715
716 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
717 #[repr(u8)]
718 enum TestKind {
719 Root,
720 Context1,
721 Context2,
722 Parse,
723 Io,
724 }
725
726 fn r<const N: usize>(s: &str) -> LiteStr<N> {
728 LiteStr::new(s)
729 }
730
731 type E3 = AnErr<TestKind, 3, 29>;
734
735 #[test]
736 fn test_new_from_and_basic_properties() {
737 let e1: E3 = AnErr::new(TestKind::Root);
738 let e2: E3 = TestKind::Root.into();
739
740 assert_eq!(e1.depth(), e2.depth());
744 assert_eq!(e1.kind(), e2.kind());
745 assert_eq!(e1.depth(), 1);
746 assert_eq!(e1.kind(), Some(TestKind::Root));
747
748 let mut trace = e1.trace();
749 let (kind, _loc, reason) = trace.next().unwrap();
750 assert_eq!(kind, TestKind::Root);
751 assert!(reason.is_none());
752 assert!(trace.next().is_none());
753 }
754
755 #[test]
756 fn test_with_reason_and_with_fmt() {
757 let e: E3 = AnErr::with_reason(TestKind::Parse, r::<29>("bad token"));
759 assert_eq!(e.depth(), 1);
760
761 let items: Vec<_> = e.trace().collect();
762 assert_eq!(items[0].2.unwrap().as_str(), "bad token");
763
764 let e2: E3 = AnErr::with_fmt(
765 TestKind::Io,
766 format_args!("file not found: {}", "config.toml"),
767 );
768 let items2: Vec<_> = e2.trace().collect();
769 assert_eq!(items2[0].2.unwrap().as_str(), "file not found: config.toml");
770 }
771
772 #[test]
773 fn test_an_err_macro_all_forms() {
774 let e1: E3 = an_err!(TestKind::Root);
775 assert_eq!(e1.kind(), Some(TestKind::Root));
776
777 let e2: E3 = an_err!(TestKind::Parse, "unexpected {}", "EOF");
778 assert_eq!(
779 e2.trace().next().unwrap().2.unwrap().as_str(),
780 "unexpected EOF"
781 );
782
783 let inner: E3 = an_err!(TestKind::Parse, "bad data");
785 let outer: E3 = an_err!(TestKind::Io, "while reading file" => inner);
786
787 assert_eq!(outer.depth(), 2);
788 let mut t = outer.trace();
789 let (k1, _, r1) = t.next().unwrap();
790 assert_eq!(k1, TestKind::Io);
791 assert_eq!(r1.unwrap().as_str(), "while reading file");
792
793 let (k2, _, r2) = t.next().unwrap();
794 assert_eq!(k2, TestKind::Parse);
795 assert_eq!(r2.unwrap().as_str(), "bad data");
796 }
797
798 #[test]
799 fn test_context_and_context_fmt() {
800 let mut e: E3 = an_err!(TestKind::Root, "initial");
801 e.context(TestKind::Context1, r::<29>("level 1"));
802 e.context_fmt(TestKind::Context2, format_args!("level {}", 2));
803
804 assert_eq!(e.depth(), 3);
805
806 let trace: Vec<_> = e.trace().collect();
807 assert_eq!(trace[0].0, TestKind::Context2);
809 assert_eq!(trace[1].0, TestKind::Context1);
810 assert_eq!(trace[2].0, TestKind::Root);
811
812 assert_eq!(trace[0].2.unwrap().as_str(), "level 2");
813 assert_eq!(trace[1].2.unwrap().as_str(), "level 1");
814 assert_eq!(trace[2].2.unwrap().as_str(), "initial");
815 }
816
817 #[test]
818 fn test_max_depth_is_no_op() {
819 let mut e: E3 = an_err!(TestKind::Root);
820 for i in 0..10 {
821 e.context(TestKind::Context1, r::<29>(&format!("extra {i}")));
822 }
823 assert_eq!(e.depth(), 3); let trace: Vec<_> = e.trace().collect();
826 assert_eq!(trace.len(), 3);
827 assert_eq!(trace[0].0, TestKind::Context1); }
829
830 #[test]
831 fn test_empty_reason_becomes_none() {
832 let e: E3 = an_err!(TestKind::Parse, "");
833 let (_, _, reason) = e.trace().next().unwrap();
834 assert!(reason.is_none());
835
836 let mut e2: E3 = an_err!(TestKind::Root);
837 e2.context(TestKind::Io, r::<29>("")); let items: Vec<_> = e2.trace().collect();
839 assert!(items[0].2.is_none());
840 }
841
842 #[test]
843 fn test_trace_iter_order_exact_size_and_size_hint() {
844 let e: E3 = an_err!(TestKind::Root, "a" => an_err!(TestKind::Io, "b" => an_err!(TestKind::Parse, "c")));
845
846 let trace = e.trace();
847 assert_eq!(trace.len(), 3); assert_eq!(trace.size_hint(), (3, Some(3)));
849
850 let collected: Vec<_> = trace.collect();
851 assert_eq!(collected.len(), 3);
852 assert_eq!(collected[0].0, TestKind::Root); assert_eq!(collected[1].0, TestKind::Io);
854 assert_eq!(collected[2].0, TestKind::Parse); }
856
857 #[test]
858 fn test_kind_returns_most_recent() {
859 let mut e: E3 = an_err!(TestKind::Parse);
860 e.context(TestKind::Context1, r::<29>("ctx1"));
861 e.context(TestKind::Context2, r::<29>("ctx2"));
862
863 assert_eq!(e.kind(), Some(TestKind::Context2)); }
865
866 #[test]
867 fn test_display() {
868 let inner: E3 = an_err!(TestKind::Parse, "bad syntax");
869 let e: E3 = an_err!(TestKind::Io, "while loading config" => inner);
870
871 let display = format!("{}", e);
872 assert!(display.contains("--"));
873 assert!(display.contains("Error:"));
874 assert!(display.contains("Io"));
875 assert!(display.contains("while loading config"));
876 assert!(display.contains("Parse"));
877 assert!(display.contains("bad syntax"));
878 }
879
880 #[cfg(feature = "wire")]
881 type E4 = AnErr<TestKind, 4, 29>;
882 #[cfg(feature = "wire")]
883 use alloc::vec;
884
885 #[cfg(feature = "wire")]
886 #[test]
887 fn test_wire_roundtrip() {
888 let inner: E4 = an_err!(TestKind::Parse, "unexpected char");
889 let e: E4 = an_err!(TestKind::Io, "while processing file" => inner);
890
891 const FILE_LEN: usize = 64;
892 let wire_size = E4::wire_size::<FILE_LEN>();
893 let mut buf = vec![0u8; wire_size];
894
895 let written = e.to_wire_bytes::<FILE_LEN>(|k| k as u16, &mut buf).unwrap();
897 assert_eq!(written, wire_size);
898
899 let wire_err = WireErr::<4, 29, FILE_LEN>::from_wire_bytes(&buf[..written]).unwrap();
900
901 assert_eq!(wire_err.len, 2);
902
903 assert_eq!(wire_err.kinds[0], Some(TestKind::Parse as u16));
905 assert_eq!(wire_err.kinds[1], Some(TestKind::Io as u16));
906
907 assert_eq!(
908 wire_err.reasons[0].as_ref().unwrap().as_str(),
909 "unexpected char"
910 );
911 assert_eq!(
912 wire_err.reasons[1].as_ref().unwrap().as_str(),
913 "while processing file"
914 );
915 }
916
917 #[cfg(feature = "wire")]
918 #[test]
919 fn test_wire_invalid_cases() {
920 assert!(WireErr::<3, 29, 64>::from_wire_bytes(&[0u8; 10]).is_none());
922
923 let mut buf = vec![0u8; E4::wire_size::<64>()];
925 buf[0] = 99; assert!(WireErr::<4, 29, 64>::from_wire_bytes(&buf).is_none());
927 }
928}