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}