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 #[derive(Default)]
183 pub enum Severity {
184 /// Just some help. Here's how you could be doing it better.
185 Advice,
186 /// Warning. Please take note.
187 Warning,
188 /// Critical failure. The program cannot continue.
189 /// This is the default severity, if you don't specify another one.
190 #[default]
191 Error,
192 }
193
194 #[cfg(feature = "serde")]
195 #[test]
test_serialize_severity()196 fn test_serialize_severity() {
197 use serde_json::json;
198
199 assert_eq!(json!(Severity::Advice), json!("Advice"));
200 assert_eq!(json!(Severity::Warning), json!("Warning"));
201 assert_eq!(json!(Severity::Error), json!("Error"));
202 }
203
204 #[cfg(feature = "serde")]
205 #[test]
test_deserialize_severity()206 fn test_deserialize_severity() {
207 use serde_json::json;
208
209 let severity: Severity = serde_json::from_value(json!("Advice")).unwrap();
210 assert_eq!(severity, Severity::Advice);
211
212 let severity: Severity = serde_json::from_value(json!("Warning")).unwrap();
213 assert_eq!(severity, Severity::Warning);
214
215 let severity: Severity = serde_json::from_value(json!("Error")).unwrap();
216 assert_eq!(severity, Severity::Error);
217 }
218
219 /**
220 Represents readable source code of some sort.
221
222 This trait is able to support simple `SourceCode` types like [`String`]s, as
223 well as more involved types like indexes into centralized `SourceMap`-like
224 types, file handles, and even network streams.
225
226 If you can read it, you can source it, and it's not necessary to read the
227 whole thing--meaning you should be able to support `SourceCode`s which are
228 gigabytes or larger in size.
229 */
230 pub trait SourceCode: Send + Sync {
231 /// Read the bytes for a specific span from this `SourceCode`, keeping a
232 /// 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>233 fn read_span<'a>(
234 &'a self,
235 span: &SourceSpan,
236 context_lines_before: usize,
237 context_lines_after: usize,
238 ) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError>;
239 }
240
241 /// A labeled [`SourceSpan`].
242 #[derive(Debug, Clone, PartialEq, Eq)]
243 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
244 pub struct LabeledSpan {
245 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
246 label: Option<String>,
247 span: SourceSpan,
248 primary: bool,
249 }
250
251 impl LabeledSpan {
252 /// Makes a new labeled span.
new(label: Option<String>, offset: ByteOffset, len: usize) -> Self253 pub const fn new(label: Option<String>, offset: ByteOffset, len: usize) -> Self {
254 Self {
255 label,
256 span: SourceSpan::new(SourceOffset(offset), len),
257 primary: false,
258 }
259 }
260
261 /// Makes a new labeled span using an existing span.
new_with_span(label: Option<String>, span: impl Into<SourceSpan>) -> Self262 pub fn new_with_span(label: Option<String>, span: impl Into<SourceSpan>) -> Self {
263 Self {
264 label,
265 span: span.into(),
266 primary: false,
267 }
268 }
269
270 /// Makes a new labeled primary span using an existing span.
new_primary_with_span(label: Option<String>, span: impl Into<SourceSpan>) -> Self271 pub fn new_primary_with_span(label: Option<String>, span: impl Into<SourceSpan>) -> Self {
272 Self {
273 label,
274 span: span.into(),
275 primary: true,
276 }
277 }
278
279 /// Makes a new label at specified span
280 ///
281 /// # Examples
282 /// ```
283 /// use miette::LabeledSpan;
284 ///
285 /// let source = "Cpp is the best";
286 /// let label = LabeledSpan::at(0..3, "should be Rust");
287 /// assert_eq!(
288 /// label,
289 /// LabeledSpan::new(Some("should be Rust".to_string()), 0, 3)
290 /// )
291 /// ```
at(span: impl Into<SourceSpan>, label: impl Into<String>) -> Self292 pub fn at(span: impl Into<SourceSpan>, label: impl Into<String>) -> Self {
293 Self::new_with_span(Some(label.into()), span)
294 }
295
296 /// Makes a new label that points at a specific offset.
297 ///
298 /// # Examples
299 /// ```
300 /// use miette::LabeledSpan;
301 ///
302 /// let source = "(2 + 2";
303 /// let label = LabeledSpan::at_offset(4, "expected a closing parenthesis");
304 /// assert_eq!(
305 /// label,
306 /// LabeledSpan::new(Some("expected a closing parenthesis".to_string()), 4, 0)
307 /// )
308 /// ```
at_offset(offset: ByteOffset, label: impl Into<String>) -> Self309 pub fn at_offset(offset: ByteOffset, label: impl Into<String>) -> Self {
310 Self::new(Some(label.into()), offset, 0)
311 }
312
313 /// Makes a new label without text, that underlines a specific span.
314 ///
315 /// # Examples
316 /// ```
317 /// use miette::LabeledSpan;
318 ///
319 /// let source = "You have an eror here";
320 /// let label = LabeledSpan::underline(12..16);
321 /// assert_eq!(label, LabeledSpan::new(None, 12, 4))
322 /// ```
underline(span: impl Into<SourceSpan>) -> Self323 pub fn underline(span: impl Into<SourceSpan>) -> Self {
324 Self::new_with_span(None, span)
325 }
326
327 /// Gets the (optional) label string for this `LabeledSpan`.
label(&self) -> Option<&str>328 pub fn label(&self) -> Option<&str> {
329 self.label.as_deref()
330 }
331
332 /// Returns a reference to the inner [`SourceSpan`].
inner(&self) -> &SourceSpan333 pub const fn inner(&self) -> &SourceSpan {
334 &self.span
335 }
336
337 /// Returns the 0-based starting byte offset.
offset(&self) -> usize338 pub const fn offset(&self) -> usize {
339 self.span.offset()
340 }
341
342 /// Returns the number of bytes this `LabeledSpan` spans.
len(&self) -> usize343 pub const fn len(&self) -> usize {
344 self.span.len()
345 }
346
347 /// True if this `LabeledSpan` is empty.
is_empty(&self) -> bool348 pub const fn is_empty(&self) -> bool {
349 self.span.is_empty()
350 }
351
352 /// True if this `LabeledSpan` is a primary span.
primary(&self) -> bool353 pub const fn primary(&self) -> bool {
354 self.primary
355 }
356 }
357
358 #[cfg(feature = "serde")]
359 #[test]
test_serialize_labeled_span()360 fn test_serialize_labeled_span() {
361 use serde_json::json;
362
363 assert_eq!(
364 json!(LabeledSpan::new(None, 0, 0)),
365 json!({
366 "span": { "offset": 0, "length": 0, },
367 "primary": false,
368 })
369 );
370
371 assert_eq!(
372 json!(LabeledSpan::new(Some("label".to_string()), 0, 0)),
373 json!({
374 "label": "label",
375 "span": { "offset": 0, "length": 0, },
376 "primary": false,
377 })
378 );
379 }
380
381 #[cfg(feature = "serde")]
382 #[test]
test_deserialize_labeled_span()383 fn test_deserialize_labeled_span() {
384 use serde_json::json;
385
386 let span: LabeledSpan = serde_json::from_value(json!({
387 "label": null,
388 "span": { "offset": 0, "length": 0, },
389 "primary": false,
390 }))
391 .unwrap();
392 assert_eq!(span, LabeledSpan::new(None, 0, 0));
393
394 let span: LabeledSpan = serde_json::from_value(json!({
395 "span": { "offset": 0, "length": 0, },
396 "primary": false
397 }))
398 .unwrap();
399 assert_eq!(span, LabeledSpan::new(None, 0, 0));
400
401 let span: LabeledSpan = serde_json::from_value(json!({
402 "label": "label",
403 "span": { "offset": 0, "length": 0, },
404 "primary": false
405 }))
406 .unwrap();
407 assert_eq!(span, LabeledSpan::new(Some("label".to_string()), 0, 0));
408 }
409
410 /**
411 Contents of a [`SourceCode`] covered by [`SourceSpan`].
412
413 Includes line and column information to optimize highlight calculations.
414 */
415 pub trait SpanContents<'a> {
416 /// Reference to the data inside the associated span, in bytes.
data(&self) -> &'a [u8]417 fn data(&self) -> &'a [u8];
418 /// [`SourceSpan`] representing the span covered by this `SpanContents`.
span(&self) -> &SourceSpan419 fn span(&self) -> &SourceSpan;
420 /// An optional (file?) name for the container of this `SpanContents`.
name(&self) -> Option<&str>421 fn name(&self) -> Option<&str> {
422 None
423 }
424 /// The 0-indexed line in the associated [`SourceCode`] where the data
425 /// begins.
line(&self) -> usize426 fn line(&self) -> usize;
427 /// The 0-indexed column in the associated [`SourceCode`] where the data
428 /// begins, relative to `line`.
column(&self) -> usize429 fn column(&self) -> usize;
430 /// Total number of lines covered by this `SpanContents`.
line_count(&self) -> usize431 fn line_count(&self) -> usize;
432
433 /// Optional method. The language name for this source code, if any.
434 /// This is used to drive syntax highlighting.
435 ///
436 /// Examples: Rust, TOML, C
437 ///
language(&self) -> Option<&str>438 fn language(&self) -> Option<&str> {
439 None
440 }
441 }
442
443 /**
444 Basic implementation of the [`SpanContents`] trait, for convenience.
445 */
446 #[derive(Clone, Debug)]
447 pub struct MietteSpanContents<'a> {
448 // Data from a [`SourceCode`], in bytes.
449 data: &'a [u8],
450 // span actually covered by this SpanContents.
451 span: SourceSpan,
452 // The 0-indexed line where the associated [`SourceSpan`] _starts_.
453 line: usize,
454 // The 0-indexed column where the associated [`SourceSpan`] _starts_.
455 column: usize,
456 // Number of line in this snippet.
457 line_count: usize,
458 // Optional filename
459 name: Option<String>,
460 // Optional language
461 language: Option<String>,
462 }
463
464 impl<'a> MietteSpanContents<'a> {
465 /// Make a new [`MietteSpanContents`] object.
new( data: &'a [u8], span: SourceSpan, line: usize, column: usize, line_count: usize, ) -> MietteSpanContents<'a>466 pub const fn new(
467 data: &'a [u8],
468 span: SourceSpan,
469 line: usize,
470 column: usize,
471 line_count: usize,
472 ) -> MietteSpanContents<'a> {
473 MietteSpanContents {
474 data,
475 span,
476 line,
477 column,
478 line_count,
479 name: None,
480 language: None,
481 }
482 }
483
484 /// 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>485 pub const fn new_named(
486 name: String,
487 data: &'a [u8],
488 span: SourceSpan,
489 line: usize,
490 column: usize,
491 line_count: usize,
492 ) -> MietteSpanContents<'a> {
493 MietteSpanContents {
494 data,
495 span,
496 line,
497 column,
498 line_count,
499 name: Some(name),
500 language: None,
501 }
502 }
503
504 /// Sets the [`language`](SourceCode::language) for syntax highlighting.
with_language(mut self, language: impl Into<String>) -> Self505 pub fn with_language(mut self, language: impl Into<String>) -> Self {
506 self.language = Some(language.into());
507 self
508 }
509 }
510
511 impl<'a> SpanContents<'a> for MietteSpanContents<'a> {
data(&self) -> &'a [u8]512 fn data(&self) -> &'a [u8] {
513 self.data
514 }
span(&self) -> &SourceSpan515 fn span(&self) -> &SourceSpan {
516 &self.span
517 }
line(&self) -> usize518 fn line(&self) -> usize {
519 self.line
520 }
column(&self) -> usize521 fn column(&self) -> usize {
522 self.column
523 }
line_count(&self) -> usize524 fn line_count(&self) -> usize {
525 self.line_count
526 }
name(&self) -> Option<&str>527 fn name(&self) -> Option<&str> {
528 self.name.as_deref()
529 }
language(&self) -> Option<&str>530 fn language(&self) -> Option<&str> {
531 self.language.as_deref()
532 }
533 }
534
535 /// Span within a [`SourceCode`]
536 #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
537 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
538 pub struct SourceSpan {
539 /// The start of the span.
540 offset: SourceOffset,
541 /// The total length of the span
542 length: usize,
543 }
544
545 impl SourceSpan {
546 /// Create a new [`SourceSpan`].
new(start: SourceOffset, length: usize) -> Self547 pub const fn new(start: SourceOffset, length: usize) -> Self {
548 Self {
549 offset: start,
550 length,
551 }
552 }
553
554 /// The absolute offset, in bytes, from the beginning of a [`SourceCode`].
offset(&self) -> usize555 pub const fn offset(&self) -> usize {
556 self.offset.offset()
557 }
558
559 /// Total length of the [`SourceSpan`], in bytes.
len(&self) -> usize560 pub const fn len(&self) -> usize {
561 self.length
562 }
563
564 /// Whether this [`SourceSpan`] has a length of zero. It may still be useful
565 /// to point to a specific point.
is_empty(&self) -> bool566 pub const fn is_empty(&self) -> bool {
567 self.length == 0
568 }
569 }
570
571 impl From<(ByteOffset, usize)> for SourceSpan {
from((start, len): (ByteOffset, usize)) -> Self572 fn from((start, len): (ByteOffset, usize)) -> Self {
573 Self {
574 offset: start.into(),
575 length: len,
576 }
577 }
578 }
579
580 impl From<(SourceOffset, usize)> for SourceSpan {
from((start, len): (SourceOffset, usize)) -> Self581 fn from((start, len): (SourceOffset, usize)) -> Self {
582 Self::new(start, len)
583 }
584 }
585
586 impl From<std::ops::Range<ByteOffset>> for SourceSpan {
from(range: std::ops::Range<ByteOffset>) -> Self587 fn from(range: std::ops::Range<ByteOffset>) -> Self {
588 Self {
589 offset: range.start.into(),
590 length: range.len(),
591 }
592 }
593 }
594
595 impl From<SourceOffset> for SourceSpan {
from(offset: SourceOffset) -> Self596 fn from(offset: SourceOffset) -> Self {
597 Self { offset, length: 0 }
598 }
599 }
600
601 impl From<ByteOffset> for SourceSpan {
from(offset: ByteOffset) -> Self602 fn from(offset: ByteOffset) -> Self {
603 Self {
604 offset: offset.into(),
605 length: 0,
606 }
607 }
608 }
609
610 #[cfg(feature = "serde")]
611 #[test]
test_serialize_source_span()612 fn test_serialize_source_span() {
613 use serde_json::json;
614
615 assert_eq!(
616 json!(SourceSpan::from(0)),
617 json!({ "offset": 0, "length": 0})
618 );
619 }
620
621 #[cfg(feature = "serde")]
622 #[test]
test_deserialize_source_span()623 fn test_deserialize_source_span() {
624 use serde_json::json;
625
626 let span: SourceSpan = serde_json::from_value(json!({ "offset": 0, "length": 0})).unwrap();
627 assert_eq!(span, SourceSpan::from(0));
628 }
629
630 /**
631 "Raw" type for the byte offset from the beginning of a [`SourceCode`].
632 */
633 pub type ByteOffset = usize;
634
635 /**
636 Newtype that represents the [`ByteOffset`] from the beginning of a [`SourceCode`]
637 */
638 #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
639 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
640 pub struct SourceOffset(ByteOffset);
641
642 impl SourceOffset {
643 /// Actual byte offset.
offset(&self) -> ByteOffset644 pub const fn offset(&self) -> ByteOffset {
645 self.0
646 }
647
648 /// Little utility to help convert 1-based line/column locations into
649 /// miette-compatible Spans
650 ///
651 /// This function is infallible: Giving an out-of-range line/column pair
652 /// will return the offset of the last byte in the source.
from_location(source: impl AsRef<str>, loc_line: usize, loc_col: usize) -> Self653 pub fn from_location(source: impl AsRef<str>, loc_line: usize, loc_col: usize) -> Self {
654 let mut line = 0usize;
655 let mut col = 0usize;
656 let mut offset = 0usize;
657 for char in source.as_ref().chars() {
658 if line + 1 >= loc_line && col + 1 >= loc_col {
659 break;
660 }
661 if char == '\n' {
662 col = 0;
663 line += 1;
664 } else {
665 col += 1;
666 }
667 offset += char.len_utf8();
668 }
669
670 SourceOffset(offset)
671 }
672
673 /// Returns an offset for the _file_ location of wherever this function is
674 /// called. If you want to get _that_ caller's location, mark this
675 /// function's caller with `#[track_caller]` (and so on and so forth).
676 ///
677 /// Returns both the filename that was given and the offset of the caller
678 /// as a [`SourceOffset`].
679 ///
680 /// Keep in mind that this fill only work if the file your Rust source
681 /// file was compiled from is actually available at that location. If
682 /// you're shipping binaries for your application, you'll want to ignore
683 /// the Err case or otherwise report it.
684 #[track_caller]
from_current_location() -> Result<(String, Self), MietteError>685 pub fn from_current_location() -> Result<(String, Self), MietteError> {
686 let loc = Location::caller();
687 Ok((
688 loc.file().into(),
689 fs::read_to_string(loc.file())
690 .map(|txt| Self::from_location(txt, loc.line() as usize, loc.column() as usize))?,
691 ))
692 }
693 }
694
695 impl From<ByteOffset> for SourceOffset {
from(bytes: ByteOffset) -> Self696 fn from(bytes: ByteOffset) -> Self {
697 SourceOffset(bytes)
698 }
699 }
700
701 #[test]
test_source_offset_from_location()702 fn test_source_offset_from_location() {
703 let source = "f\n\noo\r\nbar";
704
705 assert_eq!(SourceOffset::from_location(source, 1, 1).offset(), 0);
706 assert_eq!(SourceOffset::from_location(source, 1, 2).offset(), 1);
707 assert_eq!(SourceOffset::from_location(source, 2, 1).offset(), 2);
708 assert_eq!(SourceOffset::from_location(source, 3, 1).offset(), 3);
709 assert_eq!(SourceOffset::from_location(source, 3, 2).offset(), 4);
710 assert_eq!(SourceOffset::from_location(source, 3, 3).offset(), 5);
711 assert_eq!(SourceOffset::from_location(source, 3, 4).offset(), 6);
712 assert_eq!(SourceOffset::from_location(source, 4, 1).offset(), 7);
713 assert_eq!(SourceOffset::from_location(source, 4, 2).offset(), 8);
714 assert_eq!(SourceOffset::from_location(source, 4, 3).offset(), 9);
715 assert_eq!(SourceOffset::from_location(source, 4, 4).offset(), 10);
716
717 // Out-of-range
718 assert_eq!(
719 SourceOffset::from_location(source, 5, 1).offset(),
720 source.len()
721 );
722 }
723
724 #[cfg(feature = "serde")]
725 #[test]
test_serialize_source_offset()726 fn test_serialize_source_offset() {
727 use serde_json::json;
728
729 assert_eq!(json!(SourceOffset::from(0)), 0);
730 }
731
732 #[cfg(feature = "serde")]
733 #[test]
test_deserialize_source_offset()734 fn test_deserialize_source_offset() {
735 let offset: SourceOffset = serde_json::from_str("0").unwrap();
736 assert_eq!(offset, SourceOffset::from(0));
737 }
738