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