1use std::{
7 fmt::{self, Display},
8 fs,
9 panic::Location,
10};
11
12#[cfg(feature = "serde")]
13use serde::{Deserialize, Serialize};
14
15use crate::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<'a> From<&str> for Box<dyn Diagnostic + Send + Sync + 'a> {
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 #[derive(thiserror::Error)]
178 #[error(transparent)]
179 struct BoxedDiagnostic(Box<dyn std::error::Error + Send + Sync>);
180 impl fmt::Debug for BoxedDiagnostic {
181 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
182 fmt::Debug::fmt(&self.0, f)
183 }
184 }
185
186 impl Diagnostic for BoxedDiagnostic {}
187
188 Box::new(BoxedDiagnostic(s))
189 }
190}
191
192#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
198#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
199#[derive(Default)]
200pub enum Severity {
201 Advice,
203 Warning,
205 #[default]
208 Error,
209}
210
211#[cfg(feature = "serde")]
212#[test]
213fn test_serialize_severity() {
214 use serde_json::json;
215
216 assert_eq!(json!(Severity::Advice), json!("Advice"));
217 assert_eq!(json!(Severity::Warning), json!("Warning"));
218 assert_eq!(json!(Severity::Error), json!("Error"));
219}
220
221#[cfg(feature = "serde")]
222#[test]
223fn test_deserialize_severity() {
224 use serde_json::json;
225
226 let severity: Severity = serde_json::from_value(json!("Advice")).unwrap();
227 assert_eq!(severity, Severity::Advice);
228
229 let severity: Severity = serde_json::from_value(json!("Warning")).unwrap();
230 assert_eq!(severity, Severity::Warning);
231
232 let severity: Severity = serde_json::from_value(json!("Error")).unwrap();
233 assert_eq!(severity, Severity::Error);
234}
235
236pub trait SourceCode: Send + Sync {
248 fn read_span<'a>(
251 &'a self,
252 span: &SourceSpan,
253 context_lines_before: usize,
254 context_lines_after: usize,
255 ) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError>;
256}
257
258#[derive(Debug, Clone, PartialEq, Eq)]
260#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
261pub struct LabeledSpan {
262 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
263 label: Option<String>,
264 span: SourceSpan,
265 primary: bool,
266}
267
268impl LabeledSpan {
269 pub const fn new(label: Option<String>, offset: ByteOffset, len: usize) -> Self {
271 Self {
272 label,
273 span: SourceSpan::new(SourceOffset(offset), len),
274 primary: false,
275 }
276 }
277
278 pub fn new_with_span(label: Option<String>, span: impl Into<SourceSpan>) -> Self {
280 Self {
281 label,
282 span: span.into(),
283 primary: false,
284 }
285 }
286
287 pub fn new_primary_with_span(label: Option<String>, span: impl Into<SourceSpan>) -> Self {
289 Self {
290 label,
291 span: span.into(),
292 primary: true,
293 }
294 }
295
296 pub fn set_label(&mut self, label: Option<String>) {
298 self.label = label;
299 }
300
301 pub fn at(span: impl Into<SourceSpan>, label: impl Into<String>) -> Self {
315 Self::new_with_span(Some(label.into()), span)
316 }
317
318 pub fn at_offset(offset: ByteOffset, label: impl Into<String>) -> Self {
332 Self::new(Some(label.into()), offset, 0)
333 }
334
335 pub fn underline(span: impl Into<SourceSpan>) -> Self {
346 Self::new_with_span(None, span)
347 }
348
349 pub fn label(&self) -> Option<&str> {
351 self.label.as_deref()
352 }
353
354 pub const fn inner(&self) -> &SourceSpan {
356 &self.span
357 }
358
359 pub const fn offset(&self) -> usize {
361 self.span.offset()
362 }
363
364 pub const fn len(&self) -> usize {
366 self.span.len()
367 }
368
369 pub const fn is_empty(&self) -> bool {
371 self.span.is_empty()
372 }
373
374 pub const fn primary(&self) -> bool {
376 self.primary
377 }
378}
379
380#[cfg(feature = "serde")]
381#[test]
382fn test_serialize_labeled_span() {
383 use serde_json::json;
384
385 assert_eq!(
386 json!(LabeledSpan::new(None, 0, 0)),
387 json!({
388 "span": { "offset": 0, "length": 0, },
389 "primary": false,
390 })
391 );
392
393 assert_eq!(
394 json!(LabeledSpan::new(Some("label".to_string()), 0, 0)),
395 json!({
396 "label": "label",
397 "span": { "offset": 0, "length": 0, },
398 "primary": false,
399 })
400 );
401}
402
403#[cfg(feature = "serde")]
404#[test]
405fn test_deserialize_labeled_span() {
406 use serde_json::json;
407
408 let span: LabeledSpan = serde_json::from_value(json!({
409 "label": null,
410 "span": { "offset": 0, "length": 0, },
411 "primary": false,
412 }))
413 .unwrap();
414 assert_eq!(span, LabeledSpan::new(None, 0, 0));
415
416 let span: LabeledSpan = serde_json::from_value(json!({
417 "span": { "offset": 0, "length": 0, },
418 "primary": false
419 }))
420 .unwrap();
421 assert_eq!(span, LabeledSpan::new(None, 0, 0));
422
423 let span: LabeledSpan = serde_json::from_value(json!({
424 "label": "label",
425 "span": { "offset": 0, "length": 0, },
426 "primary": false
427 }))
428 .unwrap();
429 assert_eq!(span, LabeledSpan::new(Some("label".to_string()), 0, 0));
430}
431
432pub trait SpanContents<'a> {
438 fn data(&self) -> &'a [u8];
440 fn span(&self) -> &SourceSpan;
442 fn name(&self) -> Option<&str> {
444 None
445 }
446 fn line(&self) -> usize;
449 fn column(&self) -> usize;
452 fn line_count(&self) -> usize;
454
455 fn language(&self) -> Option<&str> {
461 None
462 }
463}
464
465#[derive(Clone, Debug)]
469pub struct MietteSpanContents<'a> {
470 data: &'a [u8],
472 span: SourceSpan,
474 line: usize,
476 column: usize,
478 line_count: usize,
480 name: Option<String>,
482 language: Option<String>,
484}
485
486impl<'a> MietteSpanContents<'a> {
487 pub const fn new(
489 data: &'a [u8],
490 span: SourceSpan,
491 line: usize,
492 column: usize,
493 line_count: usize,
494 ) -> MietteSpanContents<'a> {
495 MietteSpanContents {
496 data,
497 span,
498 line,
499 column,
500 line_count,
501 name: None,
502 language: None,
503 }
504 }
505
506 pub const fn new_named(
508 name: String,
509 data: &'a [u8],
510 span: SourceSpan,
511 line: usize,
512 column: usize,
513 line_count: usize,
514 ) -> MietteSpanContents<'a> {
515 MietteSpanContents {
516 data,
517 span,
518 line,
519 column,
520 line_count,
521 name: Some(name),
522 language: None,
523 }
524 }
525
526 pub fn with_language(mut self, language: impl Into<String>) -> Self {
528 self.language = Some(language.into());
529 self
530 }
531}
532
533impl<'a> SpanContents<'a> for MietteSpanContents<'a> {
534 fn data(&self) -> &'a [u8] {
535 self.data
536 }
537 fn span(&self) -> &SourceSpan {
538 &self.span
539 }
540 fn line(&self) -> usize {
541 self.line
542 }
543 fn column(&self) -> usize {
544 self.column
545 }
546 fn line_count(&self) -> usize {
547 self.line_count
548 }
549 fn name(&self) -> Option<&str> {
550 self.name.as_deref()
551 }
552 fn language(&self) -> Option<&str> {
553 self.language.as_deref()
554 }
555}
556
557#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
559#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
560pub struct SourceSpan {
561 offset: SourceOffset,
563 length: usize,
565}
566
567impl SourceSpan {
568 pub const fn new(start: SourceOffset, length: usize) -> Self {
570 Self {
571 offset: start,
572 length,
573 }
574 }
575
576 pub const fn offset(&self) -> usize {
578 self.offset.offset()
579 }
580
581 pub const fn len(&self) -> usize {
583 self.length
584 }
585
586 pub const fn is_empty(&self) -> bool {
589 self.length == 0
590 }
591}
592
593impl From<(ByteOffset, usize)> for SourceSpan {
594 fn from((start, len): (ByteOffset, usize)) -> Self {
595 Self {
596 offset: start.into(),
597 length: len,
598 }
599 }
600}
601
602impl From<(SourceOffset, usize)> for SourceSpan {
603 fn from((start, len): (SourceOffset, usize)) -> Self {
604 Self::new(start, len)
605 }
606}
607
608impl From<std::ops::Range<ByteOffset>> for SourceSpan {
609 fn from(range: std::ops::Range<ByteOffset>) -> Self {
610 Self {
611 offset: range.start.into(),
612 length: range.len(),
613 }
614 }
615}
616
617impl From<std::ops::RangeInclusive<ByteOffset>> for SourceSpan {
618 fn from(range: std::ops::RangeInclusive<ByteOffset>) -> Self {
623 let (start, end) = range.clone().into_inner();
624 Self {
625 offset: start.into(),
626 length: if range.is_empty() {
627 0
628 } else {
629 (end - start)
632 .checked_add(1)
633 .expect("length of inclusive range should fit in a usize")
634 },
635 }
636 }
637}
638
639impl From<SourceOffset> for SourceSpan {
640 fn from(offset: SourceOffset) -> Self {
641 Self { offset, length: 0 }
642 }
643}
644
645impl From<ByteOffset> for SourceSpan {
646 fn from(offset: ByteOffset) -> Self {
647 Self {
648 offset: offset.into(),
649 length: 0,
650 }
651 }
652}
653
654#[cfg(feature = "serde")]
655#[test]
656fn test_serialize_source_span() {
657 use serde_json::json;
658
659 assert_eq!(
660 json!(SourceSpan::from(0)),
661 json!({ "offset": 0, "length": 0})
662 );
663}
664
665#[cfg(feature = "serde")]
666#[test]
667fn test_deserialize_source_span() {
668 use serde_json::json;
669
670 let span: SourceSpan = serde_json::from_value(json!({ "offset": 0, "length": 0})).unwrap();
671 assert_eq!(span, SourceSpan::from(0));
672}
673
674pub type ByteOffset = usize;
678
679#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
683#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
684pub struct SourceOffset(ByteOffset);
685
686impl SourceOffset {
687 pub const fn offset(&self) -> ByteOffset {
689 self.0
690 }
691
692 pub fn from_location(source: impl AsRef<str>, loc_line: usize, loc_col: usize) -> Self {
698 let mut line = 0usize;
699 let mut col = 0usize;
700 let mut offset = 0usize;
701 for char in source.as_ref().chars() {
702 if line + 1 >= loc_line && col + 1 >= loc_col {
703 break;
704 }
705 if char == '\n' {
706 col = 0;
707 line += 1;
708 } else {
709 col += 1;
710 }
711 offset += char.len_utf8();
712 }
713
714 SourceOffset(offset)
715 }
716
717 #[track_caller]
729 pub fn from_current_location() -> Result<(String, Self), MietteError> {
730 let loc = Location::caller();
731 Ok((
732 loc.file().into(),
733 fs::read_to_string(loc.file())
734 .map(|txt| Self::from_location(txt, loc.line() as usize, loc.column() as usize))?,
735 ))
736 }
737}
738
739impl From<ByteOffset> for SourceOffset {
740 fn from(bytes: ByteOffset) -> Self {
741 SourceOffset(bytes)
742 }
743}
744
745#[test]
746fn test_source_offset_from_location() {
747 let source = "f\n\noo\r\nbar";
748
749 assert_eq!(SourceOffset::from_location(source, 1, 1).offset(), 0);
750 assert_eq!(SourceOffset::from_location(source, 1, 2).offset(), 1);
751 assert_eq!(SourceOffset::from_location(source, 2, 1).offset(), 2);
752 assert_eq!(SourceOffset::from_location(source, 3, 1).offset(), 3);
753 assert_eq!(SourceOffset::from_location(source, 3, 2).offset(), 4);
754 assert_eq!(SourceOffset::from_location(source, 3, 3).offset(), 5);
755 assert_eq!(SourceOffset::from_location(source, 3, 4).offset(), 6);
756 assert_eq!(SourceOffset::from_location(source, 4, 1).offset(), 7);
757 assert_eq!(SourceOffset::from_location(source, 4, 2).offset(), 8);
758 assert_eq!(SourceOffset::from_location(source, 4, 3).offset(), 9);
759 assert_eq!(SourceOffset::from_location(source, 4, 4).offset(), 10);
760
761 assert_eq!(
763 SourceOffset::from_location(source, 5, 1).offset(),
764 source.len()
765 );
766}
767
768#[cfg(feature = "serde")]
769#[test]
770fn test_serialize_source_offset() {
771 use serde_json::json;
772
773 assert_eq!(json!(SourceOffset::from(0)), 0);
774}
775
776#[cfg(feature = "serde")]
777#[test]
778fn test_deserialize_source_offset() {
779 let offset: SourceOffset = serde_json::from_str("0").unwrap();
780 assert_eq!(offset, SourceOffset::from(0));
781}