suzunari_error/display_error.rs
1use core::error::Error;
2use core::fmt::{Debug, Display, Formatter};
3use core::hash::{Hash, Hasher};
4
5/// Wrapper that converts a `Debug + Display` type (without `Error` impl) into
6/// a `core::error::Error`.
7///
8/// Useful for wrapping third-party error types that don't implement `Error`,
9/// making them usable as snafu `source` fields.
10///
11/// # Usage
12///
13/// ## Pattern A: `#[suzu(from)]` — auto-wraps type and generates `source(from(...))` (recommended)
14///
15/// ```
16/// use suzunari_error::*;
17///
18/// // A third-party type that implements Debug + Display but not Error.
19/// #[derive(Debug)]
20/// struct LibError(String);
21/// impl std::fmt::Display for LibError {
22/// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23/// f.write_str(&self.0)
24/// }
25/// }
26///
27/// #[suzunari_error]
28/// #[suzu(display("operation failed"))]
29/// struct AppError {
30/// #[suzu(from)]
31/// source: LibError, // becomes DisplayError<LibError>
32/// }
33/// ```
34///
35/// **Note:** `#[suzu(from)]` requires the field type to be a concrete type.
36/// Fields whose type contains a generic type parameter of the enclosing struct
37/// or enum are rejected at compile time.
38///
39/// ## Pattern B: Manual `source(from(...))` — explicit control
40///
41/// Uses `#[snafu(source(from(...)))]` directly with `DisplayError::new`.
42/// Note: `DisplayError::new` always returns `None` from `source()`, so this
43/// pattern does not preserve the source chain **even if `LibError` implements
44/// `Error`**. Use Pattern A (`#[suzu(from)]`) for automatic source chain
45/// preservation.
46///
47/// ```
48/// use suzunari_error::*;
49///
50/// // A third-party type that implements Error.
51/// #[derive(Debug)]
52/// struct LibError(String);
53/// impl std::fmt::Display for LibError {
54/// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55/// f.write_str(&self.0)
56/// }
57/// }
58/// // LibError implements Error, but DisplayError::new does not preserve its source chain.
59/// impl std::error::Error for LibError {}
60///
61/// #[suzunari_error]
62/// #[suzu(display("operation failed"))]
63/// struct AppError {
64/// #[snafu(source(from(LibError, DisplayError::new)))]
65/// source: DisplayError<LibError>,
66/// }
67/// ```
68///
69/// ## Source chain preservation
70///
71/// When constructed via `#[suzu(from)]`, `DisplayError` automatically detects
72/// whether the wrapped type implements `Error` at compile time (using autoref
73/// specialization). If it does, `source()` delegates to the inner type's
74/// `source()`. If not, `source()` returns `None`.
75///
76/// When constructed manually via [`DisplayError::new()`], `source()` always
77/// returns `None`. Use `#[suzu(from)]` for automatic source chain preservation.
78///
79/// ## Pattern C: `map_err` — direct wrapping without snafu context
80///
81/// ```
82/// use suzunari_error::DisplayError;
83///
84/// #[derive(Debug)]
85/// struct LibError(String);
86/// impl std::fmt::Display for LibError {
87/// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
88/// f.write_str(&self.0)
89/// }
90/// }
91///
92/// fn fallible() -> Result<(), LibError> {
93/// Err(LibError("boom".into()))
94/// }
95///
96/// // Wrap non-Error type into Error for use with ? or error combinators
97/// fn do_something() -> Result<(), Box<dyn std::error::Error>> {
98/// fallible().map_err(DisplayError::new)?;
99/// Ok(())
100/// }
101/// ```
102pub struct DisplayError<E> {
103 inner: E,
104 get_source: fn(&E) -> Option<&(dyn core::error::Error + 'static)>,
105}
106
107impl<E: Debug + Display> DisplayError<E> {
108 /// Wraps `error` in a `DisplayError`, making it usable as a `source` field.
109 ///
110 /// `source()` will always return `None`. For automatic source chain
111 /// preservation, use `#[suzu(from)]` instead.
112 #[must_use]
113 pub fn new(error: E) -> Self {
114 Self {
115 inner: error,
116 get_source: |_| None,
117 }
118 }
119
120 /// Internal constructor with an explicit `get_source` resolver.
121 /// Use [`DisplayError::new`] in application code.
122 pub(crate) fn with_get_source(
123 error: E,
124 get_source: fn(&E) -> Option<&(dyn core::error::Error + 'static)>,
125 ) -> Self {
126 Self {
127 inner: error,
128 get_source,
129 }
130 }
131}
132
133impl<E> DisplayError<E> {
134 /// Returns a reference to the wrapped value.
135 #[must_use]
136 pub fn inner(&self) -> &E {
137 &self.inner
138 }
139
140 /// Unwraps and returns the inner value.
141 #[must_use]
142 pub fn into_inner(self) -> E {
143 self.inner
144 }
145}
146
147/// Clones the inner `E` value and copies the `get_source` function pointer,
148/// preserving source chain delegation behavior in the clone.
149impl<E: Clone> Clone for DisplayError<E> {
150 fn clone(&self) -> Self {
151 Self {
152 inner: self.inner.clone(),
153 get_source: self.get_source,
154 }
155 }
156}
157
158impl<E: Display> Display for DisplayError<E> {
159 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
160 Display::fmt(&self.inner, f)
161 }
162}
163
164impl<E: Debug> Debug for DisplayError<E> {
165 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
166 Debug::fmt(&self.inner, f)
167 }
168}
169
170/// Compares only the inner `E` value. Two `DisplayError<E>` instances are
171/// considered equal if their inner values are equal, regardless of how they
172/// were constructed. The `get_source` function pointer is an implementation
173/// detail for source chain delegation and is not part of the value identity.
174impl<E: PartialEq> PartialEq for DisplayError<E> {
175 fn eq(&self, other: &Self) -> bool {
176 self.inner == other.inner
177 }
178}
179
180impl<E: Eq> Eq for DisplayError<E> {}
181
182impl<E: Hash> Hash for DisplayError<E> {
183 fn hash<H: Hasher>(&self, state: &mut H) {
184 self.inner.hash(state);
185 }
186}
187
188/// Delegates `source()` to the stored `get_source` function pointer.
189///
190/// When constructed via `#[suzu(from)]` (macro-generated code), this
191/// automatically delegates to the inner type's `source()` if it implements
192/// `Error`, or returns `None` otherwise. When constructed via `new()`,
193/// always returns `None`.
194impl<E: Debug + Display> Error for DisplayError<E> {
195 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
196 (self.get_source)(&self.inner)
197 }
198}
199
200// No From impl — intentionally omitted to prevent implicit .into() conversions.
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205
206 struct FakeLibError {
207 message: &'static str,
208 }
209 impl Display for FakeLibError {
210 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
211 write!(f, "{}", self.message)
212 }
213 }
214 impl Debug for FakeLibError {
215 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
216 write!(f, "FakeLibError({})", self.message)
217 }
218 }
219
220 #[test]
221 fn test_new_and_into_inner() {
222 let original = FakeLibError { message: "oops" };
223 let wrapped = DisplayError::new(original);
224 let inner = wrapped.into_inner();
225 assert_eq!(inner.message, "oops");
226 }
227
228 #[test]
229 fn test_inner_ref() {
230 let wrapped = DisplayError::new(FakeLibError {
231 message: "ref access",
232 });
233 assert_eq!(wrapped.inner().message, "ref access");
234 }
235
236 #[test]
237 fn test_clone() {
238 #[derive(Clone)]
239 struct ClonableError {
240 message: &'static str,
241 }
242 impl Display for ClonableError {
243 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
244 write!(f, "{}", self.message)
245 }
246 }
247 impl Debug for ClonableError {
248 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
249 write!(f, "ClonableError({})", self.message)
250 }
251 }
252
253 let original = DisplayError::new(ClonableError { message: "test" });
254 let cloned = original.clone();
255 assert_eq!(cloned.inner().message, "test");
256 }
257
258 #[test]
259 fn test_error_source_is_none() {
260 let wrapped = DisplayError::new(FakeLibError {
261 message: "no source",
262 });
263 let err: &dyn Error = &wrapped;
264 assert!(err.source().is_none());
265 }
266
267 #[test]
268 fn test_with_get_source_none_for_non_error() {
269 let wrapped = DisplayError::with_get_source(
270 FakeLibError {
271 message: "no error impl",
272 },
273 |_| None,
274 );
275 let err: &dyn Error = &wrapped;
276 assert!(err.source().is_none());
277 }
278
279 #[test]
280 fn test_partial_eq() {
281 let a = DisplayError::new(42);
282 let b = DisplayError::new(42);
283 let c = DisplayError::new(99);
284 assert_eq!(a, b);
285 assert_ne!(a, c);
286 }
287
288 #[test]
289 fn test_hash() {
290 // Simple deterministic hasher that does not require std.
291 struct SimpleHasher(u64);
292 impl Hasher for SimpleHasher {
293 fn finish(&self) -> u64 {
294 self.0
295 }
296 fn write(&mut self, bytes: &[u8]) {
297 for &b in bytes {
298 self.0 = self.0.wrapping_mul(31).wrapping_add(b as u64);
299 }
300 }
301 }
302 fn hash_one<T: Hash>(val: &T) -> u64 {
303 let mut h = SimpleHasher(0);
304 val.hash(&mut h);
305 h.finish()
306 }
307 let a = DisplayError::new(42);
308 let b = DisplayError::new(42);
309 assert_eq!(hash_one(&a), hash_one(&b));
310 }
311
312 #[cfg(feature = "alloc")]
313 mod alloc_tests {
314 use super::*;
315
316 #[test]
317 fn test_with_get_source_delegates_to_inner() {
318 #[derive(Debug)]
319 struct InnerError;
320 impl Display for InnerError {
321 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
322 f.write_str("inner")
323 }
324 }
325 impl Error for InnerError {}
326
327 #[derive(Debug)]
328 struct OuterError(InnerError);
329 impl Display for OuterError {
330 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
331 f.write_str("outer")
332 }
333 }
334 impl Error for OuterError {
335 fn source(&self) -> Option<&(dyn Error + 'static)> {
336 Some(&self.0)
337 }
338 }
339
340 let wrapped = DisplayError::with_get_source(OuterError(InnerError), |e| e.source());
341 let err: &dyn Error = &wrapped;
342 let source = err.source().expect("source should delegate");
343 assert_eq!(alloc::format!("{source}"), "inner");
344 }
345
346 #[test]
347 fn test_clone_preserves_source_delegation() {
348 #[derive(Clone, Debug)]
349 struct InnerError;
350 impl Display for InnerError {
351 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
352 f.write_str("inner")
353 }
354 }
355 impl Error for InnerError {}
356
357 #[derive(Clone, Debug)]
358 struct OuterError(InnerError);
359 impl Display for OuterError {
360 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
361 f.write_str("outer")
362 }
363 }
364 impl Error for OuterError {
365 fn source(&self) -> Option<&(dyn Error + 'static)> {
366 Some(&self.0)
367 }
368 }
369
370 let original = DisplayError::with_get_source(OuterError(InnerError), |e| e.source());
371 let cloned = original.clone();
372 let err: &dyn Error = &cloned;
373 let source = err
374 .source()
375 .expect("clone should preserve source delegation");
376 assert_eq!(alloc::format!("{source}"), "inner");
377 }
378
379 #[test]
380 fn test_display_delegates() {
381 let wrapped = DisplayError::new(FakeLibError {
382 message: "display me",
383 });
384 let s = alloc::format!("{wrapped}");
385 assert_eq!(s, "display me");
386 }
387
388 #[test]
389 fn test_debug_delegates() {
390 let wrapped = DisplayError::new(FakeLibError {
391 message: "debug me",
392 });
393 let s = alloc::format!("{wrapped:?}");
394 assert_eq!(s, "FakeLibError(debug me)");
395 }
396 }
397}