1use std::{
7 borrow::Cow,
8 fmt::{self, Display},
9 fs,
10 panic::Location,
11};
12
13#[cfg(feature = "serde")]
14use serde::{Deserialize, Serialize};
15
16use crate::MietteError;
17
18pub trait Diagnostic: std::error::Error {
22 fn code(&self) -> Option<Cow<'_, str>> {
28 None
29 }
30
31 fn severity(&self) -> Option<Severity> {
37 None
38 }
39
40 fn help(&self) -> Option<Cow<'_, str>> {
43 None
44 }
45
46 fn note(&self) -> Option<Cow<'_, str>> {
50 None
51 }
52
53 fn url(&self) -> Option<Cow<'_, str>> {
56 None
57 }
58
59 fn source_code(&self) -> Option<&dyn SourceCode> {
61 None
62 }
63
64 fn labels(&self) -> crate::Labels {
70 crate::Labels::None
71 }
72
73 fn related(&self) -> Related<'_> {
79 Related::None
80 }
81
82 fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
84 None
85 }
86}
87
88#[derive(Default)]
93pub enum Related<'a> {
94 #[default]
96 None,
97 One([&'a dyn Diagnostic; 1]),
99 Two([&'a dyn Diagnostic; 2]),
101 Many(Vec<&'a dyn Diagnostic>),
103}
104
105impl<'a> Related<'a> {
106 #[must_use]
108 pub fn as_slice(&self) -> &[&'a dyn Diagnostic] {
109 match self {
110 Related::None => &[],
111 Related::One(related) => related,
112 Related::Two(related) => related,
113 Related::Many(related) => related,
114 }
115 }
116
117 #[must_use]
119 pub fn is_empty(&self) -> bool {
120 matches!(self, Related::None)
121 }
122
123 #[must_use]
125 pub fn len(&self) -> usize {
126 self.as_slice().len()
127 }
128
129 pub fn push(&mut self, related: &'a dyn Diagnostic) {
131 if let Related::Many(items) = self {
132 items.push(related);
133 return;
134 }
135 *self = match std::mem::take(self) {
136 Related::None => Related::One([related]),
137 Related::One([a]) => Related::Two([a, related]),
138 Related::Two([a, b]) => Related::Many(vec![a, b, related]),
139 Related::Many(_) => unreachable!("handled by the fast path above"),
140 };
141 }
142}
143
144impl<'a> std::ops::Deref for Related<'a> {
145 type Target = [&'a dyn Diagnostic];
146
147 fn deref(&self) -> &Self::Target {
148 self.as_slice()
149 }
150}
151
152impl<'a> Extend<&'a dyn Diagnostic> for Related<'a> {
153 fn extend<I: IntoIterator<Item = &'a dyn Diagnostic>>(&mut self, iter: I) {
154 let mut iter = iter.into_iter();
155 while !matches!(self, Related::Many(_)) {
156 match iter.next() {
157 Some(related) => self.push(related),
158 None => return,
159 }
160 }
161 if let Related::Many(items) = self {
162 items.reserve(iter.size_hint().0);
163 items.extend(iter);
164 }
165 }
166}
167
168impl<'a> FromIterator<&'a dyn Diagnostic> for Related<'a> {
169 fn from_iter<I: IntoIterator<Item = &'a dyn Diagnostic>>(iter: I) -> Self {
170 let mut iter = iter.into_iter();
171 if iter.size_hint().0 > 2 {
172 return Related::Many(iter.collect());
173 }
174 let Some(a) = iter.next() else { return Related::None };
175 let Some(b) = iter.next() else { return Related::One([a]) };
176 let Some(c) = iter.next() else { return Related::Two([a, b]) };
177 let mut items = Vec::with_capacity(3 + iter.size_hint().0);
178 items.extend([a, b, c]);
179 items.extend(iter);
180 Related::Many(items)
181 }
182}
183
184macro_rules! box_error_impls {
185 ($($box_type:ty),*) => {
186 $(
187 impl std::error::Error for $box_type {
188 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
189 (**self).source()
190 }
191
192 fn cause(&self) -> Option<&dyn std::error::Error> {
193 self.source()
194 }
195 }
196 )*
197 }
198}
199
200box_error_impls! {
201 Box<dyn Diagnostic>,
202 Box<dyn Diagnostic + Send>,
203 Box<dyn Diagnostic + Send + Sync>
204}
205
206macro_rules! box_borrow_impls {
207 ($($box_type:ty),*) => {
208 $(
209 impl std::borrow::Borrow<dyn Diagnostic> for $box_type {
210 fn borrow(&self) -> &(dyn Diagnostic + 'static) {
211 self.as_ref()
212 }
213 }
214 )*
215 }
216}
217
218box_borrow_impls! {
219 Box<dyn Diagnostic + Send>,
220 Box<dyn Diagnostic + Send + Sync>
221}
222
223impl<T: Diagnostic + Send + Sync + 'static> From<T>
224 for Box<dyn Diagnostic + Send + Sync + 'static>
225{
226 fn from(diag: T) -> Self {
227 Box::new(diag)
228 }
229}
230
231impl<T: Diagnostic + Send + Sync + 'static> From<T> for Box<dyn Diagnostic + Send + 'static> {
232 fn from(diag: T) -> Self {
233 Box::<dyn Diagnostic + Send + Sync>::from(diag)
234 }
235}
236
237impl<T: Diagnostic + Send + Sync + 'static> From<T> for Box<dyn Diagnostic + 'static> {
238 fn from(diag: T) -> Self {
239 Box::<dyn Diagnostic + Send + Sync>::from(diag)
240 }
241}
242
243impl From<&str> for Box<dyn Diagnostic> {
244 fn from(s: &str) -> Self {
245 From::from(String::from(s))
246 }
247}
248
249impl From<&str> for Box<dyn Diagnostic + Send + Sync + '_> {
250 fn from(s: &str) -> Self {
251 From::from(String::from(s))
252 }
253}
254
255impl From<String> for Box<dyn Diagnostic> {
256 fn from(s: String) -> Self {
257 let err1: Box<dyn Diagnostic + Send + Sync> = From::from(s);
258 let err2: Box<dyn Diagnostic> = err1;
259 err2
260 }
261}
262
263impl From<String> for Box<dyn Diagnostic + Send + Sync> {
264 fn from(s: String) -> Self {
265 struct StringError(String);
266
267 impl std::error::Error for StringError {}
268 impl Diagnostic for StringError {}
269
270 impl Display for StringError {
271 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
272 Display::fmt(&self.0, f)
273 }
274 }
275
276 impl fmt::Debug for StringError {
278 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
279 fmt::Debug::fmt(&self.0, f)
280 }
281 }
282
283 Box::new(StringError(s))
284 }
285}
286
287impl From<Box<dyn std::error::Error + Send + Sync>> for Box<dyn Diagnostic + Send + Sync> {
288 fn from(s: Box<dyn std::error::Error + Send + Sync>) -> Self {
289 #[derive(thiserror::Error)]
290 #[error(transparent)]
291 struct BoxedDiagnostic(Box<dyn std::error::Error + Send + Sync>);
292 impl fmt::Debug for BoxedDiagnostic {
293 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
294 fmt::Debug::fmt(&self.0, f)
295 }
296 }
297
298 impl Diagnostic for BoxedDiagnostic {}
299
300 Box::new(BoxedDiagnostic(s))
301 }
302}
303
304#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
310#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
311#[derive(Default)]
312pub enum Severity {
313 Advice,
315 Warning,
317 #[default]
320 Error,
321}
322
323#[cfg(feature = "serde")]
324#[test]
325fn test_serialize_severity() {
326 use serde_json::json;
327
328 assert_eq!(json!(Severity::Advice), json!("Advice"));
329 assert_eq!(json!(Severity::Warning), json!("Warning"));
330 assert_eq!(json!(Severity::Error), json!("Error"));
331}
332
333#[cfg(feature = "serde")]
334#[test]
335fn test_deserialize_severity() {
336 use serde_json::json;
337
338 let severity: Severity = serde_json::from_value(json!("Advice")).unwrap();
339 assert_eq!(severity, Severity::Advice);
340
341 let severity: Severity = serde_json::from_value(json!("Warning")).unwrap();
342 assert_eq!(severity, Severity::Warning);
343
344 let severity: Severity = serde_json::from_value(json!("Error")).unwrap();
345 assert_eq!(severity, Severity::Error);
346}
347
348pub trait SourceCode: Send + Sync {
360 fn read_span<'a>(
363 &'a self,
364 span: &SourceSpan,
365 context_lines_before: usize,
366 context_lines_after: usize,
367 ) -> Result<MietteSpanContents<'a>, MietteError>;
368
369 fn name(&self) -> Option<&str> {
371 None
372 }
373}
374
375#[derive(Debug, Clone, PartialEq, Eq)]
377#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
378pub struct LabeledSpan {
379 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
380 label: Option<String>,
381 span: SourceSpan,
382 primary: bool,
383}
384
385impl LabeledSpan {
386 #[must_use]
388 pub const fn new(label: Option<String>, offset: ByteOffset, len: u32) -> Self {
389 Self { label, span: SourceSpan::new(SourceOffset(offset), len), primary: false }
390 }
391
392 #[must_use]
394 pub fn new_with_span(label: Option<String>, span: impl Into<SourceSpan>) -> Self {
395 Self { label, span: span.into(), primary: false }
396 }
397
398 #[must_use]
400 pub fn new_primary_with_span(label: Option<String>, span: impl Into<SourceSpan>) -> Self {
401 Self { label, span: span.into(), primary: true }
402 }
403
404 pub fn set_label(&mut self, label: Option<String>) {
406 self.label = label;
407 }
408
409 pub fn set_span_offset(&mut self, offset: ByteOffset) {
411 self.span.offset = SourceOffset(offset);
412 }
413
414 #[must_use]
428 pub fn at(span: impl Into<SourceSpan>, label: impl Into<String>) -> Self {
429 Self::new_with_span(Some(label.into()), span)
430 }
431
432 pub fn at_offset(offset: ByteOffset, label: impl Into<String>) -> Self {
446 Self::new(Some(label.into()), offset, 0)
447 }
448
449 #[must_use]
460 pub fn underline(span: impl Into<SourceSpan>) -> Self {
461 Self::new_with_span(None, span)
462 }
463
464 pub fn label(&self) -> Option<&str> {
466 self.label.as_deref()
467 }
468
469 pub const fn inner(&self) -> &SourceSpan {
471 &self.span
472 }
473
474 pub const fn offset(&self) -> ByteOffset {
476 self.span.offset()
477 }
478
479 pub const fn len(&self) -> u32 {
481 self.span.len()
482 }
483
484 pub const fn is_empty(&self) -> bool {
486 self.span.is_empty()
487 }
488
489 pub const fn primary(&self) -> bool {
491 self.primary
492 }
493}
494
495#[test]
496fn test_set_span_offset() {
497 let mut span = LabeledSpan::new(None, 10, 10);
498 assert_eq!(span.offset(), 10);
499
500 span.set_span_offset(20);
501 assert_eq!(span.offset(), 20);
502}
503
504#[cfg(feature = "serde")]
505#[test]
506fn test_serialize_labeled_span() {
507 use serde_json::json;
508
509 assert_eq!(
510 json!(LabeledSpan::new(None, 0, 0)),
511 json!({
512 "span": { "offset": 0, "length": 0, },
513 "primary": false,
514 })
515 );
516
517 assert_eq!(
518 json!(LabeledSpan::new(Some("label".to_string()), 0, 0)),
519 json!({
520 "label": "label",
521 "span": { "offset": 0, "length": 0, },
522 "primary": false,
523 })
524 );
525}
526
527#[cfg(feature = "serde")]
528#[test]
529fn test_deserialize_labeled_span() {
530 use serde_json::json;
531
532 let span: LabeledSpan = serde_json::from_value(json!({
533 "label": null,
534 "span": { "offset": 0, "length": 0, },
535 "primary": false,
536 }))
537 .unwrap();
538 assert_eq!(span, LabeledSpan::new(None, 0, 0));
539
540 let span: LabeledSpan = serde_json::from_value(json!({
541 "span": { "offset": 0, "length": 0, },
542 "primary": false
543 }))
544 .unwrap();
545 assert_eq!(span, LabeledSpan::new(None, 0, 0));
546
547 let span: LabeledSpan = serde_json::from_value(json!({
548 "label": "label",
549 "span": { "offset": 0, "length": 0, },
550 "primary": false
551 }))
552 .unwrap();
553 assert_eq!(span, LabeledSpan::new(Some("label".to_string()), 0, 0));
554}
555
556pub trait SpanContents<'a> {
562 fn data(&self) -> &'a [u8];
564 fn span(&self) -> &SourceSpan;
566 fn name(&self) -> Option<&str> {
568 None
569 }
570 fn line(&self) -> usize;
573 fn column(&self) -> usize;
576 fn line_count(&self) -> usize;
578
579 fn language(&self) -> Option<&str> {
585 None
586 }
587}
588
589#[derive(Clone, Debug)]
593pub struct MietteSpanContents<'a> {
594 data: &'a [u8],
596 span: SourceSpan,
598 line: usize,
600 column: usize,
602 line_count: usize,
604 name: Option<Cow<'a, str>>,
606 language: Option<Cow<'a, str>>,
608}
609
610impl<'a> MietteSpanContents<'a> {
611 pub const fn new(
613 data: &'a [u8],
614 span: SourceSpan,
615 line: usize,
616 column: usize,
617 line_count: usize,
618 ) -> MietteSpanContents<'a> {
619 MietteSpanContents { data, span, line, column, line_count, name: None, language: None }
620 }
621
622 pub const fn new_named(
624 name: Cow<'a, str>,
625 data: &'a [u8],
626 span: SourceSpan,
627 line: usize,
628 column: usize,
629 line_count: usize,
630 ) -> MietteSpanContents<'a> {
631 MietteSpanContents {
632 data,
633 span,
634 line,
635 column,
636 line_count,
637 name: Some(name),
638 language: None,
639 }
640 }
641
642 #[must_use]
644 pub fn with_language(mut self, language: impl Into<Cow<'a, str>>) -> Self {
645 self.language = Some(language.into());
646 self
647 }
648}
649
650impl<'a> SpanContents<'a> for MietteSpanContents<'a> {
651 fn data(&self) -> &'a [u8] {
652 self.data
653 }
654
655 fn span(&self) -> &SourceSpan {
656 &self.span
657 }
658
659 fn line(&self) -> usize {
660 self.line
661 }
662
663 fn column(&self) -> usize {
664 self.column
665 }
666
667 fn line_count(&self) -> usize {
668 self.line_count
669 }
670
671 fn name(&self) -> Option<&str> {
672 self.name.as_deref()
673 }
674
675 fn language(&self) -> Option<&str> {
676 self.language.as_deref()
677 }
678}
679
680#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
682#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
683pub struct SourceSpan {
684 offset: SourceOffset,
686 length: u32,
688}
689
690impl SourceSpan {
691 pub const fn new(start: SourceOffset, length: u32) -> Self {
693 Self { offset: start, length }
694 }
695
696 pub const fn offset(&self) -> ByteOffset {
698 self.offset.offset()
699 }
700
701 pub const fn len(&self) -> u32 {
703 self.length
704 }
705
706 pub const fn is_empty(&self) -> bool {
709 self.length == 0
710 }
711}
712
713impl From<(ByteOffset, u32)> for SourceSpan {
714 fn from((start, len): (ByteOffset, u32)) -> Self {
715 Self { offset: start.into(), length: len }
716 }
717}
718
719impl From<(SourceOffset, u32)> for SourceSpan {
720 fn from((start, len): (SourceOffset, u32)) -> Self {
721 Self::new(start, len)
722 }
723}
724
725impl From<std::ops::Range<ByteOffset>> for SourceSpan {
726 fn from(range: std::ops::Range<ByteOffset>) -> Self {
727 Self { offset: range.start.into(), length: range.len() as u32 }
730 }
731}
732
733impl From<SourceOffset> for SourceSpan {
734 fn from(offset: SourceOffset) -> Self {
735 Self { offset, length: 0 }
736 }
737}
738
739impl From<ByteOffset> for SourceSpan {
740 fn from(offset: ByteOffset) -> Self {
741 Self { offset: offset.into(), length: 0 }
742 }
743}
744
745#[cfg(feature = "serde")]
746#[test]
747fn test_serialize_source_span() {
748 use serde_json::json;
749
750 assert_eq!(json!(SourceSpan::from(0)), json!({ "offset": 0, "length": 0}));
751}
752
753#[cfg(feature = "serde")]
754#[test]
755fn test_deserialize_source_span() {
756 use serde_json::json;
757
758 let span: SourceSpan = serde_json::from_value(json!({ "offset": 0, "length": 0})).unwrap();
759 assert_eq!(span, SourceSpan::from(0));
760}
761
762pub type ByteOffset = u32;
766
767#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
771#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
772pub struct SourceOffset(ByteOffset);
773
774impl SourceOffset {
775 pub const fn offset(&self) -> ByteOffset {
777 self.0
778 }
779
780 pub fn from_location(source: impl AsRef<str>, loc_line: usize, loc_col: usize) -> Self {
786 let mut line = 0usize;
787 let mut col = 0usize;
788 let mut offset = 0usize;
789 for char in source.as_ref().chars() {
790 if line + 1 >= loc_line && col + 1 >= loc_col {
791 break;
792 }
793 if char == '\n' {
794 col = 0;
795 line += 1;
796 } else {
797 col += 1;
798 }
799 offset += char.len_utf8();
800 }
801
802 SourceOffset(offset as u32)
803 }
804
805 #[track_caller]
817 pub fn from_current_location() -> Result<(String, Self), MietteError> {
818 let loc = Location::caller();
819 Ok((
820 loc.file().into(),
821 fs::read_to_string(loc.file())
822 .map(|txt| Self::from_location(txt, loc.line() as usize, loc.column() as usize))?,
823 ))
824 }
825}
826
827impl From<ByteOffset> for SourceOffset {
828 fn from(bytes: ByteOffset) -> Self {
829 SourceOffset(bytes)
830 }
831}
832
833#[test]
834fn test_source_offset_from_location() {
835 let source = "f\n\noo\r\nbar";
836
837 assert_eq!(SourceOffset::from_location(source, 1, 1).offset(), 0);
838 assert_eq!(SourceOffset::from_location(source, 1, 2).offset(), 1);
839 assert_eq!(SourceOffset::from_location(source, 2, 1).offset(), 2);
840 assert_eq!(SourceOffset::from_location(source, 3, 1).offset(), 3);
841 assert_eq!(SourceOffset::from_location(source, 3, 2).offset(), 4);
842 assert_eq!(SourceOffset::from_location(source, 3, 3).offset(), 5);
843 assert_eq!(SourceOffset::from_location(source, 3, 4).offset(), 6);
844 assert_eq!(SourceOffset::from_location(source, 4, 1).offset(), 7);
845 assert_eq!(SourceOffset::from_location(source, 4, 2).offset(), 8);
846 assert_eq!(SourceOffset::from_location(source, 4, 3).offset(), 9);
847 assert_eq!(SourceOffset::from_location(source, 4, 4).offset(), 10);
848
849 assert_eq!(SourceOffset::from_location(source, 5, 1).offset(), source.len() as u32);
851}
852
853#[cfg(feature = "serde")]
854#[test]
855fn test_serialize_source_offset() {
856 use serde_json::json;
857
858 assert_eq!(json!(SourceOffset::from(0)), 0);
859}
860
861#[cfg(feature = "serde")]
862#[test]
863fn test_deserialize_source_offset() {
864 let offset: SourceOffset = serde_json::from_str("0").unwrap();
865 assert_eq!(offset, SourceOffset::from(0));
866}