Skip to main content

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