mediasan_common/
error.rs

1//! Error types returned by the public API.
2
3use 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//
13// public types
14//
15
16/// Error type returned by `mediasan`.
17#[derive(Debug, thiserror::Error)]
18pub enum Error<E: ReportableError> {
19    /// An IO error occurred while reading the given input.
20    #[error("IO error: {0}")]
21    Io(#[from] io::Error),
22
23    /// The input could not be parsed as a media file.
24    #[error("Parse error: {0}")]
25    Parse(#[from] Report<E>),
26}
27
28/// A report with additional debugging info for an error.
29///
30/// A `Report<E>` can be used to identify exactly where the error `E` occurred in `mediasan`. The [`Debug`]
31/// implementation will print a human-readable parser stack trace. The underlying error of type `E` can also be
32/// retrieved e.g. for matching against with [`get_ref`](Self::get_ref) or [`into_inner`](Self::into_inner).
33#[derive(thiserror::Error)]
34#[error("{error}")]
35pub struct Report<E: ReportableError> {
36    #[source]
37    error: E,
38    stack: E::Stack,
39}
40
41/// A [`Display`]-able indicating there was extra trailing input after parsing.
42#[derive(Clone, Copy, Debug, Display)]
43#[display(fmt = "extra unparsed input")]
44pub struct ExtraUnparsedInput;
45
46/// A [`Display`]-able indicating an error occurred while parsing a certain type.
47#[derive(Clone, Copy, Debug, Display)]
48#[display(fmt = "while parsing value of type `{}`", _0)]
49pub struct WhileParsingType(&'static str);
50
51/// A convenience type alias for a [`Result`](std::result::Result) containing an error wrapped by a [`Report`].
52pub type Result<T, E> = StdResult<T, Report<E>>;
53
54/// An trait providing [`Report`]-related extensions for [`Result`](std::result::Result).
55pub trait ResultExt: Sized {
56    #[track_caller]
57    /// Attach a [`Display`]-able type to the error [`Report`]'s stack trace.
58    fn attach_printable<P: Display + Send + Sync + 'static>(self, printable: P) -> Self;
59
60    #[track_caller]
61    /// Attach the message "while parsing type T" to the error [`Report`]'s stack trace.
62    fn while_parsing_type(self) -> Self;
63}
64
65/// An error stack.
66pub struct ReportStack {
67    location: &'static Location<'static>,
68    entries: Vec<ReportEntry>,
69}
70
71/// A null error stack which ignores all data attached to it.
72#[derive(Clone, Copy, Debug, Display)]
73#[display(fmt = "")]
74pub struct NullReportStack;
75
76/// A trait for error types which can be used in a [`Report`].
77pub trait ReportableError: Display {
78    /// The error stack type corresponding to this error.
79    type Stack: ReportableErrorStack;
80}
81
82/// A trait for error stack types for use within a [`Report`].
83pub trait ReportableErrorStack: Display {
84    #[track_caller]
85    /// Construct a new instance of [`Self`].
86    fn new() -> Self;
87
88    #[track_caller]
89    /// Attach a [`Display`]-able type to the error [`Report`]'s stack trace.
90    fn attach_printable<P: Display + Send + Sync + 'static>(self, printable: P) -> Self;
91}
92
93//
94// private types
95//
96
97#[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
104//
105// Report impls
106//
107
108impl<E: ReportableError> Report<E> {
109    /// Get a reference to the underlying error.
110    pub fn get_ref(&self) -> &E {
111        &self.error
112    }
113
114    /// Unwrap this report, returning the underlying error.
115    pub fn into_inner(self) -> E {
116        self.error
117    }
118
119    #[track_caller]
120    /// Attach a [`Display`]-able type to the stack trace.
121    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
141//
142// ReportErrorStack impls
143//
144
145//
146// WhileParsingType impls
147//
148
149impl WhileParsingType {
150    /// Construct a new [`WhileParsingType`] where the type described is `T`.
151    pub fn new<T: ?Sized>() -> Self {
152        Self(type_name::<T>())
153    }
154}
155
156//
157// ReportStack impls
158//
159
160impl 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
187//
188// ReportableErrorStack impls
189//
190
191impl 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
201//
202// ResultExt impls
203//
204
205impl<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}