Skip to main content

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_chained`].
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    // Abstraction to read the sources from outside of this crate without exposing the actual
49    // implementation.
50    pub fn sources_iter(self) -> impl Iterator<Item = ShellError> {
51        self.sources.into_iter()
52    }
53}
54
55impl miette::Diagnostic for ChainedError {
56    fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn miette::Diagnostic> + 'a>> {
57        if self.first {
58            self.sources[0].related()
59        } else {
60            Some(Box::new(self.sources.iter().map(|s| s as _)))
61        }
62    }
63
64    fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
65        if self.first {
66            self.sources[0].code()
67        } else {
68            Some(Box::new("chained_error"))
69        }
70    }
71
72    fn severity(&self) -> Option<Severity> {
73        if self.first {
74            self.sources[0].severity()
75        } else {
76            None
77        }
78    }
79
80    fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
81        if self.first {
82            self.sources[0].help()
83        } else {
84            None
85        }
86    }
87
88    fn url<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
89        if self.first {
90            self.sources[0].url()
91        } else {
92            None
93        }
94    }
95
96    fn labels<'a>(&'a self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + 'a>> {
97        if self.first {
98            self.sources[0].labels()
99        } else {
100            Some(Box::new(
101                vec![LabeledSpan::new_with_span(
102                    Some("error happened when running this".to_string()),
103                    self.span,
104                )]
105                .into_iter(),
106            ))
107        }
108    }
109
110    // Finally, we redirect the source_code method to our own source.
111    fn source_code(&self) -> Option<&dyn SourceCode> {
112        if self.first {
113            self.sources[0].source_code()
114        } else {
115            None
116        }
117    }
118
119    fn diagnostic_source(&self) -> Option<&dyn miette::Diagnostic> {
120        if self.first {
121            self.sources[0].diagnostic_source()
122        } else {
123            None
124        }
125    }
126}