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::{Borrow, Cow},
8    error::Error,
9    fmt::{self, Display},
10    fs, mem,
11    ops::{Deref, Range},
12    panic::Location,
13};
14
15#[cfg(feature = "serde")]
16use serde::{Deserialize, Serialize};
17
18use crate::MietteError;
19
20/// Adds rich metadata to your Error that can be used by
21/// [`Report`](crate::Report) to print really nice and human-friendly error
22/// messages.
23pub trait Diagnostic: Error {
24    /// Unique diagnostic code that can be used to look up more information
25    /// about this `Diagnostic`. Ideally also globally unique, and documented
26    /// in the toplevel crate's documentation for easy searching. Rust path
27    /// format (`foo::bar::baz`) is recommended, but more classic codes like
28    /// `E0123` or enums will work just fine.
29    fn code(&self) -> Option<Cow<'_, str>> {
30        None
31    }
32
33    /// Diagnostic severity. This may be used by
34    /// [`ReportHandler`](crate::ReportHandler)s to change the display format
35    /// of this diagnostic.
36    ///
37    /// If `None`, reporters should treat this as [`Severity::Error`].
38    fn severity(&self) -> Option<Severity> {
39        None
40    }
41
42    /// Additional help text related to this `Diagnostic`. Do you have any
43    /// advice for the poor soul who's just run into this issue?
44    fn help(&self) -> Option<Cow<'_, str>> {
45        None
46    }
47
48    /// Supplementary context for this `Diagnostic`, separate from help text.
49    /// Notes mirror rustc-style `= note:` lines and offer additional
50    /// information when guidance (help) is insufficient.
51    fn note(&self) -> Option<Cow<'_, str>> {
52        None
53    }
54
55    /// URL to visit for a more detailed explanation/help about this
56    /// `Diagnostic`.
57    fn url(&self) -> Option<Cow<'_, str>> {
58        None
59    }
60
61    /// Source code to apply this `Diagnostic`'s [`Diagnostic::labels`] to.
62    fn source_code(&self) -> Option<&dyn SourceCode> {
63        None
64    }
65
66    /// Labels to apply to this `Diagnostic`'s [`Diagnostic::source_code`]
67    ///
68    /// Returns the owned [`Labels`](crate::Labels) container. For the common one/two-label
69    /// case this is allocation-free (the labels are stored inline), and it
70    /// avoids the boxed-iterator allocation the previous signature required.
71    fn labels(&self) -> crate::Labels {
72        crate::Labels::None
73    }
74
75    /// Additional related `Diagnostic`s.
76    ///
77    /// Returns the owned [`Related`] container. For the common one/two-related
78    /// case this is allocation-free, and it avoids the boxed-iterator
79    /// allocation the previous signature required.
80    fn related(&self) -> Related<'_> {
81        Related::None
82    }
83
84    /// The cause of the error.
85    fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
86        None
87    }
88}
89
90/// Container for a [`Diagnostic`]'s related diagnostics.
91///
92/// Most diagnostics carry only one or two related entries, so those cases are
93/// stored inline without a heap allocation. Three or more spill to a [`Vec`].
94#[derive(Default)]
95pub enum Related<'a> {
96    /// No related diagnostics.
97    #[default]
98    None,
99    /// A single related diagnostic, stored inline.
100    One([&'a dyn Diagnostic; 1]),
101    /// Two related diagnostics, stored inline.
102    Two([&'a dyn Diagnostic; 2]),
103    /// Three or more related diagnostics, stored on the heap.
104    Many(Vec<&'a dyn Diagnostic>),
105}
106
107impl<'a> Related<'a> {
108    /// Returns the related diagnostics as a contiguous slice.
109    #[must_use]
110    pub fn as_slice(&self) -> &[&'a dyn Diagnostic] {
111        match self {
112            Related::None => &[],
113            Related::One(related) => related,
114            Related::Two(related) => related,
115            Related::Many(related) => related,
116        }
117    }
118
119    /// Returns `true` if there are no related diagnostics.
120    #[must_use]
121    pub fn is_empty(&self) -> bool {
122        matches!(self, Related::None)
123    }
124
125    /// Returns the number of related diagnostics.
126    #[must_use]
127    pub fn len(&self) -> usize {
128        self.as_slice().len()
129    }
130
131    /// Appends a related diagnostic, keeping the storage inline while possible.
132    pub fn push(&mut self, related: &'a dyn Diagnostic) {
133        if let Related::Many(items) = self {
134            items.push(related);
135            return;
136        }
137        *self = match mem::take(self) {
138            Related::None => Related::One([related]),
139            Related::One([a]) => Related::Two([a, related]),
140            Related::Two([a, b]) => Related::Many(vec![a, b, related]),
141            Related::Many(_) => unreachable!("handled by the fast path above"),
142        };
143    }
144}
145
146impl<'a> Deref for Related<'a> {
147    type Target = [&'a dyn Diagnostic];
148
149    fn deref(&self) -> &Self::Target {
150        self.as_slice()
151    }
152}
153
154impl<'a> Extend<&'a dyn Diagnostic> for Related<'a> {
155    fn extend<I: IntoIterator<Item = &'a dyn Diagnostic>>(&mut self, iter: I) {
156        let mut iter = iter.into_iter();
157        while !matches!(self, Related::Many(_)) {
158            match iter.next() {
159                Some(related) => self.push(related),
160                None => return,
161            }
162        }
163        if let Related::Many(items) = self {
164            items.reserve(iter.size_hint().0);
165            items.extend(iter);
166        }
167    }
168}
169
170impl<'a> FromIterator<&'a dyn Diagnostic> for Related<'a> {
171    fn from_iter<I: IntoIterator<Item = &'a dyn Diagnostic>>(iter: I) -> Self {
172        let mut iter = iter.into_iter();
173        if iter.size_hint().0 > 2 {
174            return Related::Many(iter.collect());
175        }
176        let Some(a) = iter.next() else { return Related::None };
177        let Some(b) = iter.next() else { return Related::One([a]) };
178        let Some(c) = iter.next() else { return Related::Two([a, b]) };
179        let mut items = Vec::with_capacity(3 + iter.size_hint().0);
180        items.extend([a, b, c]);
181        items.extend(iter);
182        Related::Many(items)
183    }
184}
185
186macro_rules! box_error_impls {
187    ($($box_type:ty),*) => {
188        $(
189            impl Error for $box_type {
190                fn source(&self) -> Option<&(dyn Error + 'static)> {
191                    (**self).source()
192                }
193
194                fn cause(&self) -> Option<&dyn Error> {
195                    self.source()
196                }
197            }
198        )*
199    }
200}
201
202box_error_impls! {
203    Box<dyn Diagnostic>,
204    Box<dyn Diagnostic + Send>,
205    Box<dyn Diagnostic + Send + Sync>
206}
207
208macro_rules! box_borrow_impls {
209    ($($box_type:ty),*) => {
210        $(
211            impl Borrow<dyn Diagnostic> for $box_type {
212                fn borrow(&self) -> &(dyn Diagnostic + 'static) {
213                    self.as_ref()
214                }
215            }
216        )*
217    }
218}
219
220box_borrow_impls! {
221    Box<dyn Diagnostic + Send>,
222    Box<dyn Diagnostic + Send + Sync>
223}
224
225impl<T: Diagnostic + Send + Sync + 'static> From<T>
226    for Box<dyn Diagnostic + Send + Sync + 'static>
227{
228    fn from(diag: T) -> Self {
229        Box::new(diag)
230    }
231}
232
233impl<T: Diagnostic + Send + Sync + 'static> From<T> for Box<dyn Diagnostic + Send + 'static> {
234    fn from(diag: T) -> Self {
235        Box::<dyn Diagnostic + Send + Sync>::from(diag)
236    }
237}
238
239impl<T: Diagnostic + Send + Sync + 'static> From<T> for Box<dyn Diagnostic + 'static> {
240    fn from(diag: T) -> Self {
241        Box::<dyn Diagnostic + Send + Sync>::from(diag)
242    }
243}
244
245impl From<&str> for Box<dyn Diagnostic> {
246    fn from(s: &str) -> Self {
247        From::from(String::from(s))
248    }
249}
250
251impl From<&str> for Box<dyn Diagnostic + Send + Sync + '_> {
252    fn from(s: &str) -> Self {
253        From::from(String::from(s))
254    }
255}
256
257impl From<String> for Box<dyn Diagnostic> {
258    fn from(s: String) -> Self {
259        let err1: Box<dyn Diagnostic + Send + Sync> = From::from(s);
260        let err2: Box<dyn Diagnostic> = err1;
261        err2
262    }
263}
264
265impl From<String> for Box<dyn Diagnostic + Send + Sync> {
266    fn from(s: String) -> Self {
267        struct StringError(String);
268
269        impl Error for StringError {}
270        impl Diagnostic for StringError {}
271
272        impl Display for StringError {
273            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
274                Display::fmt(&self.0, f)
275            }
276        }
277
278        // Purposefully skip printing "StringError(..)"
279        impl fmt::Debug for StringError {
280            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
281                fmt::Debug::fmt(&self.0, f)
282            }
283        }
284
285        Box::new(StringError(s))
286    }
287}
288
289impl From<Box<dyn Error + Send + Sync>> for Box<dyn Diagnostic + Send + Sync> {
290    fn from(s: Box<dyn Error + Send + Sync>) -> Self {
291        #[derive(thiserror::Error)]
292        #[error(transparent)]
293        struct BoxedDiagnostic(Box<dyn Error + Send + Sync>);
294        impl fmt::Debug for BoxedDiagnostic {
295            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
296                fmt::Debug::fmt(&self.0, f)
297            }
298        }
299
300        impl Diagnostic for BoxedDiagnostic {}
301
302        Box::new(BoxedDiagnostic(s))
303    }
304}
305
306/**
307[`Diagnostic`] severity. Intended to be used by
308[`ReportHandler`](crate::ReportHandler)s to change the way different
309[`Diagnostic`]s are displayed. Defaults to [`Severity::Error`].
310*/
311#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
312#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
313#[derive(Default)]
314pub enum Severity {
315    /// Just some help. Here's how you could be doing it better.
316    Advice,
317    /// Warning. Please take note.
318    Warning,
319    /// Critical failure. The program cannot continue.
320    /// This is the default severity, if you don't specify another one.
321    #[default]
322    Error,
323}
324
325#[cfg(feature = "serde")]
326#[test]
327fn test_serialize_severity() {
328    use serde_json::json;
329
330    assert_eq!(json!(Severity::Advice), json!("Advice"));
331    assert_eq!(json!(Severity::Warning), json!("Warning"));
332    assert_eq!(json!(Severity::Error), json!("Error"));
333}
334
335#[cfg(feature = "serde")]
336#[test]
337fn test_deserialize_severity() {
338    use serde_json::json;
339
340    let severity: Severity = serde_json::from_value(json!("Advice")).unwrap();
341    assert_eq!(severity, Severity::Advice);
342
343    let severity: Severity = serde_json::from_value(json!("Warning")).unwrap();
344    assert_eq!(severity, Severity::Warning);
345
346    let severity: Severity = serde_json::from_value(json!("Error")).unwrap();
347    assert_eq!(severity, Severity::Error);
348}
349
350/**
351Represents readable source code of some sort.
352
353This trait is able to support simple `SourceCode` types like [`String`]s, as
354well as more involved types like indexes into centralized `SourceMap`-like
355types, file handles, and even network streams.
356
357If you can read it, you can source it, and it's not necessary to read the
358whole thing--meaning you should be able to support `SourceCode`s which are
359gigabytes or larger in size.
360*/
361pub trait SourceCode: Send + Sync {
362    /// Read the bytes for a specific span from this `SourceCode`, keeping a
363    /// certain number of lines before and after the span as context.
364    fn read_span<'a>(
365        &'a self,
366        span: &SourceSpan,
367        context_lines_before: usize,
368        context_lines_after: usize,
369    ) -> Result<MietteSpanContents<'a>, MietteError>;
370
371    /// Returns the name of this source code, if any.
372    fn name(&self) -> Option<&str> {
373        None
374    }
375}
376
377/// A labeled [`SourceSpan`].
378#[derive(Debug, Clone, PartialEq, Eq)]
379#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
380pub struct LabeledSpan {
381    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
382    label: Option<String>,
383    span: SourceSpan,
384    primary: bool,
385}
386
387impl LabeledSpan {
388    /// Makes a new labeled span.
389    #[must_use]
390    pub const fn new(label: Option<String>, offset: ByteOffset, len: u32) -> Self {
391        Self { label, span: SourceSpan::new(SourceOffset(offset), len), primary: false }
392    }
393
394    /// Makes a new labeled span using an existing span.
395    #[must_use]
396    pub fn new_with_span(label: Option<String>, span: impl Into<SourceSpan>) -> Self {
397        Self { label, span: span.into(), primary: false }
398    }
399
400    /// Makes a new labeled primary span using an existing span.
401    #[must_use]
402    pub fn new_primary_with_span(label: Option<String>, span: impl Into<SourceSpan>) -> Self {
403        Self { label, span: span.into(), primary: true }
404    }
405
406    /// Change the text of the label
407    pub fn set_label(&mut self, label: Option<String>) {
408        self.label = label;
409    }
410
411    /// Change the offset of the span
412    pub fn set_span_offset(&mut self, offset: ByteOffset) {
413        self.span.offset = SourceOffset(offset);
414    }
415
416    /// Makes a new label at specified span
417    ///
418    /// # Examples
419    /// ```
420    /// use miette::LabeledSpan;
421    ///
422    /// let source = "Cpp is the best";
423    /// let label = LabeledSpan::at(0..3, "should be Rust");
424    /// assert_eq!(
425    ///     label,
426    ///     LabeledSpan::new(Some("should be Rust".to_string()), 0, 3)
427    /// )
428    /// ```
429    #[must_use]
430    pub fn at(span: impl Into<SourceSpan>, label: impl Into<String>) -> Self {
431        Self::new_with_span(Some(label.into()), span)
432    }
433
434    /// Makes a new label that points at a specific offset.
435    ///
436    /// # Examples
437    /// ```
438    /// use miette::LabeledSpan;
439    ///
440    /// let source = "(2 + 2";
441    /// let label = LabeledSpan::at_offset(4, "expected a closing parenthesis");
442    /// assert_eq!(
443    ///     label,
444    ///     LabeledSpan::new(Some("expected a closing parenthesis".to_string()), 4, 0)
445    /// )
446    /// ```
447    pub fn at_offset(offset: ByteOffset, label: impl Into<String>) -> Self {
448        Self::new(Some(label.into()), offset, 0)
449    }
450
451    /// Makes a new label without text, that underlines a specific span.
452    ///
453    /// # Examples
454    /// ```
455    /// use miette::LabeledSpan;
456    ///
457    /// let source = "You have an error here";
458    /// let label = LabeledSpan::underline(12..16);
459    /// assert_eq!(label, LabeledSpan::new(None, 12, 4))
460    /// ```
461    #[must_use]
462    pub fn underline(span: impl Into<SourceSpan>) -> Self {
463        Self::new_with_span(None, span)
464    }
465
466    /// Gets the (optional) label string for this `LabeledSpan`.
467    pub fn label(&self) -> Option<&str> {
468        self.label.as_deref()
469    }
470
471    /// Returns a reference to the inner [`SourceSpan`].
472    pub const fn inner(&self) -> &SourceSpan {
473        &self.span
474    }
475
476    /// Returns the 0-based starting byte offset.
477    pub const fn offset(&self) -> ByteOffset {
478        self.span.offset()
479    }
480
481    /// Returns the number of bytes this `LabeledSpan` spans.
482    pub const fn len(&self) -> u32 {
483        self.span.len()
484    }
485
486    /// True if this `LabeledSpan` is empty.
487    pub const fn is_empty(&self) -> bool {
488        self.span.is_empty()
489    }
490
491    /// True if this `LabeledSpan` is a primary span.
492    pub const fn primary(&self) -> bool {
493        self.primary
494    }
495}
496
497#[test]
498fn test_set_span_offset() {
499    let mut span = LabeledSpan::new(None, 10, 10);
500    assert_eq!(span.offset(), 10);
501
502    span.set_span_offset(20);
503    assert_eq!(span.offset(), 20);
504}
505
506#[cfg(feature = "serde")]
507#[test]
508fn test_serialize_labeled_span() {
509    use serde_json::json;
510
511    assert_eq!(
512        json!(LabeledSpan::new(None, 0, 0)),
513        json!({
514            "span": { "offset": 0, "length": 0, },
515            "primary": false,
516        })
517    );
518
519    assert_eq!(
520        json!(LabeledSpan::new(Some("label".to_string()), 0, 0)),
521        json!({
522            "label": "label",
523            "span": { "offset": 0, "length": 0, },
524            "primary": false,
525        })
526    );
527}
528
529#[cfg(feature = "serde")]
530#[test]
531fn test_deserialize_labeled_span() {
532    use serde_json::json;
533
534    let span: LabeledSpan = serde_json::from_value(json!({
535        "label": null,
536        "span": { "offset": 0, "length": 0, },
537        "primary": false,
538    }))
539    .unwrap();
540    assert_eq!(span, LabeledSpan::new(None, 0, 0));
541
542    let span: LabeledSpan = serde_json::from_value(json!({
543        "span": { "offset": 0, "length": 0, },
544        "primary": false
545    }))
546    .unwrap();
547    assert_eq!(span, LabeledSpan::new(None, 0, 0));
548
549    let span: LabeledSpan = serde_json::from_value(json!({
550        "label": "label",
551        "span": { "offset": 0, "length": 0, },
552        "primary": false
553    }))
554    .unwrap();
555    assert_eq!(span, LabeledSpan::new(Some("label".to_string()), 0, 0));
556}
557
558/**
559Contents of a [`SourceCode`] covered by [`SourceSpan`].
560
561Includes line and column information to optimize highlight calculations.
562*/
563pub trait SpanContents<'a> {
564    /// Reference to the data inside the associated span, in bytes.
565    fn data(&self) -> &'a [u8];
566    /// [`SourceSpan`] representing the span covered by this `SpanContents`.
567    fn span(&self) -> &SourceSpan;
568    /// An optional (file?) name for the container of this `SpanContents`.
569    fn name(&self) -> Option<&str> {
570        None
571    }
572    /// The 0-indexed line in the associated [`SourceCode`] where the data
573    /// begins.
574    fn line(&self) -> usize;
575    /// The 0-indexed column in the associated [`SourceCode`] where the data
576    /// begins, relative to `line`.
577    fn column(&self) -> usize;
578    /// Total number of lines covered by this `SpanContents`.
579    fn line_count(&self) -> usize;
580
581    /// Optional method. The language name for this source code, if any.
582    /// This is used to drive syntax highlighting.
583    ///
584    /// Examples: Rust, TOML, C
585    ///
586    fn language(&self) -> Option<&str> {
587        None
588    }
589}
590
591/**
592Basic implementation of the [`SpanContents`] trait, for convenience.
593*/
594#[derive(Clone, Debug)]
595pub struct MietteSpanContents<'a> {
596    // Data from a [`SourceCode`], in bytes.
597    data: &'a [u8],
598    // span actually covered by this SpanContents.
599    span: SourceSpan,
600    // The 0-indexed line where the associated [`SourceSpan`] _starts_.
601    line: usize,
602    // The 0-indexed column where the associated [`SourceSpan`] _starts_.
603    column: usize,
604    // Number of line in this snippet.
605    line_count: usize,
606    // Optional filename
607    name: Option<Cow<'a, str>>,
608    // Optional language
609    language: Option<Cow<'a, str>>,
610}
611
612impl<'a> MietteSpanContents<'a> {
613    /// Make a new [`MietteSpanContents`] object.
614    pub const fn new(
615        data: &'a [u8],
616        span: SourceSpan,
617        line: usize,
618        column: usize,
619        line_count: usize,
620    ) -> MietteSpanContents<'a> {
621        MietteSpanContents { data, span, line, column, line_count, name: None, language: None }
622    }
623
624    /// Make a new [`MietteSpanContents`] object, with a name for its 'file'.
625    pub const fn new_named(
626        name: Cow<'a, str>,
627        data: &'a [u8],
628        span: SourceSpan,
629        line: usize,
630        column: usize,
631        line_count: usize,
632    ) -> MietteSpanContents<'a> {
633        MietteSpanContents {
634            data,
635            span,
636            line,
637            column,
638            line_count,
639            name: Some(name),
640            language: None,
641        }
642    }
643
644    /// Sets the `language` for syntax highlighting.
645    #[must_use]
646    pub fn with_language(mut self, language: impl Into<Cow<'a, str>>) -> Self {
647        self.language = Some(language.into());
648        self
649    }
650}
651
652impl<'a> SpanContents<'a> for MietteSpanContents<'a> {
653    fn data(&self) -> &'a [u8] {
654        self.data
655    }
656
657    fn span(&self) -> &SourceSpan {
658        &self.span
659    }
660
661    fn line(&self) -> usize {
662        self.line
663    }
664
665    fn column(&self) -> usize {
666        self.column
667    }
668
669    fn line_count(&self) -> usize {
670        self.line_count
671    }
672
673    fn name(&self) -> Option<&str> {
674        self.name.as_deref()
675    }
676
677    fn language(&self) -> Option<&str> {
678        self.language.as_deref()
679    }
680}
681
682/// Span within a [`SourceCode`]
683#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
684#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
685pub struct SourceSpan {
686    /// The start of the span.
687    offset: SourceOffset,
688    /// The total length of the span, in bytes.
689    length: u32,
690}
691
692impl SourceSpan {
693    /// Create a new [`SourceSpan`].
694    pub const fn new(start: SourceOffset, length: u32) -> Self {
695        Self { offset: start, length }
696    }
697
698    /// The absolute offset, in bytes, from the beginning of a [`SourceCode`].
699    pub const fn offset(&self) -> ByteOffset {
700        self.offset.offset()
701    }
702
703    /// Total length of the [`SourceSpan`], in bytes.
704    pub const fn len(&self) -> u32 {
705        self.length
706    }
707
708    /// Whether this [`SourceSpan`] has a length of zero. It may still be useful
709    /// to point to a specific point.
710    pub const fn is_empty(&self) -> bool {
711        self.length == 0
712    }
713}
714
715impl From<(ByteOffset, u32)> for SourceSpan {
716    fn from((start, len): (ByteOffset, u32)) -> Self {
717        Self { offset: start.into(), length: len }
718    }
719}
720
721impl From<(SourceOffset, u32)> for SourceSpan {
722    fn from((start, len): (SourceOffset, u32)) -> Self {
723        Self::new(start, len)
724    }
725}
726
727impl From<Range<ByteOffset>> for SourceSpan {
728    fn from(range: Range<ByteOffset>) -> Self {
729        // `Range::len` returns `0` for empty/reversed ranges, matching the
730        // previous behavior and avoiding underflow.
731        Self { offset: range.start.into(), length: range.len() as u32 }
732    }
733}
734
735impl From<SourceOffset> for SourceSpan {
736    fn from(offset: SourceOffset) -> Self {
737        Self { offset, length: 0 }
738    }
739}
740
741impl From<ByteOffset> for SourceSpan {
742    fn from(offset: ByteOffset) -> Self {
743        Self { offset: offset.into(), length: 0 }
744    }
745}
746
747#[cfg(feature = "serde")]
748#[test]
749fn test_serialize_source_span() {
750    use serde_json::json;
751
752    assert_eq!(json!(SourceSpan::from(0)), json!({ "offset": 0, "length": 0}));
753}
754
755#[cfg(feature = "serde")]
756#[test]
757fn test_deserialize_source_span() {
758    use serde_json::json;
759
760    let span: SourceSpan = serde_json::from_value(json!({ "offset": 0, "length": 0})).unwrap();
761    assert_eq!(span, SourceSpan::from(0));
762}
763
764/**
765"Raw" type for the byte offset from the beginning of a [`SourceCode`].
766*/
767pub type ByteOffset = u32;
768
769/**
770Newtype that represents the [`ByteOffset`] from the beginning of a [`SourceCode`]
771*/
772#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
773#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
774pub struct SourceOffset(ByteOffset);
775
776impl SourceOffset {
777    /// Actual byte offset.
778    pub const fn offset(&self) -> ByteOffset {
779        self.0
780    }
781
782    /// Little utility to help convert 1-based line/column locations into
783    /// miette-compatible Spans
784    ///
785    /// This function is infallible: Giving an out-of-range line/column pair
786    /// will return the offset of the last byte in the source.
787    pub fn from_location(source: impl AsRef<str>, loc_line: usize, loc_col: usize) -> Self {
788        let mut line = 0usize;
789        let mut col = 0usize;
790        let mut offset = 0usize;
791        for char in source.as_ref().chars() {
792            if line + 1 >= loc_line && col + 1 >= loc_col {
793                break;
794            }
795            if char == '\n' {
796                col = 0;
797                line += 1;
798            } else {
799                col += 1;
800            }
801            offset += char.len_utf8();
802        }
803
804        SourceOffset(offset as u32)
805    }
806
807    /// Returns an offset for the _file_ location of wherever this function is
808    /// called. If you want to get _that_ caller's location, mark this
809    /// function's caller with `#[track_caller]` (and so on and so forth).
810    ///
811    /// Returns both the filename that was given and the offset of the caller
812    /// as a [`SourceOffset`].
813    ///
814    /// Keep in mind that this fill only work if the file your Rust source
815    /// file was compiled from is actually available at that location. If
816    /// you're shipping binaries for your application, you'll want to ignore
817    /// the Err case or otherwise report it.
818    #[track_caller]
819    pub fn from_current_location() -> Result<(String, Self), MietteError> {
820        let loc = Location::caller();
821        Ok((
822            loc.file().into(),
823            fs::read_to_string(loc.file())
824                .map(|txt| Self::from_location(txt, loc.line() as usize, loc.column() as usize))?,
825        ))
826    }
827}
828
829impl From<ByteOffset> for SourceOffset {
830    fn from(bytes: ByteOffset) -> Self {
831        SourceOffset(bytes)
832    }
833}
834
835#[test]
836fn test_source_offset_from_location() {
837    let source = "f\n\noo\r\nbar";
838
839    assert_eq!(SourceOffset::from_location(source, 1, 1).offset(), 0);
840    assert_eq!(SourceOffset::from_location(source, 1, 2).offset(), 1);
841    assert_eq!(SourceOffset::from_location(source, 2, 1).offset(), 2);
842    assert_eq!(SourceOffset::from_location(source, 3, 1).offset(), 3);
843    assert_eq!(SourceOffset::from_location(source, 3, 2).offset(), 4);
844    assert_eq!(SourceOffset::from_location(source, 3, 3).offset(), 5);
845    assert_eq!(SourceOffset::from_location(source, 3, 4).offset(), 6);
846    assert_eq!(SourceOffset::from_location(source, 4, 1).offset(), 7);
847    assert_eq!(SourceOffset::from_location(source, 4, 2).offset(), 8);
848    assert_eq!(SourceOffset::from_location(source, 4, 3).offset(), 9);
849    assert_eq!(SourceOffset::from_location(source, 4, 4).offset(), 10);
850
851    // Out-of-range
852    assert_eq!(SourceOffset::from_location(source, 5, 1).offset(), source.len() as u32);
853}
854
855#[cfg(feature = "serde")]
856#[test]
857fn test_serialize_source_offset() {
858    use serde_json::json;
859
860    assert_eq!(json!(SourceOffset::from(0)), 0);
861}
862
863#[cfg(feature = "serde")]
864#[test]
865fn test_deserialize_source_offset() {
866    let offset: SourceOffset = serde_json::from_str("0").unwrap();
867    assert_eq!(offset, SourceOffset::from(0));
868}