Skip to main content

ntex_error/
message.rs

1use std::{error::Error as StdError, fmt, fmt::Write, rc::Rc};
2
3use ntex_bytes::ByteString;
4
5use crate::{ErrorDiagnostic, ResultKind, ResultType};
6
7struct Wrt<'a> {
8    written: usize,
9    fmt: &'a mut dyn fmt::Write,
10}
11
12impl<'a> Wrt<'a> {
13    fn new(fmt: &'a mut dyn fmt::Write) -> Self {
14        Wrt { fmt, written: 0 }
15    }
16
17    fn wrote(&mut self) -> bool {
18        let res = self.written != 0;
19        self.written = 0;
20        res
21    }
22}
23
24impl fmt::Write for Wrt<'_> {
25    fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> {
26        self.written += s.len();
27        self.fmt.write_str(s)
28    }
29
30    fn write_char(&mut self, c: char) -> Result<(), fmt::Error> {
31        self.written += 1;
32        self.fmt.write_char(c)
33    }
34}
35
36pub fn fmt_err_string(e: &dyn StdError) -> String {
37    let mut buf = String::new();
38    _ = fmt_err(&mut buf, e);
39    buf
40}
41
42pub fn fmt_err(f: &mut dyn fmt::Write, e: &dyn StdError) -> fmt::Result {
43    let mut wrt = Wrt::new(f);
44    let mut current = Some(e);
45    while let Some(std_err) = current {
46        write!(&mut wrt, "{std_err}")?;
47        if wrt.wrote() {
48            writeln!(wrt.fmt)?;
49        }
50        current = std_err.source();
51    }
52    Ok(())
53}
54
55/// Formats a full diagnostic view of an error for logging and tracing.
56pub fn fmt_diag_string<K: ResultKind>(e: &dyn ErrorDiagnostic<Kind = K>) -> String {
57    let mut buf = String::new();
58    _ = fmt_diag(&mut buf, e);
59    buf
60}
61
62/// Formats a full diagnostic view of an error for logging and tracing.
63///
64/// For `ServiceError` types, this includes debug representations of all nested errors,
65/// and a backtrace when available.
66pub fn fmt_diag<K>(f: &mut dyn fmt::Write, e: &dyn ErrorDiagnostic<Kind = K>) -> fmt::Result
67where
68    K: ResultKind,
69{
70    let k = e.kind();
71    let tp = k.tp();
72
73    writeln!(f, "err: {e}")?;
74    writeln!(f, "type: {}", tp.as_str())?;
75    writeln!(f, "signature: {}", k.signature())?;
76
77    if let Some(svc) = e.service() {
78        writeln!(f, "service: {svc}")?;
79    }
80    writeln!(f)?;
81
82    let mut wrt = Wrt::new(f);
83    write!(&mut wrt, "{e:?}")?;
84    if wrt.wrote() {
85        writeln!(wrt.fmt)?;
86    }
87
88    let mut current = e.source();
89    while let Some(err) = current {
90        write!(&mut wrt, "{err:?}")?;
91        if wrt.wrote() {
92            writeln!(wrt.fmt)?;
93        }
94        current = err.source();
95    }
96
97    if tp == ResultType::ServiceError
98        && let Some(bt) = e.backtrace()
99        && let Some(repr) = bt.repr()
100    {
101        writeln!(wrt.fmt, "{repr}")?;
102    }
103
104    Ok(())
105}
106
107#[derive(Clone, PartialEq, Eq, thiserror::Error)]
108pub struct ErrorMessage(ByteString);
109
110#[derive(Clone)]
111pub struct ErrorMessageChained {
112    msg: ByteString,
113    source: Option<Rc<dyn StdError>>,
114}
115
116impl ErrorMessageChained {
117    pub fn new<M, E>(ctx: M, source: E) -> Self
118    where
119        M: Into<ErrorMessage>,
120        E: StdError + 'static,
121    {
122        ErrorMessageChained {
123            msg: ctx.into().into_string(),
124            source: Some(Rc::new(source)),
125        }
126    }
127
128    /// Construct `ErrorMessageChained` from `ByteString`
129    pub const fn from_bstr(msg: ByteString) -> Self {
130        Self { msg, source: None }
131    }
132
133    pub fn msg(&self) -> &ByteString {
134        &self.msg
135    }
136}
137
138impl ErrorMessage {
139    /// Construct a new empty `ErrorMessage`
140    pub const fn empty() -> Self {
141        Self(ByteString::from_static(""))
142    }
143
144    /// Construct `ErrorMessage` from `ByteString`
145    pub const fn from_bstr(msg: ByteString) -> ErrorMessage {
146        ErrorMessage(msg)
147    }
148
149    /// Construct `ErrorMessage` from static string
150    pub const fn from_static(msg: &'static str) -> Self {
151        ErrorMessage(ByteString::from_static(msg))
152    }
153
154    pub fn is_empty(&self) -> bool {
155        self.0.is_empty()
156    }
157
158    pub fn as_str(&self) -> &str {
159        &self.0
160    }
161
162    pub fn as_bstr(&self) -> &ByteString {
163        &self.0
164    }
165
166    pub fn into_string(self) -> ByteString {
167        self.0
168    }
169
170    pub fn with_source<E: StdError + 'static>(self, source: E) -> ErrorMessageChained {
171        ErrorMessageChained::new(self, source)
172    }
173}
174
175impl From<String> for ErrorMessage {
176    fn from(value: String) -> Self {
177        Self(ByteString::from(value))
178    }
179}
180
181impl From<ByteString> for ErrorMessage {
182    fn from(value: ByteString) -> Self {
183        Self(value)
184    }
185}
186
187impl From<&'static str> for ErrorMessage {
188    fn from(value: &'static str) -> Self {
189        Self(ByteString::from_static(value))
190    }
191}
192
193impl fmt::Debug for ErrorMessage {
194    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
195        fmt::Display::fmt(&self.0, f)
196    }
197}
198
199impl fmt::Display for ErrorMessage {
200    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
201        fmt::Display::fmt(&self.0, f)
202    }
203}
204
205impl From<ErrorMessage> for ByteString {
206    fn from(msg: ErrorMessage) -> Self {
207        msg.0
208    }
209}
210
211impl<'a> From<&'a ErrorMessage> for ByteString {
212    fn from(msg: &'a ErrorMessage) -> Self {
213        msg.0.clone()
214    }
215}
216
217impl<M: Into<ErrorMessage>> From<M> for ErrorMessageChained {
218    fn from(value: M) -> Self {
219        ErrorMessageChained {
220            msg: value.into().0,
221            source: None,
222        }
223    }
224}
225
226impl StdError for ErrorMessageChained {
227    fn source(&self) -> Option<&(dyn StdError + 'static)> {
228        self.source.as_ref().map(AsRef::as_ref)
229    }
230}
231
232impl fmt::Debug for ErrorMessageChained {
233    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
234        fmt::Display::fmt(&self, f)
235    }
236}
237
238impl fmt::Display for ErrorMessageChained {
239    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
240        if self.msg.is_empty() {
241            Ok(())
242        } else {
243            fmt::Display::fmt(&self.msg, f)
244        }
245    }
246}
247
248#[cfg(test)]
249#[allow(dead_code)]
250mod tests {
251    use std::io;
252
253    use super::*;
254
255    #[test]
256    fn error_message() {
257        let msg = ErrorMessage::empty();
258        assert!(msg.is_empty());
259        assert_eq!(msg.as_str(), "");
260        assert_eq!(msg.as_bstr(), ByteString::new());
261        assert_eq!(ByteString::new(), msg.as_bstr());
262        assert_eq!(ByteString::new(), msg.into_string());
263
264        let msg = ErrorMessage::from("test");
265        assert!(!msg.is_empty());
266        assert_eq!(format!("{msg}"), "test");
267        assert_eq!(format!("{msg:?}"), "test");
268        assert_eq!(msg.as_str(), "test");
269        assert_eq!(msg.as_bstr(), ByteString::from("test"));
270
271        let msg = ErrorMessage::from("test".to_string());
272        assert!(!msg.is_empty());
273        assert_eq!(msg.as_str(), "test");
274        assert_eq!(msg.as_bstr(), ByteString::from("test"));
275
276        let msg = ErrorMessage::from_bstr(ByteString::from("test"));
277        assert!(!msg.is_empty());
278        assert_eq!(msg.as_str(), "test");
279        assert_eq!(msg.as_bstr(), ByteString::from("test"));
280
281        let msg = ErrorMessage::from(ByteString::from("test"));
282        assert!(!msg.is_empty());
283        assert_eq!(msg.as_str(), "test");
284        assert_eq!(msg.as_bstr(), ByteString::from("test"));
285
286        let msg = ErrorMessage::from_static("test");
287        assert!(!msg.is_empty());
288        assert_eq!(msg.as_str(), "test");
289        assert_eq!(msg.as_bstr(), ByteString::from("test"));
290
291        assert_eq!(ByteString::from(&msg), "test");
292        assert_eq!(ByteString::from(msg), "test");
293    }
294
295    #[test]
296    fn error_message_chained() {
297        let chained = ErrorMessageChained::from(ByteString::from("test"));
298        assert_eq!(chained.msg(), "test");
299        assert!(chained.source().is_none());
300
301        let chained = ErrorMessageChained::from_bstr(ByteString::from("test"));
302        assert_eq!(chained.msg(), "test");
303        assert!(chained.source().is_none());
304        assert_eq!(format!("{chained}"), "test");
305        assert_eq!(format!("{chained:?}"), "test");
306
307        let msg = ErrorMessage::from(ByteString::from("test"));
308        let chained = msg.with_source(io::Error::other("io-test"));
309        assert_eq!(chained.msg(), "test");
310        assert!(chained.source().is_some());
311
312        let err = ErrorMessageChained::new("test", io::Error::other("io-test"));
313        let msg = fmt_err_string(&err);
314        assert_eq!(msg, "test\nio-test\n");
315
316        let chained = ErrorMessageChained::from(ByteString::new());
317        assert_eq!(format!("{chained}"), "");
318    }
319
320    #[derive(thiserror::Error, derive_more::Debug)]
321    enum TestError {
322        #[error("Disconnect")]
323        #[debug("")]
324        Disconnect(#[source] io::Error),
325        #[error("InternalServiceError")]
326        #[debug("InternalServiceError {_0}")]
327        Service(&'static str),
328    }
329
330    impl Clone for TestError {
331        fn clone(&self) -> Self {
332            panic!()
333        }
334    }
335
336    impl ErrorDiagnostic for TestError {
337        type Kind = ResultType;
338
339        fn kind(&self) -> Self::Kind {
340            match self {
341                TestError::Service(_) => ResultType::ServiceError,
342                TestError::Disconnect(_) => ResultType::ClientError,
343            }
344        }
345    }
346
347    #[test]
348    fn fmt_diag() {
349        let err = TestError::Service("409 Error");
350
351        let msg = fmt_err_string(&err);
352        assert_eq!(msg, "InternalServiceError\n");
353
354        let err =
355            crate::Error::from(TestError::Disconnect(io::Error::other("Test io error")));
356        if let Some(bt) = err.backtrace() {
357            bt.resolver().resolve();
358        }
359        let msg = fmt_diag_string(&err);
360        assert!(msg.contains("Test io error"));
361
362        assert!(
363            format!("{:?}", err.source()).contains("Test io erro"),
364            "{:?}",
365            err.source().unwrap()
366        );
367    }
368}