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 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 #[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 fn name(&self) -> Option<&str> {
259 None
260 }
261}
262
263#[derive(Debug, Clone, PartialEq, Eq)]
265#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
266pub struct LabeledSpan {
267 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
268 label: Option<String>,
269 span: SourceSpan,
270 primary: bool,
271}
272
273impl LabeledSpan {
274 #[must_use]
276 pub const fn new(label: Option<String>, offset: ByteOffset, len: usize) -> Self {
277 Self { label, span: SourceSpan::new(SourceOffset(offset), len), primary: false }
278 }
279
280 #[must_use]
282 pub fn new_with_span(label: Option<String>, span: impl Into<SourceSpan>) -> Self {
283 Self { label, span: span.into(), primary: false }
284 }
285
286 #[must_use]
288 pub fn new_primary_with_span(label: Option<String>, span: impl Into<SourceSpan>) -> Self {
289 Self { label, span: span.into(), primary: true }
290 }
291
292 pub fn set_label(&mut self, label: Option<String>) {
294 self.label = label;
295 }
296
297 pub fn set_span_offset(&mut self, offset: usize) {
299 self.span.offset = SourceOffset(offset);
300 }
301
302 #[must_use]
316 pub fn at(span: impl Into<SourceSpan>, label: impl Into<String>) -> Self {
317 Self::new_with_span(Some(label.into()), span)
318 }
319
320 pub fn at_offset(offset: ByteOffset, label: impl Into<String>) -> Self {
334 Self::new(Some(label.into()), offset, 0)
335 }
336
337 #[must_use]
348 pub fn underline(span: impl Into<SourceSpan>) -> Self {
349 Self::new_with_span(None, span)
350 }
351
352 pub fn label(&self) -> Option<&str> {
354 self.label.as_deref()
355 }
356
357 pub const fn inner(&self) -> &SourceSpan {
359 &self.span
360 }
361
362 pub const fn offset(&self) -> usize {
364 self.span.offset()
365 }
366
367 pub const fn len(&self) -> usize {
369 self.span.len()
370 }
371
372 pub const fn is_empty(&self) -> bool {
374 self.span.is_empty()
375 }
376
377 pub const fn primary(&self) -> bool {
379 self.primary
380 }
381}
382
383#[test]
384fn test_set_span_offset() {
385 let mut span = LabeledSpan::new(None, 10, 10);
386 assert_eq!(span.offset(), 10);
387
388 span.set_span_offset(20);
389 assert_eq!(span.offset(), 20);
390}
391
392#[cfg(feature = "serde")]
393#[test]
394fn test_serialize_labeled_span() {
395 use serde_json::json;
396
397 assert_eq!(
398 json!(LabeledSpan::new(None, 0, 0)),
399 json!({
400 "span": { "offset": 0, "length": 0, },
401 "primary": false,
402 })
403 );
404
405 assert_eq!(
406 json!(LabeledSpan::new(Some("label".to_string()), 0, 0)),
407 json!({
408 "label": "label",
409 "span": { "offset": 0, "length": 0, },
410 "primary": false,
411 })
412 );
413}
414
415#[cfg(feature = "serde")]
416#[test]
417fn test_deserialize_labeled_span() {
418 use serde_json::json;
419
420 let span: LabeledSpan = serde_json::from_value(json!({
421 "label": null,
422 "span": { "offset": 0, "length": 0, },
423 "primary": false,
424 }))
425 .unwrap();
426 assert_eq!(span, LabeledSpan::new(None, 0, 0));
427
428 let span: LabeledSpan = serde_json::from_value(json!({
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 "label": "label",
437 "span": { "offset": 0, "length": 0, },
438 "primary": false
439 }))
440 .unwrap();
441 assert_eq!(span, LabeledSpan::new(Some("label".to_string()), 0, 0));
442}
443
444pub trait SpanContents<'a> {
450 fn data(&self) -> &'a [u8];
452 fn span(&self) -> &SourceSpan;
454 fn name(&self) -> Option<&str> {
456 None
457 }
458 fn line(&self) -> usize;
461 fn column(&self) -> usize;
464 fn line_count(&self) -> usize;
466
467 fn language(&self) -> Option<&str> {
473 None
474 }
475}
476
477#[derive(Clone, Debug)]
481pub struct MietteSpanContents<'a> {
482 data: &'a [u8],
484 span: SourceSpan,
486 line: usize,
488 column: usize,
490 line_count: usize,
492 name: Option<String>,
494 language: Option<String>,
496}
497
498impl<'a> MietteSpanContents<'a> {
499 pub const fn new(
501 data: &'a [u8],
502 span: SourceSpan,
503 line: usize,
504 column: usize,
505 line_count: usize,
506 ) -> MietteSpanContents<'a> {
507 MietteSpanContents { data, span, line, column, line_count, name: None, language: None }
508 }
509
510 pub const fn new_named(
512 name: String,
513 data: &'a [u8],
514 span: SourceSpan,
515 line: usize,
516 column: usize,
517 line_count: usize,
518 ) -> MietteSpanContents<'a> {
519 MietteSpanContents {
520 data,
521 span,
522 line,
523 column,
524 line_count,
525 name: Some(name),
526 language: None,
527 }
528 }
529
530 #[must_use]
532 pub fn with_language(mut self, language: impl Into<String>) -> Self {
533 self.language = Some(language.into());
534 self
535 }
536}
537
538impl<'a> SpanContents<'a> for MietteSpanContents<'a> {
539 fn data(&self) -> &'a [u8] {
540 self.data
541 }
542
543 fn span(&self) -> &SourceSpan {
544 &self.span
545 }
546
547 fn line(&self) -> usize {
548 self.line
549 }
550
551 fn column(&self) -> usize {
552 self.column
553 }
554
555 fn line_count(&self) -> usize {
556 self.line_count
557 }
558
559 fn name(&self) -> Option<&str> {
560 self.name.as_deref()
561 }
562
563 fn language(&self) -> Option<&str> {
564 self.language.as_deref()
565 }
566}
567
568#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
570#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
571pub struct SourceSpan {
572 offset: SourceOffset,
574 length: usize,
576}
577
578impl SourceSpan {
579 pub const fn new(start: SourceOffset, length: usize) -> Self {
581 Self { offset: start, length }
582 }
583
584 pub const fn offset(&self) -> usize {
586 self.offset.offset()
587 }
588
589 pub const fn len(&self) -> usize {
591 self.length
592 }
593
594 pub const fn is_empty(&self) -> bool {
597 self.length == 0
598 }
599}
600
601impl From<(ByteOffset, usize)> for SourceSpan {
602 fn from((start, len): (ByteOffset, usize)) -> Self {
603 Self { offset: start.into(), length: len }
604 }
605}
606
607impl From<(SourceOffset, usize)> for SourceSpan {
608 fn from((start, len): (SourceOffset, usize)) -> Self {
609 Self::new(start, len)
610 }
611}
612
613impl From<std::ops::Range<ByteOffset>> for SourceSpan {
614 fn from(range: std::ops::Range<ByteOffset>) -> Self {
615 Self { offset: range.start.into(), length: range.len() }
616 }
617}
618
619impl From<SourceOffset> for SourceSpan {
620 fn from(offset: SourceOffset) -> Self {
621 Self { offset, length: 0 }
622 }
623}
624
625impl From<ByteOffset> for SourceSpan {
626 fn from(offset: ByteOffset) -> Self {
627 Self { offset: offset.into(), length: 0 }
628 }
629}
630
631#[cfg(feature = "serde")]
632#[test]
633fn test_serialize_source_span() {
634 use serde_json::json;
635
636 assert_eq!(json!(SourceSpan::from(0)), json!({ "offset": 0, "length": 0}));
637}
638
639#[cfg(feature = "serde")]
640#[test]
641fn test_deserialize_source_span() {
642 use serde_json::json;
643
644 let span: SourceSpan = serde_json::from_value(json!({ "offset": 0, "length": 0})).unwrap();
645 assert_eq!(span, SourceSpan::from(0));
646}
647
648pub type ByteOffset = usize;
652
653#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
657#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
658pub struct SourceOffset(ByteOffset);
659
660impl SourceOffset {
661 pub const fn offset(&self) -> ByteOffset {
663 self.0
664 }
665
666 pub fn from_location(source: impl AsRef<str>, loc_line: usize, loc_col: usize) -> Self {
672 let mut line = 0usize;
673 let mut col = 0usize;
674 let mut offset = 0usize;
675 for char in source.as_ref().chars() {
676 if line + 1 >= loc_line && col + 1 >= loc_col {
677 break;
678 }
679 if char == '\n' {
680 col = 0;
681 line += 1;
682 } else {
683 col += 1;
684 }
685 offset += char.len_utf8();
686 }
687
688 SourceOffset(offset)
689 }
690
691 #[track_caller]
703 pub fn from_current_location() -> Result<(String, Self), MietteError> {
704 let loc = Location::caller();
705 Ok((
706 loc.file().into(),
707 fs::read_to_string(loc.file())
708 .map(|txt| Self::from_location(txt, loc.line() as usize, loc.column() as usize))?,
709 ))
710 }
711}
712
713impl From<ByteOffset> for SourceOffset {
714 fn from(bytes: ByteOffset) -> Self {
715 SourceOffset(bytes)
716 }
717}
718
719#[test]
720fn test_source_offset_from_location() {
721 let source = "f\n\noo\r\nbar";
722
723 assert_eq!(SourceOffset::from_location(source, 1, 1).offset(), 0);
724 assert_eq!(SourceOffset::from_location(source, 1, 2).offset(), 1);
725 assert_eq!(SourceOffset::from_location(source, 2, 1).offset(), 2);
726 assert_eq!(SourceOffset::from_location(source, 3, 1).offset(), 3);
727 assert_eq!(SourceOffset::from_location(source, 3, 2).offset(), 4);
728 assert_eq!(SourceOffset::from_location(source, 3, 3).offset(), 5);
729 assert_eq!(SourceOffset::from_location(source, 3, 4).offset(), 6);
730 assert_eq!(SourceOffset::from_location(source, 4, 1).offset(), 7);
731 assert_eq!(SourceOffset::from_location(source, 4, 2).offset(), 8);
732 assert_eq!(SourceOffset::from_location(source, 4, 3).offset(), 9);
733 assert_eq!(SourceOffset::from_location(source, 4, 4).offset(), 10);
734
735 assert_eq!(SourceOffset::from_location(source, 5, 1).offset(), source.len());
737}
738
739#[cfg(feature = "serde")]
740#[test]
741fn test_serialize_source_offset() {
742 use serde_json::json;
743
744 assert_eq!(json!(SourceOffset::from(0)), 0);
745}
746
747#[cfg(feature = "serde")]
748#[test]
749fn test_deserialize_source_offset() {
750 let offset: SourceOffset = serde_json::from_str("0").unwrap();
751 assert_eq!(offset, SourceOffset::from(0));
752}