nu_protocol/errors/
chained_error.rs

1use super::shell_error::ShellError;
2use crate::Span;
3use miette::{LabeledSpan, Severity, SourceCode};
4use thiserror::Error;
5
6/// An error struct that contains source errors.
7///
8/// However, it's a bit special; if the error is constructed for the first time using
9/// [`ChainedError::new`], it will behave the same as the single source error.
10///
11/// If it's constructed nestedly using [`ChainedError::new_chained`], it will treat all underlying errors as related.
12///
13/// For a usage example, please check [`ShellError::into_chainned`].
14#[derive(Debug, Clone, PartialEq, Error)]
15pub struct ChainedError {
16    first: bool,
17    pub(crate) sources: Vec<ShellError>,
18    span: Span,
19}
20
21impl std::fmt::Display for ChainedError {
22    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23        if self.first {
24            write!(f, "{}", self.sources[0])
25        } else {
26            write!(f, "oops")
27        }
28    }
29}
30
31impl ChainedError {
32    pub fn new(source: ShellError, span: Span) -> Self {
33        Self {
34            first: true,
35            sources: vec![source],
36            span,
37        }
38    }
39
40    pub fn new_chained(sources: Self, span: Span) -> Self {
41        Self {
42            first: false,
43            sources: vec![ShellError::ChainedError(sources)],
44            span,
45        }
46    }
47}
48
49impl miette::Diagnostic for ChainedError {
50    fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn miette::Diagnostic> + 'a>> {
51        if self.first {
52            self.sources[0].related()
53        } else {
54            Some(Box::new(self.sources.iter().map(|s| s as _)))
55        }
56    }
57
58    fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
59        if self.first {
60            self.sources[0].code()
61        } else {
62            Some(Box::new("chained_error"))
63        }
64    }
65
66    fn severity(&self) -> Option<Severity> {
67        if self.first {
68            self.sources[0].severity()
69        } else {
70            None
71        }
72    }
73
74    fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
75        if self.first {
76            self.sources[0].help()
77        } else {
78            None
79        }
80    }
81
82    fn url<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
83        if self.first {
84            self.sources[0].url()
85        } else {
86            None
87        }
88    }
89
90    fn labels<'a>(&'a self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + 'a>> {
91        if self.first {
92            self.sources[0].labels()
93        } else {
94            Some(Box::new(
95                vec![LabeledSpan::new_with_span(
96                    Some("error happened when running this".to_string()),
97                    self.span,
98                )]
99                .into_iter(),
100            ))
101        }
102    }
103
104    // Finally, we redirect the source_code method to our own source.
105    fn source_code(&self) -> Option<&dyn SourceCode> {
106        if self.first {
107            self.sources[0].source_code()
108        } else {
109            None
110        }
111    }
112
113    fn diagnostic_source(&self) -> Option<&dyn miette::Diagnostic> {
114        if self.first {
115            self.sources[0].diagnostic_source()
116        } else {
117            None
118        }
119    }
120}