suzunari_error/
boxed_stack_error.rs1use alloc::boxed::Box;
2
3use crate::{Location, StackError};
4use core::error::Error;
5use core::fmt::{Debug, Display, Formatter, Result};
6
7pub struct BoxedStackError {
52 inner: Box<dyn StackError + Send + Sync>,
53}
54
55impl BoxedStackError {
56 #[must_use]
58 pub fn new<T: StackError + Send + Sync + 'static>(inner: T) -> Self {
59 Self {
60 inner: Box::new(inner),
61 }
62 }
63
64 #[must_use]
66 pub fn inner(&self) -> &(dyn StackError + Send + Sync) {
67 &*self.inner
68 }
69
70 #[must_use]
72 pub fn into_inner(self) -> Box<dyn StackError + Send + Sync> {
73 self.inner
74 }
75}
76
77impl Display for BoxedStackError {
78 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
79 write!(f, "{}", self.inner)
80 }
81}
82
83impl Debug for BoxedStackError {
84 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
85 write!(f, "{:?}", self.inner)
86 }
87}
88
89impl Error for BoxedStackError {
90 fn source(&self) -> Option<&(dyn Error + 'static)> {
91 self.inner.source()
92 }
93}
94
95impl StackError for BoxedStackError {
96 fn location(&self) -> Location {
97 self.inner.location()
98 }
99 fn type_name(&self) -> &'static str {
100 self.inner.type_name()
101 }
102 fn stack_source(&self) -> Option<&dyn StackError> {
103 self.inner.stack_source()
104 }
105}
106
107impl From<Box<dyn StackError + Send + Sync>> for BoxedStackError {
108 fn from(inner: Box<dyn StackError + Send + Sync>) -> Self {
109 Self { inner }
110 }
111}
112
113impl From<BoxedStackError> for Box<dyn StackError + Send + Sync> {
114 fn from(inner: BoxedStackError) -> Self {
115 inner.into_inner()
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
124 use crate::Location;
125 use alloc::format;
126 use snafu::IntoError;
127 use snafu::prelude::*;
128
129 #[derive(Debug, Snafu)]
130 #[snafu(display("Test error: {}", message))]
131 struct TestError {
132 message: alloc::string::String,
133 #[snafu(implicit)]
134 location: Location,
135 }
136
137 impl StackError for TestError {
138 fn location(&self) -> Location {
139 self.location
140 }
141 fn type_name(&self) -> &'static str {
142 "TestError"
143 }
144 }
145
146 #[test]
147 fn test_basic_error() {
148 let test_error = TestSnafu {
149 message: "Test message",
150 }
151 .build();
152 let error = BoxedStackError::new(test_error);
153
154 assert!(format!("{}", error).contains("Test error"));
155 assert!(format!("{}", error).contains("Test message"));
156 assert!(format!("{:?}", error).contains("Test message"));
158 assert!(error.source().is_none());
159
160 handle_stack_error(error);
161 }
162
163 #[test]
164 fn test_error_location() {
165 let test_error = TestSnafu {
166 message: "Location test",
167 }
168 .build();
169 let original_line = test_error.location().line();
170 let error = BoxedStackError::new(test_error);
171
172 assert_eq!(error.location().file(), file!());
173 assert_eq!(error.location().line(), original_line);
174
175 handle_stack_error(error);
176 }
177
178 #[test]
179 fn test_error_conversion() {
180 let test_error = TestSnafu {
181 message: "Convert test",
182 }
183 .build();
184 let boxed: Box<dyn StackError + Send + Sync> = Box::new(test_error);
185 let generic: BoxedStackError = boxed.into();
186 let back_to_box: Box<dyn StackError + Send + Sync> = generic.into();
187
188 assert!(format!("{:?}", back_to_box).contains("Convert test"));
189 }
190
191 #[derive(Debug, Snafu)]
194 #[snafu(display("Wrapper: {}", message))]
195 struct WrapperTestError {
196 message: alloc::string::String,
197 source: BoxedStackError,
198 #[snafu(implicit)]
199 location: Location,
200 }
201 impl StackError for WrapperTestError {
202 fn location(&self) -> Location {
203 self.location
204 }
205 fn type_name(&self) -> &'static str {
206 "WrapperTestError"
207 }
208 fn stack_source(&self) -> Option<&dyn StackError> {
209 Some(&self.source)
210 }
211 }
212
213 #[test]
214 fn test_source_chain_delegation() {
215 let inner = TestSnafu { message: "root" }.build();
217 let boxed = BoxedStackError::new(inner);
218 let wrapper = WrapperTestSnafu { message: "wrap" }.into_error(boxed);
219 let outer = BoxedStackError::new(wrapper);
220
221 assert!(outer.source().is_some());
223 assert!(outer.stack_source().is_some());
225 }
226
227 #[test]
228 fn test_depth() {
229 let leaf = BoxedStackError::new(TestSnafu { message: "leaf" }.build());
231 assert_eq!(leaf.depth(), 0);
232
233 let inner = BoxedStackError::new(TestSnafu { message: "inner" }.build());
235 let wrapper = WrapperTestSnafu { message: "outer" }.into_error(inner);
236 let outer = BoxedStackError::new(wrapper);
237 assert_eq!(outer.depth(), 1);
238 }
239
240 fn handle_stack_error<T: StackError>(_: T) {}
241
242 #[test]
243 fn test_inner_ref() {
244 let test_error = TestSnafu {
245 message: "inner ref test",
246 }
247 .build();
248 let original_line = test_error.location().line();
249 let error = BoxedStackError::new(test_error);
250
251 let inner = error.inner();
252 assert_eq!(inner.location().line(), original_line);
253 assert_eq!(inner.type_name(), "TestError");
254 }
255
256 #[test]
257 fn test_into_inner_round_trip() {
258 let test_error = TestSnafu {
259 message: "round trip",
260 }
261 .build();
262 let original_line = test_error.location().line();
263
264 let boxed = BoxedStackError::new(test_error);
266 let inner: Box<dyn StackError + Send + Sync> = boxed.into_inner();
267
268 assert_eq!(inner.location().line(), original_line);
269 assert_eq!(inner.type_name(), "TestError");
270 assert!(format!("{inner}").contains("round trip"));
271
272 let boxed_again: BoxedStackError = inner.into();
274 assert_eq!(boxed_again.location().line(), original_line);
275 }
276}