gix_error/exn/
impls.rs

1// Copyright 2025 FastLabs Developers
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::collections::VecDeque;
16use std::error::Error;
17use std::fmt;
18use std::marker::PhantomData;
19use std::ops::Deref;
20use std::panic::Location;
21
22use crate::{write_location, ChainedError, Exn};
23
24impl<E: Error + Send + Sync + 'static> From<E> for Exn<E> {
25    #[track_caller]
26    fn from(error: E) -> Self {
27        Exn::new(error)
28    }
29}
30
31impl<E: Error + Send + Sync + 'static> Exn<E> {
32    /// Create a new exception with the given error.
33    ///
34    /// This will automatically walk the [source chain of the error] and add them as children
35    /// frames.
36    ///
37    /// See also [`ErrorExt::raise`](crate::ErrorExt) for a fluent way to convert an error into an `Exn` instance.
38    ///
39    /// Note that **sources of `error` are degenerated to their string representation** and all type information is erased.
40    ///
41    /// [source chain of the error]: Error::source
42    #[track_caller]
43    pub fn new(error: E) -> Self {
44        fn walk_sources(error: &dyn Error, location: &'static Location<'static>) -> Vec<Frame> {
45            if let Some(source) = error.source() {
46                let children = vec![Frame {
47                    error: Box::new(SourceError::new(source)),
48                    location,
49                    children: walk_sources(source, location),
50                }];
51                children
52            } else {
53                vec![]
54            }
55        }
56
57        let location = Location::caller();
58        let children = walk_sources(&error, location);
59        let frame = Frame {
60            error: Box::new(error),
61            location,
62            children,
63        };
64
65        Self {
66            frame: Box::new(frame),
67            phantom: PhantomData,
68        }
69    }
70
71    /// Create a new exception with the given error and children.
72    #[track_caller]
73    pub fn raise_all<T, I>(children: I, err: E) -> Self
74    where
75        T: Error + Send + Sync + 'static,
76        I: IntoIterator,
77        I::Item: Into<Exn<T>>,
78    {
79        let mut new_exn = Exn::new(err);
80        for exn in children {
81            let exn = exn.into();
82            new_exn.frame.children.push(*exn.frame);
83        }
84        new_exn
85    }
86
87    /// Raise a new exception; this will make the current exception a child of the new one.
88    #[track_caller]
89    pub fn raise<T: Error + Send + Sync + 'static>(self, err: T) -> Exn<T> {
90        let mut new_exn = Exn::new(err);
91        new_exn.frame.children.push(*self.frame);
92        new_exn
93    }
94
95    /// Use the current exception as the head of a chain, adding `err` to its children.
96    #[track_caller]
97    pub fn chain<T: Error + Send + Sync + 'static>(mut self, err: impl Into<Exn<T>>) -> Exn<E> {
98        let err = err.into();
99        self.frame.children.push(*err.frame);
100        self
101    }
102
103    /// Use the current exception the head of a chain, adding `errors` to its children.
104    #[track_caller]
105    pub fn chain_all<T, I>(mut self, errors: I) -> Exn<E>
106    where
107        T: Error + Send + Sync + 'static,
108        I: IntoIterator,
109        I::Item: Into<Exn<T>>,
110    {
111        for err in errors {
112            let err = err.into();
113            self.frame.children.push(*err.frame);
114        }
115        self
116    }
117
118    /// Drain all sources of this error as untyped [`Exn`].
119    ///
120    /// This is useful if one wants to re-organise errors, and the error layout is well known.
121    pub fn drain_children(&mut self) -> impl Iterator<Item = Exn> + '_ {
122        self.frame.children.drain(..).map(Exn::from)
123    }
124
125    /// Erase the type of this instance and turn it into a bare `Exn`.
126    pub fn erased(self) -> Exn {
127        let untyped_frame = {
128            let Frame {
129                error,
130                location,
131                children,
132            } = *self.frame;
133            // Unfortunately, we have to double-box here.
134            // TODO: figure out tricks to make this unnecessary.
135            let error = Untyped(error);
136            Frame {
137                error: Box::new(error),
138                location,
139                children,
140            }
141        };
142        Exn {
143            frame: Box::new(untyped_frame),
144            phantom: Default::default(),
145        }
146    }
147
148    /// Return the current exception.
149    pub fn error(&self) -> &E {
150        self.frame
151            .error
152            .downcast_ref()
153            .expect("the owned frame always matches the compile-time error type")
154    }
155
156    /// Discard all error context and return the underlying error in a Box.
157    ///
158    /// This is useful to retain the allocation, as internally it's also stored in a box,
159    /// when comparing it to [`Self::into_inner()`].
160    pub fn into_box(self) -> Box<E> {
161        match self.frame.error.downcast() {
162            Ok(err) => err,
163            Err(_) => unreachable!("The type in the frame is always the type of this instance"),
164        }
165    }
166
167    /// Discard all error context and return the underlying error.
168    ///
169    /// This may be needed to obtain something that once again implements `Error`.
170    /// Note that this destroys the internal Box and moves the value back onto the stack.
171    pub fn into_inner(self) -> E {
172        *self.into_box()
173    }
174
175    /// Turn ourselves into a top-level [Error] that implements [`std::error::Error`].
176    ///
177    /// [Error]: crate::Error
178    pub fn into_error(self) -> crate::Error {
179        self.into()
180    }
181
182    /// Convert this error tree into a chain of errors, breadth first, which flattens the tree
183    /// but retains all type dynamic type information.
184    ///
185    /// This is useful for inter-op with `anyhow`.
186    pub fn into_chain(self) -> crate::ChainedError {
187        self.into()
188    }
189
190    /// Return the underlying exception frame.
191    pub fn frame(&self) -> &Frame {
192        &self.frame
193    }
194
195    /// Iterate over all frames in breadth-first order. The first frame is this instance,
196    /// followed by all of its children.
197    pub fn iter(&self) -> impl Iterator<Item = &Frame> {
198        self.frame().iter_frames()
199    }
200
201    /// Iterate over all frames and find one that downcasts into error of type `T`.
202    /// Note that the search includes this instance as well.
203    pub fn downcast_any_ref<T: Error + 'static>(&self) -> Option<&T> {
204        self.iter().find_map(|e| e.error.downcast_ref())
205    }
206}
207
208impl<E> Deref for Exn<E>
209where
210    E: Error + Send + Sync + 'static,
211{
212    type Target = E;
213
214    fn deref(&self) -> &Self::Target {
215        self.error()
216    }
217}
218
219impl<E: Error + Send + Sync + 'static> fmt::Debug for Exn<E> {
220    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
221        write_frame_recursive(f, self.frame(), "", ErrorMode::Display, TreeMode::Linearize)
222    }
223}
224
225impl fmt::Debug for Frame {
226    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
227        write_frame_recursive(f, self, "", ErrorMode::Display, TreeMode::Linearize)
228    }
229}
230
231#[derive(Copy, Clone)]
232enum ErrorMode {
233    Display,
234    Debug,
235}
236
237#[derive(Copy, Clone)]
238enum TreeMode {
239    Linearize,
240    Verbatim,
241}
242
243fn write_frame_recursive(
244    f: &mut fmt::Formatter<'_>,
245    frame: &Frame,
246    prefix: &str,
247    err_mode: ErrorMode,
248    tree_mode: TreeMode,
249) -> fmt::Result {
250    match err_mode {
251        ErrorMode::Display => fmt::Display::fmt(frame.error(), f),
252        ErrorMode::Debug => {
253            write!(f, "{:?}", frame.error())
254        }
255    }?;
256    if !f.alternate() {
257        write_location(f, frame.location)?;
258    }
259
260    let children = frame.children();
261    let children_len = children.len();
262
263    for (cidx, child) in children.iter().enumerate() {
264        write!(f, "\n{prefix}|")?;
265        write!(f, "\n{prefix}└─ ")?;
266
267        let child_child_len = child.children().len();
268        let may_linearize_chain = matches!(tree_mode, TreeMode::Linearize) && children_len == 1 && child_child_len == 1;
269        if may_linearize_chain {
270            write_frame_recursive(f, child, prefix, err_mode, tree_mode)?;
271        } else if cidx < children_len - 1 {
272            write_frame_recursive(f, child, &format!("{prefix}|   "), err_mode, tree_mode)?;
273        } else {
274            write_frame_recursive(f, child, &format!("{prefix}    "), err_mode, tree_mode)?;
275        }
276    }
277
278    Ok(())
279}
280
281impl<E: Error + Send + Sync + 'static> fmt::Display for Exn<E> {
282    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
283        fmt::Display::fmt(&self.frame, f)
284    }
285}
286
287impl fmt::Display for Frame {
288    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
289        if f.alternate() {
290            // Avoid printing alternate versions of the debug info, keep it in one line, also print the tree.
291            write_frame_recursive(f, self, "", ErrorMode::Debug, TreeMode::Verbatim)
292        } else {
293            fmt::Display::fmt(self.error(), f)
294        }
295    }
296}
297
298/// A frame in the exception tree.
299pub struct Frame {
300    /// The error that occurred at this frame.
301    error: Box<dyn Error + Send + Sync + 'static>,
302    /// The source code location where this exception frame was created.
303    location: &'static Location<'static>,
304    /// Child exception frames that provide additional context or source errors.
305    children: Vec<Frame>,
306}
307
308impl Frame {
309    /// Return the error as a reference to [`Error`].
310    pub fn error(&self) -> &(dyn Error + Send + Sync + 'static) {
311        &*self.error
312    }
313
314    /// Return the source code location where this exception frame was created.
315    pub fn location(&self) -> &'static Location<'static> {
316        self.location
317    }
318
319    /// Return a slice of the children of the exception.
320    pub fn children(&self) -> &[Frame] {
321        &self.children
322    }
323}
324
325/// Navigation
326impl Frame {
327    /// Find the best possible cause:
328    ///
329    /// * in a linear chain of a single error each, it's the last-most child frame
330    /// * in trees, find the deepest-possible frame that has the most leafs as children
331    ///
332    /// Return `None` if there are no children.
333    pub fn probable_cause(&self) -> Option<&Frame> {
334        fn walk<'a>(frame: &'a Frame, depth: usize) -> (usize, usize, &'a Frame) {
335            if frame.children.is_empty() {
336                return (1, depth, frame);
337            }
338
339            let mut total_leafs = 0;
340            let mut best: Option<(usize, usize, &'a Frame)> = None;
341
342            for child in &frame.children {
343                let (leafs, d, f) = walk(child, depth + 1);
344                total_leafs += leafs;
345
346                match best {
347                    None => best = Some((leafs, d, f)),
348                    Some((bl, bd, _)) => {
349                        if leafs > bl || (leafs == bl && d > bd) {
350                            best = Some((leafs, d, f));
351                        }
352                    }
353                }
354            }
355
356            let self_candidate = (total_leafs, depth, frame);
357            match best {
358                None => self_candidate,
359                Some(best_child) => {
360                    if total_leafs > best_child.0 || (total_leafs == best_child.0 && depth > best_child.1) {
361                        self_candidate
362                    } else {
363                        best_child
364                    }
365                }
366            }
367        }
368
369        // Special case: a simple error just with children would render the same as a chain,
370        //               so pretend the last child is the cause.
371        if self.children().iter().all(|c| c.children().is_empty()) {
372            if let Some(last) = self.children().last() {
373                return Some(last);
374            }
375        }
376
377        let res = walk(self, 0).2;
378        if std::ptr::addr_eq(res, self) {
379            None
380        } else {
381            Some(res)
382        }
383    }
384
385    /// Iterate over all frames in breadth-first order. The first frame is this instance,
386    /// followed by all of its children.
387    pub fn iter_frames(&self) -> impl Iterator<Item = &Frame> + '_ {
388        let mut queue = std::collections::VecDeque::new();
389        queue.push_back(self);
390        BreadthFirstFrames { queue }
391    }
392}
393
394/// Breadth-first iterator over `Frame`s.
395pub struct BreadthFirstFrames<'a> {
396    queue: std::collections::VecDeque<&'a Frame>,
397}
398
399impl<'a> Iterator for BreadthFirstFrames<'a> {
400    type Item = &'a Frame;
401
402    fn next(&mut self) -> Option<Self::Item> {
403        let frame = self.queue.pop_front()?;
404        for child in frame.children() {
405            self.queue.push_back(child);
406        }
407        Some(frame)
408    }
409}
410
411impl<E> From<Exn<E>> for Box<Frame>
412where
413    E: Error + Send + Sync + 'static,
414{
415    fn from(err: Exn<E>) -> Self {
416        err.frame
417    }
418}
419
420#[cfg(feature = "anyhow")]
421impl<E> From<Exn<E>> for anyhow::Error
422where
423    E: Error + Send + Sync + 'static,
424{
425    fn from(err: Exn<E>) -> Self {
426        anyhow::Error::from(err.into_chain())
427    }
428}
429
430impl<E> From<Exn<E>> for Frame
431where
432    E: Error + Send + Sync + 'static,
433{
434    fn from(err: Exn<E>) -> Self {
435        *err.frame
436    }
437}
438
439impl From<Frame> for Exn {
440    fn from(frame: Frame) -> Self {
441        Exn {
442            frame: Box::new(frame),
443            phantom: Default::default(),
444        }
445    }
446}
447
448/// A marker to show that type information is not available,
449/// while storing all extractable information about the erased type.
450/// It's the default type for [Exn].
451pub struct Untyped(Box<dyn Error + Send + Sync + 'static>);
452
453impl fmt::Display for Untyped {
454    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
455        fmt::Display::fmt(&self.0, f)
456    }
457}
458
459impl fmt::Debug for Untyped {
460    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
461        fmt::Debug::fmt(&self.0, f)
462    }
463}
464
465impl Error for Untyped {}
466
467/// An error that merely says that something is wrong.
468pub struct Something;
469
470impl fmt::Display for Something {
471    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
472        f.write_str("Something went wrong")
473    }
474}
475
476impl fmt::Debug for Something {
477    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
478        fmt::Display::fmt(&self, f)
479    }
480}
481
482impl Error for Something {}
483
484/// A way to keep all information of errors returned by `source()` chains.
485struct SourceError {
486    display: String,
487    alt_display: String,
488    debug: String,
489    alt_debug: String,
490}
491
492impl fmt::Debug for SourceError {
493    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
494        let dbg = if f.alternate() { &self.alt_debug } else { &self.debug };
495        f.write_str(dbg)
496    }
497}
498
499impl fmt::Display for SourceError {
500    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
501        let ds = if f.alternate() {
502            &self.alt_display
503        } else {
504            &self.display
505        };
506        f.write_str(ds)
507    }
508}
509
510impl Error for SourceError {}
511
512impl SourceError {
513    fn new(err: &dyn Error) -> Self {
514        SourceError {
515            display: err.to_string(),
516            alt_display: format!("{err:#}"),
517            debug: format!("{err:?}"),
518            alt_debug: format!("{err:#?}"),
519        }
520    }
521}
522
523impl<E> From<Exn<E>> for ChainedError
524where
525    E: std::error::Error + Send + Sync + 'static,
526{
527    fn from(mut err: Exn<E>) -> Self {
528        let stack: VecDeque<_> = err.frame.children.drain(..).collect();
529        let location = err.frame.location;
530        ChainedError {
531            err: err.into_box(),
532            location,
533            source: recurse_source_frames(stack),
534        }
535    }
536}
537
538fn recurse_source_frames(mut stack: VecDeque<Frame>) -> Option<Box<ChainedError>> {
539    let frame = stack.pop_front()?;
540    stack.extend(frame.children);
541    Box::new(ChainedError {
542        err: frame.error,
543        location: frame.location,
544        source: recurse_source_frames(stack),
545    })
546    .into()
547}