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