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