1use std::{
7 fmt::{self, Display},
8 fs,
9 panic::Location,
10};
11
12#[cfg(feature = "serde")]
13use serde::{Deserialize, Serialize};
14
15use crate::{DiagnosticError, MietteError};
16
17pub trait Diagnostic: std::error::Error {
21 fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
27 None
28 }
29
30 fn severity(&self) -> Option<Severity> {
36 None
37 }
38
39 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
42 None
43 }
44
45 fn url<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
48 None
49 }
50
51 fn source_code(&self) -> Option<&dyn SourceCode> {
53 None
54 }
55
56 fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
58 None
59 }
60
61 fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
63 None
64 }
65
66 fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
68 None
69 }
70}
71
72macro_rules! box_error_impls {
73 ($($box_type:ty),*) => {
74 $(
75 impl std::error::Error for $box_type {
76 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
77 (**self).source()
78 }
79
80 fn cause(&self) -> Option<&dyn std::error::Error> {
81 self.source()
82 }
83 }
84 )*
85 }
86}
87
88box_error_impls! {
89 Box<dyn Diagnostic>,
90 Box<dyn Diagnostic + Send>,
91 Box<dyn Diagnostic + Send + Sync>
92}
93
94macro_rules! box_borrow_impls {
95 ($($box_type:ty),*) => {
96 $(
97 impl std::borrow::Borrow<dyn Diagnostic> for $box_type {
98 fn borrow(&self) -> &(dyn Diagnostic + 'static) {
99 self.as_ref()
100 }
101 }
102 )*
103 }
104}
105
106box_borrow_impls! {
107 Box<dyn Diagnostic + Send>,
108 Box<dyn Diagnostic + Send + Sync>
109}
110
111impl<T: Diagnostic + Send + Sync + 'static> From<T>
112 for Box<dyn Diagnostic + Send + Sync + 'static>
113{
114 fn from(diag: T) -> Self {
115 Box::new(diag)
116 }
117}
118
119impl<T: Diagnostic + Send + Sync + 'static> From<T> for Box<dyn Diagnostic + Send + 'static> {
120 fn from(diag: T) -> Self {
121 Box::<dyn Diagnostic + Send + Sync>::from(diag)
122 }
123}
124
125impl<T: Diagnostic + Send + Sync + 'static> From<T> for Box<dyn Diagnostic + 'static> {
126 fn from(diag: T) -> Self {
127 Box::<dyn Diagnostic + Send + Sync>::from(diag)
128 }
129}
130
131impl From<&str> for Box<dyn Diagnostic> {
132 fn from(s: &str) -> Self {
133 From::from(String::from(s))
134 }
135}
136
137impl From<&str> for Box<dyn Diagnostic + Send + Sync + '_> {
138 fn from(s: &str) -> Self {
139 From::from(String::from(s))
140 }
141}
142
143impl From<String> for Box<dyn Diagnostic> {
144 fn from(s: String) -> Self {
145 let err1: Box<dyn Diagnostic + Send + Sync> = From::from(s);
146 let err2: Box<dyn Diagnostic> = err1;
147 err2
148 }
149}
150
151impl From<String> for Box<dyn Diagnostic + Send + Sync> {
152 fn from(s: String) -> Self {
153 struct StringError(String);
154
155 impl std::error::Error for StringError {}
156 impl Diagnostic for StringError {}
157
158 impl Display for StringError {
159 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
160 Display::fmt(&self.0, f)
161 }
162 }
163
164 impl fmt::Debug for StringError {
166 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167 fmt::Debug::fmt(&self.0, f)
168 }
169 }
170
171 Box::new(StringError(s))
172 }
173}
174
175impl From<Box<dyn std::error::Error + Send + Sync>> for Box<dyn Diagnostic + Send + Sync> {
176 fn from(s: Box<dyn std::error::Error + Send + Sync>) -> Self {
177 Box::new(DiagnosticError(s))
178 }
179}
180
181#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
187#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
188#[derive(Default)]
189pub enum Severity {
190 Advice,
192 Warning,
194 #[default]
197 Error,
198}
199
200#[cfg(feature = "serde")]
201#[test]
202fn test_serialize_severity() {
203 use serde_json::json;
204
205 assert_eq!(json!(Severity::Advice), json!("Advice"));
206 assert_eq!(json!(Severity::Warning), json!("Warning"));
207 assert_eq!(json!(Severity::Error), json!("Error"));
208}
209
210#[cfg(feature = "serde")]
211#[test]
212fn test_deserialize_severity() {
213 use serde_json::json;
214
215 let severity: Severity = serde_json::from_value(json!("Advice")).unwrap();
216 assert_eq!(severity, Severity::Advice);
217
218 let severity: Severity = serde_json::from_value(json!("Warning")).unwrap();
219 assert_eq!(severity, Severity::Warning);
220
221 let severity: Severity = serde_json::from_value(json!("Error")).unwrap();
222 assert_eq!(severity, Severity::Error);
223}
224
225pub trait SourceCode: Send + Sync {
237 fn read_span<'a>(
240 &'a self,
241 span: &SourceSpan,
242 context_lines_before: usize,
243 context_lines_after: usize,
244 ) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError>;
245}
246
247#[derive(Debug, Clone, PartialEq, Eq)]
249#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
250pub struct LabeledSpan {
251 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
252 label: Option<String>,
253 span: SourceSpan,
254 primary: bool,
255}
256
257impl LabeledSpan {
258 pub const fn new(label: Option<String>, offset: ByteOffset, len: usize) -> Self {
260 Self {
261 label,
262 span: SourceSpan::new(SourceOffset(offset), len),
263 primary: false,
264 }
265 }
266
267 pub fn new_with_span(label: Option<String>, span: impl Into<SourceSpan>) -> Self {
269 Self {
270 label,
271 span: span.into(),
272 primary: false,
273 }
274 }
275
276 pub fn new_primary_with_span(label: Option<String>, span: impl Into<SourceSpan>) -> Self {
278 Self {
279 label,
280 span: span.into(),
281 primary: true,
282 }
283 }
284
285 pub fn set_label(&mut self, label: Option<String>) {
287 self.label = label;
288 }
289
290 pub fn at(span: impl Into<SourceSpan>, label: impl Into<String>) -> Self {
304 Self::new_with_span(Some(label.into()), span)
305 }
306
307 pub fn at_offset(offset: ByteOffset, label: impl Into<String>) -> Self {
321 Self::new(Some(label.into()), offset, 0)
322 }
323
324 pub fn underline(span: impl Into<SourceSpan>) -> Self {
335 Self::new_with_span(None, span)
336 }
337
338 pub fn label(&self) -> Option<&str> {
340 self.label.as_deref()
341 }
342
343 pub const fn inner(&self) -> &SourceSpan {
345 &self.span
346 }
347
348 pub const fn offset(&self) -> usize {
350 self.span.offset()
351 }
352
353 pub const fn len(&self) -> usize {
355 self.span.len()
356 }
357
358 pub const fn is_empty(&self) -> bool {
360 self.span.is_empty()
361 }
362
363 pub const fn primary(&self) -> bool {
365 self.primary
366 }
367}
368
369#[cfg(feature = "serde")]
370#[test]
371fn test_serialize_labeled_span() {
372 use serde_json::json;
373
374 assert_eq!(
375 json!(LabeledSpan::new(None, 0, 0)),
376 json!({
377 "span": { "offset": 0, "length": 0, },
378 "primary": false,
379 })
380 );
381
382 assert_eq!(
383 json!(LabeledSpan::new(Some("label".to_string()), 0, 0)),
384 json!({
385 "label": "label",
386 "span": { "offset": 0, "length": 0, },
387 "primary": false,
388 })
389 );
390}
391
392#[cfg(feature = "serde")]
393#[test]
394fn test_deserialize_labeled_span() {
395 use serde_json::json;
396
397 let span: LabeledSpan = serde_json::from_value(json!({
398 "label": null,
399 "span": { "offset": 0, "length": 0, },
400 "primary": false,
401 }))
402 .unwrap();
403 assert_eq!(span, LabeledSpan::new(None, 0, 0));
404
405 let span: LabeledSpan = serde_json::from_value(json!({
406 "span": { "offset": 0, "length": 0, },
407 "primary": false
408 }))
409 .unwrap();
410 assert_eq!(span, LabeledSpan::new(None, 0, 0));
411
412 let span: LabeledSpan = serde_json::from_value(json!({
413 "label": "label",
414 "span": { "offset": 0, "length": 0, },
415 "primary": false
416 }))
417 .unwrap();
418 assert_eq!(span, LabeledSpan::new(Some("label".to_string()), 0, 0));
419}
420
421pub trait SpanContents<'a> {
427 fn data(&self) -> &'a [u8];
429 fn span(&self) -> &SourceSpan;
431 fn name(&self) -> Option<&str> {
433 None
434 }
435 fn line(&self) -> usize;
438 fn column(&self) -> usize;
441 fn line_count(&self) -> usize;
443
444 fn language(&self) -> Option<&str> {
450 None
451 }
452}
453
454#[derive(Clone, Debug)]
458pub struct MietteSpanContents<'a> {
459 data: &'a [u8],
461 span: SourceSpan,
463 line: usize,
465 column: usize,
467 line_count: usize,
469 name: Option<String>,
471 language: Option<String>,
473}
474
475impl<'a> MietteSpanContents<'a> {
476 pub const fn new(
478 data: &'a [u8],
479 span: SourceSpan,
480 line: usize,
481 column: usize,
482 line_count: usize,
483 ) -> MietteSpanContents<'a> {
484 MietteSpanContents {
485 data,
486 span,
487 line,
488 column,
489 line_count,
490 name: None,
491 language: None,
492 }
493 }
494
495 pub const fn new_named(
497 name: String,
498 data: &'a [u8],
499 span: SourceSpan,
500 line: usize,
501 column: usize,
502 line_count: usize,
503 ) -> MietteSpanContents<'a> {
504 MietteSpanContents {
505 data,
506 span,
507 line,
508 column,
509 line_count,
510 name: Some(name),
511 language: None,
512 }
513 }
514
515 pub fn with_language(mut self, language: impl Into<String>) -> Self {
517 self.language = Some(language.into());
518 self
519 }
520}
521
522impl<'a> SpanContents<'a> for MietteSpanContents<'a> {
523 fn data(&self) -> &'a [u8] {
524 self.data
525 }
526 fn span(&self) -> &SourceSpan {
527 &self.span
528 }
529 fn line(&self) -> usize {
530 self.line
531 }
532 fn column(&self) -> usize {
533 self.column
534 }
535 fn line_count(&self) -> usize {
536 self.line_count
537 }
538 fn name(&self) -> Option<&str> {
539 self.name.as_deref()
540 }
541 fn language(&self) -> Option<&str> {
542 self.language.as_deref()
543 }
544}
545
546#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
548#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
549pub struct SourceSpan {
550 offset: SourceOffset,
552 length: usize,
554}
555
556impl SourceSpan {
557 pub const fn new(start: SourceOffset, length: usize) -> Self {
559 Self {
560 offset: start,
561 length,
562 }
563 }
564
565 pub const fn offset(&self) -> usize {
567 self.offset.offset()
568 }
569
570 pub const fn len(&self) -> usize {
572 self.length
573 }
574
575 pub const fn is_empty(&self) -> bool {
578 self.length == 0
579 }
580}
581
582impl From<(ByteOffset, usize)> for SourceSpan {
583 fn from((start, len): (ByteOffset, usize)) -> Self {
584 Self {
585 offset: start.into(),
586 length: len,
587 }
588 }
589}
590
591impl From<(SourceOffset, usize)> for SourceSpan {
592 fn from((start, len): (SourceOffset, usize)) -> Self {
593 Self::new(start, len)
594 }
595}
596
597impl From<std::ops::Range<ByteOffset>> for SourceSpan {
598 fn from(range: std::ops::Range<ByteOffset>) -> Self {
599 Self {
600 offset: range.start.into(),
601 length: range.len(),
602 }
603 }
604}
605
606impl From<std::ops::RangeInclusive<ByteOffset>> for SourceSpan {
607 fn from(range: std::ops::RangeInclusive<ByteOffset>) -> Self {
612 let (start, end) = range.clone().into_inner();
613 Self {
614 offset: start.into(),
615 length: if range.is_empty() {
616 0
617 } else {
618 (end - start)
621 .checked_add(1)
622 .expect("length of inclusive range should fit in a usize")
623 },
624 }
625 }
626}
627
628impl From<SourceOffset> for SourceSpan {
629 fn from(offset: SourceOffset) -> Self {
630 Self { offset, length: 0 }
631 }
632}
633
634impl From<ByteOffset> for SourceSpan {
635 fn from(offset: ByteOffset) -> Self {
636 Self {
637 offset: offset.into(),
638 length: 0,
639 }
640 }
641}
642
643#[cfg(feature = "serde")]
644#[test]
645fn test_serialize_source_span() {
646 use serde_json::json;
647
648 assert_eq!(
649 json!(SourceSpan::from(0)),
650 json!({ "offset": 0, "length": 0})
651 );
652}
653
654#[cfg(feature = "serde")]
655#[test]
656fn test_deserialize_source_span() {
657 use serde_json::json;
658
659 let span: SourceSpan = serde_json::from_value(json!({ "offset": 0, "length": 0})).unwrap();
660 assert_eq!(span, SourceSpan::from(0));
661}
662
663pub type ByteOffset = usize;
667
668#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
672#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
673pub struct SourceOffset(ByteOffset);
674
675impl SourceOffset {
676 pub const fn offset(&self) -> ByteOffset {
678 self.0
679 }
680
681 pub fn from_location(source: impl AsRef<str>, loc_line: usize, loc_col: usize) -> Self {
687 let mut line = 0usize;
688 let mut col = 0usize;
689 let mut offset = 0usize;
690 for char in source.as_ref().chars() {
691 if line + 1 >= loc_line && col + 1 >= loc_col {
692 break;
693 }
694 if char == '\n' {
695 col = 0;
696 line += 1;
697 } else {
698 col += 1;
699 }
700 offset += char.len_utf8();
701 }
702
703 SourceOffset(offset)
704 }
705
706 #[track_caller]
718 pub fn from_current_location() -> Result<(String, Self), MietteError> {
719 let loc = Location::caller();
720 Ok((
721 loc.file().into(),
722 fs::read_to_string(loc.file())
723 .map(|txt| Self::from_location(txt, loc.line() as usize, loc.column() as usize))?,
724 ))
725 }
726}
727
728impl From<ByteOffset> for SourceOffset {
729 fn from(bytes: ByteOffset) -> Self {
730 SourceOffset(bytes)
731 }
732}
733
734#[test]
735fn test_source_offset_from_location() {
736 let source = "f\n\noo\r\nbar";
737
738 assert_eq!(SourceOffset::from_location(source, 1, 1).offset(), 0);
739 assert_eq!(SourceOffset::from_location(source, 1, 2).offset(), 1);
740 assert_eq!(SourceOffset::from_location(source, 2, 1).offset(), 2);
741 assert_eq!(SourceOffset::from_location(source, 3, 1).offset(), 3);
742 assert_eq!(SourceOffset::from_location(source, 3, 2).offset(), 4);
743 assert_eq!(SourceOffset::from_location(source, 3, 3).offset(), 5);
744 assert_eq!(SourceOffset::from_location(source, 3, 4).offset(), 6);
745 assert_eq!(SourceOffset::from_location(source, 4, 1).offset(), 7);
746 assert_eq!(SourceOffset::from_location(source, 4, 2).offset(), 8);
747 assert_eq!(SourceOffset::from_location(source, 4, 3).offset(), 9);
748 assert_eq!(SourceOffset::from_location(source, 4, 4).offset(), 10);
749
750 assert_eq!(
752 SourceOffset::from_location(source, 5, 1).offset(),
753 source.len()
754 );
755}
756
757#[cfg(feature = "serde")]
758#[test]
759fn test_serialize_source_offset() {
760 use serde_json::json;
761
762 assert_eq!(json!(SourceOffset::from(0)), 0);
763}
764
765#[cfg(feature = "serde")]
766#[test]
767fn test_deserialize_source_offset() {
768 let offset: SourceOffset = serde_json::from_str("0").unwrap();
769 assert_eq!(offset, SourceOffset::from(0));
770}