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