tracing_filter/
diagnostics.rs

1use miette::{GraphicalReportHandler, GraphicalTheme};
2
3use {
4    miette::{Diagnostic, ReportHandler},
5    std::{borrow::Cow, error::Error, fmt},
6};
7
8/// Resulting diagnostics from compiling a filter directive string.
9pub struct Diagnostics<'a> {
10    pub(crate) error: Option<Box<dyn Diagnostic + Send + Sync + 'static>>,
11    pub(crate) ignored: Vec<Box<dyn Diagnostic + Send + Sync + 'static>>,
12    pub(crate) disabled: Option<Box<dyn Diagnostic + Send + Sync + 'static>>,
13    pub(crate) source: Cow<'a, str>,
14}
15
16/// How to render a set of [`Diagnostics`].
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
18pub enum DiagnosticsTheme {
19    /// Render with ASCII art and ANSI colors.
20    Ascii,
21    /// Render with ASCII art but no ANSI colors.
22    AsciiNocolor,
23    /// Render with Unicode drawing characters and ANSI colors.
24    Unicode,
25    /// Render with Unicode drawing characters but no ANSI colors.
26    UnicodeNocolor,
27    /// Guess the best render format for stdout/stderr.
28    Guess,
29}
30
31impl Default for DiagnosticsTheme {
32    fn default() -> Self {
33        DiagnosticsTheme::Guess
34    }
35}
36
37impl DiagnosticsTheme {
38    fn report_handler(self) -> GraphicalReportHandler {
39        match self {
40            Self::Ascii => GraphicalReportHandler::new_themed(GraphicalTheme::ascii()),
41            Self::AsciiNocolor => GraphicalReportHandler::new_themed(GraphicalTheme::none()),
42            Self::Unicode => GraphicalReportHandler::new_themed(GraphicalTheme::unicode()),
43            Self::UnicodeNocolor => {
44                GraphicalReportHandler::new_themed(GraphicalTheme::unicode_nocolor())
45            },
46            Self::Guess => GraphicalReportHandler::new(),
47        }
48    }
49}
50
51impl Diagnostics<'_> {
52    /// Does this diagnostic set include any [errors](Self::error)?
53    pub fn is_error(&self) -> bool {
54        self.error.is_some()
55    }
56
57    /// Does this diagnostic set include any [warnings](Self::warn)?
58    pub fn is_warning(&self) -> bool {
59        !self.ignored.is_empty() || self.disabled.is_some()
60    }
61
62    /// Is this diagnostic set empty, indicating a successful compilation?
63    pub fn is_empty(&self) -> bool {
64        !self.is_error() && !self.is_warning()
65    }
66
67    /// Create an owned version of the diagnostic set. This requires cloning the
68    /// directive string, which is normally just referenced.
69    pub fn into_owned(self) -> Diagnostics<'static> {
70        Diagnostics {
71            error: self.error,
72            ignored: self.ignored,
73            disabled: self.disabled,
74            source: Cow::Owned(self.source.into()),
75        }
76    }
77
78    /// Any errors generated by parsing a filter directive string. This means
79    /// that no filters were applied! You should probably `error!` this into
80    /// your logging backend.
81    pub fn error(&self, theme: DiagnosticsTheme) -> Option<impl fmt::Display + '_> {
82        struct ErrorDiagnostics<'a>(&'a Diagnostics<'a>, DiagnosticsTheme);
83        impl fmt::Display for ErrorDiagnostics<'_> {
84            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85                let report_handler = self.1.report_handler();
86                if let Some(error) = &self.0.error {
87                    report_handler.debug(&DiagnosticWithStr::new(&**error, &self.0.source), f)?;
88                }
89                Ok(())
90            }
91        }
92        if self.is_error() {
93            Some(ErrorDiagnostics(self, theme))
94        } else {
95            None
96        }
97    }
98
99    /// Any errors generated by parsing a filter directive string. This means
100    /// that some filters were not applied! You should probably `warn!` this
101    /// into your logging backend.
102    pub fn warn(&self, theme: DiagnosticsTheme) -> Option<impl fmt::Display + '_> {
103        struct WarnDiagnostics<'a>(&'a Diagnostics<'a>, DiagnosticsTheme);
104        impl fmt::Display for WarnDiagnostics<'_> {
105            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106                let report_handler = self.1.report_handler();
107
108                if !self.0.ignored.is_empty() {
109                    writeln!(
110                        f,
111                        "{} directives were ignored as invalid",
112                        self.0.ignored.len()
113                    )?;
114                }
115
116                for ignored in &self.0.ignored {
117                    report_handler.debug(&DiagnosticWithStr::new(&**ignored, &self.0.source), f)?;
118                }
119
120                if let Some(disabled) = &self.0.disabled {
121                    report_handler
122                        .debug(&DiagnosticWithStr::new(&**disabled, &self.0.source), f)?;
123                }
124
125                Ok(())
126            }
127        }
128        if self.is_warning() {
129            Some(WarnDiagnostics(self, theme))
130        } else {
131            None
132        }
133    }
134}
135
136impl fmt::Display for Diagnostics<'_> {
137    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138        if f.alternate() {
139            f.debug_struct(stringify!(Diagnostics))
140                .field(stringify!(error), &self.error)
141                .field(stringify!(ignored), &self.ignored)
142                .field(stringify!(disabled), &self.disabled)
143                .finish()
144        } else {
145            if let Some(error) = &self.error {
146                writeln!(f, "{}", error)?;
147            }
148
149            if !self.ignored.is_empty() {
150                writeln!(
151                    f,
152                    "{} directives were ignored as invalid",
153                    self.ignored.len()
154                )?;
155                for ignored in &self.ignored {
156                    writeln!(f, "{}", ignored)?;
157                }
158            }
159
160            if let Some(disabled) = &self.disabled {
161                writeln!(f, "{}", disabled)?;
162            }
163
164            Ok(())
165        }
166    }
167}
168
169impl fmt::Debug for Diagnostics<'_> {
170    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
171        if f.alternate() {
172            f.debug_struct(stringify!(Diagnostics))
173                .field(stringify!(error), &self.error)
174                .field(stringify!(ignored), &self.ignored)
175                .field(stringify!(disabled), &self.disabled)
176                .finish()
177        } else {
178            if let Some(error) = self.error(DiagnosticsTheme::Guess) {
179                writeln!(f, "{}", error)?;
180            }
181            if let Some(warn) = self.warn(DiagnosticsTheme::Guess) {
182                writeln!(f, "{}", warn)?;
183            }
184            Ok(())
185        }
186    }
187}
188
189#[derive(Debug)]
190struct DiagnosticWithStr<'a> {
191    diagnostic: &'a dyn Diagnostic,
192    source: &'a str,
193}
194
195impl<'a> DiagnosticWithStr<'a> {
196    fn new(diagnostic: &'a dyn Diagnostic, source: &'a str) -> Self {
197        Self { diagnostic, source }
198    }
199}
200
201impl fmt::Display for DiagnosticWithStr<'_> {
202    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
203        fmt::Display::fmt(&self.diagnostic, f)
204    }
205}
206
207impl Error for DiagnosticWithStr<'_> {}
208
209impl Diagnostic for DiagnosticWithStr<'_> {
210    fn code<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
211        self.diagnostic.code()
212    }
213
214    fn severity(&self) -> Option<miette::Severity> {
215        self.diagnostic.severity()
216    }
217
218    fn help<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
219        self.diagnostic.help()
220    }
221
222    fn url<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
223        self.diagnostic.url()
224    }
225
226    fn source_code(&self) -> Option<&dyn miette::SourceCode> {
227        Some(&self.source)
228    }
229
230    fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
231        self.diagnostic.labels()
232    }
233
234    fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
235        self.diagnostic.related()
236    }
237}