eyre_impl/
lib.rs

1mod chain;
2
3pub use chain::Chain;
4use std::fmt;
5
6/// Helper trait for inserting generic types representing context into a Context
7pub trait ErrorContext<CO> {
8    fn push(&mut self, context: CO);
9}
10
11/// A general purpose error reporting type for holding generic errors and associating generic
12/// context with those errors
13///
14/// This type is intended to be consumed by a library that implements a concrete error type that is
15/// designed for the specific use cases the user has. The library implementer should provide two
16/// types, an error type of their choice, and a Context type that implements the two main helper
17/// traits, `ErrorFormatter` and `ErrorContext`
18pub struct ErrorReporter<E, C>
19where
20    E: std::error::Error + Send + Sync + 'static,
21{
22    pub error: E,
23    pub context: C,
24}
25
26impl<E, C> ErrorReporter<E, C>
27where
28    E: std::error::Error + Send + Sync + 'static,
29{
30    /// Construct an iterator over the internal error and its sources
31    pub fn chain(&self) -> Chain {
32        Chain::new(&self.error)
33    }
34}
35
36impl<C, E> From<E> for ErrorReporter<E, C>
37where
38    C: Default,
39    E: std::error::Error + Send + Sync + 'static,
40{
41    fn from(error: E) -> Self {
42        Self {
43            context: Default::default(),
44            error,
45        }
46    }
47}
48
49/// Helper trait for creating error reporters from context in extension Traits
50///
51/// This trait is intended to be used by extension traits for Result so that you can write
52/// combinators that work on either std::error::Error types directly (thus creating a new
53/// ErrorReporter) or on types that own an ErrorReporter, which lets you implement error types with
54/// APIs such as in the example
55///
56/// ## Example of supported APIs
57///
58/// ```rust
59/// let val = fallible_fn()
60///             .note("This function can only be used in certain situations")
61///             .warn("Be careful not to use this fn in this specific situation")?;
62/// ```
63pub trait IntoErrorReporter<E, C, CO>
64where
65    E: std::error::Error + Send + Sync + 'static,
66{
67    fn ext_context(self, context: CO) -> ErrorReporter<E, C>;
68}
69
70impl<E, C, CO> IntoErrorReporter<E, C, CO> for E
71where
72    C: Default + ErrorContext<CO>,
73    E: std::error::Error + Send + Sync + 'static,
74{
75    fn ext_context(self, context: CO) -> ErrorReporter<E, C> {
76        let mut error = ErrorReporter::<E, C>::from(self);
77        error.context.push(context);
78        error
79    }
80}
81
82impl<C, CO, E> IntoErrorReporter<E, C, CO> for ErrorReporter<E, C>
83where
84    E: std::error::Error + Send + Sync + 'static,
85    C: ErrorContext<CO>,
86{
87    fn ext_context(mut self, context: CO) -> ErrorReporter<E, C> {
88        self.context.push(context);
89        self
90    }
91}
92
93/// Helper struct for efficiently numbering and correctly indenting multi line display
94/// implementations
95pub struct Indented<'a, D> {
96    inner: &'a mut D,
97    ind: Option<usize>,
98    started: bool,
99}
100
101impl<'a, D> Indented<'a, D> {
102    /// Wrap a formatter number the first line and indent all lines of input before forwarding the
103    /// output to the inner formatter
104    pub fn numbered(inner: &'a mut D, ind: usize) -> Self {
105        Self {
106            inner,
107            ind: Some(ind),
108            started: false,
109        }
110    }
111}
112
113impl<T> fmt::Write for Indented<'_, T>
114where
115    T: fmt::Write,
116{
117    fn write_str(&mut self, s: &str) -> fmt::Result {
118        for (ind, mut line) in s.split('\n').enumerate() {
119            if !self.started {
120                // trim first line to ensure it lines up with the number nicely
121                line = line.trim_start();
122                // Don't render the first line unless its actually got text on it
123                if line.is_empty() {
124                    continue;
125                }
126
127                self.started = true;
128                match self.ind {
129                    Some(ind) => self.inner.write_fmt(format_args!("{: >5}: ", ind))?,
130                    None => self.inner.write_fmt(format_args!("    "))?,
131                }
132            } else if ind > 0 {
133                self.inner.write_char('\n')?;
134                if self.ind.is_some() {
135                    self.inner.write_fmt(format_args!("       "))?;
136                } else {
137                    self.inner.write_fmt(format_args!("    "))?;
138                }
139            }
140
141            self.inner.write_fmt(format_args!("{}", line))?;
142        }
143
144        Ok(())
145    }
146}
147
148#[cfg(test)]
149mod tests {
150    use super::*;
151    use std::fmt::Write as _;
152
153    #[test]
154    fn one_digit() {
155        let input = "verify\nthis";
156        let expected = "    2: verify\n       this";
157        let mut output = String::new();
158
159        Indented {
160            inner: &mut output,
161            ind: Some(2),
162            started: false,
163        }
164        .write_str(input)
165        .unwrap();
166
167        assert_eq!(expected, output);
168    }
169
170    #[test]
171    fn two_digits() {
172        let input = "verify\nthis";
173        let expected = "   12: verify\n       this";
174        let mut output = String::new();
175
176        Indented {
177            inner: &mut output,
178            ind: Some(12),
179            started: false,
180        }
181        .write_str(input)
182        .unwrap();
183
184        assert_eq!(expected, output);
185    }
186
187    #[test]
188    fn no_digits() {
189        let input = "verify\nthis";
190        let expected = "    verify\n    this";
191        let mut output = String::new();
192
193        Indented {
194            inner: &mut output,
195            ind: None,
196            started: false,
197        }
198        .write_str(input)
199        .unwrap();
200
201        assert_eq!(expected, output);
202    }
203}