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 note<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
49 None
50 }
51
52 fn url<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
55 None
56 }
57
58 fn source_code(&self) -> Option<&dyn SourceCode> {
60 None
61 }
62
63 fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
65 None
66 }
67
68 fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
70 None
71 }
72
73 fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
75 None
76 }
77}
78
79macro_rules! box_error_impls {
80 ($($box_type:ty),*) => {
81 $(
82 impl std::error::Error for $box_type {
83 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
84 (**self).source()
85 }
86
87 fn cause(&self) -> Option<&dyn std::error::Error> {
88 self.source()
89 }
90 }
91 )*
92 }
93}
94
95box_error_impls! {
96 Box<dyn Diagnostic>,
97 Box<dyn Diagnostic + Send>,
98 Box<dyn Diagnostic + Send + Sync>
99}
100
101macro_rules! box_borrow_impls {
102 ($($box_type:ty),*) => {
103 $(
104 impl std::borrow::Borrow<dyn Diagnostic> for $box_type {
105 fn borrow(&self) -> &(dyn Diagnostic + 'static) {
106 self.as_ref()
107 }
108 }
109 )*
110 }
111}
112
113box_borrow_impls! {
114 Box<dyn Diagnostic + Send>,
115 Box<dyn Diagnostic + Send + Sync>
116}
117
118impl<T: Diagnostic + Send + Sync + 'static> From<T>
119 for Box<dyn Diagnostic + Send + Sync + 'static>
120{
121 fn from(diag: T) -> Self {
122 Box::new(diag)
123 }
124}
125
126impl<T: Diagnostic + Send + Sync + 'static> From<T> for Box<dyn Diagnostic + Send + 'static> {
127 fn from(diag: T) -> Self {
128 Box::<dyn Diagnostic + Send + Sync>::from(diag)
129 }
130}
131
132impl<T: Diagnostic + Send + Sync + 'static> From<T> for Box<dyn Diagnostic + 'static> {
133 fn from(diag: T) -> Self {
134 Box::<dyn Diagnostic + Send + Sync>::from(diag)
135 }
136}
137
138impl From<&str> for Box<dyn Diagnostic> {
139 fn from(s: &str) -> Self {
140 From::from(String::from(s))
141 }
142}
143
144impl From<&str> for Box<dyn Diagnostic + Send + Sync + '_> {
145 fn from(s: &str) -> Self {
146 From::from(String::from(s))
147 }
148}
149
150impl From<String> for Box<dyn Diagnostic> {
151 fn from(s: String) -> Self {
152 let err1: Box<dyn Diagnostic + Send + Sync> = From::from(s);
153 let err2: Box<dyn Diagnostic> = err1;
154 err2
155 }
156}
157
158impl From<String> for Box<dyn Diagnostic + Send + Sync> {
159 fn from(s: String) -> Self {
160 struct StringError(String);
161
162 impl std::error::Error for StringError {}
163 impl Diagnostic for StringError {}
164
165 impl Display for StringError {
166 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167 Display::fmt(&self.0, f)
168 }
169 }
170
171 impl fmt::Debug for StringError {
173 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174 fmt::Debug::fmt(&self.0, f)
175 }
176 }
177
178 Box::new(StringError(s))
179 }
180}
181
182impl From<Box<dyn std::error::Error + Send + Sync>> for Box<dyn Diagnostic + Send + Sync> {
183 fn from(s: Box<dyn std::error::Error + Send + Sync>) -> Self {
184 #[derive(thiserror::Error)]
185 #[error(transparent)]
186 struct BoxedDiagnostic(Box<dyn std::error::Error + Send + Sync>);
187 impl fmt::Debug for BoxedDiagnostic {
188 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
189 fmt::Debug::fmt(&self.0, f)
190 }
191 }
192
193 impl Diagnostic for BoxedDiagnostic {}
194
195 Box::new(BoxedDiagnostic(s))
196 }
197}
198
199#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
205#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
206#[derive(Default)]
207pub enum Severity {
208 Advice,
210 Warning,
212 #[default]
215 Error,
216}
217
218#[cfg(feature = "serde")]
219#[test]
220fn test_serialize_severity() {
221 use serde_json::json;
222
223 assert_eq!(json!(Severity::Advice), json!("Advice"));
224 assert_eq!(json!(Severity::Warning), json!("Warning"));
225 assert_eq!(json!(Severity::Error), json!("Error"));
226}
227
228#[cfg(feature = "serde")]
229#[test]
230fn test_deserialize_severity() {
231 use serde_json::json;
232
233 let severity: Severity = serde_json::from_value(json!("Advice")).unwrap();
234 assert_eq!(severity, Severity::Advice);
235
236 let severity: Severity = serde_json::from_value(json!("Warning")).unwrap();
237 assert_eq!(severity, Severity::Warning);
238
239 let severity: Severity = serde_json::from_value(json!("Error")).unwrap();
240 assert_eq!(severity, Severity::Error);
241}
242
243pub trait SourceCode: Send + Sync {
255 fn read_span<'a>(
258 &'a self,
259 span: &SourceSpan,
260 context_lines_before: usize,
261 context_lines_after: usize,
262 ) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError>;
263
264 fn name(&self) -> Option<&str> {
266 None
267 }
268}
269
270#[derive(Debug, Clone, PartialEq, Eq)]
272#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
273pub struct LabeledSpan {
274 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
275 label: Option<String>,
276 span: SourceSpan,
277 primary: bool,
278}
279
280impl LabeledSpan {
281 #[must_use]
283 pub const fn new(label: Option<String>, offset: ByteOffset, len: usize) -> Self {
284 Self { label, span: SourceSpan::new(SourceOffset(offset), len), primary: false }
285 }
286
287 #[must_use]
289 pub fn new_with_span(label: Option<String>, span: impl Into<SourceSpan>) -> Self {
290 Self { label, span: span.into(), primary: false }
291 }
292
293 #[must_use]
295 pub fn new_primary_with_span(label: Option<String>, span: impl Into<SourceSpan>) -> Self {
296 Self { label, span: span.into(), primary: true }
297 }
298
299 pub fn set_label(&mut self, label: Option<String>) {
301 self.label = label;
302 }
303
304 pub fn set_span_offset(&mut self, offset: usize) {
306 self.span.offset = SourceOffset(offset);
307 }
308
309 #[must_use]
323 pub fn at(span: impl Into<SourceSpan>, label: impl Into<String>) -> Self {
324 Self::new_with_span(Some(label.into()), span)
325 }
326
327 pub fn at_offset(offset: ByteOffset, label: impl Into<String>) -> Self {
341 Self::new(Some(label.into()), offset, 0)
342 }
343
344 #[must_use]
355 pub fn underline(span: impl Into<SourceSpan>) -> Self {
356 Self::new_with_span(None, span)
357 }
358
359 pub fn label(&self) -> Option<&str> {
361 self.label.as_deref()
362 }
363
364 pub const fn inner(&self) -> &SourceSpan {
366 &self.span
367 }
368
369 pub const fn offset(&self) -> usize {
371 self.span.offset()
372 }
373
374 pub const fn len(&self) -> usize {
376 self.span.len()
377 }
378
379 pub const fn is_empty(&self) -> bool {
381 self.span.is_empty()
382 }
383
384 pub const fn primary(&self) -> bool {
386 self.primary
387 }
388}
389
390#[test]
391fn test_set_span_offset() {
392 let mut span = LabeledSpan::new(None, 10, 10);
393 assert_eq!(span.offset(), 10);
394
395 span.set_span_offset(20);
396 assert_eq!(span.offset(), 20);
397}
398
399#[cfg(feature = "serde")]
400#[test]
401fn test_serialize_labeled_span() {
402 use serde_json::json;
403
404 assert_eq!(
405 json!(LabeledSpan::new(None, 0, 0)),
406 json!({
407 "span": { "offset": 0, "length": 0, },
408 "primary": false,
409 })
410 );
411
412 assert_eq!(
413 json!(LabeledSpan::new(Some("label".to_string()), 0, 0)),
414 json!({
415 "label": "label",
416 "span": { "offset": 0, "length": 0, },
417 "primary": false,
418 })
419 );
420}
421
422#[cfg(feature = "serde")]
423#[test]
424fn test_deserialize_labeled_span() {
425 use serde_json::json;
426
427 let span: LabeledSpan = serde_json::from_value(json!({
428 "label": null,
429 "span": { "offset": 0, "length": 0, },
430 "primary": false,
431 }))
432 .unwrap();
433 assert_eq!(span, LabeledSpan::new(None, 0, 0));
434
435 let span: LabeledSpan = serde_json::from_value(json!({
436 "span": { "offset": 0, "length": 0, },
437 "primary": false
438 }))
439 .unwrap();
440 assert_eq!(span, LabeledSpan::new(None, 0, 0));
441
442 let span: LabeledSpan = serde_json::from_value(json!({
443 "label": "label",
444 "span": { "offset": 0, "length": 0, },
445 "primary": false
446 }))
447 .unwrap();
448 assert_eq!(span, LabeledSpan::new(Some("label".to_string()), 0, 0));
449}
450
451pub trait SpanContents<'a> {
457 fn data(&self) -> &'a [u8];
459 fn span(&self) -> &SourceSpan;
461 fn name(&self) -> Option<&str> {
463 None
464 }
465 fn line(&self) -> usize;
468 fn column(&self) -> usize;
471 fn line_count(&self) -> usize;
473
474 fn language(&self) -> Option<&str> {
480 None
481 }
482}
483
484#[derive(Clone, Debug)]
488pub struct MietteSpanContents<'a> {
489 data: &'a [u8],
491 span: SourceSpan,
493 line: usize,
495 column: usize,
497 line_count: usize,
499 name: Option<String>,
501 language: Option<String>,
503}
504
505impl<'a> MietteSpanContents<'a> {
506 pub const fn new(
508 data: &'a [u8],
509 span: SourceSpan,
510 line: usize,
511 column: usize,
512 line_count: usize,
513 ) -> MietteSpanContents<'a> {
514 MietteSpanContents { data, span, line, column, line_count, name: None, language: None }
515 }
516
517 pub const fn new_named(
519 name: String,
520 data: &'a [u8],
521 span: SourceSpan,
522 line: usize,
523 column: usize,
524 line_count: usize,
525 ) -> MietteSpanContents<'a> {
526 MietteSpanContents {
527 data,
528 span,
529 line,
530 column,
531 line_count,
532 name: Some(name),
533 language: None,
534 }
535 }
536
537 #[must_use]
539 pub fn with_language(mut self, language: impl Into<String>) -> Self {
540 self.language = Some(language.into());
541 self
542 }
543}
544
545impl<'a> SpanContents<'a> for MietteSpanContents<'a> {
546 fn data(&self) -> &'a [u8] {
547 self.data
548 }
549
550 fn span(&self) -> &SourceSpan {
551 &self.span
552 }
553
554 fn line(&self) -> usize {
555 self.line
556 }
557
558 fn column(&self) -> usize {
559 self.column
560 }
561
562 fn line_count(&self) -> usize {
563 self.line_count
564 }
565
566 fn name(&self) -> Option<&str> {
567 self.name.as_deref()
568 }
569
570 fn language(&self) -> Option<&str> {
571 self.language.as_deref()
572 }
573}
574
575#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
577#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
578pub struct SourceSpan {
579 offset: SourceOffset,
581 length: usize,
583}
584
585impl SourceSpan {
586 pub const fn new(start: SourceOffset, length: usize) -> Self {
588 Self { offset: start, length }
589 }
590
591 pub const fn offset(&self) -> usize {
593 self.offset.offset()
594 }
595
596 pub const fn len(&self) -> usize {
598 self.length
599 }
600
601 pub const fn is_empty(&self) -> bool {
604 self.length == 0
605 }
606}
607
608impl From<(ByteOffset, usize)> for SourceSpan {
609 fn from((start, len): (ByteOffset, usize)) -> Self {
610 Self { offset: start.into(), length: len }
611 }
612}
613
614impl From<(SourceOffset, usize)> for SourceSpan {
615 fn from((start, len): (SourceOffset, usize)) -> Self {
616 Self::new(start, len)
617 }
618}
619
620impl From<std::ops::Range<ByteOffset>> for SourceSpan {
621 fn from(range: std::ops::Range<ByteOffset>) -> Self {
622 Self { offset: range.start.into(), length: range.len() }
623 }
624}
625
626impl From<SourceOffset> for SourceSpan {
627 fn from(offset: SourceOffset) -> Self {
628 Self { offset, length: 0 }
629 }
630}
631
632impl From<ByteOffset> for SourceSpan {
633 fn from(offset: ByteOffset) -> Self {
634 Self { offset: offset.into(), length: 0 }
635 }
636}
637
638#[cfg(feature = "serde")]
639#[test]
640fn test_serialize_source_span() {
641 use serde_json::json;
642
643 assert_eq!(json!(SourceSpan::from(0)), json!({ "offset": 0, "length": 0}));
644}
645
646#[cfg(feature = "serde")]
647#[test]
648fn test_deserialize_source_span() {
649 use serde_json::json;
650
651 let span: SourceSpan = serde_json::from_value(json!({ "offset": 0, "length": 0})).unwrap();
652 assert_eq!(span, SourceSpan::from(0));
653}
654
655pub type ByteOffset = usize;
659
660#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
664#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
665pub struct SourceOffset(ByteOffset);
666
667impl SourceOffset {
668 pub const fn offset(&self) -> ByteOffset {
670 self.0
671 }
672
673 pub fn from_location(source: impl AsRef<str>, loc_line: usize, loc_col: usize) -> Self {
679 let mut line = 0usize;
680 let mut col = 0usize;
681 let mut offset = 0usize;
682 for char in source.as_ref().chars() {
683 if line + 1 >= loc_line && col + 1 >= loc_col {
684 break;
685 }
686 if char == '\n' {
687 col = 0;
688 line += 1;
689 } else {
690 col += 1;
691 }
692 offset += char.len_utf8();
693 }
694
695 SourceOffset(offset)
696 }
697
698 #[track_caller]
710 pub fn from_current_location() -> Result<(String, Self), MietteError> {
711 let loc = Location::caller();
712 Ok((
713 loc.file().into(),
714 fs::read_to_string(loc.file())
715 .map(|txt| Self::from_location(txt, loc.line() as usize, loc.column() as usize))?,
716 ))
717 }
718}
719
720impl From<ByteOffset> for SourceOffset {
721 fn from(bytes: ByteOffset) -> Self {
722 SourceOffset(bytes)
723 }
724}
725
726#[test]
727fn test_source_offset_from_location() {
728 let source = "f\n\noo\r\nbar";
729
730 assert_eq!(SourceOffset::from_location(source, 1, 1).offset(), 0);
731 assert_eq!(SourceOffset::from_location(source, 1, 2).offset(), 1);
732 assert_eq!(SourceOffset::from_location(source, 2, 1).offset(), 2);
733 assert_eq!(SourceOffset::from_location(source, 3, 1).offset(), 3);
734 assert_eq!(SourceOffset::from_location(source, 3, 2).offset(), 4);
735 assert_eq!(SourceOffset::from_location(source, 3, 3).offset(), 5);
736 assert_eq!(SourceOffset::from_location(source, 3, 4).offset(), 6);
737 assert_eq!(SourceOffset::from_location(source, 4, 1).offset(), 7);
738 assert_eq!(SourceOffset::from_location(source, 4, 2).offset(), 8);
739 assert_eq!(SourceOffset::from_location(source, 4, 3).offset(), 9);
740 assert_eq!(SourceOffset::from_location(source, 4, 4).offset(), 10);
741
742 assert_eq!(SourceOffset::from_location(source, 5, 1).offset(), source.len());
744}
745
746#[cfg(feature = "serde")]
747#[test]
748fn test_serialize_source_offset() {
749 use serde_json::json;
750
751 assert_eq!(json!(SourceOffset::from(0)), 0);
752}
753
754#[cfg(feature = "serde")]
755#[test]
756fn test_deserialize_source_offset() {
757 let offset: SourceOffset = serde_json::from_str("0").unwrap();
758 assert_eq!(offset, SourceOffset::from(0));
759}