Skip to main content

ntex_error/
message.rs

1use std::{error::Error as StdError, fmt, fmt::Write, sync::Arc};
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) -> usize {
18        let res = self.written;
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
36/// Formats an error and its source chain into a string.
37pub fn fmt_err_string(e: &dyn StdError) -> String {
38    let mut buf = String::new();
39    _ = fmt_err(&mut buf, e);
40    buf
41}
42
43/// Writes an error and its source chain.
44pub fn fmt_err(f: &mut dyn fmt::Write, e: &dyn StdError) -> fmt::Result {
45    let mut wrt = Wrt::new(f);
46    let mut current = Some(e);
47    while let Some(std_err) = current {
48        write!(&mut wrt, "{std_err}")?;
49        if wrt.wrote() > 0 {
50            writeln!(wrt.fmt)?;
51        }
52        current = std_err.source();
53    }
54    Ok(())
55}
56
57/// Formats a full diagnostic view of an error for logging and tracing.
58pub fn fmt_diag_string<'a, T>(e: &'a T) -> String
59where
60    T: ErrorDiagnostic + AsError,
61    ResultType: From<&'a T::Target>,
62{
63    let mut buf = String::new();
64    _ = fmt_diag(&mut buf, e);
65    buf
66}
67
68/// Formats a full diagnostic view of an error for logging and tracing.
69///
70/// Includes the result type, signature, tag, service, and the display
71/// representation of the error and its source chain. For `ServiceError`
72/// results, a resolved backtrace is appended when available.
73pub fn fmt_diag<'a, T>(f: &mut dyn fmt::Write, container: &'a T) -> fmt::Result
74where
75    T: ErrorDiagnostic + AsError,
76    ResultType: From<&'a T::Target>,
77{
78    fmt_diag_typ(f, Some(ResultType::from(container.as_diag())), container)
79}
80
81/// Formats a full diagnostic view of an error for logging and tracing.
82///
83/// Includes the result type, signature, tag, service, and the display
84/// representation of the error and its source chain. For `ServiceError`
85/// results, a resolved backtrace is appended when available.
86pub fn fmt_diag_typ<T>(f: &mut dyn fmt::Write, typ: Option<ResultType>, e: &T) -> 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() > 0 {
111        writeln!(wrt.fmt)?;
112    }
113
114    let mut nesting = 0;
115    let mut current = e.source();
116    while let Some(err) = current {
117        nesting += 2;
118        write!(&mut wrt, "{}", " ".repeat(nesting))?;
119        write!(&mut wrt, "{err}")?;
120        if wrt.wrote() > nesting {
121            writeln!(wrt.fmt)?;
122        }
123        current = err.source();
124    }
125
126    if typ == Some(ResultType::ServiceError)
127        && let Some(bt) = e.backtrace()
128        && let Some(repr) = bt.repr()
129    {
130        writeln!(wrt.fmt, "{repr}")?;
131    }
132
133    Ok(())
134}
135
136/// A lightweight owned error message.
137#[derive(Clone, PartialEq, Eq, thiserror::Error)]
138pub struct ErrorMessage(ByteString);
139
140/// An owned error message with an optional source error.
141#[derive(Clone)]
142pub struct ErrorMessageChained {
143    msg: ByteString,
144    source: Option<Arc<dyn StdError + Send + Sync>>,
145}
146
147impl ErrorMessageChained {
148    /// Creates a message error with a source.
149    pub fn new<M, E>(ctx: M, source: E) -> Self
150    where
151        M: Into<ErrorMessage>,
152        E: StdError + Send + Sync + 'static,
153    {
154        ErrorMessageChained {
155            msg: ctx.into().into_string(),
156            source: Some(Arc::new(source)),
157        }
158    }
159
160    /// Creates an empty message error without a source.
161    pub const fn empty() -> Self {
162        Self::from_static("")
163    }
164
165    /// Creates a message error without a source from a [`ByteString`].
166    pub const fn from_bstr(msg: ByteString) -> Self {
167        Self { msg, source: None }
168    }
169
170    /// Creates a message error without a source from a static string.
171    pub const fn from_static(msg: &'static str) -> Self {
172        Self::from_bstr(ByteString::from_static(msg))
173    }
174
175    /// Returns `true` if the message is empty.
176    pub fn is_empty(&self) -> bool {
177        self.msg.is_empty()
178    }
179
180    /// Returns the message as a string slice.
181    pub fn as_str(&self) -> &str {
182        &self.msg
183    }
184
185    /// Returns the message as a [`ByteString`].
186    pub fn as_bstr(&self) -> &ByteString {
187        &self.msg
188    }
189
190    /// Converts this error into its message, the source is dropped.
191    pub fn into_string(self) -> ByteString {
192        self.msg
193    }
194
195    /// Sets the source error, replacing the current one.
196    #[must_use]
197    pub fn with_source<E: StdError + Send + Sync + 'static>(self, source: E) -> Self {
198        Self {
199            msg: self.msg,
200            source: Some(Arc::new(source)),
201        }
202    }
203}
204
205impl ErrorMessage {
206    /// Creates an empty error message.
207    pub const fn empty() -> Self {
208        Self(ByteString::from_static(""))
209    }
210
211    /// Creates an error message from a [`ByteString`].
212    pub const fn from_bstr(msg: ByteString) -> Self {
213        ErrorMessage(msg)
214    }
215
216    /// Creates an error message from a static string.
217    pub const fn from_static(msg: &'static str) -> Self {
218        ErrorMessage(ByteString::from_static(msg))
219    }
220
221    /// Returns `true` if the message is empty.
222    pub fn is_empty(&self) -> bool {
223        self.0.is_empty()
224    }
225
226    /// Returns the message as a string slice.
227    pub fn as_str(&self) -> &str {
228        &self.0
229    }
230
231    /// Returns the underlying [`ByteString`].
232    pub fn as_bstr(&self) -> &ByteString {
233        &self.0
234    }
235
236    /// Converts this message into its underlying [`ByteString`].
237    pub fn into_string(self) -> ByteString {
238        self.0
239    }
240
241    /// Attaches a source error to this message.
242    pub fn with_source<E: StdError + Send + Sync + 'static>(
243        self,
244        source: E,
245    ) -> ErrorMessageChained {
246        ErrorMessageChained::new(self, source)
247    }
248}
249
250impl From<String> for ErrorMessage {
251    fn from(value: String) -> Self {
252        Self(ByteString::from(value))
253    }
254}
255
256impl From<ByteString> for ErrorMessage {
257    fn from(value: ByteString) -> Self {
258        Self(value)
259    }
260}
261
262impl From<&'static str> for ErrorMessage {
263    fn from(value: &'static str) -> Self {
264        Self(ByteString::from_static(value))
265    }
266}
267
268impl fmt::Debug for ErrorMessage {
269    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
270        fmt::Display::fmt(&self.0, f)
271    }
272}
273
274impl fmt::Display for ErrorMessage {
275    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
276        fmt::Display::fmt(&self.0, f)
277    }
278}
279
280impl From<ErrorMessage> for ByteString {
281    fn from(msg: ErrorMessage) -> Self {
282        msg.0
283    }
284}
285
286impl<'a> From<&'a ErrorMessage> for ByteString {
287    fn from(msg: &'a ErrorMessage) -> Self {
288        msg.0.clone()
289    }
290}
291
292impl<M: Into<ErrorMessage>> From<M> for ErrorMessageChained {
293    fn from(value: M) -> Self {
294        ErrorMessageChained {
295            msg: value.into().0,
296            source: None,
297        }
298    }
299}
300
301impl StdError for ErrorMessageChained {
302    fn source(&self) -> Option<&(dyn StdError + 'static)> {
303        self.source
304            .as_ref()
305            .map(|e| e.as_ref() as &(dyn StdError + 'static))
306    }
307}
308
309impl fmt::Debug for ErrorMessageChained {
310    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
311        fmt::Display::fmt(&self, f)
312    }
313}
314
315impl fmt::Display for ErrorMessageChained {
316    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
317        if self.msg.is_empty() {
318            Ok(())
319        } else {
320            fmt::Display::fmt(&self.msg, f)
321        }
322    }
323}
324
325#[cfg(test)]
326#[allow(dead_code)]
327mod tests {
328    use ntex_bytes::Bytes;
329    use std::{error::Error, io};
330
331    use super::*;
332
333    #[test]
334    fn error_message() {
335        let msg = ErrorMessage::empty();
336        assert!(msg.is_empty());
337        assert_eq!(msg.as_str(), "");
338        assert_eq!(msg.as_bstr(), ByteString::new());
339        assert_eq!(ByteString::new(), msg.as_bstr());
340        assert_eq!(ByteString::new(), msg.into_string());
341
342        let msg = ErrorMessage::from("test");
343        assert!(!msg.is_empty());
344        assert_eq!(format!("{msg}"), "test");
345        assert_eq!(format!("{msg:?}"), "test");
346        assert_eq!(msg.as_str(), "test");
347        assert_eq!(msg.as_bstr(), ByteString::from("test"));
348
349        let msg = ErrorMessage::from("test".to_string());
350        assert!(!msg.is_empty());
351        assert_eq!(msg.as_str(), "test");
352        assert_eq!(msg.as_bstr(), ByteString::from("test"));
353
354        let msg = ErrorMessage::from_bstr(ByteString::from("test"));
355        assert!(!msg.is_empty());
356        assert_eq!(msg.as_str(), "test");
357        assert_eq!(msg.as_bstr(), ByteString::from("test"));
358
359        let msg = ErrorMessage::from(ByteString::from("test"));
360        assert!(!msg.is_empty());
361        assert_eq!(msg.as_str(), "test");
362        assert_eq!(msg.as_bstr(), ByteString::from("test"));
363
364        let msg = ErrorMessage::from_static("test");
365        assert!(!msg.is_empty());
366        assert_eq!(msg.as_str(), "test");
367        assert_eq!(msg.as_bstr(), ByteString::from("test"));
368
369        assert_eq!(ByteString::from(&msg), "test");
370        assert_eq!(ByteString::from(msg), "test");
371    }
372
373    #[test]
374    fn error_message_chained() {
375        let chained = ErrorMessageChained::from(ByteString::from("test"));
376        assert_eq!(chained.as_bstr(), "test");
377        assert!(chained.source().is_none());
378
379        let chained = ErrorMessageChained::from_bstr(ByteString::from("test"));
380        assert_eq!(chained.as_bstr(), "test");
381        assert!(chained.source().is_none());
382        assert_eq!(format!("{chained}"), "test");
383        assert_eq!(format!("{chained:?}"), "test");
384
385        let msg = ErrorMessage::from(ByteString::from("test"));
386        let chained = msg.with_source(io::Error::other("io-test"));
387        assert_eq!(chained.as_bstr(), "test");
388        assert!(chained.source().is_some());
389
390        let err = ErrorMessageChained::new("test", io::Error::other("io-test"));
391        let msg = fmt_err_string(&err);
392        assert_eq!(msg, "test\nio-test\n");
393
394        let chained = ErrorMessageChained::from(ByteString::new());
395        assert_eq!(format!("{chained}"), "");
396        assert!(chained.is_empty());
397
398        let chained = ErrorMessageChained::empty();
399        assert!(chained.is_empty());
400        assert_eq!(chained.as_str(), "");
401        assert!(chained.source().is_none());
402
403        let chained = ErrorMessageChained::from_static("test");
404        assert!(!chained.is_empty());
405        assert_eq!(chained.as_str(), "test");
406        assert!(chained.source().is_none());
407
408        let chained = chained.with_source(io::Error::other("first"));
409        let chained = chained.with_source(io::Error::other("second"));
410        assert_eq!(chained.source().unwrap().to_string(), "second");
411        assert_eq!(chained.into_string(), "test");
412    }
413
414    #[derive(thiserror::Error, derive_more::Debug)]
415    enum TestError {
416        #[error("Disconnect")]
417        #[debug("")]
418        Disconnect(#[source] io::Error),
419        #[error("InternalServiceError")]
420        #[debug("InternalServiceError {_0}")]
421        Service(&'static str),
422    }
423
424    impl Clone for TestError {
425        fn clone(&self) -> Self {
426            panic!()
427        }
428    }
429
430    impl ErrorDiagnostic for TestError {
431        fn signature(&self) -> &'static str {
432            match self {
433                TestError::Service(_) => ResultType::ServiceError.as_str(),
434                TestError::Disconnect(_) => ResultType::ClientError.as_str(),
435            }
436        }
437    }
438
439    impl From<&TestError> for ResultType {
440        fn from(err: &TestError) -> ResultType {
441            match err {
442                TestError::Service(_) => ResultType::ServiceError,
443                TestError::Disconnect(_) => ResultType::ClientError,
444            }
445        }
446    }
447
448    #[test]
449    fn fmt_diag() {
450        let err = TestError::Service("409 Error");
451
452        let msg = fmt_err_string(&err);
453        assert_eq!(msg, "InternalServiceError\n");
454
455        let err = crate::Error::from(TestError::Disconnect(io::Error::other("Test io error")));
456        if let Some(bt) = err.backtrace() {
457            bt.resolver().resolve();
458        }
459        let msg = fmt_diag_string(&err);
460        assert!(msg.contains("Test io error"), "{msg}");
461
462        assert!(
463            format!("{:?}", err.source()).contains("Test io erro"),
464            "{:?}",
465            err.source().unwrap()
466        );
467
468        let err = err.with_tag(Bytes::from("test-tag"));
469        let msg = fmt_diag_string(&err);
470        assert!(msg.contains("test-tag"), "{msg}");
471    }
472}