rich_err/
lib.rs

1#![doc = include_str!("../README.md")]
2
3pub mod error_code;
4pub mod label;
5pub mod note;
6pub mod span;
7
8pub use error_code::{ErrorCode, ToErrorCode};
9pub use label::Label;
10pub use note::Note;
11pub use span::Span;
12
13use std::{borrow::Cow, path::Path};
14
15/// A highly detailed error type designed for:
16///
17/// - Compilers (or anything similar)
18/// - Tracebacks
19/// - Any situation where a source file would provide valuable context for the user
20///
21/// This serves as a sort of wrapper around [`ariadne`], although it is missing much of
22/// [`ariadne`]'s functionality. The point is to have a simple interface for constructing error
23/// reports that works well enough for sufficiently simple applications.
24///
25/// As a word of caution, this higher level of detail comes at the cost of higher memory usage. The
26/// `struct` alone is around 7 times larger than a [`String`], and some features may incur extra
27/// heap allocations as well. Thus, it is not advisable to use rich errors unless an ordinary error
28/// is truly insufficient.
29#[derive(Clone, Debug, Eq, PartialEq)]
30pub struct RichError {
31    /// The [error code](ErrorCode).
32    pub code: ErrorCode,
33    /// The error message.
34    ///
35    /// This is meant to be a description of the general error specified by `code` rather than a
36    /// verbose description of precisely what failed. More details should be sent through `label`
37    /// and/or `notes`.
38    pub message: String,
39    /// The broad area in the source file that caused the error.
40    ///
41    /// The exact location of the error can be specified via `label`.
42    pub broad_span: Span,
43    /// An additional error message associated with a specific area of the source code.
44    pub label: Option<Label>,
45    /// An arbitrary number of notes that provide additional context and/or assistance to the user.
46    pub notes: Vec<Note>,
47}
48
49pub type RichResult<T> = Result<T, RichError>;
50
51impl RichError {
52    /// Constructs a new rich error.
53    ///
54    /// To add additional metadata to this message, use the relevant builder methods.
55    pub fn new<E>(err: E, broad_span: Span) -> Self
56    where
57        E: ToErrorCode + ToString,
58    {
59        Self {
60            code: err.code(),
61            message: err.to_string(),
62            broad_span,
63            label: None,
64            notes: Vec::new(),
65        }
66    }
67
68    /// Adds an ordinary note to this error.
69    pub fn with_note<S>(mut self, message: S) -> Self
70    where
71        S: ToString,
72    {
73        self.notes.push(Note::new_note(message));
74        self
75    }
76
77    /// Adds a note with a help message to this error.
78    pub fn with_help<S>(mut self, message: S) -> Self
79    where
80        S: ToString,
81    {
82        self.notes.push(Note::new_help(message));
83        self
84    }
85
86    /// Adds a label to this error.
87    ///
88    /// If a label was already present, this overwrites it.
89    pub fn with_label(mut self, label: Label) -> Self {
90        self.label = Some(label);
91        self
92    }
93
94    /// Specifies the exact location of the error within the broader span.
95    ///
96    /// This is similar to creating a blank [label](Label) with the provided span, but it also
97    /// handles the case where `label` was already set. In that case, `label`'s message will be
98    /// preserved while its span is overwritten.
99    pub fn with_narrow_span(mut self, span: Span) -> Self {
100        let new_label = Label::new(span);
101        self.label = Some(match self.label {
102            Some(label) => new_label.with_message(label.message),
103            None => new_label,
104        });
105        self
106    }
107
108    /// Reports the error to the user.
109    ///
110    /// This is a relatively expensive operation, so depending on the use case, it might be wise to
111    /// either report all errors at once or have a separate thread report errors as they crop up.
112    pub fn report<P>(self, source: &str, source_path: P) -> std::io::Result<()>
113    where
114        P: AsRef<Path>,
115    {
116        let source_name = match source_path.as_ref().file_name() {
117            Some(name) => name.to_string_lossy(),
118            None => Cow::Owned("<source>".to_string()),
119        };
120
121        let mut builder = ariadne::Report::build(
122            ariadne::ReportKind::Error,
123            (source_name.clone(), self.broad_span),
124        )
125        .with_config(ariadne::Config::new())
126        .with_code(self.code)
127        .with_message(self.message);
128
129        for note in self.notes {
130            note.add_to(&mut builder);
131        }
132
133        if let Some(label) = self.label {
134            label.add_to(&mut builder, source_name.clone());
135        }
136
137        builder
138            .finish()
139            .eprint((source_name, ariadne::Source::from(source)))
140    }
141}