1use crate::file::{Slice, SourceFile};
5pub use miette::{miette as miette_error, Context, Report};
6use miette::{Diagnostic, LabeledSpan};
7use std::{error::Error as StdError, fmt, sync::Arc};
8
9#[macro_export]
10macro_rules! error {
11 ($($tt:tt)*) => {{
12 let error: $crate::diagnostic::Error = $crate::diagnostic::miette_error!($($tt)*).into();
13 error
14 }};
15}
16
17#[derive(Clone)]
18pub struct Error(Arc<Report>);
19
20impl Error {
21 pub fn snapshot(&self) -> Snapshot {
22 Snapshot(self.clone())
23 }
24}
25
26impl std::error::Error for Error {
27 fn source(&self) -> Option<&(dyn StdError + 'static)> {
28 self.0.source()
29 }
30}
31
32impl fmt::Display for Error {
33 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
34 self.0.fmt(f)
35 }
36}
37
38impl fmt::Debug for Error {
39 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
40 self.0.fmt(f)
41 }
42}
43
44impl Error {
45 pub fn with_source_slice(
46 &self,
47 source_code: Slice<SourceFile>,
48 label: impl AsRef<str>,
49 ) -> Self {
50 Report::new(WithSourceCode {
51 error: self.clone(),
52 source_code,
53 label: label.as_ref().to_string(),
54 })
55 .into()
56 }
57
58 pub fn with_help<H: fmt::Display>(&self, help: H) -> Self {
59 Report::new(WithHelp {
60 error: self.clone(),
61 help: help.to_string(),
62 })
63 .into()
64 }
65}
66
67impl Diagnostic for Error {
68 fn code<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
69 self.0.code()
70 }
71
72 fn severity(&self) -> Option<miette::Severity> {
73 self.0.severity()
74 }
75
76 fn help<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
77 self.0.help()
78 }
79
80 fn url<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
81 self.0.url()
82 }
83
84 fn labels<'a>(&'a self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + 'a>> {
85 self.0.labels()
86 }
87
88 fn source_code(&self) -> Option<&dyn miette::SourceCode> {
89 self.0.source_code()
90 }
91
92 fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
93 self.0.related()
94 }
95
96 fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
97 self.0.diagnostic_source()
98 }
99}
100
101impl From<std::io::Error> for Error {
102 fn from(value: std::io::Error) -> Self {
103 Report::msg(value).into()
104 }
105}
106
107impl From<serde_json::Error> for Error {
108 fn from(value: serde_json::Error) -> Self {
109 Report::msg(value).into()
110 }
111}
112
113impl From<Report> for Error {
114 fn from(err: Report) -> Self {
115 Self(Arc::new(err))
116 }
117}
118
119impl From<&Error> for Error {
120 fn from(error: &Error) -> Self {
121 error.clone()
122 }
123}
124
125impl From<Vec<Error>> for Error {
126 fn from(err: Vec<Error>) -> Self {
127 Set::from(err).into()
128 }
129}
130
131impl From<Set> for Error {
132 fn from(err: Set) -> Self {
133 miette::IntoDiagnostic::into_diagnostic(Err::<(), _>(err))
134 .unwrap_err()
135 .into()
136 }
137}
138
139pub struct Snapshot(Error);
140
141impl fmt::Display for Snapshot {
142 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143 use miette::ReportHandler;
144
145 let handler = miette::DebugReportHandler::new();
146
147 if let Some(set) = (self.0).0.downcast_ref::<Set>() {
148 for error in set.errors.iter() {
149 handler.display(error, f)?;
150 }
151 } else {
152 handler.display(&self.0, f)?;
153 }
154
155 Ok(())
156 }
157}
158
159pub trait IntoDiagnostic<T, E> {
160 fn into_diagnostic(self) -> Result<T, Error>;
161}
162
163impl<T, E: 'static + std::error::Error + Send + Sync> IntoDiagnostic<T, E> for Result<T, E> {
164 fn into_diagnostic(self) -> Result<T, Error> {
165 miette::IntoDiagnostic::into_diagnostic(self).map_err(Error::from)
166 }
167}
168
169impl IntoDiagnostic<(), ()> for Vec<Error> {
170 fn into_diagnostic(self) -> Result<(), Error> {
171 if self.is_empty() {
172 Ok(())
173 } else {
174 Err(self.into())
175 }
176 }
177}
178
179struct WithHelp {
180 error: Error,
181 help: String,
182}
183
184impl fmt::Display for WithHelp {
185 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
186 self.error.fmt(f)
187 }
188}
189
190impl fmt::Debug for WithHelp {
191 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
192 self.error.fmt(f)
193 }
194}
195
196impl StdError for WithHelp {
197 fn source(&self) -> Option<&(dyn StdError + 'static)> {
198 self.error.source()
199 }
200}
201
202impl Diagnostic for WithHelp {
203 fn code<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
204 self.error.code()
205 }
206
207 fn severity(&self) -> Option<miette::Severity> {
208 self.error.severity()
209 }
210
211 fn help<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
212 Some(Box::new(&self.help))
213 }
214
215 fn url<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
216 self.error.url()
217 }
218
219 fn labels<'a>(&'a self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + 'a>> {
220 self.error.labels()
221 }
222
223 fn source_code(&self) -> Option<&dyn miette::SourceCode> {
224 self.error.source_code()
225 }
226
227 fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
228 self.error.related()
229 }
230
231 fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
232 self.error.diagnostic_source()
233 }
234}
235
236struct WithSourceCode {
237 error: Error,
238
239 source_code: Slice<SourceFile>,
240
241 label: String,
242}
243
244impl fmt::Display for WithSourceCode {
245 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
246 self.error.fmt(f)
247 }
248}
249
250impl fmt::Debug for WithSourceCode {
251 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
252 self.error.fmt(f)
253 }
254}
255
256impl StdError for WithSourceCode {
257 fn source(&self) -> Option<&(dyn StdError + 'static)> {
258 self.error.source()
259 }
260}
261
262impl Diagnostic for WithSourceCode {
263 fn code<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
264 self.error.code()
265 }
266
267 fn severity(&self) -> Option<miette::Severity> {
268 self.error.severity()
269 }
270
271 fn help<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
272 self.error.help()
273 }
274
275 fn url<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
276 self.error.url()
277 }
278
279 fn labels<'a>(&'a self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + 'a>> {
280 if self.label.is_empty() {
281 return None;
282 }
283
284 let label = if self.label.is_empty() {
285 None
286 } else {
287 Some(self.label.clone())
288 };
289
290 let iter = core::iter::once(LabeledSpan::new_with_span(label, self.source_code.range()));
291
292 Some(if let Some(prev) = self.error.labels() {
293 Box::new(prev.chain(iter))
294 } else {
295 Box::new(iter)
296 })
297 }
298
299 fn source_code(&self) -> Option<&dyn miette::SourceCode> {
300 Some(self.source_code.file())
301 }
302
303 fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
304 self.error.related()
305 }
306
307 fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
308 self.error.diagnostic_source()
309 }
310}
311
312#[derive(Diagnostic)]
313pub struct Set {
314 #[diagnostic_source]
315 main: Error,
316 #[related]
317 errors: Arc<[Error]>,
318}
319
320impl From<Vec<Error>> for Set {
321 fn from(errors: Vec<Error>) -> Self {
322 let main = error!("encountered {} errors", errors.len());
323 let errors = Arc::from(errors);
324 Self { main, errors }
325 }
326}
327
328impl fmt::Display for Set {
329 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
330 for error in self.errors.iter() {
331 writeln!(f, "{:?}", error)?;
332 }
333 Ok(())
334 }
335}
336
337impl fmt::Debug for Set {
338 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
339 f.debug_list().entries(self.errors.iter()).finish()
340 }
341}
342
343impl StdError for Set {
344 fn source(&self) -> Option<&(dyn StdError + 'static)> {
345 Some(&self.main)
346 }
347}