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