Skip to main content

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}