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