1use std::{error::Error, fmt::Display, iter, sync::Arc};
2
3use miette::{Diagnostic, LabeledSpan, Severity, SourceSpan};
4
5#[cfg(doc)]
6use {
7 crate::KdlNode,
8 std::convert::{TryFrom, TryInto},
9};
10
11#[derive(Debug, Clone, Eq, PartialEq)]
37pub struct KdlError {
38 pub input: Arc<String>,
40
41 pub diagnostics: Vec<KdlDiagnostic>,
43}
44
45impl Display for KdlError {
46 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47 write!(f, "Failed to parse KDL document")
48 }
49}
50impl Error for KdlError {}
51
52impl Diagnostic for KdlError {
53 fn source_code(&self) -> Option<&dyn miette::SourceCode> {
54 Some(&self.input)
55 }
56
57 fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
58 Some(Box::new(
59 self.diagnostics.iter().map(|d| d as &dyn Diagnostic),
60 ))
61 }
62}
63
64#[derive(Debug, Clone, Eq, PartialEq)]
68pub struct KdlDiagnostic {
69 pub input: Arc<String>,
71
72 pub span: SourceSpan,
74
75 pub message: Option<String>,
77
78 pub label: Option<String>,
80
81 pub help: Option<String>,
83
84 pub severity: Severity,
86}
87
88impl Display for KdlDiagnostic {
89 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90 let message = self
91 .message
92 .clone()
93 .unwrap_or_else(|| "Unexpected error".into());
94 write!(f, "{message}")
95 }
96}
97impl Error for KdlDiagnostic {}
98
99impl Diagnostic for KdlDiagnostic {
100 fn source_code(&self) -> Option<&dyn miette::SourceCode> {
101 Some(&self.input)
102 }
103
104 fn severity(&self) -> Option<Severity> {
105 Some(self.severity)
106 }
107
108 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
109 self.help.as_ref().map(|s| Box::new(s) as Box<dyn Display>)
110 }
111
112 fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
113 let label = self.label.clone().unwrap_or_else(|| "here".to_owned());
114 let labeled_span = LabeledSpan::new_with_span(Some(label), self.span);
115
116 Some(Box::new(iter::once(labeled_span)))
117 }
118}
119
120#[cfg(feature = "v1")]
121impl From<kdlv1::KdlError> for KdlError {
122 fn from(value: kdlv1::KdlError) -> Self {
123 let input = Arc::new(value.input);
124 Self {
125 input: input.clone(),
126 diagnostics: vec![KdlDiagnostic {
127 input,
128 span: SourceSpan::new(value.span.offset().into(), value.span.len()),
129 message: Some(format!("{}", value.kind)),
130 label: value.label.map(|x| x.into()),
131 help: value.help.map(|x| x.into()),
132 severity: Severity::Error,
133 }],
134 }
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141
142 #[test]
143 fn kdl_error() {
144 let kdl_diagnostic = KdlDiagnostic {
145 input: Default::default(),
146 span: SourceSpan::new(0.into(), 0),
147 message: Default::default(),
148 label: Default::default(),
149 help: Default::default(),
150 severity: Default::default(),
151 };
152
153 let kdl_error = KdlError {
154 input: Arc::new("bark? i guess?".to_owned()),
155 diagnostics: vec![kdl_diagnostic.clone(), kdl_diagnostic],
156 };
157
158 assert_eq!(kdl_error.to_string(), "Failed to parse KDL document");
160 assert!(kdl_error.source().is_none());
161
162 let related: Vec<_> = kdl_error.related().unwrap().collect();
164 assert_eq!(related.len(), 2);
165 assert_eq!(
166 kdl_error
167 .source_code()
168 .unwrap()
169 .read_span(&SourceSpan::new(0.into(), 5), 0, 0)
170 .unwrap()
171 .data(),
172 b"bark?"
173 );
174 }
175
176 #[test]
177 fn kdl_diagnostic() {
178 let mut kdl_diagnostic = KdlDiagnostic {
179 input: Arc::new("Catastrophic failure!!!".to_owned()),
180 span: SourceSpan::new(0.into(), 3),
181 message: None,
182 label: Some("cute".to_owned()),
183 help: Some("try harder?".to_owned()),
184 severity: Severity::Error,
185 };
186
187 assert_eq!(kdl_diagnostic.to_string(), "Unexpected error");
189 assert!(kdl_diagnostic.source().is_none());
190
191 kdl_diagnostic.message = Some("mega bad news, kiddo".to_owned());
192
193 assert_eq!(kdl_diagnostic.to_string(), "mega bad news, kiddo");
194 assert!(kdl_diagnostic.source().is_none());
195
196 let labels: Vec<_> = kdl_diagnostic.labels().unwrap().collect();
198 assert_eq!(labels.len(), 1);
199 assert_eq!(labels[0].label().unwrap(), "cute");
200 assert_eq!(
201 kdl_diagnostic
202 .source_code()
203 .unwrap()
204 .read_span(labels[0].inner(), 0, 0)
205 .unwrap()
206 .data(),
207 b"Cat"
208 );
209 assert_eq!(kdl_diagnostic.help().unwrap().to_string(), "try harder?");
210 assert_eq!(kdl_diagnostic.severity().unwrap(), Severity::Error);
211 }
212}