1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
use crate::{
Diagnostic, Label, Reporter, Severity, Suggestion,
intelligence::{
cause_chain_from_sources, find_io_error_in_sources, io::suggestion as io_suggestion,
},
};
use std::error::Error;
/// Optional diagnostic metadata implemented by typed application errors.
///
/// This trait is intentionally independent of any derive crate. Errors built
/// with `thiserror`, handwritten `std::error::Error` implementations, and
/// other error libraries can all implement the same metadata contract.
///
/// The trait supplies structured information that cannot be recovered safely
/// from an error's formatted text.
pub trait DiagnosticMetadata {
/// Severity to use when converting this error into a diagnostic.
fn diagnostic_severity(&self) -> Severity {
Severity::Error
}
/// Stable machine-readable/application-readable diagnostic code.
fn diagnostic_code(&self) -> Option<String> {
None
}
/// Human-readable remediation guidance.
fn diagnostic_help(&self) -> Option<String> {
None
}
/// Additional contextual notes.
fn diagnostic_notes(&self) -> Vec<String> {
Vec::new()
}
/// Structured source labels associated with this error.
fn diagnostic_labels(&self) -> Vec<Label> {
Vec::new()
}
/// Structured remediation suggestions supplied by the error type.
fn diagnostic_suggestions(&self) -> Vec<Suggestion> {
Vec::new()
}
}
/// Convenience conversion methods for typed errors carrying
/// [`DiagnosticMetadata`].
pub trait DiagnosticErrorExt: Error + DiagnosticMetadata + 'static {
/// Converts the error into a structured `diagprint` diagnostic.
fn to_diagprint(&self, reporter: &Reporter) -> Diagnostic {
reporter.diagnostic_from_typed_error(self)
}
}
impl<T> DiagnosticErrorExt for T where T: Error + DiagnosticMetadata + 'static + ?Sized {}
impl Reporter {
/// Converts a typed error into a structured diagnostic.
///
/// The outer error becomes the diagnostic message. Its
/// `std::error::Error::source()` chain becomes the diagnostic cause chain.
/// Metadata supplied by [`DiagnosticMetadata`] is then attached.
///
/// Recognized nested standard-library errors may add conservative
/// suggestions. Such inferred suggestions are never machine-applicable.
pub fn diagnostic_from_typed_error<E>(&self, error: &E) -> Diagnostic
where
E: Error + DiagnosticMetadata + 'static + ?Sized,
{
let mut diagnostic = self.diagnostic(error.diagnostic_severity(), error.to_string());
if let Some(code) = error.diagnostic_code() {
diagnostic = diagnostic.code(code);
}
if let Some(help) = error.diagnostic_help() {
diagnostic = diagnostic.help(help);
}
for note in error.diagnostic_notes() {
diagnostic = diagnostic.note(note);
}
diagnostic = diagnostic.labels(error.diagnostic_labels());
if let Some(cause) = cause_chain_from_sources(error) {
diagnostic = diagnostic.cause_chain(cause);
}
let mut suggestions = error.diagnostic_suggestions();
if let Some(io_error) = find_io_error_in_sources(error) {
let inferred = io_suggestion(io_error);
let duplicate = suggestions
.iter()
.any(|existing| existing.title == inferred.title);
if !duplicate {
suggestions.push(inferred);
}
}
diagnostic.suggestions(suggestions)
}
}