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
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 #[must_use]
271 pub const fn new(label: Option<String>, offset: ByteOffset, len: usize) -> Self {
272 Self { label, span: SourceSpan::new(SourceOffset(offset), len), primary: false }
273 }
274
275 #[must_use]
277 pub fn new_with_span(label: Option<String>, span: impl Into<SourceSpan>) -> Self {
278 Self { label, span: span.into(), primary: false }
279 }
280
281 #[must_use]
283 pub fn new_primary_with_span(label: Option<String>, span: impl Into<SourceSpan>) -> Self {
284 Self { label, span: span.into(), primary: true }
285 }
286
287 pub fn set_label(&mut self, label: Option<String>) {
289 self.label = label;
290 }
291
292 #[must_use]
306 pub fn at(span: impl Into<SourceSpan>, label: impl Into<String>) -> Self {
307 Self::new_with_span(Some(label.into()), span)
308 }
309
310 pub fn at_offset(offset: ByteOffset, label: impl Into<String>) -> Self {
324 Self::new(Some(label.into()), offset, 0)
325 }
326
327 #[must_use]
338 pub fn underline(span: impl Into<SourceSpan>) -> Self {
339 Self::new_with_span(None, span)
340 }
341
342 pub fn label(&self) -> Option<&str> {
344 self.label.as_deref()
345 }
346
347 pub const fn inner(&self) -> &SourceSpan {
349 &self.span
350 }
351
352 pub const fn offset(&self) -> usize {
354 self.span.offset()
355 }
356
357 pub const fn len(&self) -> usize {
359 self.span.len()
360 }
361
362 pub const fn is_empty(&self) -> bool {
364 self.span.is_empty()
365 }
366
367 pub const fn primary(&self) -> bool {
369 self.primary
370 }
371}
372
373#[cfg(feature = "serde")]
374#[test]
375fn test_serialize_labeled_span() {
376 use serde_json::json;
377
378 assert_eq!(
379 json!(LabeledSpan::new(None, 0, 0)),
380 json!({
381 "span": { "offset": 0, "length": 0, },
382 "primary": false,
383 })
384 );
385
386 assert_eq!(
387 json!(LabeledSpan::new(Some("label".to_string()), 0, 0)),
388 json!({
389 "label": "label",
390 "span": { "offset": 0, "length": 0, },
391 "primary": false,
392 })
393 );
394}
395
396#[cfg(feature = "serde")]
397#[test]
398fn test_deserialize_labeled_span() {
399 use serde_json::json;
400
401 let span: LabeledSpan = serde_json::from_value(json!({
402 "label": null,
403 "span": { "offset": 0, "length": 0, },
404 "primary": false,
405 }))
406 .unwrap();
407 assert_eq!(span, LabeledSpan::new(None, 0, 0));
408
409 let span: LabeledSpan = serde_json::from_value(json!({
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 "label": "label",
418 "span": { "offset": 0, "length": 0, },
419 "primary": false
420 }))
421 .unwrap();
422 assert_eq!(span, LabeledSpan::new(Some("label".to_string()), 0, 0));
423}
424
425pub trait SpanContents<'a> {
431 fn data(&self) -> &'a [u8];
433 fn span(&self) -> &SourceSpan;
435 fn name(&self) -> Option<&str> {
437 None
438 }
439 fn line(&self) -> usize;
442 fn column(&self) -> usize;
445 fn line_count(&self) -> usize;
447
448 fn language(&self) -> Option<&str> {
454 None
455 }
456}
457
458#[derive(Clone, Debug)]
462pub struct MietteSpanContents<'a> {
463 data: &'a [u8],
465 span: SourceSpan,
467 line: usize,
469 column: usize,
471 line_count: usize,
473 name: Option<String>,
475 language: Option<String>,
477}
478
479impl<'a> MietteSpanContents<'a> {
480 pub const fn new(
482 data: &'a [u8],
483 span: SourceSpan,
484 line: usize,
485 column: usize,
486 line_count: usize,
487 ) -> MietteSpanContents<'a> {
488 MietteSpanContents { data, span, line, column, line_count, name: None, language: None }
489 }
490
491 pub const fn new_named(
493 name: String,
494 data: &'a [u8],
495 span: SourceSpan,
496 line: usize,
497 column: usize,
498 line_count: usize,
499 ) -> MietteSpanContents<'a> {
500 MietteSpanContents {
501 data,
502 span,
503 line,
504 column,
505 line_count,
506 name: Some(name),
507 language: None,
508 }
509 }
510
511 #[must_use]
513 pub fn with_language(mut self, language: impl Into<String>) -> Self {
514 self.language = Some(language.into());
515 self
516 }
517}
518
519impl<'a> SpanContents<'a> for MietteSpanContents<'a> {
520 fn data(&self) -> &'a [u8] {
521 self.data
522 }
523
524 fn span(&self) -> &SourceSpan {
525 &self.span
526 }
527
528 fn line(&self) -> usize {
529 self.line
530 }
531
532 fn column(&self) -> usize {
533 self.column
534 }
535
536 fn line_count(&self) -> usize {
537 self.line_count
538 }
539
540 fn name(&self) -> Option<&str> {
541 self.name.as_deref()
542 }
543
544 fn language(&self) -> Option<&str> {
545 self.language.as_deref()
546 }
547}
548
549#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
551#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
552pub struct SourceSpan {
553 offset: SourceOffset,
555 length: usize,
557}
558
559impl SourceSpan {
560 pub const fn new(start: SourceOffset, length: usize) -> Self {
562 Self { offset: start, length }
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 { offset: start.into(), length: len }
585 }
586}
587
588impl From<(SourceOffset, usize)> for SourceSpan {
589 fn from((start, len): (SourceOffset, usize)) -> Self {
590 Self::new(start, len)
591 }
592}
593
594impl From<std::ops::Range<ByteOffset>> for SourceSpan {
595 fn from(range: std::ops::Range<ByteOffset>) -> Self {
596 Self { offset: range.start.into(), length: range.len() }
597 }
598}
599
600impl From<SourceOffset> for SourceSpan {
601 fn from(offset: SourceOffset) -> Self {
602 Self { offset, length: 0 }
603 }
604}
605
606impl From<ByteOffset> for SourceSpan {
607 fn from(offset: ByteOffset) -> Self {
608 Self { offset: offset.into(), length: 0 }
609 }
610}
611
612#[cfg(feature = "serde")]
613#[test]
614fn test_serialize_source_span() {
615 use serde_json::json;
616
617 assert_eq!(json!(SourceSpan::from(0)), json!({ "offset": 0, "length": 0}));
618}
619
620#[cfg(feature = "serde")]
621#[test]
622fn test_deserialize_source_span() {
623 use serde_json::json;
624
625 let span: SourceSpan = serde_json::from_value(json!({ "offset": 0, "length": 0})).unwrap();
626 assert_eq!(span, SourceSpan::from(0));
627}
628
629pub type ByteOffset = usize;
633
634#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
638#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
639pub struct SourceOffset(ByteOffset);
640
641impl SourceOffset {
642 pub const fn offset(&self) -> ByteOffset {
644 self.0
645 }
646
647 pub fn from_location(source: impl AsRef<str>, loc_line: usize, loc_col: usize) -> Self {
653 let mut line = 0usize;
654 let mut col = 0usize;
655 let mut offset = 0usize;
656 for char in source.as_ref().chars() {
657 if line + 1 >= loc_line && col + 1 >= loc_col {
658 break;
659 }
660 if char == '\n' {
661 col = 0;
662 line += 1;
663 } else {
664 col += 1;
665 }
666 offset += char.len_utf8();
667 }
668
669 SourceOffset(offset)
670 }
671
672 #[track_caller]
684 pub fn from_current_location() -> Result<(String, Self), MietteError> {
685 let loc = Location::caller();
686 Ok((
687 loc.file().into(),
688 fs::read_to_string(loc.file())
689 .map(|txt| Self::from_location(txt, loc.line() as usize, loc.column() as usize))?,
690 ))
691 }
692}
693
694impl From<ByteOffset> for SourceOffset {
695 fn from(bytes: ByteOffset) -> Self {
696 SourceOffset(bytes)
697 }
698}
699
700#[test]
701fn test_source_offset_from_location() {
702 let source = "f\n\noo\r\nbar";
703
704 assert_eq!(SourceOffset::from_location(source, 1, 1).offset(), 0);
705 assert_eq!(SourceOffset::from_location(source, 1, 2).offset(), 1);
706 assert_eq!(SourceOffset::from_location(source, 2, 1).offset(), 2);
707 assert_eq!(SourceOffset::from_location(source, 3, 1).offset(), 3);
708 assert_eq!(SourceOffset::from_location(source, 3, 2).offset(), 4);
709 assert_eq!(SourceOffset::from_location(source, 3, 3).offset(), 5);
710 assert_eq!(SourceOffset::from_location(source, 3, 4).offset(), 6);
711 assert_eq!(SourceOffset::from_location(source, 4, 1).offset(), 7);
712 assert_eq!(SourceOffset::from_location(source, 4, 2).offset(), 8);
713 assert_eq!(SourceOffset::from_location(source, 4, 3).offset(), 9);
714 assert_eq!(SourceOffset::from_location(source, 4, 4).offset(), 10);
715
716 assert_eq!(SourceOffset::from_location(source, 5, 1).offset(), source.len());
718}
719
720#[cfg(feature = "serde")]
721#[test]
722fn test_serialize_source_offset() {
723 use serde_json::json;
724
725 assert_eq!(json!(SourceOffset::from(0)), 0);
726}
727
728#[cfg(feature = "serde")]
729#[test]
730fn test_deserialize_source_offset() {
731 let offset: SourceOffset = serde_json::from_str("0").unwrap();
732 assert_eq!(offset, SourceOffset::from(0));
733}