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}