1use alloc::boxed::Box;
13use thiserror::Error;
14
15pub type BoxedError = Box<dyn core::error::Error + Send + Sync + 'static>;
17
18#[derive(Debug, Error)]
25#[error("{0}")]
26pub struct LoxError(#[source] BoxedError);
27
28impl LoxError {
29 pub fn new(error: impl Into<BoxedError>) -> Self {
31 LoxError(error.into())
32 }
33
34 pub fn into_inner(self) -> BoxedError {
36 self.0
37 }
38
39 pub fn downcast_ref<E: core::error::Error + 'static>(&self) -> Option<&E> {
41 find_source(self.0.as_ref())
42 }
43}
44
45impl From<&str> for LoxError {
46 fn from(s: &str) -> Self {
47 LoxError(s.into())
48 }
49}
50
51impl From<BoxedError> for LoxError {
52 fn from(e: BoxedError) -> Self {
53 LoxError(e)
54 }
55}
56
57impl From<core::convert::Infallible> for LoxError {
58 fn from(x: core::convert::Infallible) -> Self {
59 match x {}
60 }
61}
62
63pub fn find_source<'a, E: core::error::Error + 'static>(
70 err: &'a (dyn core::error::Error + 'static),
71) -> Option<&'a E> {
72 let mut current: Option<&(dyn core::error::Error + 'static)> = Some(err);
73 while let Some(e) = current {
74 if let Some(found) = e.downcast_ref::<E>() {
75 return Some(found);
76 }
77 current = e.source();
78 }
79 None
80}
81
82#[cfg(test)]
83mod tests {
84 use alloc::string::ToString;
85
86 use super::*;
87
88 #[derive(Debug, Error)]
89 #[error("leaf failure")]
90 struct LeafError;
91
92 #[derive(Debug, Error)]
93 #[error(transparent)]
94 struct Transparent(#[from] LoxError);
95
96 #[test]
97 fn test_lox_error_display_is_inner() {
98 let err = LoxError::new(LeafError);
99 assert_eq!(err.to_string(), "leaf failure");
100
101 let from_str: LoxError = "boom".into();
102 assert_eq!(from_str.to_string(), "boom");
103
104 let boxed: BoxedError = "kaboom".into();
105 let from_boxed: LoxError = boxed.into();
106 assert_eq!(from_boxed.to_string(), "kaboom");
107 }
108
109 #[test]
110 fn test_into_inner_downcasts_to_original() {
111 let err = LoxError::new(LeafError);
112 let inner = err.into_inner();
113 assert!(inner.downcast_ref::<LeafError>().is_some());
114 }
115
116 #[test]
117 fn test_find_source_through_transparent_wrappers() {
118 let err = Transparent(LoxError::new(LeafError));
121 let found = find_source::<LeafError>(&err).expect("leaf must be reachable");
122 assert_eq!(found.to_string(), "leaf failure");
123 assert!(find_source::<core::fmt::Error>(&err).is_none());
124 }
125
126 #[test]
127 fn test_downcast_ref_finds_wrapped_error() {
128 let err = LoxError::new(LeafError);
129 assert!(err.downcast_ref::<LeafError>().is_some());
130 assert!(err.downcast_ref::<core::fmt::Error>().is_none());
131 }
132}