1use crate::Metadata;
2
3macro_rules! classification_predicates {
5 () => {
6 pub fn is_retryable(&self) -> bool {
12 self.classify().is_retryable()
13 }
14
15 pub fn is_resource_exhausted(&self) -> bool {
22 self.classify().is_resource_exhausted()
23 }
24
25 pub fn can_retry(&self) -> bool {
33 self.classify().can_retry()
34 }
35
36 pub fn can_retry_lenient(&self) -> bool {
42 self.classify().can_retry_lenient()
43 }
44
45 pub fn is_corrupted(&self) -> bool {
47 self.classify().is_corrupted()
48 }
49
50 pub fn is_not_found(&self) -> bool {
52 self.classify().is_not_found()
53 }
54
55 pub fn is_validation(&self) -> bool {
57 self.classify().is_validation()
58 }
59 };
60}
61
62#[derive(Clone, Copy, Debug)]
75pub struct DisplaySource<'a> {
76 error: &'a (dyn std::error::Error + 'static),
77 location: Option<&'static std::panic::Location<'static>>,
78}
79
80impl<'a> DisplaySource<'a> {
81 pub fn error(&self) -> &'a (dyn std::error::Error + 'static) {
83 self.error
84 }
85
86 pub fn location(&self) -> Option<&'static std::panic::Location<'static>> {
88 self.location
89 }
90}
91
92impl std::fmt::Display for DisplaySource<'_> {
93 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
94 std::fmt::Display::fmt(self.error, f)?;
95 if !f.alternate()
96 && let Some(location) = self.location
97 {
98 crate::write_location(f, location)?;
99 }
100 Ok(())
101 }
102}
103
104impl crate::Error {
105 pub fn iter_errors(&self) -> impl Iterator<Item = &(dyn std::error::Error + 'static)> + '_ {
112 self.iter_errors_with_locations().map(|source| source.error)
113 }
114
115 pub fn iter_errors_with_locations(&self) -> impl Iterator<Item = DisplaySource<'_>> + '_ {
119 Errors::new(self.iter_root()).filter(|source| !is_transparent_marker(source.error))
120 }
121
122 pub fn downcast_any_ref<T: std::error::Error + 'static>(&self) -> Option<&T> {
125 self.iter_errors().find_map(|error| error.downcast_ref())
126 }
127
128 pub fn probable_cause(&self) -> &(dyn std::error::Error + 'static) {
134 self.iter_root().probable_cause().unwrap_or_else(|| self.error())
135 }
136
137 pub fn metadata(&self) -> impl Iterator<Item = &Metadata> + '_ {
144 self.iter_errors()
145 .filter_map(|error| error.downcast_ref::<crate::Message>())
146 .map(|error| &error.values)
147 .filter(|values| !values.is_empty())
148 }
149
150 pub fn classify(&self) -> Classifications<'_> {
155 classify(self)
156 }
157
158 classification_predicates!();
159}
160
161impl<E: std::error::Error + Send + Sync + 'static> crate::Exn<E> {
168 pub fn classify(&self) -> Classifications<'_> {
174 Classifications(Errors::new(Node::Frame(self.frame())))
175 }
176
177 classification_predicates!();
178}
179
180#[derive(Clone, Copy, Debug, Eq, PartialEq)]
182#[non_exhaustive]
183pub enum Class {
184 Validation,
186 Corruption,
188 NotFound,
190 Retryable,
192 ResourceExhaustion(crate::ResourceExhaustionKind),
194 Io(std::io::ErrorKind),
196 Tagged(&'static str),
201}
202
203#[derive(Clone, Copy, Debug)]
205pub struct Classification<'a> {
206 class: Class,
207 error: &'a (dyn std::error::Error + 'static),
208}
209
210pub fn classify<'a>(err: &'a (dyn std::error::Error + 'static)) -> Classifications<'a> {
218 Classifications(Errors::new(err.downcast_ref::<crate::Error>().map_or(
219 Node::Source {
220 error: err,
221 location: None,
222 },
223 crate::Error::iter_root,
224 )))
225}
226
227pub struct Classifications<'a>(Errors<'a>);
229
230impl<'a> Iterator for Classifications<'a> {
231 type Item = Classification<'a>;
232
233 fn next(&mut self) -> Option<Self::Item> {
234 self.0.find_map(|source| classify_one(source.error))
235 }
236}
237
238impl Classifications<'_> {
239 pub fn is_retryable(self) -> bool {
241 self.has(Class::Retryable)
242 }
243
244 pub fn can_retry(mut self) -> bool {
246 self.any(|classification| class_can_retry(classification.class()))
247 }
248
249 pub fn can_retry_lenient(mut self) -> bool {
251 self.any(classification_can_retry_lenient)
252 }
253
254 pub fn is_not_found(self) -> bool {
256 self.has(Class::NotFound)
257 }
258
259 pub fn is_validation(self) -> bool {
261 self.has(Class::Validation)
262 }
263
264 pub fn is_corrupted(self) -> bool {
266 self.has(Class::Corruption)
267 }
268
269 pub fn is_resource_exhausted(mut self) -> bool {
271 self.any(|classification| matches!(classification.class(), Class::ResourceExhaustion(_)))
272 }
273
274 pub fn has(mut self, class: Class) -> bool {
276 self.any(|classification| classification.class() == class)
277 }
278}
279
280impl<'a> Classification<'a> {
281 pub fn class(&self) -> Class {
283 self.class
284 }
285
286 pub fn error(&self) -> &'a (dyn std::error::Error + 'static) {
288 self.error
289 }
290
291 pub fn io_kind(&self) -> Option<std::io::ErrorKind> {
293 self.error.downcast_ref::<std::io::Error>().map(std::io::Error::kind)
294 }
295}
296
297fn classify_one<'a>(error: &'a (dyn std::error::Error + 'static)) -> Option<Classification<'a>> {
298 let class = if let Some(marker) = error.downcast_ref::<crate::ClassificationMarker>() {
299 marker.class()
300 } else if let Some(error) = error.downcast_ref::<crate::Message>() {
301 error.class?
302 } else if error.is::<std::collections::TryReserveError>() {
303 Class::ResourceExhaustion(crate::ResourceExhaustionKind::AllocationFailure)
304 } else {
305 let error = error.downcast_ref::<std::io::Error>()?;
306 match error.kind() {
307 std::io::ErrorKind::NotFound => Class::NotFound,
308 std::io::ErrorKind::OutOfMemory => {
309 Class::ResourceExhaustion(crate::ResourceExhaustionKind::AllocationFailure)
310 }
311 kind => Class::Io(kind),
312 }
313 };
314 Some(Classification { class, error })
315}
316
317fn class_can_retry(class: Class) -> bool {
318 matches!(
319 class,
320 Class::Retryable | Class::Io(std::io::ErrorKind::Interrupted | std::io::ErrorKind::TimedOut)
321 )
322}
323
324fn classification_can_retry_lenient(classification: Classification<'_>) -> bool {
325 class_can_retry(classification.class())
326 || classification.io_kind().is_some_and(|kind| {
327 use std::io::ErrorKind::*;
328 matches!(
329 kind,
330 UnexpectedEof
331 | OutOfMemory
332 | BrokenPipe
333 | AddrInUse
334 | ConnectionAborted
335 | ConnectionReset
336 | ConnectionRefused
337 )
338 })
339}
340
341#[derive(Clone, Copy)]
342enum Node<'a> {
343 Frame(&'a crate::exn::Frame),
344 Source {
345 error: &'a (dyn std::error::Error + 'static),
346 location: Option<&'static std::panic::Location<'static>>,
347 },
348 #[cfg(all(feature = "auto-chain-error", not(feature = "tree-error")))]
349 Chain {
350 node: &'a crate::types::ChainedError,
351 index: usize,
352 cursor: Option<usize>,
353 },
354}
355
356impl<'a> Node<'a> {
357 fn display(self) -> DisplaySource<'a> {
358 let (error, location) = match self {
359 Node::Frame(frame) => (
360 frame.error() as &(dyn std::error::Error + 'static),
361 Some(frame.location()),
362 ),
363 Node::Source { error, location } => (error, location),
364 #[cfg(all(feature = "auto-chain-error", not(feature = "tree-error")))]
365 Node::Chain { node, .. } => (node.err.error(), node.err.has_frame_location().then_some(node.location)),
366 };
367 DisplaySource { error, location }
368 }
369
370 fn children(self) -> std::collections::VecDeque<Node<'a>> {
371 #[cfg(all(feature = "auto-chain-error", not(feature = "tree-error")))]
373 let root = match self {
374 Node::Chain { node, index, .. } => Node::Chain {
375 node,
376 index,
377 cursor: None,
378 },
379 root => root,
380 };
381 #[cfg(any(feature = "tree-error", not(feature = "auto-chain-error")))]
382 let root = self;
383 let mut traversal = Errors::new(root);
384 traversal.children(root);
385 traversal.pending
386 }
387
388 fn probable_cause(self) -> Option<&'a (dyn std::error::Error + 'static)> {
389 let mut node = self;
390 let mut cause = None;
392 loop {
393 let mut pending = node.children();
394 let mut only_child = None;
395 while let Some(child) = pending.pop_front() {
396 if is_transparent_marker(child.display().error) {
397 pending.extend(child.children());
399 } else if only_child.replace(child).is_some() {
400 return cause;
401 }
402 }
403 node = match only_child {
404 Some(child) => child,
405 None => return cause,
406 };
407 cause = Some(node.display().error);
408 }
409 }
410}
411
412struct Errors<'a> {
413 root: Option<Node<'a>>,
414 previous: Option<Node<'a>>,
415 pending: std::collections::VecDeque<Node<'a>>,
416 #[cfg(all(feature = "auto-chain-error", not(feature = "tree-error")))]
417 chains: Vec<(usize, Option<&'a crate::types::ChainedError>)>,
418}
419
420impl<'a> Errors<'a> {
421 fn new(root: Node<'a>) -> Self {
422 Errors {
423 root: Some(root),
424 previous: None,
425 pending: Default::default(),
426 #[cfg(all(feature = "auto-chain-error", not(feature = "tree-error")))]
427 chains: Vec::new(),
428 }
429 }
430
431 fn source(
432 &mut self,
433 error: &'a (dyn std::error::Error + 'static),
434 location: Option<&'static std::panic::Location<'static>>,
435 ) {
436 if let Some(error) = error.downcast_ref::<crate::Error>() {
437 self.pending.push_back(error.iter_root());
438 } else if let Some(source) = native_source(error) {
439 self.pending.push_back(Node::Source {
440 error: source,
441 location: location.filter(|_| is_transparent_marker(error)),
442 });
443 }
444 }
445
446 fn children(&mut self, node: Node<'a>) {
447 match node {
448 Node::Frame(frame) => {
449 self.source(frame.error(), Some(frame.location()));
450 self.pending.extend(frame.children().iter().map(Node::Frame));
451 }
452 Node::Source { error, location } => self.source(error, location),
453 #[cfg(all(feature = "auto-chain-error", not(feature = "tree-error")))]
454 Node::Chain { node, index, cursor } => {
455 if let Some(error) = node.err.error().downcast_ref::<crate::Error>() {
456 self.pending.push_back(error.iter_root());
457 }
458 let cursor = match cursor {
459 Some(cursor) => cursor,
460 None if node.source.is_none() => return,
461 None => {
462 self.chains.push((index + 1, node.source.as_deref()));
463 self.chains.len() - 1
464 }
465 };
466 let (child_index, next) = &mut self.chains[cursor];
469 while let Some(child) = next.filter(|child| child.logical_parent.is_some_and(|parent| parent < index)) {
471 *child_index += 1;
472 *next = child.source.as_deref();
473 }
474 while let Some(child) = next.filter(|child| child.logical_parent == Some(index)) {
475 self.pending.push_back(Node::Chain {
476 node: child,
477 index: *child_index,
478 cursor: Some(cursor),
479 });
480 *child_index += 1;
481 *next = child.source.as_deref();
482 }
483 }
484 }
485 }
486}
487
488impl<'a> Iterator for Errors<'a> {
489 type Item = DisplaySource<'a>;
490
491 fn next(&mut self) -> Option<Self::Item> {
492 if let Some(previous) = self.previous.take() {
494 self.children(previous);
495 }
496 let node = self.root.take().or_else(|| self.pending.pop_front())?;
497 self.previous = Some(node);
498 Some(node.display())
499 }
500}
501
502impl crate::exn::Frame {
503 pub(crate) fn probable_cause_inner(&self) -> Option<&(dyn std::error::Error + 'static)> {
504 Node::Frame(self).probable_cause()
505 }
506
507 pub(crate) fn iter_errors_with_locations(&self) -> impl Iterator<Item = DisplaySource<'_>> + '_ {
508 Errors::new(Node::Frame(self)).filter(|source| !is_transparent_marker(source.error))
509 }
510}
511
512#[cfg(any(feature = "tree-error", not(feature = "auto-chain-error")))]
513mod _impl {
514 use crate::{Error, Exn};
515 use std::fmt::Formatter;
516
517 impl Error {
519 pub fn error(&self) -> &(dyn std::error::Error + 'static) {
524 self.inner.frame().error()
525 }
526
527 pub(super) fn iter_root(&self) -> super::Node<'_> {
528 super::Node::Frame(self.inner.frame())
529 }
530 }
531
532 pub(crate) enum Inner {
533 ExnAsError(Box<crate::exn::Frame>),
534 Exn(Box<crate::exn::Frame>),
535 }
536
537 impl Inner {
538 pub(crate) fn frame(&self) -> &crate::exn::Frame {
539 match self {
540 Inner::ExnAsError(f) | Inner::Exn(f) => f,
541 }
542 }
543 }
544
545 impl Error {
546 #[track_caller]
548 pub fn from_error(error: impl std::error::Error + Send + Sync + 'static) -> Self {
549 Error {
550 inner: Inner::ExnAsError(Exn::new(error).into()),
551 }
552 }
553
554 #[track_caller]
556 pub fn from_boxed(error: Box<dyn std::error::Error + Send + Sync + 'static>) -> Self {
557 Self::from_error(crate::exn::Untyped::from_boxed(error))
558 }
559 }
560
561 impl std::fmt::Display for Error {
562 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
563 match &self.inner {
564 Inner::ExnAsError(err) => std::fmt::Display::fmt(err.error(), f),
565 Inner::Exn(frame) => std::fmt::Display::fmt(frame, f),
566 }
567 }
568 }
569
570 impl std::fmt::Debug for Error {
571 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
572 match &self.inner {
573 Inner::ExnAsError(err) => std::fmt::Debug::fmt(err.error(), f),
574 Inner::Exn(frame) => std::fmt::Debug::fmt(frame, f),
575 }
576 }
577 }
578
579 impl std::error::Error for Error {
580 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
582 match &self.inner {
583 Inner::ExnAsError(frame) | Inner::Exn(frame) => {
584 let error = frame.error();
585 (!error.is::<Error>())
586 .then(|| super::native_source(error))
587 .flatten()
588 .or_else(|| frame.children().first().map(|frame| frame.error() as _))
589 }
590 }
591 }
592 }
593
594 impl<E> From<Exn<E>> for Error
595 where
596 E: std::error::Error + Send + Sync + 'static,
597 {
598 fn from(err: Exn<E>) -> Self {
599 Error {
600 inner: Inner::Exn(err.into()),
601 }
602 }
603 }
604}
605#[cfg(any(feature = "tree-error", not(feature = "auto-chain-error")))]
606pub(super) use _impl::Inner;
607
608#[cfg(all(feature = "auto-chain-error", not(feature = "tree-error")))]
609mod _impl {
610 use crate::{Error, Exn};
611 use std::fmt::Formatter;
612
613 impl Error {
615 pub fn error(&self) -> &(dyn std::error::Error + 'static) {
620 self.inner.err.error()
621 }
622
623 pub(super) fn iter_root(&self) -> super::Node<'_> {
624 super::Node::Chain {
625 node: &self.inner,
626 index: 0,
627 cursor: None,
628 }
629 }
630 }
631
632 impl Error {
633 #[track_caller]
635 pub fn from_error(error: impl std::error::Error + Send + Sync + 'static) -> Self {
636 Error {
637 inner: Exn::new(error).into_chain(),
638 }
639 }
640
641 #[track_caller]
643 pub fn from_boxed(error: Box<dyn std::error::Error + Send + Sync + 'static>) -> Self {
644 Self::from_error(crate::exn::Untyped::from_boxed(error))
645 }
646 }
647
648 impl std::fmt::Display for Error {
649 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
650 if super::is_transparent_marker(self.error())
651 && let Some(diagnostic) = self.iter_errors_with_locations().next()
652 {
653 return std::fmt::Display::fmt(&diagnostic, f);
654 }
655 std::fmt::Display::fmt(&self.inner, f)
656 }
657 }
658
659 impl std::fmt::Debug for Error {
660 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
661 if super::is_transparent_marker(self.error())
662 && let Some(diagnostic) = self.iter_errors().next()
663 {
664 return std::fmt::Debug::fmt(diagnostic, f);
665 }
666 std::fmt::Debug::fmt(&self.inner, f)
667 }
668 }
669
670 impl std::error::Error for Error {
671 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
673 self.inner.source()
674 }
675 }
676
677 impl<E> From<Exn<E>> for Error
678 where
679 E: std::error::Error + Send + Sync + 'static,
680 {
681 fn from(err: Exn<E>) -> Self {
682 Error {
683 inner: err.into_chain(),
684 }
685 }
686 }
687}
688
689pub(crate) fn native_source<'a>(
691 err: &'a (dyn std::error::Error + 'static),
692) -> Option<&'a (dyn std::error::Error + 'static)> {
693 match err.downcast_ref::<std::io::Error>() {
694 Some(err) => err.get_ref().map(|err| err as _),
695 None => err.source(),
696 }
697}
698
699pub(crate) fn is_transparent_marker(mut error: &(dyn std::error::Error + 'static)) -> bool {
700 while let Some(nested) = error.downcast_ref::<crate::Error>() {
701 error = nested.error();
702 }
703 error.is::<crate::ClassificationMarker>()
704}