Skip to main content

error_snippet/
handler.rs

1use crate::{Diagnostic, Renderer, Severity};
2
3/// Represents an error which can occur when draining errors
4/// from the [`DiagnosticHandler::drain()`] and [`DiagnosticHandler::report_and_drain`].
5pub enum DrainError {
6    /// Defines that the error occured when attempting to write
7    /// the diagnostic to the output buffer.
8    Fmt(std::fmt::Error),
9
10    /// Defines that one-or-more errors were reported during the drain,
11    /// which are not propogating upwards to the calling function.
12    ///
13    /// The variant defines the number of errors which were reported. Note that
14    /// this number does *not* include non-errors such as warnings, nor does
15    /// it count any sub-diagnostics, such as labels or related errors.
16    CompoundError(usize),
17}
18
19impl From<std::fmt::Error> for DrainError {
20    fn from(err: std::fmt::Error) -> Self {
21        Self::Fmt(err)
22    }
23}
24
25impl std::error::Error for DrainError {}
26
27impl std::fmt::Debug for DrainError {
28    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29        match self {
30            Self::Fmt(e) => e.fmt(f),
31            Self::CompoundError(cnt) => f.debug_tuple("CompoundError").field(cnt).finish(),
32        }
33    }
34}
35
36impl std::fmt::Display for DrainError {
37    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38        match self {
39            Self::Fmt(e) => e.fmt(f),
40            Self::CompoundError(cnt) => write!(f, "aborting due to {cnt} previous errors"),
41        }
42    }
43}
44
45/// Abstract handler type for reporting diagnostics.
46///
47/// Handlers are nothing more than a "store" for diagnostics, which
48/// decides when to drain the diagnostics to the user.
49pub trait Handler: std::any::Any {
50    /// Reports the diagnostic to the handler, without emitting it immediately.
51    fn report(&mut self, diagnostic: Box<dyn Diagnostic>);
52
53    /// Drains all the diagnostics to the console and empties the local store.
54    fn drain(&mut self) -> Result<(), DrainError>;
55
56    /// Reports the diagnostic to the handler and emits it immediately, along
57    /// with all other stored diagnostics within the handler.
58    fn report_and_drain(&mut self, diagnostic: Box<dyn Diagnostic>) -> Result<(), DrainError> {
59        self.report(diagnostic);
60
61        self.drain()
62    }
63}
64
65/// The default diagnostic handler.
66///
67/// The [`DiagnosticHandler`] allows to report to the user immediately or deferred until drained,
68/// and aborting upon draining an error (or worse) diagnostic.
69///
70/// # Examples
71///
72/// To use deferred reporting:
73///
74/// ```
75/// use error_snippet::{SimpleDiagnostic, GraphicalRenderer, Handler, DiagnosticHandler};
76///
77/// let diagnostic = SimpleDiagnostic::new("An error occurred");
78///
79/// let renderer = GraphicalRenderer::new();
80/// let mut handler = DiagnosticHandler::with_renderer(Box::new(renderer));
81///
82/// handler.report(Box::new(diagnostic));
83/// ```
84///
85/// If not, you can drain the diagnostics immediately after reporting it:
86///
87/// ```
88/// use error_snippet::{SimpleDiagnostic, GraphicalRenderer, Handler, DiagnosticHandler};
89///
90/// let diagnostic = SimpleDiagnostic::new("An error occurred");
91///
92/// let renderer = GraphicalRenderer::new();
93/// let mut handler = DiagnosticHandler::with_renderer(Box::new(renderer));
94///
95/// handler.report_and_drain(Box::new(diagnostic));
96/// ```
97///
98/// To abort upon draining an error diagnostic, use the [`DiagnosticHandler::exit_on_error()`] method:
99///
100/// ```
101/// use error_snippet::{DiagnosticHandler, GraphicalRenderer};
102///
103/// let renderer = GraphicalRenderer::new();
104/// let mut handler = DiagnosticHandler::with_renderer(Box::new(renderer));
105/// handler.exit_on_error();
106///
107/// // ...
108/// ```
109pub struct DiagnosticHandler {
110    /// Defines whether to exit upon emitting an error.
111    exit_on_error: bool,
112
113    /// Stores all the diagnostics which have been reported.
114    emitted_diagnostics: Vec<Box<dyn Diagnostic>>,
115
116    /// Defines the renderer to use when rendering the diagnostics.
117    renderer: Box<dyn Renderer + Send + Sync>,
118}
119
120impl DiagnosticHandler {
121    /// Creates a new empty handler.
122    pub fn with_renderer(renderer: Box<dyn Renderer + Send + Sync>) -> Self {
123        DiagnosticHandler {
124            exit_on_error: false,
125            emitted_diagnostics: Vec::new(),
126            renderer,
127        }
128    }
129
130    /// Enables the handler to exit upon emitting an error.
131    pub fn exit_on_error(&mut self) {
132        self.exit_on_error = true
133    }
134
135    /// Gets an [`Iterator`] over all the emitted diagnostics to the handler,
136    /// which have yet to be drained.
137    pub fn emitted(&self) -> impl Iterator<Item = &Box<dyn Diagnostic>> {
138        self.emitted_diagnostics.iter()
139    }
140
141    /// Gets the amount of diagnostics within the handler, which have
142    /// yet to be drained.
143    pub fn count(&self) -> usize {
144        self.emitted_diagnostics.len()
145    }
146}
147
148impl Handler for DiagnosticHandler {
149    fn report(&mut self, diagnostic: Box<dyn Diagnostic>) {
150        self.emitted_diagnostics.push(diagnostic);
151    }
152
153    fn drain(&mut self) -> Result<(), DrainError> {
154        let mut encountered_errors = 0usize;
155
156        for diagnostic in self.emitted_diagnostics.drain(..) {
157            self.renderer.render_stderr(diagnostic.as_ref())?;
158
159            // If the diagnostic is an error, mark it down.
160            if diagnostic.severity() == Severity::Error {
161                encountered_errors += 1;
162            }
163        }
164
165        // If we've encountered any errors, and we're enabled to propogate errors upwards,
166        // return a specific error to compound all encountered errors.
167        if encountered_errors > 0 && self.exit_on_error {
168            return Err(DrainError::CompoundError(encountered_errors));
169        }
170
171        Ok(())
172    }
173}
174
175/// A buffered version of [`DiagnosticHandler`].
176///
177/// The [`BufferedDiagnosticHandler`] will save rendered diagnostics to an internal buffer,
178/// allowing them to be read back as [`String`]-values. This is mostly used for UI testing.
179pub struct BufferedDiagnosticHandler {
180    /// Stores all the rendered diagnostics which have been drained.
181    buffer: String,
182
183    /// Stores all the diagnostics which have been reported.
184    emitted_diagnostics: Vec<Box<dyn Diagnostic>>,
185
186    /// Defines the renderer to use when rendering the diagnostics.
187    renderer: Box<dyn Renderer + Send + Sync>,
188}
189
190impl BufferedDiagnosticHandler {
191    /// Creates a new empty handler.
192    pub fn with_renderer(capacity: usize, renderer: Box<dyn Renderer + Send + Sync>) -> Self {
193        Self {
194            buffer: String::with_capacity(capacity),
195            emitted_diagnostics: Vec::new(),
196            renderer,
197        }
198    }
199
200    /// Gets the [`String`] buffer which contains the rendered diagnostics.
201    pub fn buffer(&self) -> &str {
202        &self.buffer
203    }
204
205    /// Gets an [`Iterator`] over all the emitted diagnostics to the handler,
206    /// which have yet to be drained.
207    pub fn emitted(&self) -> impl Iterator<Item = &Box<dyn Diagnostic>> {
208        self.emitted_diagnostics.iter()
209    }
210
211    /// Gets the amount of diagnostics within the handler, which have
212    /// yet to be drained.
213    pub fn count(&self) -> usize {
214        self.emitted_diagnostics.len()
215    }
216}
217
218impl Handler for BufferedDiagnosticHandler {
219    fn report(&mut self, diagnostic: Box<dyn Diagnostic>) {
220        self.emitted_diagnostics.push(diagnostic);
221    }
222
223    fn drain(&mut self) -> Result<(), DrainError> {
224        for diagnostic in self.emitted_diagnostics.drain(..) {
225            let rendered = self.renderer.render(diagnostic.as_ref())?;
226
227            self.buffer.push_str(&rendered);
228        }
229
230        Ok(())
231    }
232}