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