duvet_core/
diagnostic.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4use 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}