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
201        let err = err.set_service("SVC");
202        assert_eq!(err.service(), Some("SVC"));
203
204        let err2: Error<TestError> = Error::new(TestError::Service("409 Error"), "TEST");
205        assert!(err != err2);
206        assert_eq!(err, TestError::Service("409 Error"));
207
208        let err2 = err2.set_service("SVC");
209        assert_eq!(err, err2);
210        let err2 = err2.map(|_| TestError::Disconnect);
211        assert!(err != err2);
212        let err2 = err2.forward(|_| TestError::Disconnect);
213        assert!(err != err2);
214
215        assert_eq!(TestError::Connect("").kind().tp(), ResultType::ClientError);
216        assert_eq!(TestError::Disconnect.kind().tp(), ResultType::ClientError);
217        assert_eq!(TestError::Service("").kind().tp(), ResultType::ServiceError);
218        assert_eq!(TestError::Connect("").to_string(), "Connect err: ");
219        assert_eq!(TestError::Disconnect.to_string(), "Disconnect");
220        assert_eq!(TestError::Disconnect.service(), Some("test"));
221        assert!(TestError::Disconnect.backtrace().is_none());
222
223        assert_eq!(ResultType::ClientError.as_str(), "ClientError");
224        assert_eq!(ResultType::ServiceError.as_str(), "ServiceError");
225        assert_eq!(ResultType::ClientError.tp(), ResultType::ClientError);
226        assert_eq!(ResultType::ServiceError.tp(), ResultType::ServiceError);
227        assert_eq!(ResultType::ClientError.to_string(), "ClientError");
228        assert_eq!(ResultType::ServiceError.to_string(), "ServiceError");
229        assert_eq!(format!("{}", ResultType::ClientError), "ClientError");
230
231        assert_eq!(TestKind::Connect.to_string(), "Connect");
232        assert_eq!(TestError::Connect("").kind().signature(), "Client-Connect");
233        assert_eq!(TestKind::Disconnect.to_string(), "Disconnect");
234        assert_eq!(
235            TestError::Disconnect.kind().signature(),
236            "Client-Disconnect"
237        );
238        assert_eq!(TestKind::ServiceError.to_string(), "ServiceError");
239        assert_eq!(
240            TestError::Service("").kind().signature(),
241            "Service-Internal"
242        );
243
244        let err = err.into_error().chain();
245        assert_eq!(err.kind(), TestKind::ServiceError);
246        assert_eq!(err.kind(), TestError::Service("409 Error").kind());
247        assert_eq!(err.to_string(), "InternalServiceError");
248        assert!(format!("{err:?}").contains("Service(\"409 Error\")"));
249        assert!(
250            format!("{:?}", err.source()).contains("Service(\"409 Error\")"),
251            "{:?}",
252            err.source().unwrap()
253        );
254
255        let err: Error<TestError> = TestError::Service("404 Error").into();
256        if let Some(bt) = err.backtrace() {
257            assert!(
258                format!("{bt}").contains("ntex_error::tests::test_error"),
259                "{bt}",
260            );
261            assert!(bt.repr().contains("ntex_error::tests::test_error"), "{bt}",);
262        }
263
264        let err: ErrorChain<TestKind> = err.into();
265        assert_eq!(err.kind(), TestKind::ServiceError);
266        assert_eq!(err.kind(), TestError::Service("404 Error").kind());
267        assert_eq!(err.service(), Some("test"));
268        assert_eq!(err.kind().signature(), "Service-Internal");
269        assert_eq!(err.to_string(), "InternalServiceError");
270        assert!(err.backtrace().is_some());
271        assert!(format!("{err:?}").contains("Service(\"404 Error\")"));
272
273        assert_eq!(24, mem::size_of::<TestError>());
274        assert_eq!(8, mem::size_of::<Error<TestError>>());
275
276        assert_eq!(TestError2.service(), None);
277        assert_eq!(TestError2.kind().signature(), "ClientError");
278
279        // ErrorInformation
280        let err: Error<TestError> = TestError::Service("409 Error").into();
281        let msg = fmt_err_string(&err);
282        assert_eq!(msg, "InternalServiceError\n");
283        let msg = fmt_diag_string(&err);
284        assert!(msg.contains("err: InternalServiceError"));
285
286        let err: ErrorInfo = err.set_service("SVC").into();
287        assert_eq!(err.tp(), ResultType::ServiceError);
288        assert_eq!(err.service(), Some("SVC"));
289        assert_eq!(err.signature(), "Service-Internal");
290        assert!(err.backtrace().is_some());
291
292        let res = Err(TestError::Service("409 Error"));
293        let res: Result<(), Error<TestError>> = res.into_error();
294        let _res: Result<(), Error<TestError2>> = res.into_error();
295
296        let msg = fmt_err_string(&err);
297        assert_eq!(msg, "InternalServiceError\n");
298        let msg = fmt_diag_string(&err);
299        assert!(msg.contains("err: InternalServiceError"));
300
301        // Error extensions
302        let err: Error<TestError> = TestError::Service("409 Error").into();
303        assert_eq!(err.get_item::<&str>(), None);
304        let err = err.insert_item("Test");
305        assert_eq!(err.get_item::<&str>(), Some(&"Test"));
306        let err2 = err.clone();
307        assert_eq!(err2.get_item::<&str>(), Some(&"Test"));
308        let err2 = err2.insert_item("Test2");
309        assert_eq!(err2.get_item::<&str>(), Some(&"Test2"));
310        assert_eq!(err.get_item::<&str>(), Some(&"Test"));
311
312        let info = ErrorInfo::from(err2);
313        assert_eq!(info.get_item::<&str>(), Some(&"Test2"));
314    }
315}