1use std::any::type_name;
4use std::fmt;
5use std::fmt::{Debug, Display};
6use std::io;
7use std::panic::Location;
8use std::result::Result as StdResult;
9
10use derive_more::Display;
11
12#[derive(Debug, thiserror::Error)]
18pub enum Error<E: ReportableError> {
19 #[error("IO error: {0}")]
21 Io(#[from] io::Error),
22
23 #[error("Parse error: {0}")]
25 Parse(#[from] Report<E>),
26}
27
28#[derive(thiserror::Error)]
34#[error("{error}")]
35pub struct Report<E: ReportableError> {
36 #[source]
37 error: E,
38 stack: E::Stack,
39}
40
41#[derive(Clone, Copy, Debug, Display)]
43#[display(fmt = "extra unparsed input")]
44pub struct ExtraUnparsedInput;
45
46#[derive(Clone, Copy, Debug, Display)]
48#[display(fmt = "while parsing value of type `{}`", _0)]
49pub struct WhileParsingType(&'static str);
50
51pub type Result<T, E> = StdResult<T, Report<E>>;
53
54pub trait ResultExt: Sized {
56 #[track_caller]
57 fn attach_printable<P: Display + Send + Sync + 'static>(self, printable: P) -> Self;
59
60 #[track_caller]
61 fn while_parsing_type(self) -> Self;
63}
64
65pub struct ReportStack {
67 location: &'static Location<'static>,
68 entries: Vec<ReportEntry>,
69}
70
71#[derive(Clone, Copy, Debug, Display)]
73#[display(fmt = "")]
74pub struct NullReportStack;
75
76pub trait ReportableError: Display {
78 type Stack: ReportableErrorStack;
80}
81
82pub trait ReportableErrorStack: Display {
84 #[track_caller]
85 fn new() -> Self;
87
88 #[track_caller]
89 fn attach_printable<P: Display + Send + Sync + 'static>(self, printable: P) -> Self;
91}
92
93#[derive(derive_more::Display)]
98#[display(fmt = "{message} at {location}")]
99struct ReportEntry {
100 message: Box<dyn Display + Send + Sync + 'static>,
101 location: &'static Location<'static>,
102}
103
104impl<E: ReportableError> Report<E> {
109 pub fn get_ref(&self) -> &E {
111 &self.error
112 }
113
114 pub fn into_inner(self) -> E {
116 self.error
117 }
118
119 #[track_caller]
120 pub fn attach_printable<P: Display + Send + Sync + 'static>(mut self, message: P) -> Self {
122 self.stack = self.stack.attach_printable(message);
123 self
124 }
125}
126
127impl<E: ReportableError> From<E> for Report<E> {
128 #[track_caller]
129 fn from(error: E) -> Self {
130 Self { error, stack: E::Stack::new() }
131 }
132}
133
134impl<E: ReportableError> Debug for Report<E> {
135 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
136 let Self { error, stack } = self;
137 write!(f, "{error}{stack}")
138 }
139}
140
141impl WhileParsingType {
150 pub fn new<T: ?Sized>() -> Self {
152 Self(type_name::<T>())
153 }
154}
155
156impl Display for ReportStack {
161 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
162 let Self { location, entries } = self;
163 writeln!(f, " at {location}")?;
164 for entry in &entries[..self.entries.len().saturating_sub(1)] {
165 writeln!(f, " - {entry}")?;
166 }
167 if let Some(entry) = entries.last() {
168 write!(f, " - {entry}")?;
169 }
170 Ok(())
171 }
172}
173
174impl ReportableErrorStack for ReportStack {
175 #[track_caller]
176 fn new() -> Self {
177 Self { location: Location::caller(), entries: Default::default() }
178 }
179
180 fn attach_printable<P: Display + Send + Sync + 'static>(mut self, printable: P) -> Self {
181 let entry = ReportEntry { message: Box::new(printable), location: Location::caller() };
182 self.entries.push(entry);
183 self
184 }
185}
186
187impl ReportableErrorStack for NullReportStack {
192 fn new() -> Self {
193 Self
194 }
195
196 fn attach_printable<P: Display + Send + Sync + 'static>(self, _printable: P) -> Self {
197 Self
198 }
199}
200
201impl<T, E: ReportableError> ResultExt for Result<T, E> {
206 #[track_caller]
207 fn attach_printable<P: Display + Send + Sync + 'static>(self, printable: P) -> Self {
208 match self {
209 Ok(value) => Ok(value),
210 Err(err) => Err(err.attach_printable(printable)),
211 }
212 }
213
214 #[track_caller]
215 fn while_parsing_type(self) -> Self {
216 self.attach_printable(WhileParsingType::new::<T>())
217 }
218}
219
220impl<T, E: ReportableError> ResultExt for StdResult<T, Error<E>> {
221 #[track_caller]
222 fn attach_printable<P: Display + Send + Sync + 'static>(self, printable: P) -> Self {
223 match self {
224 Err(Error::Io(err)) => Err(Error::Io(err)),
225 Err(Error::Parse(err)) => Err(Error::Parse(err.attach_printable(printable))),
226 _ => self,
227 }
228 }
229
230 #[track_caller]
231 fn while_parsing_type(self) -> Self {
232 self.attach_printable(WhileParsingType::new::<T>())
233 }
234}
235
236#[cfg(test)]
237mod test {
238 use super::*;
239
240 const TEST_ERROR_DISPLAY: &str = "test error display";
241 const TEST_ATTACHMENT: &str = "test attachment";
242
243 #[derive(Debug, thiserror::Error)]
244 #[error("{}", TEST_ERROR_DISPLAY)]
245 struct TestError;
246
247 impl ReportableError for TestError {
248 type Stack = ReportStack;
249 }
250
251 fn test_report() -> Report<TestError> {
252 report_attach!(TestError, TEST_ATTACHMENT)
253 }
254
255 #[test]
256 fn test_report_display() {
257 assert_eq!(test_report().to_string(), TEST_ERROR_DISPLAY);
258 }
259
260 #[test]
261 fn test_report_debug() {
262 let report_debug = format!("{report:?}", report = test_report());
263 assert!(report_debug.starts_with(TEST_ERROR_DISPLAY));
264 assert!(report_debug.contains(TEST_ATTACHMENT));
265 }
266}