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