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<'a> From<&str> for Box<dyn Diagnostic + Send + Sync + 'a> {
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
258/// A labeled [`SourceSpan`].
259#[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    /// Makes a new labeled span.
270    pub const fn new(label: Option<String>, offset: ByteOffset, len: usize) -> Self {
271        Self {
272            label,
273            span: SourceSpan::new(SourceOffset(offset), len),
274            primary: false,
275        }
276    }
277
278    /// Makes a new labeled span using an existing span.
279    pub fn new_with_span(label: Option<String>, span: impl Into<SourceSpan>) -> Self {
280        Self {
281            label,
282            span: span.into(),
283            primary: false,
284        }
285    }
286
287    /// Makes a new labeled primary span using an existing span.
288    pub fn new_primary_with_span(label: Option<String>, span: impl Into<SourceSpan>) -> Self {
289        Self {
290            label,
291            span: span.into(),
292            primary: true,
293        }
294    }
295
296    /// Change the text of the label
297    pub fn set_label(&mut self, label: Option<String>) {
298        self.label = label;
299    }
300
301    /// Makes a new label at specified span
302    ///
303    /// # Examples
304    /// ```
305    /// use miette::LabeledSpan;
306    ///
307    /// let source = "Cpp is the best";
308    /// let label = LabeledSpan::at(0..3, "should be Rust");
309    /// assert_eq!(
310    ///     label,
311    ///     LabeledSpan::new(Some("should be Rust".to_string()), 0, 3)
312    /// )
313    /// ```
314    pub fn at(span: impl Into<SourceSpan>, label: impl Into<String>) -> Self {
315        Self::new_with_span(Some(label.into()), span)
316    }
317
318    /// Makes a new label that points at a specific offset.
319    ///
320    /// # Examples
321    /// ```
322    /// use miette::LabeledSpan;
323    ///
324    /// let source = "(2 + 2";
325    /// let label = LabeledSpan::at_offset(4, "expected a closing parenthesis");
326    /// assert_eq!(
327    ///     label,
328    ///     LabeledSpan::new(Some("expected a closing parenthesis".to_string()), 4, 0)
329    /// )
330    /// ```
331    pub fn at_offset(offset: ByteOffset, label: impl Into<String>) -> Self {
332        Self::new(Some(label.into()), offset, 0)
333    }
334
335    /// Makes a new label without text, that underlines a specific span.
336    ///
337    /// # Examples
338    /// ```
339    /// use miette::LabeledSpan;
340    ///
341    /// let source = "You have an eror here";
342    /// let label = LabeledSpan::underline(12..16);
343    /// assert_eq!(label, LabeledSpan::new(None, 12, 4))
344    /// ```
345    pub fn underline(span: impl Into<SourceSpan>) -> Self {
346        Self::new_with_span(None, span)
347    }
348
349    /// Gets the (optional) label string for this `LabeledSpan`.
350    pub fn label(&self) -> Option<&str> {
351        self.label.as_deref()
352    }
353
354    /// Returns a reference to the inner [`SourceSpan`].
355    pub const fn inner(&self) -> &SourceSpan {
356        &self.span
357    }
358
359    /// Returns the 0-based starting byte offset.
360    pub const fn offset(&self) -> usize {
361        self.span.offset()
362    }
363
364    /// Returns the number of bytes this `LabeledSpan` spans.
365    pub const fn len(&self) -> usize {
366        self.span.len()
367    }
368
369    /// True if this `LabeledSpan` is empty.
370    pub const fn is_empty(&self) -> bool {
371        self.span.is_empty()
372    }
373
374    /// True if this `LabeledSpan` is a primary span.
375    pub const fn primary(&self) -> bool {
376        self.primary
377    }
378}
379
380#[cfg(feature = "serde")]
381#[test]
382fn test_serialize_labeled_span() {
383    use serde_json::json;
384
385    assert_eq!(
386        json!(LabeledSpan::new(None, 0, 0)),
387        json!({
388            "span": { "offset": 0, "length": 0, },
389            "primary": false,
390        })
391    );
392
393    assert_eq!(
394        json!(LabeledSpan::new(Some("label".to_string()), 0, 0)),
395        json!({
396            "label": "label",
397            "span": { "offset": 0, "length": 0, },
398            "primary": false,
399        })
400    );
401}
402
403#[cfg(feature = "serde")]
404#[test]
405fn test_deserialize_labeled_span() {
406    use serde_json::json;
407
408    let span: LabeledSpan = serde_json::from_value(json!({
409        "label": null,
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        "span": { "offset": 0, "length": 0, },
418        "primary": false
419    }))
420    .unwrap();
421    assert_eq!(span, LabeledSpan::new(None, 0, 0));
422
423    let span: LabeledSpan = serde_json::from_value(json!({
424        "label": "label",
425        "span": { "offset": 0, "length": 0, },
426        "primary": false
427    }))
428    .unwrap();
429    assert_eq!(span, LabeledSpan::new(Some("label".to_string()), 0, 0));
430}
431
432/**
433Contents of a [`SourceCode`] covered by [`SourceSpan`].
434
435Includes line and column information to optimize highlight calculations.
436*/
437pub trait SpanContents<'a> {
438    /// Reference to the data inside the associated span, in bytes.
439    fn data(&self) -> &'a [u8];
440    /// [`SourceSpan`] representing the span covered by this `SpanContents`.
441    fn span(&self) -> &SourceSpan;
442    /// An optional (file?) name for the container of this `SpanContents`.
443    fn name(&self) -> Option<&str> {
444        None
445    }
446    /// The 0-indexed line in the associated [`SourceCode`] where the data
447    /// begins.
448    fn line(&self) -> usize;
449    /// The 0-indexed column in the associated [`SourceCode`] where the data
450    /// begins, relative to `line`.
451    fn column(&self) -> usize;
452    /// Total number of lines covered by this `SpanContents`.
453    fn line_count(&self) -> usize;
454
455    /// Optional method. The language name for this source code, if any.
456    /// This is used to drive syntax highlighting.
457    ///
458    /// Examples: Rust, TOML, C
459    ///
460    fn language(&self) -> Option<&str> {
461        None
462    }
463}
464
465/**
466Basic implementation of the [`SpanContents`] trait, for convenience.
467*/
468#[derive(Clone, Debug)]
469pub struct MietteSpanContents<'a> {
470    // Data from a [`SourceCode`], in bytes.
471    data: &'a [u8],
472    // span actually covered by this SpanContents.
473    span: SourceSpan,
474    // The 0-indexed line where the associated [`SourceSpan`] _starts_.
475    line: usize,
476    // The 0-indexed column where the associated [`SourceSpan`] _starts_.
477    column: usize,
478    // Number of line in this snippet.
479    line_count: usize,
480    // Optional filename
481    name: Option<String>,
482    // Optional language
483    language: Option<String>,
484}
485
486impl<'a> MietteSpanContents<'a> {
487    /// Make a new [`MietteSpanContents`] object.
488    pub const fn new(
489        data: &'a [u8],
490        span: SourceSpan,
491        line: usize,
492        column: usize,
493        line_count: usize,
494    ) -> MietteSpanContents<'a> {
495        MietteSpanContents {
496            data,
497            span,
498            line,
499            column,
500            line_count,
501            name: None,
502            language: None,
503        }
504    }
505
506    /// Make a new [`MietteSpanContents`] object, with a name for its 'file'.
507    pub const fn new_named(
508        name: String,
509        data: &'a [u8],
510        span: SourceSpan,
511        line: usize,
512        column: usize,
513        line_count: usize,
514    ) -> MietteSpanContents<'a> {
515        MietteSpanContents {
516            data,
517            span,
518            line,
519            column,
520            line_count,
521            name: Some(name),
522            language: None,
523        }
524    }
525
526    /// Sets the [`language`](SpanContents::language) for syntax highlighting.
527    pub fn with_language(mut self, language: impl Into<String>) -> Self {
528        self.language = Some(language.into());
529        self
530    }
531}
532
533impl<'a> SpanContents<'a> for MietteSpanContents<'a> {
534    fn data(&self) -> &'a [u8] {
535        self.data
536    }
537    fn span(&self) -> &SourceSpan {
538        &self.span
539    }
540    fn line(&self) -> usize {
541        self.line
542    }
543    fn column(&self) -> usize {
544        self.column
545    }
546    fn line_count(&self) -> usize {
547        self.line_count
548    }
549    fn name(&self) -> Option<&str> {
550        self.name.as_deref()
551    }
552    fn language(&self) -> Option<&str> {
553        self.language.as_deref()
554    }
555}
556
557/// Span within a [`SourceCode`]
558#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
559#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
560pub struct SourceSpan {
561    /// The start of the span.
562    offset: SourceOffset,
563    /// The total length of the span
564    length: usize,
565}
566
567impl SourceSpan {
568    /// Create a new [`SourceSpan`].
569    pub const fn new(start: SourceOffset, length: usize) -> Self {
570        Self {
571            offset: start,
572            length,
573        }
574    }
575
576    /// The absolute offset, in bytes, from the beginning of a [`SourceCode`].
577    pub const fn offset(&self) -> usize {
578        self.offset.offset()
579    }
580
581    /// Total length of the [`SourceSpan`], in bytes.
582    pub const fn len(&self) -> usize {
583        self.length
584    }
585
586    /// Whether this [`SourceSpan`] has a length of zero. It may still be useful
587    /// to point to a specific point.
588    pub const fn is_empty(&self) -> bool {
589        self.length == 0
590    }
591}
592
593impl From<(ByteOffset, usize)> for SourceSpan {
594    fn from((start, len): (ByteOffset, usize)) -> Self {
595        Self {
596            offset: start.into(),
597            length: len,
598        }
599    }
600}
601
602impl From<(SourceOffset, usize)> for SourceSpan {
603    fn from((start, len): (SourceOffset, usize)) -> Self {
604        Self::new(start, len)
605    }
606}
607
608impl From<std::ops::Range<ByteOffset>> for SourceSpan {
609    fn from(range: std::ops::Range<ByteOffset>) -> Self {
610        Self {
611            offset: range.start.into(),
612            length: range.len(),
613        }
614    }
615}
616
617impl From<std::ops::RangeInclusive<ByteOffset>> for SourceSpan {
618    /// # Panics
619    ///
620    /// Panics if the total length of the inclusive range would overflow a
621    /// `usize`. This will only occur with the range `0..=usize::MAX`.
622    fn from(range: std::ops::RangeInclusive<ByteOffset>) -> Self {
623        let (start, end) = range.clone().into_inner();
624        Self {
625            offset: start.into(),
626            length: if range.is_empty() {
627                0
628            } else {
629                // will not overflow because `is_empty() == false` guarantees
630                // that `start <= end`
631                (end - start)
632                    .checked_add(1)
633                    .expect("length of inclusive range should fit in a usize")
634            },
635        }
636    }
637}
638
639impl From<SourceOffset> for SourceSpan {
640    fn from(offset: SourceOffset) -> Self {
641        Self { offset, length: 0 }
642    }
643}
644
645impl From<ByteOffset> for SourceSpan {
646    fn from(offset: ByteOffset) -> Self {
647        Self {
648            offset: offset.into(),
649            length: 0,
650        }
651    }
652}
653
654#[cfg(feature = "serde")]
655#[test]
656fn test_serialize_source_span() {
657    use serde_json::json;
658
659    assert_eq!(
660        json!(SourceSpan::from(0)),
661        json!({ "offset": 0, "length": 0})
662    );
663}
664
665#[cfg(feature = "serde")]
666#[test]
667fn test_deserialize_source_span() {
668    use serde_json::json;
669
670    let span: SourceSpan = serde_json::from_value(json!({ "offset": 0, "length": 0})).unwrap();
671    assert_eq!(span, SourceSpan::from(0));
672}
673
674/**
675"Raw" type for the byte offset from the beginning of a [`SourceCode`].
676*/
677pub type ByteOffset = usize;
678
679/**
680Newtype that represents the [`ByteOffset`] from the beginning of a [`SourceCode`]
681*/
682#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
683#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
684pub struct SourceOffset(ByteOffset);
685
686impl SourceOffset {
687    /// Actual byte offset.
688    pub const fn offset(&self) -> ByteOffset {
689        self.0
690    }
691
692    /// Little utility to help convert 1-based line/column locations into
693    /// miette-compatible Spans
694    ///
695    /// This function is infallible: Giving an out-of-range line/column pair
696    /// will return the offset of the last byte in the source.
697    pub fn from_location(source: impl AsRef<str>, loc_line: usize, loc_col: usize) -> Self {
698        let mut line = 0usize;
699        let mut col = 0usize;
700        let mut offset = 0usize;
701        for char in source.as_ref().chars() {
702            if line + 1 >= loc_line && col + 1 >= loc_col {
703                break;
704            }
705            if char == '\n' {
706                col = 0;
707                line += 1;
708            } else {
709                col += 1;
710            }
711            offset += char.len_utf8();
712        }
713
714        SourceOffset(offset)
715    }
716
717    /// Returns an offset for the _file_ location of wherever this function is
718    /// called. If you want to get _that_ caller's location, mark this
719    /// function's caller with `#[track_caller]` (and so on and so forth).
720    ///
721    /// Returns both the filename that was given and the offset of the caller
722    /// as a [`SourceOffset`].
723    ///
724    /// Keep in mind that this fill only work if the file your Rust source
725    /// file was compiled from is actually available at that location. If
726    /// you're shipping binaries for your application, you'll want to ignore
727    /// the Err case or otherwise report it.
728    #[track_caller]
729    pub fn from_current_location() -> Result<(String, Self), MietteError> {
730        let loc = Location::caller();
731        Ok((
732            loc.file().into(),
733            fs::read_to_string(loc.file())
734                .map(|txt| Self::from_location(txt, loc.line() as usize, loc.column() as usize))?,
735        ))
736    }
737}
738
739impl From<ByteOffset> for SourceOffset {
740    fn from(bytes: ByteOffset) -> Self {
741        SourceOffset(bytes)
742    }
743}
744
745#[test]
746fn test_source_offset_from_location() {
747    let source = "f\n\noo\r\nbar";
748
749    assert_eq!(SourceOffset::from_location(source, 1, 1).offset(), 0);
750    assert_eq!(SourceOffset::from_location(source, 1, 2).offset(), 1);
751    assert_eq!(SourceOffset::from_location(source, 2, 1).offset(), 2);
752    assert_eq!(SourceOffset::from_location(source, 3, 1).offset(), 3);
753    assert_eq!(SourceOffset::from_location(source, 3, 2).offset(), 4);
754    assert_eq!(SourceOffset::from_location(source, 3, 3).offset(), 5);
755    assert_eq!(SourceOffset::from_location(source, 3, 4).offset(), 6);
756    assert_eq!(SourceOffset::from_location(source, 4, 1).offset(), 7);
757    assert_eq!(SourceOffset::from_location(source, 4, 2).offset(), 8);
758    assert_eq!(SourceOffset::from_location(source, 4, 3).offset(), 9);
759    assert_eq!(SourceOffset::from_location(source, 4, 4).offset(), 10);
760
761    // Out-of-range
762    assert_eq!(
763        SourceOffset::from_location(source, 5, 1).offset(),
764        source.len()
765    );
766}
767
768#[cfg(feature = "serde")]
769#[test]
770fn test_serialize_source_offset() {
771    use serde_json::json;
772
773    assert_eq!(json!(SourceOffset::from(0)), 0);
774}
775
776#[cfg(feature = "serde")]
777#[test]
778fn test_deserialize_source_offset() {
779    let offset: SourceOffset = serde_json::from_str("0").unwrap();
780    assert_eq!(offset, SourceOffset::from(0));
781}