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)]
199#[must_use = "this error should be handled or converted to a different type e.g `pub type DtErr = AnErr<MyError, 2, 49>;`"]
200pub struct AnErr<K, const DEPTH: usize = 3, const REASON_LEN: usize = 29>
201where
202 K: Copy + Clone + core::fmt::Debug + PartialEq + Eq,
203{
204 pub reasons: [Option<LiteStr<REASON_LEN>>; DEPTH],
207
208 pub locations: [Option<&'static Location<'static>>; DEPTH],
211
212 pub kinds: [Option<K>; DEPTH],
215
216 pub len: u8,
218}
219
220impl<K, const DEPTH: usize, const REASON_LEN: usize> AnErr<K, DEPTH, REASON_LEN>
221where
222 K: Copy + Clone + core::fmt::Debug + PartialEq + Eq,
223{
224 #[inline]
226 #[track_caller]
227 pub fn new(kind: K) -> Self {
228 let mut kinds = [None; DEPTH];
229 let mut locs = [None; DEPTH];
230 let reasons = [None; DEPTH];
231
232 kinds[0] = Some(kind);
233 locs[0] = Some(Location::caller());
234
235 Self {
236 kinds,
237 locations: locs,
238 reasons,
239 len: 1,
240 }
241 }
242
243 #[inline]
247 #[track_caller]
248 pub fn with_reason(kind: K, reason: LiteStr<REASON_LEN>) -> Self {
249 let mut kinds = [None; DEPTH];
250 let mut locs = [None; DEPTH];
251 let mut reasons = [None; DEPTH];
252
253 kinds[0] = Some(kind);
254 locs[0] = Some(Location::caller());
255 reasons[0] = if reason.as_bytes().is_empty() {
256 None
257 } else {
258 Some(reason)
259 };
260
261 Self {
262 kinds,
263 locations: locs,
264 reasons,
265 len: 1,
266 }
267 }
268
269 #[inline]
273 #[track_caller]
274 pub fn with_fmt(kind: K, args: core::fmt::Arguments<'_>) -> Self {
275 let mut kinds = [None; DEPTH];
276 let mut locs = [None; DEPTH];
277 let mut reasons = [None; DEPTH];
278
279 kinds[0] = Some(kind);
280 locs[0] = Some(Location::caller());
281 let mut reason = LiteStr::<REASON_LEN>::default();
282 let _ = write!(&mut reason, "{}", args);
283 reasons[0] = if reason.as_bytes().is_empty() {
284 None
285 } else {
286 Some(reason)
287 };
288
289 Self {
290 kinds,
291 locations: locs,
292 reasons,
293 len: 1,
294 }
295 }
296
297 #[inline]
299 pub fn depth(&self) -> u8 {
300 self.len
301 }
302
303 #[inline]
305 pub fn kind(&self) -> Option<K> {
306 if self.len == 0 {
307 None
308 } else {
309 let idx = (self.len as usize) - 1;
310 self.kinds[idx]
311 }
312 }
313
314 #[inline]
319 #[track_caller]
320 pub fn context(&mut self, kind: K, new_reason: LiteStr<REASON_LEN>) {
321 let idx = self.len as usize;
322 if idx < DEPTH {
323 self.reasons[idx] = if new_reason.as_bytes().is_empty() {
324 None
325 } else {
326 Some(new_reason)
327 };
328 self.push(kind, Location::caller());
329 }
330 }
331
332 #[inline]
337 #[track_caller]
338 pub fn context_fmt(&mut self, kind: K, args: core::fmt::Arguments<'_>) {
339 let idx = self.len as usize;
340 if idx < DEPTH {
341 let mut reason = LiteStr::<REASON_LEN>::default();
342 let _ = write!(&mut reason, "{}", args);
343
344 self.reasons[idx] = if reason.as_bytes().is_empty() {
345 None
346 } else {
347 Some(reason)
348 };
349 self.push(kind, Location::caller());
350 }
351 }
352
353 pub fn trace(&self) -> TraceIter<'_, K, DEPTH, REASON_LEN> {
359 TraceIter {
360 error: self,
361 pos: 0,
362 }
363 }
364
365 #[inline]
366 fn push(&mut self, kind: K, loc: &'static Location<'static>) {
367 if (self.len as usize) < DEPTH {
368 let idx = self.len as usize;
369 self.kinds[idx] = Some(kind);
370 self.locations[idx] = Some(loc);
371 self.len += 1;
372 }
373 }
374
375 #[inline]
382 pub fn get(
383 &self,
384 index: usize,
385 ) -> Option<(K, &'static Location<'static>, Option<&LiteStr<REASON_LEN>>)> {
386 let depth = self.len as usize;
387 if index >= depth {
388 return None;
389 }
390 let arr_idx = depth - 1 - index; Some((
392 self.kinds[arr_idx]?,
393 self.locations[arr_idx]?,
394 self.reasons[arr_idx].as_ref(),
395 ))
396 }
397
398 #[inline]
400 pub fn location(&self) -> Option<&'static Location<'static>> {
401 self.get(0).map(|(_, loc, _)| loc)
402 }
403
404 #[inline]
406 pub fn reason(&self) -> Option<&LiteStr<REASON_LEN>> {
407 self.get(0).and_then(|(_, _, r)| r)
408 }
409
410 #[inline]
412 pub fn root_kind(&self) -> Option<K> {
413 (self.len > 0).then(|| self.kinds[0]).flatten()
414 }
415
416 #[inline]
418 pub fn root_location(&self) -> Option<&'static Location<'static>> {
419 (self.len > 0).then(|| self.locations[0]).flatten()
420 }
421
422 #[inline]
424 pub fn root_reason(&self) -> Option<&LiteStr<REASON_LEN>> {
425 (self.len > 0).then(|| self.reasons[0].as_ref()).flatten()
426 }
427}
428
429impl<K, const DEPTH: usize, const REASON_LEN: usize> From<K> for AnErr<K, DEPTH, REASON_LEN>
430where
431 K: Copy + Clone + core::fmt::Debug + PartialEq + Eq,
432{
433 #[inline]
435 #[track_caller]
436 fn from(kind: K) -> Self {
437 Self::new(kind)
438 }
439}
440
441impl<K, const DEPTH: usize, const REASON_LEN: usize> core::fmt::Display
442 for AnErr<K, DEPTH, REASON_LEN>
443where
444 K: Copy + Clone + core::fmt::Debug + PartialEq + Eq,
445{
446 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
447 writeln!(f)?;
448 writeln!(f, "--")?;
449 writeln!(f, "Error:")?;
450
451 for (i, (kind, loc, reason_opt)) in self.trace().enumerate() {
452 let num = i + 1;
453
454 write!(f, " {:>2}. {:?}", num, kind)?;
455
456 if let Some(reason) = reason_opt {
457 write!(f, ": {}", reason.as_str())?;
458 }
459
460 writeln!(f, " @ {}:{}:{}", loc.file(), loc.line(), loc.column())?;
461 }
462
463 Ok(())
464 }
465}
466
467impl<K, const DEPTH: usize, const REASON_LEN: usize> fmt::Debug for AnErr<K, DEPTH, REASON_LEN>
468where
469 K: Copy + Clone + fmt::Debug + PartialEq + Eq,
470{
471 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
475 fmt::Display::fmt(self, f)
476 }
477}
478
479impl<K, const DEPTH: usize, const REASON_LEN: usize> core::error::Error
480 for AnErr<K, DEPTH, REASON_LEN>
481where
482 K: Copy + Clone + core::fmt::Debug + PartialEq + Eq,
483{
484}
485
486#[macro_export]
500macro_rules! an_err {
501 ($kind:expr) => {
503 $crate::AnErr::new($kind)
504 };
505
506 ($kind:expr, $fmt:literal $(, $arg:expr)* => $inner:expr $(,)?) => {{
508 let mut e = $inner;
509 e.context_fmt(
510 $kind,
511 format_args!($fmt $(, $arg)*)
512 );
513 e
514 }};
515
516 ($kind:expr, $fmt:literal $(, $arg:expr)* $(,)?) => {
518 $crate::AnErr::with_fmt($kind, format_args!($fmt $(, $arg)*))
519 };
520}
521
522#[cfg(feature = "wire")]
523impl<K, const DEPTH: usize, const REASON_LEN: usize> AnErr<K, DEPTH, REASON_LEN>
524where
525 K: Copy + Clone + core::fmt::Debug + PartialEq + Eq,
526{
527 pub fn to_wire_bytes<const PATH_LEN: usize>(
550 &self,
551 kind_to_u16: impl Fn(K) -> u16,
552 buf: &mut [u8],
553 ) -> Result<usize, ()> {
554 let needed = Self::wire_size::<PATH_LEN>();
555 if buf.len() < needed {
556 return Err(());
557 }
558
559 let mut offset = 0;
560
561 buf[offset] = 1; offset += 1;
564 buf[offset] = self.len;
565 offset += 1;
566
567 for i in 0..DEPTH {
568 if i < self.len as usize {
569 let kind_val = self.kinds[i].map_or(0, &kind_to_u16);
571 buf[offset..offset + 2].copy_from_slice(&kind_val.to_le_bytes());
572 offset += 2;
573
574 let defaultx = LiteStr::default();
576 let reason = self.reasons[i].as_ref().unwrap_or(&defaultx);
577 buf[offset..offset + REASON_LEN].copy_from_slice(&reason.bytes);
578 offset += REASON_LEN;
579
580 if let Some(loc) = self.locations[i] {
582 let file = LiteStr::<PATH_LEN>::new(loc.file());
583 buf[offset..offset + PATH_LEN].copy_from_slice(&file.bytes);
584 offset += PATH_LEN;
585
586 buf[offset..offset + 4].copy_from_slice(&loc.line().to_le_bytes());
587 offset += 4;
588 buf[offset..offset + 4].copy_from_slice(&loc.column().to_le_bytes());
589 offset += 4;
590 } else {
591 offset += PATH_LEN + 8; }
593 } else {
594 offset += 2 + REASON_LEN + PATH_LEN + 8;
596 }
597 }
598
599 Ok(needed)
600 }
601
602 pub const fn wire_size<const PATH_LEN: usize>() -> usize {
604 2 + DEPTH * (2 + REASON_LEN + PATH_LEN + 8)
605 }
606}
607
608#[cfg(feature = "wire")]
610#[derive(Debug, Clone, Copy, PartialEq, Eq)]
611pub struct WireLocation<const N: usize> {
612 pub file: LiteStr<N>,
613 pub line: u32,
614 pub column: u32,
615}
616
617#[cfg(feature = "wire")]
619#[derive(Debug, Clone, Copy, PartialEq, Eq)]
620pub struct WireErr<const DEPTH: usize = 3, const REASON_LEN: usize = 29, const FILE_LEN: usize = 80>
621{
622 pub len: u8,
623 pub kinds: [Option<u16>; DEPTH],
624 pub reasons: [Option<LiteStr<REASON_LEN>>; DEPTH],
625 pub locations: [Option<WireLocation<FILE_LEN>>; DEPTH],
626}
627
628#[cfg(feature = "wire")]
629impl<const DEPTH: usize, const REASON_LEN: usize, const FILE_LEN: usize>
630 WireErr<DEPTH, REASON_LEN, FILE_LEN>
631{
632 pub const fn wire_size() -> usize {
634 const fn compute_size<const D: usize, const R: usize, const F: usize>() -> usize {
635 2 + D * (2 + R + F + 8)
636 }
637 compute_size::<DEPTH, REASON_LEN, FILE_LEN>()
638 }
639
640 pub fn from_wire_bytes(bytes: &[u8]) -> Option<Self> {
645 if bytes.len() != Self::wire_size() {
646 return None;
647 }
648
649 let mut offset = 0;
650
651 let version = bytes[offset];
653 if version != 1 {
654 return None; }
656 offset += 1;
657
658 let len = bytes[offset];
659 if len == 0 || len as usize > DEPTH {
660 return None;
661 }
662 offset += 1;
663
664 let mut kinds = [None; DEPTH];
665 let mut reasons = [None; DEPTH];
666 let mut locations = [None; DEPTH];
667
668 for i in 0..(len as usize) {
669 let kind_bytes = <[u8; 2]>::try_from(&bytes[offset..offset + 2]).ok()?;
671 kinds[i] = Some(u16::from_le_bytes(kind_bytes));
672 offset += 2;
673
674 let reason_bytes = &bytes[offset..offset + REASON_LEN];
676 reasons[i] = Some(LiteStr::from_bytes(reason_bytes));
677 offset += REASON_LEN;
678
679 let file_bytes = &bytes[offset..offset + FILE_LEN];
681 let file = LiteStr::from_bytes(file_bytes);
682
683 offset += FILE_LEN;
684
685 let line_bytes = <[u8; 4]>::try_from(&bytes[offset..offset + 4]).ok()?;
686 let line = u32::from_le_bytes(line_bytes);
687 offset += 4;
688
689 let col_bytes = <[u8; 4]>::try_from(&bytes[offset..offset + 4]).ok()?;
690 let column = u32::from_le_bytes(col_bytes);
691 offset += 4;
692
693 locations[i] = Some(WireLocation { file, line, column });
694 }
695
696 Some(WireErr {
699 len,
700 kinds,
701 reasons,
702 locations,
703 })
704 }
705}
706
707#[cfg(feature = "alloc")]
708#[cfg(test)]
709mod tests {
710 use super::*;
711 use alloc::format;
712 use alloc::vec::Vec;
713
714 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
715 #[repr(u8)]
716 enum TestKind {
717 Root,
718 Context1,
719 Context2,
720 Parse,
721 Io,
722 }
723
724 fn r<const N: usize>(s: &str) -> LiteStr<N> {
726 LiteStr::new(s)
727 }
728
729 type E3 = AnErr<TestKind, 3, 29>;
732
733 #[test]
734 fn test_new_from_and_basic_properties() {
735 let e1: E3 = AnErr::new(TestKind::Root);
736 let e2: E3 = TestKind::Root.into();
737
738 assert_eq!(e1.depth(), e2.depth());
742 assert_eq!(e1.kind(), e2.kind());
743 assert_eq!(e1.depth(), 1);
744 assert_eq!(e1.kind(), Some(TestKind::Root));
745
746 let mut trace = e1.trace();
747 let (kind, _loc, reason) = trace.next().unwrap();
748 assert_eq!(kind, TestKind::Root);
749 assert!(reason.is_none());
750 assert!(trace.next().is_none());
751 }
752
753 #[test]
754 fn test_with_reason_and_with_fmt() {
755 let e: E3 = AnErr::with_reason(TestKind::Parse, r::<29>("bad token"));
757 assert_eq!(e.depth(), 1);
758
759 let items: Vec<_> = e.trace().collect();
760 assert_eq!(items[0].2.unwrap().as_str(), "bad token");
761
762 let e2: E3 = AnErr::with_fmt(
763 TestKind::Io,
764 format_args!("file not found: {}", "config.toml"),
765 );
766 let items2: Vec<_> = e2.trace().collect();
767 assert_eq!(items2[0].2.unwrap().as_str(), "file not found: config.toml");
768 }
769
770 #[test]
771 fn test_an_err_macro_all_forms() {
772 let e1: E3 = an_err!(TestKind::Root);
773 assert_eq!(e1.kind(), Some(TestKind::Root));
774
775 let e2: E3 = an_err!(TestKind::Parse, "unexpected {}", "EOF");
776 assert_eq!(
777 e2.trace().next().unwrap().2.unwrap().as_str(),
778 "unexpected EOF"
779 );
780
781 let inner: E3 = an_err!(TestKind::Parse, "bad data");
783 let outer: E3 = an_err!(TestKind::Io, "while reading file" => inner);
784
785 assert_eq!(outer.depth(), 2);
786 let mut t = outer.trace();
787 let (k1, _, r1) = t.next().unwrap();
788 assert_eq!(k1, TestKind::Io);
789 assert_eq!(r1.unwrap().as_str(), "while reading file");
790
791 let (k2, _, r2) = t.next().unwrap();
792 assert_eq!(k2, TestKind::Parse);
793 assert_eq!(r2.unwrap().as_str(), "bad data");
794 }
795
796 #[test]
797 fn test_context_and_context_fmt() {
798 let mut e: E3 = an_err!(TestKind::Root, "initial");
799 e.context(TestKind::Context1, r::<29>("level 1"));
800 e.context_fmt(TestKind::Context2, format_args!("level {}", 2));
801
802 assert_eq!(e.depth(), 3);
803
804 let trace: Vec<_> = e.trace().collect();
805 assert_eq!(trace[0].0, TestKind::Context2);
807 assert_eq!(trace[1].0, TestKind::Context1);
808 assert_eq!(trace[2].0, TestKind::Root);
809
810 assert_eq!(trace[0].2.unwrap().as_str(), "level 2");
811 assert_eq!(trace[1].2.unwrap().as_str(), "level 1");
812 assert_eq!(trace[2].2.unwrap().as_str(), "initial");
813 }
814
815 #[test]
816 fn test_max_depth_is_no_op() {
817 let mut e: E3 = an_err!(TestKind::Root);
818 for i in 0..10 {
819 e.context(TestKind::Context1, r::<29>(&format!("extra {i}")));
820 }
821 assert_eq!(e.depth(), 3); let trace: Vec<_> = e.trace().collect();
824 assert_eq!(trace.len(), 3);
825 assert_eq!(trace[0].0, TestKind::Context1); }
827
828 #[test]
829 fn test_empty_reason_becomes_none() {
830 let e: E3 = an_err!(TestKind::Parse, "");
831 let (_, _, reason) = e.trace().next().unwrap();
832 assert!(reason.is_none());
833
834 let mut e2: E3 = an_err!(TestKind::Root);
835 e2.context(TestKind::Io, r::<29>("")); let items: Vec<_> = e2.trace().collect();
837 assert!(items[0].2.is_none());
838 }
839
840 #[test]
841 fn test_trace_iter_order_exact_size_and_size_hint() {
842 let e: E3 = an_err!(TestKind::Root, "a" => an_err!(TestKind::Io, "b" => an_err!(TestKind::Parse, "c")));
843
844 let trace = e.trace();
845 assert_eq!(trace.len(), 3); assert_eq!(trace.size_hint(), (3, Some(3)));
847
848 let collected: Vec<_> = trace.collect();
849 assert_eq!(collected.len(), 3);
850 assert_eq!(collected[0].0, TestKind::Root); assert_eq!(collected[1].0, TestKind::Io);
852 assert_eq!(collected[2].0, TestKind::Parse); }
854
855 #[test]
856 fn test_kind_returns_most_recent() {
857 let mut e: E3 = an_err!(TestKind::Parse);
858 e.context(TestKind::Context1, r::<29>("ctx1"));
859 e.context(TestKind::Context2, r::<29>("ctx2"));
860
861 assert_eq!(e.kind(), Some(TestKind::Context2)); }
863
864 #[test]
865 fn test_display() {
866 let inner: E3 = an_err!(TestKind::Parse, "bad syntax");
867 let e: E3 = an_err!(TestKind::Io, "while loading config" => inner);
868
869 let display = format!("{}", e);
870 assert!(display.contains("--"));
871 assert!(display.contains("Error:"));
872 assert!(display.contains("Io"));
873 assert!(display.contains("while loading config"));
874 assert!(display.contains("Parse"));
875 assert!(display.contains("bad syntax"));
876 }
877
878 #[cfg(feature = "wire")]
879 type E4 = AnErr<TestKind, 4, 29>;
880 #[cfg(feature = "wire")]
881 use alloc::vec;
882
883 #[cfg(feature = "wire")]
884 #[test]
885 fn test_wire_roundtrip() {
886 let inner: E4 = an_err!(TestKind::Parse, "unexpected char");
887 let e: E4 = an_err!(TestKind::Io, "while processing file" => inner);
888
889 const FILE_LEN: usize = 64;
890 let wire_size = E4::wire_size::<FILE_LEN>();
891 let mut buf = vec![0u8; wire_size];
892
893 let written = e.to_wire_bytes::<FILE_LEN>(|k| k as u16, &mut buf).unwrap();
895 assert_eq!(written, wire_size);
896
897 let wire_err = WireErr::<4, 29, FILE_LEN>::from_wire_bytes(&buf[..written]).unwrap();
898
899 assert_eq!(wire_err.len, 2);
900
901 assert_eq!(wire_err.kinds[0], Some(TestKind::Parse as u16));
903 assert_eq!(wire_err.kinds[1], Some(TestKind::Io as u16));
904
905 assert_eq!(
906 wire_err.reasons[0].as_ref().unwrap().as_str(),
907 "unexpected char"
908 );
909 assert_eq!(
910 wire_err.reasons[1].as_ref().unwrap().as_str(),
911 "while processing file"
912 );
913 }
914
915 #[cfg(feature = "wire")]
916 #[test]
917 fn test_wire_invalid_cases() {
918 assert!(WireErr::<3, 29, 64>::from_wire_bytes(&[0u8; 10]).is_none());
920
921 let mut buf = vec![0u8; E4::wire_size::<64>()];
923 buf[0] = 99; assert!(WireErr::<4, 29, 64>::from_wire_bytes(&buf).is_none());
925 }
926}