1use crate::{LimitExceeded, UnsupportedOperation};
12
13pub trait CodecErrorExt {
37 fn unsupported_operation(&self) -> Option<&UnsupportedOperation>;
39
40 fn limit_exceeded(&self) -> Option<&LimitExceeded>;
42
43 fn find_cause<T: core::error::Error + 'static>(&self) -> Option<&T>;
45}
46
47impl<E: core::error::Error + 'static> CodecErrorExt for E {
48 fn unsupported_operation(&self) -> Option<&UnsupportedOperation> {
49 find_cause::<UnsupportedOperation>(self)
50 }
51
52 fn limit_exceeded(&self) -> Option<&LimitExceeded> {
53 find_cause::<LimitExceeded>(self)
54 }
55
56 fn find_cause<T: core::error::Error + 'static>(&self) -> Option<&T> {
57 find_cause::<T>(self)
58 }
59}
60
61impl CodecErrorExt for dyn core::error::Error + Send + Sync + 'static {
63 fn unsupported_operation(&self) -> Option<&UnsupportedOperation> {
64 find_cause::<UnsupportedOperation>(self)
65 }
66
67 fn limit_exceeded(&self) -> Option<&LimitExceeded> {
68 find_cause::<LimitExceeded>(self)
69 }
70
71 fn find_cause<T: core::error::Error + 'static>(&self) -> Option<&T> {
72 find_cause::<T>(self)
73 }
74}
75
76impl CodecErrorExt for dyn core::error::Error + Send + 'static {
77 fn unsupported_operation(&self) -> Option<&UnsupportedOperation> {
78 find_cause::<UnsupportedOperation>(self)
79 }
80
81 fn limit_exceeded(&self) -> Option<&LimitExceeded> {
82 find_cause::<LimitExceeded>(self)
83 }
84
85 fn find_cause<T: core::error::Error + 'static>(&self) -> Option<&T> {
86 find_cause::<T>(self)
87 }
88}
89
90impl CodecErrorExt for dyn core::error::Error + 'static {
91 fn unsupported_operation(&self) -> Option<&UnsupportedOperation> {
92 find_cause::<UnsupportedOperation>(self)
93 }
94
95 fn limit_exceeded(&self) -> Option<&LimitExceeded> {
96 find_cause::<LimitExceeded>(self)
97 }
98
99 fn find_cause<T: core::error::Error + 'static>(&self) -> Option<&T> {
100 find_cause::<T>(self)
101 }
102}
103
104pub fn find_cause<'a, T: core::error::Error + 'static>(
113 mut err: &'a (dyn core::error::Error + 'static),
114) -> Option<&'a T> {
115 loop {
116 if let Some(t) = err.downcast_ref::<T>() {
117 return Some(t);
118 }
119 err = err.source()?;
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126 use alloc::boxed::Box;
127 use alloc::string::String;
128 use core::fmt;
129
130 #[derive(Debug)]
132 enum TestCodecError {
133 Limit(LimitExceeded),
134 Unsupported(UnsupportedOperation),
135 Other(String),
136 }
137
138 impl fmt::Display for TestCodecError {
139 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140 match self {
141 Self::Limit(e) => write!(f, "limit: {e}"),
142 Self::Unsupported(e) => write!(f, "unsupported: {e}"),
143 Self::Other(s) => write!(f, "other: {s}"),
144 }
145 }
146 }
147
148 impl core::error::Error for TestCodecError {
149 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
150 match self {
151 Self::Limit(e) => Some(e),
152 Self::Unsupported(e) => Some(e),
153 Self::Other(_) => None,
154 }
155 }
156 }
157
158 #[test]
159 fn ext_limit_exceeded_direct() {
160 let err = LimitExceeded::Width {
161 actual: 5000,
162 max: 4096,
163 };
164 assert_eq!(err.limit_exceeded(), Some(&err));
165 }
166
167 #[test]
168 fn ext_limit_exceeded_through_source_chain() {
169 let inner = LimitExceeded::Pixels {
170 actual: 100_000_000,
171 max: 50_000_000,
172 };
173 let err = TestCodecError::Limit(inner.clone());
174 assert_eq!(err.limit_exceeded(), Some(&inner));
175 }
176
177 #[test]
178 fn ext_unsupported_through_source_chain() {
179 let err = TestCodecError::Unsupported(UnsupportedOperation::AnimationEncode);
180 assert_eq!(
181 err.unsupported_operation(),
182 Some(&UnsupportedOperation::AnimationEncode)
183 );
184 }
185
186 #[test]
187 fn ext_returns_none_when_absent() {
188 let err = TestCodecError::Other("something else".into());
189 assert!(err.limit_exceeded().is_none());
190 assert!(err.unsupported_operation().is_none());
191 }
192
193 #[test]
194 fn ext_through_boxed_error() {
195 let inner = LimitExceeded::Memory {
196 actual: 1_000_000_000,
197 max: 512_000_000,
198 };
199 let err = TestCodecError::Limit(inner.clone());
200 let boxed: Box<dyn core::error::Error + Send + Sync> = Box::new(err);
201 assert_eq!(boxed.limit_exceeded(), Some(&inner));
202 }
203
204 #[test]
205 fn ext_find_cause_generic() {
206 let err = TestCodecError::Unsupported(UnsupportedOperation::DecodeInto);
207 let found: Option<&UnsupportedOperation> = err.find_cause();
208 assert_eq!(found, Some(&UnsupportedOperation::DecodeInto));
209 }
210
211 #[test]
213 fn find_cause_free_fn() {
214 let err = LimitExceeded::Width {
215 actual: 5000,
216 max: 4096,
217 };
218 let found = find_cause::<LimitExceeded>(&err);
219 assert_eq!(found, Some(&err));
220 }
221}