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