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_errors_doc,
6    clippy::missing_panics_doc
7)]
8use std::{error::Error as StdError, fmt};
9
10use ntex_bytes::Bytes;
11
12mod bt;
13mod error;
14mod ext;
15mod info;
16mod message;
17mod repr;
18pub mod utils;
19
20pub use crate::bt::{Backtrace, BacktraceRaw, BacktraceResolver};
21pub use crate::error::Error;
22pub use crate::info::Failure;
23pub use crate::message::{ErrorMessage, ErrorMessageChained};
24pub use crate::message::{fmt_diag, fmt_diag_string, fmt_diag_typ, fmt_err, fmt_err_string};
25pub use crate::utils::{ResultSignature, Retryable, Success, with_service};
26
27#[doc(hidden)]
28pub use crate::bt::{set_backtrace_start, set_backtrace_start_alt};
29
30#[doc(hidden)]
31#[deprecated(since = "2.6.0")]
32pub type ErrorInfo = Failure;
33
34/// The type of the result.
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, thiserror::Error)]
36pub enum ResultType {
37    Success,
38    ClientError,
39    ServiceError,
40}
41
42impl ResultType {
43    /// Returns a str representation of the result type.
44    pub const fn as_str(&self) -> &'static str {
45        match self {
46            ResultType::Success => "Success",
47            ResultType::ClientError => "ClientError",
48            ResultType::ServiceError => "ServiceError",
49        }
50    }
51}
52
53pub trait AsError {
54    type Target: ErrorDiagnostic;
55
56    fn as_diag(&self) -> &Self::Target;
57}
58
59/// Provides diagnostic information for errors.
60///
61/// It enables classification, service attribution, and debugging context.
62pub trait ErrorDiagnostic: StdError + 'static {
63    #[doc(hidden)]
64    #[deprecated(since = "2.1.0")]
65    /// Returns the classification of the result (e.g. success, client error, service error).
66    fn typ(&self) -> ResultType {
67        ResultType::ServiceError
68    }
69
70    /// Returns a stable identifier for the specific error classification.
71    ///
72    /// It is used for logging, metrics, and diagnostics.
73    fn signature(&self) -> &'static str;
74
75    /// Returns an optional tag associated with this error.
76    ///
77    /// The tag is user-defined and can be used for additional classification
78    /// or correlation.
79    fn tag(&self) -> Option<&Bytes> {
80        None
81    }
82
83    /// Returns the name of the responsible service, if applicable.
84    ///
85    /// Used to identify upstream or internal service ownership for diagnostics.
86    fn service(&self) -> Option<&'static str> {
87        None
88    }
89
90    /// Returns a backtrace for debugging purposes, if available.
91    fn backtrace(&self) -> Option<&Backtrace> {
92        None
93    }
94}
95
96/// Helper trait for converting a value into a unified error-aware result type.
97pub trait ErrorMapping<T, E, U> {
98    /// Converts the value into a `Result`, wrapping it in a structured error type if needed.
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
108impl ErrorDiagnostic for ResultType {
109    fn signature(&self) -> &'static str {
110        self.as_str()
111    }
112}
113
114/// Helper trait for converting a value into a unified error-aware result type.
115pub trait IntoFailure: Sized {
116    fn fail(self) -> Failure;
117}
118
119#[cfg(test)]
120mod tests {
121    use std::{error::Error as StdError, mem};
122
123    use super::*;
124
125    #[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
126    enum TestError {
127        #[error("Connect err: {0}")]
128        Connect(&'static str),
129        #[error("Disconnect")]
130        Disconnect,
131        #[error("InternalServiceError")]
132        Service(&'static str),
133    }
134
135    impl ErrorDiagnostic for TestError {
136        fn signature(&self) -> &'static str {
137            match self {
138                TestError::Connect(_) => "Client-Connect",
139                TestError::Disconnect => "Client-Disconnect",
140                TestError::Service(_) => "Service-Internal",
141            }
142        }
143
144        fn service(&self) -> Option<&'static str> {
145            Some("test")
146        }
147    }
148
149    impl From<&TestError> for ResultType {
150        fn from(err: &TestError) -> ResultType {
151            match err {
152                TestError::Connect(_) | TestError::Disconnect => ResultType::ClientError,
153                TestError::Service(_) => ResultType::ServiceError,
154            }
155        }
156    }
157
158    #[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
159    #[error("TestError2")]
160    struct TestError2;
161    impl ErrorDiagnostic for TestError2 {
162        fn typ(&self) -> ResultType {
163            ResultType::ClientError
164        }
165
166        fn signature(&self) -> &'static str {
167            "TestError2"
168        }
169    }
170
171    impl From<TestError> for TestError2 {
172        fn from(_err: TestError) -> TestError2 {
173            TestError2
174        }
175    }
176
177    #[ntex::test]
178    async fn test_error() {
179        let err: Error<TestError> = TestError::Service("409 Error").into();
180        let err = err.clone();
181        assert_eq!(err.to_string(), "InternalServiceError");
182        assert_eq!(err.service(), Some("test"));
183        assert_eq!(err.signature(), "Service-Internal");
184        assert_eq!(
185            err,
186            Into::<Error<TestError>>::into(TestError::Service("409 Error"))
187        );
188        assert!(err.backtrace().is_some());
189
190        let err = err.set_service("SVC");
191        assert_eq!(err.service(), Some("SVC"));
192        let err = err.set_tag("TAG");
193        assert_eq!(err.tag().unwrap(), &b"TAG"[..]);
194
195        let err2: Error<TestError> = Error::new(TestError::Service("409 Error"), "TEST");
196        assert_ne!(err, err2);
197        assert_eq!(err, TestError::Service("409 Error"));
198
199        let err2 = err2.set_tag("TAG");
200        assert_eq!(err.tag().unwrap(), &b"TAG"[..]);
201        let err2 = err2.set_service("SVC");
202        assert_eq!(err, err2);
203        let err2 = err2.map(|_| TestError::Disconnect);
204        assert_ne!(err, err2);
205        let err2 = err2.forward(|_| TestError::Disconnect);
206        assert_ne!(err, err2);
207
208        assert_eq!(TestError::Connect("").to_string(), "Connect err: ");
209        assert_eq!(TestError::Disconnect.to_string(), "Disconnect");
210        assert_eq!(TestError::Disconnect.service(), Some("test"));
211        assert!(TestError::Disconnect.backtrace().is_none());
212
213        assert_eq!(ResultType::ClientError.as_str(), "ClientError");
214        assert_eq!(ResultType::ServiceError.as_str(), "ServiceError");
215        assert_eq!(ResultType::ClientError.to_string(), "ClientError");
216        assert_eq!(ResultType::ServiceError.to_string(), "ServiceError");
217        assert_eq!(format!("{}", ResultType::ClientError), "ClientError");
218
219        assert_eq!(TestError::Connect("").signature(), "Client-Connect");
220        assert_eq!(TestError::Disconnect.signature(), "Client-Disconnect");
221        assert_eq!(TestError::Service("").signature(), "Service-Internal");
222
223        let err = err.into_error();
224        assert_eq!(err.to_string(), "InternalServiceError");
225        assert!(err.source().is_none());
226        assert!(format!("{err:?}").contains("Service(\"409 Error\")"));
227
228        #[cfg(unix)]
229        {
230            let err: Error<TestError> = TestError::Service("404 Error").into();
231            if let Some(bt) = err.backtrace() {
232                bt.resolver().resolve();
233                assert!(
234                    format!("{bt}").contains("ntex_error::tests::test_error"),
235                    "{bt}",
236                );
237                assert!(
238                    bt.repr().unwrap().contains("ntex_error::tests::test_error"),
239                    "{bt}"
240                );
241            }
242        }
243
244        assert_eq!(24, mem::size_of::<TestError>());
245        assert_eq!(8, mem::size_of::<Error<TestError>>());
246
247        assert_eq!(TestError2.service(), None);
248        assert_eq!(TestError2.signature(), "TestError2");
249
250        // ErrorInformation
251        let err: Error<TestError> = TestError::Service("409 Error").into();
252        let msg = fmt_err_string(&err);
253        assert_eq!(msg, "InternalServiceError\n");
254        let msg = fmt_diag_string(&err);
255        assert!(msg.contains("err: InternalServiceError"));
256
257        let err: Failure = err.set_service("SVC").into();
258        assert_eq!(err.service(), Some("SVC"));
259        assert_eq!(err.signature(), "Service-Internal");
260        assert_eq!(err.as_diag().service(), Some("SVC"));
261        assert_eq!(err.as_diag().signature(), "Service-Internal");
262        assert!(err.backtrace().is_some());
263        assert!(err.as_diag().backtrace().is_some());
264
265        let res = Err(TestError::Service("409 Error"));
266        let res: Result<(), Error<TestError>> = res.into_error();
267        let _res: Result<(), Error<TestError2>> = res.into_error();
268
269        let msg = fmt_err_string(&err);
270        assert_eq!(msg, "InternalServiceError\n");
271
272        // Error extensions
273        let err: Error<TestError> = TestError::Service("409 Error").into();
274        assert_eq!(err.get_item::<&str>(), None);
275        let err = err.insert_item("Test");
276        assert_eq!(err.get_item::<&str>(), Some(&"Test"));
277        let err2 = err.clone();
278        assert_eq!(err2.get_item::<&str>(), Some(&"Test"));
279        let err2 = err2.insert_item("Test2");
280        assert_eq!(err2.get_item::<&str>(), Some(&"Test2"));
281        assert_eq!(err.get_item::<&str>(), Some(&"Test"));
282        let err2 = err.clone().map(|_| TestError::Disconnect);
283        assert_eq!(err2.get_item::<&str>(), Some(&"Test"));
284
285        let info = Failure::from(&err2);
286        assert_eq!(info.get_item::<&str>(), Some(&"Test"));
287
288        let err3 = err
289            .clone()
290            .try_map(|_| Err::<(), _>(TestError2))
291            .err()
292            .unwrap();
293        assert_eq!(err3.signature(), "TestError2");
294        assert_eq!(err3.get_item::<&str>(), Some(&"Test"));
295
296        let res = err.clone().try_map(|_| Ok::<_, TestError2>(()));
297        assert_eq!(res, Ok(()));
298        assert_eq!(format!("{Success}"), "Success");
299
300        let res = Ok::<_, TestError>(());
301        let info = ResultSignature::from(&res);
302        assert_eq!(info.signature(), "Success");
303
304        let res = Err::<(), _>(TestError::Service("409 Error"));
305        let info = ResultSignature::from(&res);
306        assert_eq!(info.signature(), "Service-Internal");
307    }
308}