miette/
protocol.rs

1/*!
2This module defines the core of the miette protocol: a series of types and
3traits that you can implement to get access to miette's (and related library's)
4full reporting and such features.
5*/
6use 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
17/// Adds rich metadata to your Error that can be used by
18/// [`Report`](crate::Report) to print really nice and human-friendly error
19/// messages.
20pub trait Diagnostic: std::error::Error {
21    /// Unique diagnostic code that can be used to look up more information
22    /// about this `Diagnostic`. Ideally also globally unique, and documented
23    /// in the toplevel crate's documentation for easy searching. Rust path
24    /// format (`foo::bar::baz`) is recommended, but more classic codes like
25    /// `E0123` or enums will work just fine.
26    fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
27        None
28    }
29
30    /// Diagnostic severity. This may be used by
31    /// [`ReportHandler`](crate::ReportHandler)s to change the display format
32    /// of this diagnostic.
33    ///
34    /// If `None`, reporters should treat this as [`Severity::Error`].
35    fn severity(&self) -> Option<Severity> {
36        None
37    }
38
39    /// Additional help text related to this `Diagnostic`. Do you have any
40    /// advice for the poor soul who's just run into this issue?
41    fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
42        None
43    }
44
45    /// URL to visit for a more detailed explanation/help about this
46    /// `Diagnostic`.
47    fn url<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
48        None
49    }
50
51    /// Source code to apply this `Diagnostic`'s [`Diagnostic::labels`] to.
52    fn source_code(&self) -> Option<&dyn SourceCode> {
53        None
54    }
55
56    /// Labels to apply to this `Diagnostic`'s [`Diagnostic::source_code`]
57    fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
58        None
59    }
60
61    /// Additional related `Diagnostic`s.
62    fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
63        None
64    }
65
66    /// The cause of the error.
67    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        // Purposefully skip printing "StringError(..)"
165        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/**
193[`Diagnostic`] severity. Intended to be used by
194[`ReportHandler`](crate::ReportHandler)s to change the way different
195[`Diagnostic`]s are displayed. Defaults to [`Severity::Error`].
196*/
197#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
198#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
199#[derive(Default)]
200pub enum Severity {
201    /// Just some help. Here's how you could be doing it better.
202    Advice,
203    /// Warning. Please take note.
204    Warning,
205    /// Critical failure. The program cannot continue.
206    /// This is the default severity, if you don't specify another one.
207    #[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
236/**
237Represents readable source code of some sort.
238
239This trait is able to support simple `SourceCode` types like [`String`]s, as
240well as more involved types like indexes into centralized `SourceMap`-like
241types, file handles, and even network streams.
242
243If you can read it, you can source it, and it's not necessary to read the
244whole thing--meaning you should be able to support `SourceCode`s which are
245gigabytes or larger in size.
246*/
247pub trait SourceCode: Send + Sync {
248    /// Read the bytes for a specific span from this `SourceCode`, keeping a
249    /// certain number of lines before and after the span as context.
250    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    /// Returns the name of this source code, if any.
258    fn name(&self) -> Option<&str> {
259        None
260    }
261}
262
263/// A labeled [`SourceSpan`].
264#[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    /// Makes a new labeled span.
275    #[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    /// Makes a new labeled span using an existing span.
281    #[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    /// Makes a new labeled primary span using an existing span.
287    #[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    /// Change the text of the label
293    pub fn set_label(&mut self, label: Option<String>) {
294        self.label = label;
295    }
296
297    /// Change the offset of the span
298    pub fn set_span_offset(&mut self, offset: usize) {
299        self.span.offset = SourceOffset(offset);
300    }
301
302    /// Makes a new label at specified span
303    ///
304    /// # Examples
305    /// ```
306    /// use miette::LabeledSpan;
307    ///
308    /// let source = "Cpp is the best";
309    /// let label = LabeledSpan::at(0..3, "should be Rust");
310    /// assert_eq!(
311    ///     label,
312    ///     LabeledSpan::new(Some("should be Rust".to_string()), 0, 3)
313    /// )
314    /// ```
315    #[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    /// Makes a new label that points at a specific offset.
321    ///
322    /// # Examples
323    /// ```
324    /// use miette::LabeledSpan;
325    ///
326    /// let source = "(2 + 2";
327    /// let label = LabeledSpan::at_offset(4, "expected a closing parenthesis");
328    /// assert_eq!(
329    ///     label,
330    ///     LabeledSpan::new(Some("expected a closing parenthesis".to_string()), 4, 0)
331    /// )
332    /// ```
333    pub fn at_offset(offset: ByteOffset, label: impl Into<String>) -> Self {
334        Self::new(Some(label.into()), offset, 0)
335    }
336
337    /// Makes a new label without text, that underlines a specific span.
338    ///
339    /// # Examples
340    /// ```
341    /// use miette::LabeledSpan;
342    ///
343    /// let source = "You have an error here";
344    /// let label = LabeledSpan::underline(12..16);
345    /// assert_eq!(label, LabeledSpan::new(None, 12, 4))
346    /// ```
347    #[must_use]
348    pub fn underline(span: impl Into<SourceSpan>) -> Self {
349        Self::new_with_span(None, span)
350    }
351
352    /// Gets the (optional) label string for this `LabeledSpan`.
353    pub fn label(&self) -> Option<&str> {
354        self.label.as_deref()
355    }
356
357    /// Returns a reference to the inner [`SourceSpan`].
358    pub const fn inner(&self) -> &SourceSpan {
359        &self.span
360    }
361
362    /// Returns the 0-based starting byte offset.
363    pub const fn offset(&self) -> usize {
364        self.span.offset()
365    }
366
367    /// Returns the number of bytes this `LabeledSpan` spans.
368    pub const fn len(&self) -> usize {
369        self.span.len()
370    }
371
372    /// True if this `LabeledSpan` is empty.
373    pub const fn is_empty(&self) -> bool {
374        self.span.is_empty()
375    }
376
377    /// True if this `LabeledSpan` is a primary span.
378    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
444/**
445Contents of a [`SourceCode`] covered by [`SourceSpan`].
446
447Includes line and column information to optimize highlight calculations.
448*/
449pub trait SpanContents<'a> {
450    /// Reference to the data inside the associated span, in bytes.
451    fn data(&self) -> &'a [u8];
452    /// [`SourceSpan`] representing the span covered by this `SpanContents`.
453    fn span(&self) -> &SourceSpan;
454    /// An optional (file?) name for the container of this `SpanContents`.
455    fn name(&self) -> Option<&str> {
456        None
457    }
458    /// The 0-indexed line in the associated [`SourceCode`] where the data
459    /// begins.
460    fn line(&self) -> usize;
461    /// The 0-indexed column in the associated [`SourceCode`] where the data
462    /// begins, relative to `line`.
463    fn column(&self) -> usize;
464    /// Total number of lines covered by this `SpanContents`.
465    fn line_count(&self) -> usize;
466
467    /// Optional method. The language name for this source code, if any.
468    /// This is used to drive syntax highlighting.
469    ///
470    /// Examples: Rust, TOML, C
471    ///
472    fn language(&self) -> Option<&str> {
473        None
474    }
475}
476
477/**
478Basic implementation of the [`SpanContents`] trait, for convenience.
479*/
480#[derive(Clone, Debug)]
481pub struct MietteSpanContents<'a> {
482    // Data from a [`SourceCode`], in bytes.
483    data: &'a [u8],
484    // span actually covered by this SpanContents.
485    span: SourceSpan,
486    // The 0-indexed line where the associated [`SourceSpan`] _starts_.
487    line: usize,
488    // The 0-indexed column where the associated [`SourceSpan`] _starts_.
489    column: usize,
490    // Number of line in this snippet.
491    line_count: usize,
492    // Optional filename
493    name: Option<String>,
494    // Optional language
495    language: Option<String>,
496}
497
498impl<'a> MietteSpanContents<'a> {
499    /// Make a new [`MietteSpanContents`] object.
500    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    /// Make a new [`MietteSpanContents`] object, with a name for its 'file'.
511    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    /// Sets the `language` for syntax highlighting.
531    #[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/// Span within a [`SourceCode`]
569#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
570#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
571pub struct SourceSpan {
572    /// The start of the span.
573    offset: SourceOffset,
574    /// The total length of the span
575    length: usize,
576}
577
578impl SourceSpan {
579    /// Create a new [`SourceSpan`].
580    pub const fn new(start: SourceOffset, length: usize) -> Self {
581        Self { offset: start, length }
582    }
583
584    /// The absolute offset, in bytes, from the beginning of a [`SourceCode`].
585    pub const fn offset(&self) -> usize {
586        self.offset.offset()
587    }
588
589    /// Total length of the [`SourceSpan`], in bytes.
590    pub const fn len(&self) -> usize {
591        self.length
592    }
593
594    /// Whether this [`SourceSpan`] has a length of zero. It may still be useful
595    /// to point to a specific point.
596    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
648/**
649"Raw" type for the byte offset from the beginning of a [`SourceCode`].
650*/
651pub type ByteOffset = usize;
652
653/**
654Newtype that represents the [`ByteOffset`] from the beginning of a [`SourceCode`]
655*/
656#[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    /// Actual byte offset.
662    pub const fn offset(&self) -> ByteOffset {
663        self.0
664    }
665
666    /// Little utility to help convert 1-based line/column locations into
667    /// miette-compatible Spans
668    ///
669    /// This function is infallible: Giving an out-of-range line/column pair
670    /// will return the offset of the last byte in the source.
671    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    /// Returns an offset for the _file_ location of wherever this function is
692    /// called. If you want to get _that_ caller's location, mark this
693    /// function's caller with `#[track_caller]` (and so on and so forth).
694    ///
695    /// Returns both the filename that was given and the offset of the caller
696    /// as a [`SourceOffset`].
697    ///
698    /// Keep in mind that this fill only work if the file your Rust source
699    /// file was compiled from is actually available at that location. If
700    /// you're shipping binaries for your application, you'll want to ignore
701    /// the Err case or otherwise report it.
702    #[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    // Out-of-range
736    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}