Skip to main content

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    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
18/// Adds rich metadata to your Error that can be used by
19/// [`Report`](crate::Report) to print really nice and human-friendly error
20/// messages.
21pub trait Diagnostic: std::error::Error {
22    /// Unique diagnostic code that can be used to look up more information
23    /// about this `Diagnostic`. Ideally also globally unique, and documented
24    /// in the toplevel crate's documentation for easy searching. Rust path
25    /// format (`foo::bar::baz`) is recommended, but more classic codes like
26    /// `E0123` or enums will work just fine.
27    fn code(&self) -> Option<Cow<'_, str>> {
28        None
29    }
30
31    /// Diagnostic severity. This may be used by
32    /// [`ReportHandler`](crate::ReportHandler)s to change the display format
33    /// of this diagnostic.
34    ///
35    /// If `None`, reporters should treat this as [`Severity::Error`].
36    fn severity(&self) -> Option<Severity> {
37        None
38    }
39
40    /// Additional help text related to this `Diagnostic`. Do you have any
41    /// advice for the poor soul who's just run into this issue?
42    fn help(&self) -> Option<Cow<'_, str>> {
43        None
44    }
45
46    /// Supplementary context for this `Diagnostic`, separate from help text.
47    /// Notes mirror rustc-style `= note:` lines and offer additional
48    /// information when guidance (help) is insufficient.
49    fn note(&self) -> Option<Cow<'_, str>> {
50        None
51    }
52
53    /// URL to visit for a more detailed explanation/help about this
54    /// `Diagnostic`.
55    fn url(&self) -> Option<Cow<'_, str>> {
56        None
57    }
58
59    /// Source code to apply this `Diagnostic`'s [`Diagnostic::labels`] to.
60    fn source_code(&self) -> Option<&dyn SourceCode> {
61        None
62    }
63
64    /// Labels to apply to this `Diagnostic`'s [`Diagnostic::source_code`]
65    ///
66    /// Returns the owned [`Labels`](crate::Labels) container. For the common one/two-label
67    /// case this is allocation-free (the labels are stored inline), and it
68    /// avoids the boxed-iterator allocation the previous signature required.
69    fn labels(&self) -> crate::Labels {
70        crate::Labels::None
71    }
72
73    /// Additional related `Diagnostic`s.
74    ///
75    /// Returns the owned [`Related`] container. For the common one/two-related
76    /// case this is allocation-free, and it avoids the boxed-iterator
77    /// allocation the previous signature required.
78    fn related(&self) -> Related<'_> {
79        Related::None
80    }
81
82    /// The cause of the error.
83    fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
84        None
85    }
86}
87
88/// Container for a [`Diagnostic`]'s related diagnostics.
89///
90/// Most diagnostics carry only one or two related entries, so those cases are
91/// stored inline without a heap allocation. Three or more spill to a [`Vec`].
92#[derive(Default)]
93pub enum Related<'a> {
94    /// No related diagnostics.
95    #[default]
96    None,
97    /// A single related diagnostic, stored inline.
98    One([&'a dyn Diagnostic; 1]),
99    /// Two related diagnostics, stored inline.
100    Two([&'a dyn Diagnostic; 2]),
101    /// Three or more related diagnostics, stored on the heap.
102    Many(Vec<&'a dyn Diagnostic>),
103}
104
105impl<'a> Related<'a> {
106    /// Returns the related diagnostics as a contiguous slice.
107    #[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    /// Returns `true` if there are no related diagnostics.
118    #[must_use]
119    pub fn is_empty(&self) -> bool {
120        matches!(self, Related::None)
121    }
122
123    /// Returns the number of related diagnostics.
124    #[must_use]
125    pub fn len(&self) -> usize {
126        self.as_slice().len()
127    }
128
129    /// Appends a related diagnostic, keeping the storage inline while possible.
130    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        // Purposefully skip printing "StringError(..)"
277        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/**
305[`Diagnostic`] severity. Intended to be used by
306[`ReportHandler`](crate::ReportHandler)s to change the way different
307[`Diagnostic`]s are displayed. Defaults to [`Severity::Error`].
308*/
309#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
310#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
311#[derive(Default)]
312pub enum Severity {
313    /// Just some help. Here's how you could be doing it better.
314    Advice,
315    /// Warning. Please take note.
316    Warning,
317    /// Critical failure. The program cannot continue.
318    /// This is the default severity, if you don't specify another one.
319    #[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
348/**
349Represents readable source code of some sort.
350
351This trait is able to support simple `SourceCode` types like [`String`]s, as
352well as more involved types like indexes into centralized `SourceMap`-like
353types, file handles, and even network streams.
354
355If you can read it, you can source it, and it's not necessary to read the
356whole thing--meaning you should be able to support `SourceCode`s which are
357gigabytes or larger in size.
358*/
359pub trait SourceCode: Send + Sync {
360    /// Read the bytes for a specific span from this `SourceCode`, keeping a
361    /// certain number of lines before and after the span as context.
362    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    /// Returns the name of this source code, if any.
370    fn name(&self) -> Option<&str> {
371        None
372    }
373}
374
375/// A labeled [`SourceSpan`].
376#[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    /// Makes a new labeled span.
387    #[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    /// Makes a new labeled span using an existing span.
393    #[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    /// Makes a new labeled primary span using an existing span.
399    #[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    /// Change the text of the label
405    pub fn set_label(&mut self, label: Option<String>) {
406        self.label = label;
407    }
408
409    /// Change the offset of the span
410    pub fn set_span_offset(&mut self, offset: ByteOffset) {
411        self.span.offset = SourceOffset(offset);
412    }
413
414    /// Makes a new label at specified span
415    ///
416    /// # Examples
417    /// ```
418    /// use miette::LabeledSpan;
419    ///
420    /// let source = "Cpp is the best";
421    /// let label = LabeledSpan::at(0..3, "should be Rust");
422    /// assert_eq!(
423    ///     label,
424    ///     LabeledSpan::new(Some("should be Rust".to_string()), 0, 3)
425    /// )
426    /// ```
427    #[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    /// Makes a new label that points at a specific offset.
433    ///
434    /// # Examples
435    /// ```
436    /// use miette::LabeledSpan;
437    ///
438    /// let source = "(2 + 2";
439    /// let label = LabeledSpan::at_offset(4, "expected a closing parenthesis");
440    /// assert_eq!(
441    ///     label,
442    ///     LabeledSpan::new(Some("expected a closing parenthesis".to_string()), 4, 0)
443    /// )
444    /// ```
445    pub fn at_offset(offset: ByteOffset, label: impl Into<String>) -> Self {
446        Self::new(Some(label.into()), offset, 0)
447    }
448
449    /// Makes a new label without text, that underlines a specific span.
450    ///
451    /// # Examples
452    /// ```
453    /// use miette::LabeledSpan;
454    ///
455    /// let source = "You have an error here";
456    /// let label = LabeledSpan::underline(12..16);
457    /// assert_eq!(label, LabeledSpan::new(None, 12, 4))
458    /// ```
459    #[must_use]
460    pub fn underline(span: impl Into<SourceSpan>) -> Self {
461        Self::new_with_span(None, span)
462    }
463
464    /// Gets the (optional) label string for this `LabeledSpan`.
465    pub fn label(&self) -> Option<&str> {
466        self.label.as_deref()
467    }
468
469    /// Returns a reference to the inner [`SourceSpan`].
470    pub const fn inner(&self) -> &SourceSpan {
471        &self.span
472    }
473
474    /// Returns the 0-based starting byte offset.
475    pub const fn offset(&self) -> ByteOffset {
476        self.span.offset()
477    }
478
479    /// Returns the number of bytes this `LabeledSpan` spans.
480    pub const fn len(&self) -> u32 {
481        self.span.len()
482    }
483
484    /// True if this `LabeledSpan` is empty.
485    pub const fn is_empty(&self) -> bool {
486        self.span.is_empty()
487    }
488
489    /// True if this `LabeledSpan` is a primary span.
490    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
556/**
557Contents of a [`SourceCode`] covered by [`SourceSpan`].
558
559Includes line and column information to optimize highlight calculations.
560*/
561pub trait SpanContents<'a> {
562    /// Reference to the data inside the associated span, in bytes.
563    fn data(&self) -> &'a [u8];
564    /// [`SourceSpan`] representing the span covered by this `SpanContents`.
565    fn span(&self) -> &SourceSpan;
566    /// An optional (file?) name for the container of this `SpanContents`.
567    fn name(&self) -> Option<&str> {
568        None
569    }
570    /// The 0-indexed line in the associated [`SourceCode`] where the data
571    /// begins.
572    fn line(&self) -> usize;
573    /// The 0-indexed column in the associated [`SourceCode`] where the data
574    /// begins, relative to `line`.
575    fn column(&self) -> usize;
576    /// Total number of lines covered by this `SpanContents`.
577    fn line_count(&self) -> usize;
578
579    /// Optional method. The language name for this source code, if any.
580    /// This is used to drive syntax highlighting.
581    ///
582    /// Examples: Rust, TOML, C
583    ///
584    fn language(&self) -> Option<&str> {
585        None
586    }
587}
588
589/**
590Basic implementation of the [`SpanContents`] trait, for convenience.
591*/
592#[derive(Clone, Debug)]
593pub struct MietteSpanContents<'a> {
594    // Data from a [`SourceCode`], in bytes.
595    data: &'a [u8],
596    // span actually covered by this SpanContents.
597    span: SourceSpan,
598    // The 0-indexed line where the associated [`SourceSpan`] _starts_.
599    line: usize,
600    // The 0-indexed column where the associated [`SourceSpan`] _starts_.
601    column: usize,
602    // Number of line in this snippet.
603    line_count: usize,
604    // Optional filename
605    name: Option<Cow<'a, str>>,
606    // Optional language
607    language: Option<Cow<'a, str>>,
608}
609
610impl<'a> MietteSpanContents<'a> {
611    /// Make a new [`MietteSpanContents`] object.
612    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    /// Make a new [`MietteSpanContents`] object, with a name for its 'file'.
623    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    /// Sets the `language` for syntax highlighting.
643    #[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/// Span within a [`SourceCode`]
681#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
682#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
683pub struct SourceSpan {
684    /// The start of the span.
685    offset: SourceOffset,
686    /// The total length of the span, in bytes.
687    length: u32,
688}
689
690impl SourceSpan {
691    /// Create a new [`SourceSpan`].
692    pub const fn new(start: SourceOffset, length: u32) -> Self {
693        Self { offset: start, length }
694    }
695
696    /// The absolute offset, in bytes, from the beginning of a [`SourceCode`].
697    pub const fn offset(&self) -> ByteOffset {
698        self.offset.offset()
699    }
700
701    /// Total length of the [`SourceSpan`], in bytes.
702    pub const fn len(&self) -> u32 {
703        self.length
704    }
705
706    /// Whether this [`SourceSpan`] has a length of zero. It may still be useful
707    /// to point to a specific point.
708    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        // `Range::len` returns `0` for empty/reversed ranges, matching the
728        // previous behavior and avoiding underflow.
729        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
762/**
763"Raw" type for the byte offset from the beginning of a [`SourceCode`].
764*/
765pub type ByteOffset = u32;
766
767/**
768Newtype that represents the [`ByteOffset`] from the beginning of a [`SourceCode`]
769*/
770#[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    /// Actual byte offset.
776    pub const fn offset(&self) -> ByteOffset {
777        self.0
778    }
779
780    /// Little utility to help convert 1-based line/column locations into
781    /// miette-compatible Spans
782    ///
783    /// This function is infallible: Giving an out-of-range line/column pair
784    /// will return the offset of the last byte in the source.
785    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    /// Returns an offset for the _file_ location of wherever this function is
806    /// called. If you want to get _that_ caller's location, mark this
807    /// function's caller with `#[track_caller]` (and so on and so forth).
808    ///
809    /// Returns both the filename that was given and the offset of the caller
810    /// as a [`SourceOffset`].
811    ///
812    /// Keep in mind that this fill only work if the file your Rust source
813    /// file was compiled from is actually available at that location. If
814    /// you're shipping binaries for your application, you'll want to ignore
815    /// the Err case or otherwise report it.
816    #[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    // Out-of-range
850    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}