Skip to main content

ntex_error/
message.rs

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