Skip to main content

ntex_error/
message.rs

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