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}