1#![no_std]
2#![warn(missing_docs)]
3#![doc = include_str!("../README.md")]
4
5pub use pseudo_backtrace_derive::StackError;
6#[doc(hidden)]
7pub mod private;
8
9#[derive(Debug, Clone)]
11pub enum Chain<'a> {
12 Stacked(&'a dyn StackError),
14 Std(&'a dyn core::error::Error),
16}
17
18impl<'a> Chain<'a> {
19 pub fn next(&self) -> Option<Chain<'a>> {
21 match self {
22 Chain::Stacked(stack_error) => stack_error.next(),
23 Chain::Std(error) => error.source().map(Chain::Std),
24 }
25 }
26
27 pub const fn inner(&'a self) -> &'a dyn core::error::Error {
29 match self {
30 Chain::Stacked(stack_error) => stack_error,
31 Chain::Std(error) => error,
32 }
33 }
34
35 pub fn location(&self) -> Option<&'static core::panic::Location<'static>> {
37 match self {
38 Chain::Stacked(stack_error) => Some(stack_error.location()),
39 _ => None,
40 }
41 }
42}
43
44impl core::fmt::Display for Chain<'_> {
45 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
46 match self {
47 Chain::Stacked(stack_error) => {
48 write!(f, "{}, at {}", stack_error, stack_error.location())
49 }
50 Chain::Std(error) => error.fmt(f),
51 }
52 }
53}
54
55impl core::error::Error for Chain<'_> {
56 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
57 self.inner().source()
58 }
59}
60
61impl<'a> IntoIterator for Chain<'a> {
62 type Item = Chain<'a>;
63 type IntoIter = Iter<'a>;
64
65 fn into_iter(self) -> Self::IntoIter {
66 Iter { stack: Some(self) }
67 }
68}
69
70impl<'a, E> From<&'a E> for Chain<'a>
71where
72 E: StackError + Sized,
73{
74 fn from(stack_error: &'a E) -> Self {
75 Chain::Stacked(stack_error)
76 }
77}
78
79pub trait StackError: core::error::Error {
81 fn location(&self) -> &'static core::panic::Location<'static>;
83 fn next<'a>(&'a self) -> Option<Chain<'a>>;
85 fn iter<'a>(&'a self) -> Iter<'a>
87 where
88 Self: Sized,
89 {
90 Iter::new(self)
91 }
92}
93
94#[derive(Debug, Clone)]
96pub struct Iter<'a> {
97 stack: Option<Chain<'a>>,
98}
99
100impl<'a> Iter<'a> {
101 const fn new<E>(source: &'a E) -> Self
102 where
103 E: StackError,
104 {
105 Iter {
106 stack: Some(Chain::Stacked(source)),
107 }
108 }
109}
110
111impl<'a> Iterator for Iter<'a> {
112 type Item = Chain<'a>;
113
114 fn next(&mut self) -> Option<Self::Item> {
115 match self.stack.take() {
116 Some(detail) => {
117 self.stack = detail.next();
118 Some(detail)
119 }
120 None => None,
121 }
122 }
123}
124
125#[derive(Debug)]
139pub struct LocatedError<E> {
140 source: E,
141 location: &'static core::panic::Location<'static>,
142}
143
144impl<E> LocatedError<E> {
145 pub fn into_inner(self) -> E {
147 self.source
148 }
149}
150
151impl<E> core::fmt::Display for LocatedError<E>
152where
153 E: core::fmt::Display,
154{
155 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
156 self.source.fmt(f)
157 }
158}
159
160impl<E> core::error::Error for LocatedError<E>
161where
162 E: core::error::Error,
163{
164 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
165 self.source.source()
166 }
167}
168
169impl<E> StackError for LocatedError<E>
170where
171 E: core::error::Error,
172{
173 fn location(&self) -> &'static core::panic::Location<'static> {
174 self.location
175 }
176
177 fn next<'a>(&'a self) -> Option<Chain<'a>> {
178 self.source.source().map(Chain::Std)
179 }
180}
181
182impl<E> From<E> for LocatedError<E> {
183 #[track_caller]
184 fn from(value: E) -> Self {
185 LocatedError {
186 source: value,
187 location: core::panic::Location::caller(),
188 }
189 }
190}
191
192#[derive(Debug, Clone)]
194pub struct ChainWriter<'a> {
195 std_limit: usize,
196 stack: Chain<'a>,
197}
198
199impl<'a> core::fmt::Display for ChainWriter<'a> {
200 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
201 let mut std_remaining = self.std_limit;
202 for (i, err) in self.stack.clone().into_iter().enumerate() {
203 if matches!(err, Chain::Std(_)) {
204 if std_remaining == 0 {
205 break;
206 }
207 std_remaining -= 1;
208 }
209
210 writeln!(f, "{}: {}", i, err)?;
211 }
212
213 Ok(())
214 }
215}
216
217pub trait StackErrorExt: StackError + Sized {
219 fn to_chain<'a>(&'a self) -> ChainWriter<'a> {
232 self.to_chain_with_limit(1)
233 }
234
235 fn to_chain_with_limit<'a>(&'a self, limit: usize) -> ChainWriter<'a> {
255 ChainWriter {
256 std_limit: limit,
257 stack: Chain::from(self),
258 }
259 }
260
261 fn last<'a>(&'a self) -> Chain<'a>
273 where
274 Self: Sized,
275 {
276 self.iter().last().unwrap_or(Chain::from(self))
277 }
278
279 fn last_stacked(&self) -> &dyn StackError {
292 self.iter()
293 .filter_map(|e| match e {
294 Chain::Stacked(stack_error) => Some(stack_error),
295 _ => None,
296 })
297 .last()
298 .unwrap_or(self)
299 }
300
301 fn first_std(&self) -> Option<&dyn core::error::Error> {
314 self.iter()
315 .filter_map(|e| match e {
316 Chain::Std(error) => Some(error),
317 _ => None,
318 })
319 .next()
320 }
321}
322
323impl<E: StackError> StackErrorExt for E {}
324
325#[cfg(test)]
326mod tests {
327 extern crate std;
328
329 use super::{Chain, StackError, StackErrorExt};
330 #[derive(Debug)]
331 struct NestedStd {
332 source: std::boxed::Box<dyn core::error::Error + 'static>,
333 }
334
335 impl NestedStd {
336 fn new<E>(source: E) -> Self
337 where
338 E: core::error::Error + 'static,
339 {
340 Self {
341 source: std::boxed::Box::new(source),
342 }
343 }
344
345 fn nest(self) -> Self {
346 Self {
347 source: std::boxed::Box::new(self),
348 }
349 }
350 }
351
352 impl core::fmt::Display for NestedStd {
353 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
354 self.source.fmt(f)
355 }
356 }
357
358 impl core::error::Error for NestedStd {
359 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
360 Some(self.source.as_ref())
361 }
362 }
363
364 #[derive(Debug)]
365 enum Stacked {
366 Stacked {
367 source: std::boxed::Box<Stacked>,
368 location: &'static core::panic::Location<'static>,
369 },
370 Std {
371 source: std::boxed::Box<dyn core::error::Error + 'static>,
372 location: &'static core::panic::Location<'static>,
373 },
374 }
375
376 impl Stacked {
377 fn new<E>(source: E) -> Self
378 where
379 E: core::error::Error + 'static,
380 {
381 Stacked::Std {
382 source: std::boxed::Box::new(source),
383 location: core::panic::Location::caller(),
384 }
385 }
386
387 #[track_caller]
388 fn stack(self) -> Self {
389 Self::Stacked {
390 source: std::boxed::Box::new(self),
391 location: core::panic::Location::caller(),
392 }
393 }
394 }
395
396 impl core::fmt::Display for Stacked {
397 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
398 match self {
399 Stacked::Stacked { .. } => "Stacked".fmt(f),
400 Stacked::Std { .. } => "Std".fmt(f),
401 }
402 }
403 }
404
405 impl core::error::Error for Stacked {
406 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
407 match self {
408 Stacked::Stacked { source, .. } => Some(source),
409 Stacked::Std { source, .. } => Some(source.as_ref()),
410 }
411 }
412 }
413
414 impl StackError for Stacked {
415 fn location(&self) -> &'static core::panic::Location<'static> {
416 match self {
417 Stacked::Stacked { location, .. } => location,
418 Stacked::Std { location, .. } => location,
419 }
420 }
421
422 fn next<'a>(&'a self) -> Option<crate::Chain<'a>> {
423 match self {
424 Stacked::Stacked { source, .. } => Some(Chain::Stacked(source.as_ref())),
425 Stacked::Std { source, .. } => Some(Chain::Std(source.as_ref())),
426 }
427 }
428 }
429
430 #[test]
431 fn stack_writer_limit() {
432 use std::string::ToString;
433 let a = std::io::Error::other("Error A");
434 let b = NestedStd::new(a);
435 let c = b.nest();
436 let d = Stacked::new(c);
437 let e = d.stack();
438 let f = e.stack();
439 let g = f.stack();
440
441 assert_eq!(g.iter().count(), 7);
442
443 let stack = g.to_chain().to_string();
444 assert_eq!(stack.lines().count(), 5);
445
446 let stack = g.to_chain_with_limit(usize::MAX).to_string();
447 std::println!("to_chain_with_limit: {stack}");
448 assert_eq!(stack.lines().count(), 7);
449 }
450}