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::fmt;
16use std::marker::PhantomData;
17use std::panic::Location;
18
19use crate::Error;
20
21/// An exception type that can hold an error tree and additional context.
22pub struct Exn<E: Error> {
23    // trade one more indirection for less stack size
24    frame: Box<Frame>,
25    phantom: PhantomData<E>,
26}
27
28impl<E: Error> From<E> for Exn<E> {
29    #[track_caller]
30    fn from(error: E) -> Self {
31        Exn::new(error)
32    }
33}
34
35impl<E: Error> Exn<E> {
36    /// Create a new exception with the given error.
37    ///
38    /// This will automatically walk the [source chain of the error] and add them as children
39    /// frames.
40    ///
41    /// See also [`Error::raise`] for a fluent way to convert an error into an `Exn` instance.
42    ///
43    /// [source chain of the error]: std::error::Error::source
44    #[track_caller]
45    pub fn new(error: E) -> Self {
46        struct SourceError(String);
47
48        impl fmt::Debug for SourceError {
49            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50                fmt::Debug::fmt(&self.0, f)
51            }
52        }
53
54        impl fmt::Display for SourceError {
55            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56                fmt::Display::fmt(&self.0, f)
57            }
58        }
59
60        impl std::error::Error for SourceError {}
61
62        fn walk(error: &dyn std::error::Error, location: &'static Location<'static>) -> Vec<Frame> {
63            if let Some(source) = error.source() {
64                let children = vec![Frame {
65                    error: Box::new(SourceError(source.to_string())),
66                    location,
67                    children: walk(source, location),
68                }];
69                children
70            } else {
71                vec![]
72            }
73        }
74
75        let location = Location::caller();
76        let children = walk(&error, location);
77        let frame = Frame {
78            error: Box::new(error),
79            location,
80            children,
81        };
82
83        Self {
84            frame: Box::new(frame),
85            phantom: PhantomData,
86        }
87    }
88
89    /// Create a new exception with the given error and children.
90    #[track_caller]
91    pub fn from_iter<T, I>(children: I, err: E) -> Self
92    where
93        T: Error,
94        I: IntoIterator,
95        I::Item: Into<Exn<T>>,
96    {
97        let mut new_exn = Exn::new(err);
98        for exn in children {
99            let exn = exn.into();
100            new_exn.frame.children.push(*exn.frame);
101        }
102        new_exn
103    }
104
105    /// Raise a new exception; this will make the current exception a child of the new one.
106    #[track_caller]
107    pub fn raise<T: Error>(self, err: T) -> Exn<T> {
108        let mut new_exn = Exn::new(err);
109        new_exn.frame.children.push(*self.frame);
110        new_exn
111    }
112
113    /// Return the current exception.
114    pub fn as_error(&self) -> &E {
115        self.frame
116            .as_any()
117            .downcast_ref()
118            .expect("error type must match")
119    }
120
121    /// Return the underlying exception frame.
122    pub fn as_frame(&self) -> &Frame {
123        &self.frame
124    }
125}
126
127/// A frame in the exception tree.
128pub struct Frame {
129    /// The error that occurred at this frame.
130    error: Box<dyn Error>,
131    /// The source code location where this exception frame was created.
132    location: &'static Location<'static>,
133    /// Child exception frames that provide additional context or source errors.
134    children: Vec<Frame>,
135}
136
137impl Frame {
138    /// Return the error as a reference to [`std::any::Any`].
139    pub fn as_any(&self) -> &dyn std::any::Any {
140        &*self.error
141    }
142
143    /// Return the error as a reference to [`std::error::Error`].
144    pub fn as_error(&self) -> &dyn std::error::Error {
145        &*self.error
146    }
147
148    /// Return the source code location where this exception frame was created.
149    pub fn location(&self) -> &'static Location<'static> {
150        self.location
151    }
152
153    /// Return a slice of the children of the exception.
154    pub fn children(&self) -> &[Frame] {
155        &self.children
156    }
157}