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