Skip to main content

ntex_error/
lib.rs

1//! Error management.
2#![deny(clippy::pedantic)]
3#![allow(
4    clippy::must_use_candidate,
5    clippy::missing_panics_doc,
6    clippy::missing_errors_doc
7)]
8use std::{error::Error as StdError, fmt};
9
10mod bt;
11mod chain;
12mod error;
13mod ext;
14mod info;
15mod message;
16mod repr;
17mod utils;
18
19pub use crate::bt::{Backtrace, BacktraceResolver};
20pub use crate::chain::ErrorChain;
21pub use crate::error::Error;
22pub use crate::info::ErrorInfo;
23pub use crate::message::{ErrorMessage, ErrorMessageChained};
24pub use crate::message::{fmt_diag, fmt_diag_string, fmt_err, fmt_err_string};
25pub use crate::utils::{Success, with_service};
26
27#[doc(hidden)]
28pub use crate::bt::{set_backtrace_start, set_backtrace_start_alt};
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
31pub enum ResultType {
32    Success,
33    ClientError,
34    ServiceError,
35    #[doc(hidden)]
36    #[deprecated]
37    Client,
38    #[doc(hidden)]
39    #[deprecated]
40    Service,
41}
42
43#[deprecated]
44#[doc(hidden)]
45pub type ErrorType = ResultType;
46
47impl ResultType {
48    #[allow(deprecated)]
49    pub const fn as_str(&self) -> &'static str {
50        match self {
51            ResultType::Success => "Success",
52            ResultType::ClientError | ResultType::Client => "ClientError",
53            ResultType::ServiceError | ResultType::Service => "ServiceError",
54        }
55    }
56}
57
58pub trait ResultKind: fmt::Debug + 'static {
59    /// Defines type of the error
60    fn tp(&self) -> ResultType;
61
62    /// Error signature
63    fn signature(&self) -> &'static str;
64}
65
66pub trait ErrorKind: fmt::Debug + 'static {
67    /// Defines type of the error
68    fn tp(&self) -> ResultType;
69
70    /// Error signature
71    fn signature(&self) -> &'static str;
72}
73
74impl ResultKind for ResultType {
75    fn tp(&self) -> ResultType {
76        *self
77    }
78
79    fn signature(&self) -> &'static str {
80        self.as_str()
81    }
82}
83
84pub trait ErrorDiagnostic: StdError + 'static {
85    type Kind: ResultKind;
86
87    /// Provides specific kind of the error
88    fn kind(&self) -> Self::Kind;
89
90    /// Provides a string to identify responsible service
91    fn service(&self) -> Option<&'static str> {
92        None
93    }
94
95    /// Provides error call location
96    fn backtrace(&self) -> Option<&Backtrace> {
97        None
98    }
99
100    #[track_caller]
101    fn chain(self) -> ErrorChain<Self::Kind>
102    where
103        Self: Sized,
104    {
105        ErrorChain::new(self)
106    }
107}
108
109pub trait ErrorMapping<T, E, U> {
110    fn into_error(self) -> Result<T, Error<U>>;
111}
112
113impl fmt::Display for ResultType {
114    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115        write!(f, "{}", self.as_str())
116    }
117}
118
119#[allow(dead_code)]
120#[cfg(test)]
121mod tests {
122    use std::{error::Error as StdError, io, mem};
123
124    use super::*;
125
126    #[derive(Copy, Clone, Debug, PartialEq, Eq, thiserror::Error)]
127    enum TestKind {
128        #[error("Connect")]
129        Connect,
130        #[error("Disconnect")]
131        Disconnect,
132        #[error("ServiceError")]
133        ServiceError,
134    }
135
136    impl ResultKind for TestKind {
137        fn tp(&self) -> ResultType {
138            match self {
139                TestKind::Connect | TestKind::Disconnect => ResultType::ClientError,
140                TestKind::ServiceError => ResultType::ServiceError,
141            }
142        }
143
144        fn signature(&self) -> &'static str {
145            match self {
146                TestKind::Connect => "Client-Connect",
147                TestKind::Disconnect => "Client-Disconnect",
148                TestKind::ServiceError => "Service-Internal",
149            }
150        }
151    }
152
153    #[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
154    enum TestError {
155        #[error("Connect err: {0}")]
156        Connect(&'static str),
157        #[error("Disconnect")]
158        Disconnect,
159        #[error("InternalServiceError")]
160        Service(&'static str),
161    }
162
163    impl ErrorDiagnostic for TestError {
164        type Kind = TestKind;
165
166        fn kind(&self) -> Self::Kind {
167            match self {
168                TestError::Connect(_) => TestKind::Connect,
169                TestError::Disconnect => TestKind::Disconnect,
170                TestError::Service(_) => TestKind::ServiceError,
171            }
172        }
173
174        fn service(&self) -> Option<&'static str> {
175            Some("test")
176        }
177    }
178
179    #[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
180    #[error("TestError2")]
181    struct TestError2;
182    impl ErrorDiagnostic for TestError2 {
183        type Kind = ResultType;
184
185        fn kind(&self) -> Self::Kind {
186            ResultType::ClientError
187        }
188    }
189
190    impl From<TestError> for TestError2 {
191        fn from(_err: TestError) -> TestError2 {
192            TestError2
193        }
194    }
195
196    #[ntex::test]
197    async fn test_error() {
198        let err: Error<TestError> = TestError::Service("409 Error").into();
199        let err = err.clone();
200        assert_eq!(err.kind(), TestKind::ServiceError);
201        assert_eq!((*err).kind(), TestKind::ServiceError);
202        assert_eq!(err.to_string(), "InternalServiceError");
203        assert_eq!(err.service(), Some("test"));
204        assert_eq!(err.kind().signature(), "Service-Internal");
205        assert_eq!(
206            err,
207            Into::<Error<TestError>>::into(TestError::Service("409 Error"))
208        );
209        assert!(err.backtrace().is_some());
210
211        let err = err.set_service("SVC");
212        assert_eq!(err.service(), Some("SVC"));
213
214        let err2: Error<TestError> = Error::new(TestError::Service("409 Error"), "TEST");
215        assert!(err != err2);
216        assert_eq!(err, TestError::Service("409 Error"));
217
218        let err2 = err2.set_service("SVC");
219        assert_eq!(err, err2);
220        let err2 = err2.map(|_| TestError::Disconnect);
221        assert!(err != err2);
222        let err2 = err2.forward(|_| TestError::Disconnect);
223        assert!(err != err2);
224
225        assert_eq!(TestError::Connect("").kind().tp(), ResultType::ClientError);
226        assert_eq!(TestError::Disconnect.kind().tp(), ResultType::ClientError);
227        assert_eq!(TestError::Service("").kind().tp(), ResultType::ServiceError);
228        assert_eq!(TestError::Connect("").to_string(), "Connect err: ");
229        assert_eq!(TestError::Disconnect.to_string(), "Disconnect");
230        assert_eq!(TestError::Disconnect.service(), Some("test"));
231        assert!(TestError::Disconnect.backtrace().is_none());
232
233        assert_eq!(ResultType::ClientError.as_str(), "ClientError");
234        assert_eq!(ResultType::ServiceError.as_str(), "ServiceError");
235        assert_eq!(ResultType::ClientError.tp(), ResultType::ClientError);
236        assert_eq!(ResultType::ServiceError.tp(), ResultType::ServiceError);
237        assert_eq!(ResultType::ClientError.to_string(), "ClientError");
238        assert_eq!(ResultType::ServiceError.to_string(), "ServiceError");
239        assert_eq!(format!("{}", ResultType::ClientError), "ClientError");
240
241        assert_eq!(TestKind::Connect.to_string(), "Connect");
242        assert_eq!(TestError::Connect("").kind().signature(), "Client-Connect");
243        assert_eq!(TestKind::Disconnect.to_string(), "Disconnect");
244        assert_eq!(
245            TestError::Disconnect.kind().signature(),
246            "Client-Disconnect"
247        );
248        assert_eq!(TestKind::ServiceError.to_string(), "ServiceError");
249        assert_eq!(
250            TestError::Service("").kind().signature(),
251            "Service-Internal"
252        );
253
254        let err = err.into_error().chain();
255        assert_eq!(err.kind(), TestKind::ServiceError);
256        assert_eq!(err.kind(), TestError::Service("409 Error").kind());
257        assert_eq!(err.to_string(), "InternalServiceError");
258        assert!(format!("{err:?}").contains("Service(\"409 Error\")"));
259        assert!(
260            format!("{:?}", err.source()).contains("Service(\"409 Error\")"),
261            "{:?}",
262            err.source().unwrap()
263        );
264
265        let err: Error<TestError> = TestError::Service("404 Error").into();
266        if let Some(bt) = err.backtrace() {
267            bt.resolver().resolve();
268            assert!(
269                format!("{bt}").contains("ntex_error::tests::test_error"),
270                "{bt}",
271            );
272            assert!(
273                bt.repr().unwrap().contains("ntex_error::tests::test_error"),
274                "{bt}"
275            );
276        }
277
278        let err: ErrorChain<TestKind> = err.into();
279        assert_eq!(err.kind(), TestKind::ServiceError);
280        assert_eq!(err.kind(), TestError::Service("404 Error").kind());
281        assert_eq!(err.service(), Some("test"));
282        assert_eq!(err.kind().signature(), "Service-Internal");
283        assert_eq!(err.to_string(), "InternalServiceError");
284        assert!(err.backtrace().is_some());
285        assert!(format!("{err:?}").contains("Service(\"404 Error\")"));
286
287        assert_eq!(24, mem::size_of::<TestError>());
288        assert_eq!(8, mem::size_of::<Error<TestError>>());
289
290        assert_eq!(TestError2.service(), None);
291        assert_eq!(TestError2.kind().signature(), "ClientError");
292
293        // ErrorInformation
294        let err: Error<TestError> = TestError::Service("409 Error").into();
295        let msg = fmt_err_string(&err);
296        assert_eq!(msg, "InternalServiceError\n");
297        let msg = fmt_diag_string(&err);
298        assert!(msg.contains("err: InternalServiceError"));
299
300        let err: ErrorInfo = err.set_service("SVC").into();
301        assert_eq!(err.tp(), ResultType::ServiceError);
302        assert_eq!(err.service(), Some("SVC"));
303        assert_eq!(err.signature(), "Service-Internal");
304        assert!(err.backtrace().is_some());
305
306        let res = Err(TestError::Service("409 Error"));
307        let res: Result<(), Error<TestError>> = res.into_error();
308        let _res: Result<(), Error<TestError2>> = res.into_error();
309
310        let msg = fmt_err_string(&err);
311        assert_eq!(msg, "InternalServiceError\n");
312        let msg = fmt_diag_string(&err);
313        assert!(msg.contains("err: InternalServiceError"));
314
315        // Error extensions
316        let err: Error<TestError> = TestError::Service("409 Error").into();
317        assert_eq!(err.get_item::<&str>(), None);
318        let err = err.insert_item("Test");
319        assert_eq!(err.get_item::<&str>(), Some(&"Test"));
320        let err2 = err.clone();
321        assert_eq!(err2.get_item::<&str>(), Some(&"Test"));
322        let err2 = err2.insert_item("Test2");
323        assert_eq!(err2.get_item::<&str>(), Some(&"Test2"));
324        assert_eq!(err.get_item::<&str>(), Some(&"Test"));
325        let err2 = err.clone().map(|_| TestError::Disconnect);
326        assert_eq!(err2.get_item::<&str>(), Some(&"Test"));
327
328        let info = ErrorInfo::from(&err2);
329        assert_eq!(info.get_item::<&str>(), Some(&"Test"));
330
331        let err3 = err
332            .clone()
333            .try_map(|_| Err::<(), _>(TestError2))
334            .err()
335            .unwrap();
336        assert_eq!(err3.kind().signature(), "ClientError");
337        assert_eq!(err3.get_item::<&str>(), Some(&"Test"));
338
339        let res = err.clone().try_map(|_| Ok::<_, TestError2>(()));
340        assert_eq!(res, Ok(()));
341
342        assert_eq!(Success.kind(), ResultType::Success);
343        assert_eq!(format!("{Success}"), "Success");
344
345        assert_eq!(
346            ErrorDiagnostic::kind(&io::Error::other("")),
347            ResultType::ServiceError
348        );
349        assert_eq!(
350            ErrorDiagnostic::kind(&io::Error::new(io::ErrorKind::InvalidData, "")),
351            ResultType::ClientError
352        );
353    }
354}