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)]
169#[must_use = "this error should be handled or converted to a different type e.g `pub type DtErr = AnErr<MyError, 2, 49>;`"]
170pub struct AnErr<K, const DEPTH: usize = 3, const REASON_LEN: usize = 29>
171where
172 K: Copy + Clone + core::fmt::Debug + PartialEq + Eq,
173{
174 pub reasons: [Option<LiteStr<REASON_LEN>>; DEPTH],
177
178 pub locations: [Option<&'static Location<'static>>; DEPTH],
181
182 pub kinds: [Option<K>; DEPTH],
185
186 pub len: u8,
188}
189
190impl<K, const DEPTH: usize, const REASON_LEN: usize> AnErr<K, DEPTH, REASON_LEN>
191where
192 K: Copy + Clone + core::fmt::Debug + PartialEq + Eq,
193{
194 #[inline]
196 #[track_caller]
197 pub fn new(kind: K) -> Self {
198 let mut kinds = [None; DEPTH];
199 let mut locs = [None; DEPTH];
200 let reasons = [None; DEPTH];
201
202 kinds[0] = Some(kind);
203 locs[0] = Some(Location::caller());
204
205 Self {
206 kinds,
207 locations: locs,
208 reasons,
209 len: 1,
210 }
211 }
212
213 #[inline]
217 #[track_caller]
218 pub fn with_reason(kind: K, reason: LiteStr<REASON_LEN>) -> Self {
219 let mut kinds = [None; DEPTH];
220 let mut locs = [None; DEPTH];
221 let mut reasons = [None; DEPTH];
222
223 kinds[0] = Some(kind);
224 locs[0] = Some(Location::caller());
225 reasons[0] = if reason.len() == 0 {
226 None
227 } else {
228 Some(reason)
229 };
230
231 Self {
232 kinds,
233 locations: locs,
234 reasons,
235 len: 1,
236 }
237 }
238
239 #[inline]
243 #[track_caller]
244 pub fn with_fmt(kind: K, args: core::fmt::Arguments<'_>) -> Self {
245 let mut kinds = [None; DEPTH];
246 let mut locs = [None; DEPTH];
247 let mut reasons = [None; DEPTH];
248
249 kinds[0] = Some(kind);
250 locs[0] = Some(Location::caller());
251 let mut reason = LiteStr::<REASON_LEN>::default();
252 let _ = write!(&mut reason, "{}", args);
253 reasons[0] = if reason.len() == 0 {
254 None
255 } else {
256 Some(reason)
257 };
258
259 Self {
260 kinds,
261 locations: locs,
262 reasons,
263 len: 1,
264 }
265 }
266
267 #[inline]
269 pub fn depth(&self) -> u8 {
270 self.len
271 }
272
273 #[inline]
275 pub fn kind(&self) -> Option<K> {
276 if self.len == 0 {
277 None
278 } else {
279 let idx = (self.len as usize) - 1;
280 self.kinds[idx]
281 }
282 }
283
284 #[inline]
289 #[track_caller]
290 pub fn context(&mut self, kind: K, new_reason: LiteStr<REASON_LEN>) {
291 let idx = self.len as usize;
292 if idx < DEPTH {
293 self.reasons[idx] = if new_reason.len() == 0 {
294 None
295 } else {
296 Some(new_reason)
297 };
298 self.push(kind, Location::caller());
299 }
300 }
301
302 #[inline]
307 #[track_caller]
308 pub fn context_fmt(&mut self, kind: K, args: core::fmt::Arguments<'_>) {
309 let idx = self.len as usize;
310 if idx < DEPTH {
311 let mut reason = LiteStr::<REASON_LEN>::default();
312 let _ = write!(&mut reason, "{}", args);
313
314 self.reasons[idx] = if reason.len() == 0 {
315 None
316 } else {
317 Some(reason)
318 };
319 self.push(kind, Location::caller());
320 }
321 }
322
323 pub fn trace(&self) -> TraceIter<'_, K, DEPTH, REASON_LEN> {
329 TraceIter {
330 error: self,
331 pos: 0,
332 }
333 }
334
335 #[inline]
336 fn push(&mut self, kind: K, loc: &'static Location<'static>) {
337 if (self.len as usize) < DEPTH {
338 let idx = self.len as usize;
339 self.kinds[idx] = Some(kind);
340 self.locations[idx] = Some(loc);
341 self.len += 1;
342 }
343 }
344
345 #[inline]
352 pub fn get(
353 &self,
354 index: usize,
355 ) -> Option<(K, &'static Location<'static>, Option<&LiteStr<REASON_LEN>>)> {
356 let depth = self.len as usize;
357 if index >= depth {
358 return None;
359 }
360 let arr_idx = depth - 1 - index; Some((
362 self.kinds[arr_idx]?,
363 self.locations[arr_idx]?,
364 self.reasons[arr_idx].as_ref(),
365 ))
366 }
367
368 #[inline]
370 pub fn location(&self) -> Option<&'static Location<'static>> {
371 self.get(0).map(|(_, loc, _)| loc)
372 }
373
374 #[inline]
376 pub fn reason(&self) -> Option<&LiteStr<REASON_LEN>> {
377 self.get(0).and_then(|(_, _, r)| r)
378 }
379
380 #[inline]
382 pub fn root_kind(&self) -> Option<K> {
383 (self.len > 0).then(|| self.kinds[0]).flatten()
384 }
385
386 #[inline]
388 pub fn root_location(&self) -> Option<&'static Location<'static>> {
389 (self.len > 0).then(|| self.locations[0]).flatten()
390 }
391
392 #[inline]
394 pub fn root_reason(&self) -> Option<&LiteStr<REASON_LEN>> {
395 (self.len > 0).then(|| self.reasons[0].as_ref()).flatten()
396 }
397}
398
399impl<K, const DEPTH: usize, const REASON_LEN: usize> From<K> for AnErr<K, DEPTH, REASON_LEN>
400where
401 K: Copy + Clone + core::fmt::Debug + PartialEq + Eq,
402{
403 #[inline]
405 #[track_caller]
406 fn from(kind: K) -> Self {
407 Self::new(kind)
408 }
409}
410
411impl<K, const DEPTH: usize, const REASON_LEN: usize> core::fmt::Display
412 for AnErr<K, DEPTH, REASON_LEN>
413where
414 K: Copy + Clone + core::fmt::Debug + PartialEq + Eq,
415{
416 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
417 writeln!(f)?;
418 writeln!(f, "--")?;
419 writeln!(f, "Error:")?;
420
421 for (i, (kind, loc, reason_opt)) in self.trace().enumerate() {
422 let num = i + 1;
423
424 write!(f, " {:>2}. {:?}", num, kind)?;
425
426 if let Some(reason) = reason_opt {
427 if let Ok(s) = reason.as_str() {
428 write!(f, ": {}", s)?;
429 } else {
430 write!(f, ": <invalid ascii>")?;
431 }
432 }
433
434 writeln!(f, " @ {}:{}:{}", loc.file(), loc.line(), loc.column())?;
435 }
436
437 Ok(())
438 }
439}
440
441impl<K, const DEPTH: usize, const REASON_LEN: usize> fmt::Debug for AnErr<K, DEPTH, REASON_LEN>
442where
443 K: Copy + Clone + fmt::Debug + PartialEq + Eq,
444{
445 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
449 fmt::Display::fmt(self, f)
450 }
451}
452
453impl<K, const DEPTH: usize, const REASON_LEN: usize> core::error::Error
454 for AnErr<K, DEPTH, REASON_LEN>
455where
456 K: Copy + Clone + core::fmt::Debug + PartialEq + Eq,
457{
458}
459
460#[macro_export]
474macro_rules! an_err {
475 ($kind:expr) => {
477 $crate::AnErr::new($kind)
478 };
479
480 ($kind:expr, $fmt:literal $(, $arg:expr)* => $inner:expr $(,)?) => {{
482 let mut e = $inner;
483 e.context_fmt(
484 $kind,
485 format_args!($fmt $(, $arg)*)
486 );
487 e
488 }};
489
490 ($kind:expr, $fmt:literal $(, $arg:expr)* $(,)?) => {
492 $crate::AnErr::with_fmt($kind, format_args!($fmt $(, $arg)*))
493 };
494}
495
496#[cfg(feature = "wire")]
497impl<K, const DEPTH: usize, const REASON_LEN: usize> AnErr<K, DEPTH, REASON_LEN>
498where
499 K: Copy + Clone + core::fmt::Debug + PartialEq + Eq,
500{
501 pub fn to_wire_bytes<const PATH_LEN: usize>(
517 &self,
518 kind_to_u16: impl Fn(K) -> u16,
519 buf: &mut [u8],
520 ) -> Result<usize, ()> {
521 let needed = Self::wire_size::<PATH_LEN>();
522 if buf.len() < needed {
523 return Err(());
524 }
525
526 let mut offset = 0;
527
528 buf[offset] = 1; offset += 1;
531 buf[offset] = self.len;
532 offset += 1;
533
534 for i in 0..DEPTH {
535 if i < self.len as usize {
536 let kind_val = self.kinds[i].map_or(0, &kind_to_u16);
538 buf[offset..offset + 2].copy_from_slice(&kind_val.to_le_bytes());
539 offset += 2;
540
541 let defaultx = LiteStr::default();
543 let reason = self.reasons[i].as_ref().unwrap_or(&defaultx);
544 buf[offset..offset + REASON_LEN].copy_from_slice(&reason.to_bytes());
545 offset += REASON_LEN;
546
547 if let Some(loc) = self.locations[i] {
549 let file = LiteStr::<PATH_LEN>::from_str(loc.file());
550 buf[offset..offset + PATH_LEN].copy_from_slice(&file.to_bytes());
551 offset += PATH_LEN;
552
553 buf[offset..offset + 4].copy_from_slice(&loc.line().to_le_bytes());
554 offset += 4;
555 buf[offset..offset + 4].copy_from_slice(&loc.column().to_le_bytes());
556 offset += 4;
557 } else {
558 offset += PATH_LEN + 8; }
560 } else {
561 offset += 2 + REASON_LEN + PATH_LEN + 8;
563 }
564 }
565
566 Ok(needed)
567 }
568
569 pub const fn wire_size<const PATH_LEN: usize>() -> usize {
572 2 + DEPTH * (2 + REASON_LEN + PATH_LEN + 8)
573 }
574}
575
576#[cfg(feature = "wire")]
578#[derive(Debug, Clone, Copy, PartialEq, Eq)]
579pub struct WireLocation<const N: usize> {
580 pub file: LiteStr<N>,
581 pub line: u32,
582 pub column: u32,
583}
584
585#[cfg(feature = "wire")]
587#[derive(Debug, Clone, Copy, PartialEq, Eq)]
588pub struct WireErr<const DEPTH: usize = 3, const REASON_LEN: usize = 29, const FILE_LEN: usize = 80>
589{
590 pub len: u8,
591 pub kinds: [Option<u16>; DEPTH],
592 pub reasons: [Option<LiteStr<REASON_LEN>>; DEPTH],
593 pub locations: [Option<WireLocation<FILE_LEN>>; DEPTH],
594}
595
596#[cfg(feature = "wire")]
597impl<const DEPTH: usize, const REASON_LEN: usize, const FILE_LEN: usize>
598 WireErr<DEPTH, REASON_LEN, FILE_LEN>
599{
600 pub const fn wire_size() -> usize {
602 const fn compute_size<const D: usize, const R: usize, const F: usize>() -> usize {
603 2 + D * (2 + R + F + 8)
604 }
605 compute_size::<DEPTH, REASON_LEN, FILE_LEN>()
606 }
607
608 pub fn from_wire_bytes(bytes: &[u8]) -> Option<Self> {
613 if bytes.len() != Self::wire_size() {
614 return None;
615 }
616
617 let mut offset = 0;
618
619 let version = bytes[offset];
621 if version != 1 {
622 return None; }
624 offset += 1;
625
626 let len = bytes[offset];
627 if len == 0 || len as usize > DEPTH {
628 return None;
629 }
630 offset += 1;
631
632 let mut kinds = [None; DEPTH];
633 let mut reasons = [None; DEPTH];
634 let mut locations = [None; DEPTH];
635
636 for i in 0..(len as usize) {
637 let kind_bytes = <[u8; 2]>::try_from(&bytes[offset..offset + 2]).ok()?;
639 kinds[i] = Some(u16::from_le_bytes(kind_bytes));
640 offset += 2;
641
642 let reason_bytes = &bytes[offset..offset + REASON_LEN];
644 reasons[i] = LiteStr::from_bytes(reason_bytes).ok();
645 offset += REASON_LEN;
646
647 let file_bytes = &bytes[offset..offset + FILE_LEN];
649 let file = LiteStr::from_bytes(file_bytes).ok()?;
650
651 offset += FILE_LEN;
652
653 let line_bytes = <[u8; 4]>::try_from(&bytes[offset..offset + 4]).ok()?;
654 let line = u32::from_le_bytes(line_bytes);
655 offset += 4;
656
657 let col_bytes = <[u8; 4]>::try_from(&bytes[offset..offset + 4]).ok()?;
658 let column = u32::from_le_bytes(col_bytes);
659 offset += 4;
660
661 locations[i] = Some(WireLocation { file, line, column });
662 }
663
664 Some(WireErr {
667 len,
668 kinds,
669 reasons,
670 locations,
671 })
672 }
673}
674
675#[cfg(feature = "alloc")]
676#[cfg(test)]
677mod tests {
678 use super::*;
679 use alloc::format;
680 use alloc::vec::Vec;
681
682 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
683 #[repr(u8)]
684 enum TestKind {
685 Root,
686 Context1,
687 Context2,
688 Parse,
689 Io,
690 }
691
692 fn r<const N: usize>(s: &str) -> LiteStr<N> {
694 LiteStr::from_str(s)
695 }
696
697 type E3 = AnErr<TestKind, 3, 29>;
700
701 #[test]
702 fn test_new_from_and_basic_properties() {
703 let e1: E3 = AnErr::new(TestKind::Root);
704 let e2: E3 = TestKind::Root.into();
705
706 assert_eq!(e1.depth(), e2.depth());
710 assert_eq!(e1.kind(), e2.kind());
711 assert_eq!(e1.depth(), 1);
712 assert_eq!(e1.kind(), Some(TestKind::Root));
713
714 let mut trace = e1.trace();
715 let (kind, _loc, reason) = trace.next().unwrap();
716 assert_eq!(kind, TestKind::Root);
717 assert!(reason.is_none());
718 assert!(trace.next().is_none());
719 }
720
721 #[test]
722 fn test_with_reason_and_with_fmt() {
723 let e: E3 = AnErr::with_reason(TestKind::Parse, r::<29>("bad token"));
725 assert_eq!(e.depth(), 1);
726
727 let items: Vec<_> = e.trace().collect();
728 assert_eq!(items[0].2.unwrap().as_str().unwrap(), "bad token");
729
730 let e2: E3 = AnErr::with_fmt(
731 TestKind::Io,
732 format_args!("file not found: {}", "config.toml"),
733 );
734 let items2: Vec<_> = e2.trace().collect();
735 assert_eq!(
736 items2[0].2.unwrap().as_str().unwrap(),
737 "file not found: config.toml"
738 );
739 }
740
741 #[test]
742 fn test_an_err_macro_all_forms() {
743 let e1: E3 = an_err!(TestKind::Root);
744 assert_eq!(e1.kind(), Some(TestKind::Root));
745
746 let e2: E3 = an_err!(TestKind::Parse, "unexpected {}", "EOF");
747 assert_eq!(
748 e2.trace().next().unwrap().2.unwrap().as_str().unwrap(),
749 "unexpected EOF"
750 );
751
752 let inner: E3 = an_err!(TestKind::Parse, "bad data");
754 let outer: E3 = an_err!(TestKind::Io, "while reading file" => inner);
755
756 assert_eq!(outer.depth(), 2);
757 let mut t = outer.trace();
758 let (k1, _, r1) = t.next().unwrap();
759 assert_eq!(k1, TestKind::Io);
760 assert_eq!(r1.unwrap().as_str().unwrap(), "while reading file");
761
762 let (k2, _, r2) = t.next().unwrap();
763 assert_eq!(k2, TestKind::Parse);
764 assert_eq!(r2.unwrap().as_str().unwrap(), "bad data");
765 }
766
767 #[test]
768 fn test_context_and_context_fmt() {
769 let mut e: E3 = an_err!(TestKind::Root, "initial");
770 e.context(TestKind::Context1, r::<29>("level 1"));
771 e.context_fmt(TestKind::Context2, format_args!("level {}", 2));
772
773 assert_eq!(e.depth(), 3);
774
775 let trace: Vec<_> = e.trace().collect();
776 assert_eq!(trace[0].0, TestKind::Context2);
778 assert_eq!(trace[1].0, TestKind::Context1);
779 assert_eq!(trace[2].0, TestKind::Root);
780
781 assert_eq!(trace[0].2.unwrap().as_str().unwrap(), "level 2");
782 assert_eq!(trace[1].2.unwrap().as_str().unwrap(), "level 1");
783 assert_eq!(trace[2].2.unwrap().as_str().unwrap(), "initial");
784 }
785
786 #[test]
787 fn test_max_depth_is_no_op() {
788 let mut e: E3 = an_err!(TestKind::Root);
789 for i in 0..10 {
790 e.context(TestKind::Context1, r::<29>(&format!("extra {i}")));
791 }
792 assert_eq!(e.depth(), 3); let trace: Vec<_> = e.trace().collect();
795 assert_eq!(trace.len(), 3);
796 assert_eq!(trace[0].0, TestKind::Context1); }
798
799 #[test]
800 fn test_empty_reason_becomes_none() {
801 let e: E3 = an_err!(TestKind::Parse, "");
802 let (_, _, reason) = e.trace().next().unwrap();
803 assert!(reason.is_none());
804
805 let mut e2: E3 = an_err!(TestKind::Root);
806 e2.context(TestKind::Io, r::<29>("")); let items: Vec<_> = e2.trace().collect();
808 assert!(items[0].2.is_none());
809 }
810
811 #[test]
812 fn test_trace_iter_order_exact_size_and_size_hint() {
813 let e: E3 = an_err!(TestKind::Root, "a" => an_err!(TestKind::Io, "b" => an_err!(TestKind::Parse, "c")));
814
815 let trace = e.trace();
816 assert_eq!(trace.len(), 3); assert_eq!(trace.size_hint(), (3, Some(3)));
818
819 let collected: Vec<_> = trace.collect();
820 assert_eq!(collected.len(), 3);
821 assert_eq!(collected[0].0, TestKind::Root); assert_eq!(collected[1].0, TestKind::Io);
823 assert_eq!(collected[2].0, TestKind::Parse); }
825
826 #[test]
827 fn test_kind_returns_most_recent() {
828 let mut e: E3 = an_err!(TestKind::Parse);
829 e.context(TestKind::Context1, r::<29>("ctx1"));
830 e.context(TestKind::Context2, r::<29>("ctx2"));
831
832 assert_eq!(e.kind(), Some(TestKind::Context2)); }
834
835 #[test]
836 fn test_display() {
837 let inner: E3 = an_err!(TestKind::Parse, "bad syntax");
838 let e: E3 = an_err!(TestKind::Io, "while loading config" => inner);
839
840 let display = format!("{}", e);
841 assert!(display.contains("--"));
842 assert!(display.contains("Error:"));
843 assert!(display.contains("Io"));
844 assert!(display.contains("while loading config"));
845 assert!(display.contains("Parse"));
846 assert!(display.contains("bad syntax"));
847 }
848
849 #[cfg(feature = "wire")]
850 type E4 = AnErr<TestKind, 4, 29>;
851 #[cfg(feature = "wire")]
852 use alloc::vec;
853
854 #[cfg(feature = "wire")]
855 #[test]
856 fn test_wire_roundtrip() {
857 let inner: E4 = an_err!(TestKind::Parse, "unexpected char");
858 let e: E4 = an_err!(TestKind::Io, "while processing file" => inner);
859
860 const FILE_LEN: usize = 64;
861 let wire_size = E4::wire_size::<FILE_LEN>();
862 let mut buf = vec![0u8; wire_size];
863
864 let written = e.to_wire_bytes::<FILE_LEN>(|k| k as u16, &mut buf).unwrap();
866 assert_eq!(written, wire_size);
867
868 let wire_err = WireErr::<4, 29, FILE_LEN>::from_wire_bytes(&buf[..written]).unwrap();
869
870 assert_eq!(wire_err.len, 2);
871
872 assert_eq!(wire_err.kinds[0], Some(TestKind::Parse as u16));
874 assert_eq!(wire_err.kinds[1], Some(TestKind::Io as u16));
875
876 assert_eq!(
877 wire_err.reasons[0].as_ref().unwrap().as_str().unwrap(),
878 "unexpected char"
879 );
880 assert_eq!(
881 wire_err.reasons[1].as_ref().unwrap().as_str().unwrap(),
882 "while processing file"
883 );
884 }
885
886 #[cfg(feature = "wire")]
887 #[test]
888 fn test_wire_invalid_cases() {
889 assert!(WireErr::<3, 29, 64>::from_wire_bytes(&[0u8; 10]).is_none());
891
892 let mut buf = vec![0u8; E4::wire_size::<64>()];
894 buf[0] = 99; assert!(WireErr::<4, 29, 64>::from_wire_bytes(&buf).is_none());
896 }
897}