1use super::{ShellError, shell_error::io::IoError};
2use crate::{FromValue, IntoValue, Span, Type, Value, record};
3use miette::{Diagnostic, LabeledSpan, NamedSource, SourceSpan};
4use serde::{Deserialize, Serialize};
5use std::{fmt, fs};
6
7#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
15pub struct LabeledError {
16 pub msg: String,
18 #[serde(default)]
20 pub labels: Box<Vec<ErrorLabel>>,
21 #[serde(default)]
24 pub code: Option<String>,
25 #[serde(default)]
27 pub url: Option<String>,
28 #[serde(default)]
30 pub help: Option<String>,
31 #[serde(default)]
33 pub inner: Box<Vec<ShellError>>,
34}
35
36impl LabeledError {
37 pub fn new(msg: impl Into<String>) -> Self {
50 Self {
51 msg: msg.into(),
52 ..Default::default()
53 }
54 }
55
56 pub fn with_label(mut self, text: impl Into<String>, span: Span) -> Self {
69 self.labels.push(ErrorLabel {
70 text: text.into(),
71 span,
72 });
73 self
74 }
75
76 pub fn with_code(mut self, code: impl Into<String>) -> Self {
88 self.code = Some(code.into());
89 self
90 }
91
92 pub fn with_url(mut self, url: impl Into<String>) -> Self {
103 self.url = Some(url.into());
104 self
105 }
106
107 pub fn with_help(mut self, help: impl Into<String>) -> Self {
118 self.help = Some(help.into());
119 self
120 }
121
122 pub fn with_inner(mut self, inner: impl Into<ShellError>) -> Self {
134 let inner_error: ShellError = inner.into();
135 self.inner.push(inner_error);
136 self
137 }
138
139 pub fn from_diagnostic(diag: &(impl miette::Diagnostic + ?Sized)) -> Self {
159 Self {
160 msg: diag.to_string(),
161 labels: diag
162 .labels()
163 .into_iter()
164 .flatten()
165 .map(|label| ErrorLabel {
166 text: label.label().unwrap_or("").into(),
167 span: Span::new(label.offset(), label.offset() + label.len()),
168 })
169 .collect::<Vec<_>>()
170 .into(),
171 code: diag.code().map(|s| s.to_string()),
172 url: diag.url().map(|s| s.to_string()),
173 help: diag.help().map(|s| s.to_string()),
174 inner: diag
175 .related()
176 .into_iter()
177 .flatten()
178 .map(|i| Self::from_diagnostic(i).into())
179 .collect::<Vec<_>>()
180 .into(),
181 }
182 }
183}
184
185#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
187pub struct ErrorLabel {
188 pub text: String,
190 pub span: Span,
192}
193
194impl From<ErrorLabel> for LabeledSpan {
195 fn from(val: ErrorLabel) -> Self {
196 LabeledSpan::new(
197 (!val.text.is_empty()).then_some(val.text),
198 val.span.start,
199 val.span.end - val.span.start,
200 )
201 }
202}
203
204impl From<ErrorLabel> for SourceSpan {
205 fn from(val: ErrorLabel) -> Self {
206 SourceSpan::new(val.span.start.into(), val.span.end - val.span.start)
207 }
208}
209
210impl FromValue for ErrorLabel {
211 fn from_value(v: Value) -> Result<Self, ShellError> {
212 let record = v.clone().into_record()?;
213 let text = String::from_value(match record.get("text") {
214 Some(val) => val.clone(),
215 None => Value::string("", v.span()),
216 })
217 .unwrap_or("originates from here".into());
218 let span = Span::from_value(match record.get("span") {
219 Some(val) => val.clone(),
220 None => Value::record(
222 record! {
223 "start" => Value::int(v.span().start as i64, v.span()),
224 "end" => Value::int(v.span().end as i64, v.span()),
225 },
226 v.span(),
227 ),
228 });
229
230 match span {
231 Ok(s) => Ok(Self { text, span: s }),
232 Err(e) => Err(e),
233 }
234 }
235 fn expected_type() -> crate::Type {
236 Type::Record(
237 vec![
238 ("text".into(), Type::String),
239 ("span".into(), Type::record()),
240 ]
241 .into(),
242 )
243 }
244}
245
246impl IntoValue for ErrorLabel {
247 fn into_value(self, span: Span) -> Value {
248 record! {
249 "text" => Value::string(self.text, span),
250 "span" => span.into_value(span),
251 }
252 .into_value(span)
253 }
254}
255
256#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
258pub struct ErrorSource {
259 name: Option<String>,
260 text: Option<String>,
261 path: Option<String>,
262}
263
264impl ErrorSource {
265 pub fn new(name: Option<String>, text: String) -> Self {
266 Self {
267 name,
268 text: Some(text),
269 path: None,
270 }
271 }
272}
273
274impl From<ErrorSource> for NamedSource<String> {
275 fn from(value: ErrorSource) -> Self {
276 let name = value.name.unwrap_or_default();
277 match value {
278 ErrorSource {
279 text: Some(text),
280 path: None,
281 ..
282 } => NamedSource::new(name, text),
283 ErrorSource {
284 text: None,
285 path: Some(path),
286 ..
287 } => {
288 let text = fs::read_to_string(&path).unwrap_or_default();
289 NamedSource::new(path, text)
290 }
291 _ => NamedSource::new(name, "".into()),
292 }
293 }
294}
295
296impl FromValue for ErrorSource {
297 fn from_value(v: Value) -> Result<Self, ShellError> {
298 let record = v.clone().into_record()?;
299 let name = record
300 .get("name")
301 .and_then(|s| String::from_value(s.clone()).ok());
302 let text = if let Some(text) = record.get("text") {
305 String::from_value(text.clone()).ok()
306 } else {
307 None
308 };
309 let path = if let Some(path) = record.get("path") {
310 String::from_value(path.clone()).ok()
311 } else {
312 None
313 };
314
315 match (text, path) {
316 (text @ Some(_), _) => Ok(ErrorSource {
318 name,
319 text,
320 path: None,
321 }),
322 (_, path @ Some(_)) => Ok(ErrorSource {
323 name: path.clone(),
324 text: None,
325 path,
326 }),
327 _ => Err(ShellError::CantConvert {
328 to_type: Self::expected_type().to_string(),
329 from_type: v.get_type().to_string(),
330 span: v.span(),
331 help: None,
332 }),
333 }
334 }
335 fn expected_type() -> crate::Type {
336 Type::Record(
337 vec![
338 ("name".into(), Type::String),
339 ("text".into(), Type::String),
340 ("path".into(), Type::String),
341 ]
342 .into(),
343 )
344 }
345}
346
347impl IntoValue for ErrorSource {
348 fn into_value(self, span: Span) -> Value {
349 match self {
350 Self {
351 name: Some(name),
352 text: Some(text),
353 ..
354 } => record! {
355 "name" => Value::string(name, span),
356 "text" => Value::string(text, span),
357 },
358 Self {
359 text: Some(text), ..
360 } => record! {
361 "text" => Value::string(text, span)
362 },
363 Self {
364 name: Some(name),
365 path: Some(path),
366 ..
367 } => record! {
368 "name" => Value::string(name, span),
369 "path" => Value::string(path, span),
370 },
371 Self {
372 path: Some(path), ..
373 } => record! {
374 "path" => Value::string(path, span),
375 },
376 _ => record! {},
377 }
378 .into_value(span)
379 }
380}
381
382impl fmt::Display for LabeledError {
383 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
384 f.write_str(&self.msg)
385 }
386}
387
388impl std::error::Error for LabeledError {
389 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
390 self.inner.first().map(|r| r as _)
391 }
392}
393
394impl Diagnostic for LabeledError {
395 fn code<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
396 self.code.as_ref().map(Box::new).map(|b| b as _)
397 }
398
399 fn help<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
400 self.help.as_ref().map(Box::new).map(|b| b as _)
401 }
402
403 fn url<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
404 self.url.as_ref().map(Box::new).map(|b| b as _)
405 }
406
407 fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
408 Some(Box::new(
409 self.labels.iter().map(|label| label.clone().into()),
410 ))
411 }
412
413 fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
414 Some(Box::new(self.inner.iter().map(|r| r as _)))
415 }
416}
417
418impl From<ShellError> for LabeledError {
419 fn from(err: ShellError) -> Self {
420 Self::from_diagnostic(&err)
421 }
422}
423
424impl From<IoError> for LabeledError {
425 fn from(err: IoError) -> Self {
426 Self::from_diagnostic(&err)
427 }
428}