Skip to main content

ntex_error/
lib.rs

1//! Error management.
2#![deny(clippy::pedantic)]
3#![allow(clippy::must_use_candidate, clippy::missing_panics_doc)]
4use std::{error, fmt, ops, panic::Location, sync::Arc};
5
6mod bt;
7mod chain;
8mod info;
9mod repr;
10
11pub use crate::bt::{Backtrace, set_backtrace_start, set_backtrace_start_alt};
12pub use crate::chain::ErrorChain;
13pub use crate::info::ErrorInformation;
14
15use self::repr::ErrorRepr;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
18pub enum ErrorType {
19    Client,
20    Service,
21}
22
23impl ErrorType {
24    pub const fn as_str(&self) -> &'static str {
25        match self {
26            ErrorType::Client => "ClientError",
27            ErrorType::Service => "ServiceError",
28        }
29    }
30}
31
32pub trait ErrorKind: fmt::Display + fmt::Debug + 'static {
33    /// Defines type of the error
34    fn error_type(&self) -> ErrorType;
35}
36
37impl ErrorKind for ErrorType {
38    fn error_type(&self) -> ErrorType {
39        *self
40    }
41}
42
43pub trait ErrorDiagnostic: error::Error + 'static {
44    type Kind: ErrorKind;
45
46    /// Provides specific kind of the error
47    fn kind(&self) -> Self::Kind;
48
49    /// Provides a string to identify responsible service
50    fn service(&self) -> Option<&'static str> {
51        None
52    }
53
54    /// Check if error is service related
55    fn is_service(&self) -> bool {
56        self.kind().error_type() == ErrorType::Service
57    }
58
59    /// Provides a string to identify specific kind of the error
60    fn signature(&self) -> &'static str {
61        self.kind().error_type().as_str()
62    }
63
64    /// Provides error call location
65    fn backtrace(&self) -> Option<&Backtrace> {
66        None
67    }
68
69    #[track_caller]
70    fn chain(self) -> ErrorChain<Self::Kind>
71    where
72        Self: Sized,
73    {
74        ErrorChain::new(self)
75    }
76}
77
78pub struct Error<E: ErrorDiagnostic> {
79    pub(crate) inner: Arc<ErrorRepr<E, E::Kind>>,
80}
81
82impl<E: ErrorDiagnostic> Error<E> {
83    #[track_caller]
84    pub fn new<T>(error: T, service: &'static str) -> Self
85    where
86        E: ErrorDiagnostic,
87        E: From<T>,
88    {
89        Self {
90            inner: Arc::new(ErrorRepr::new(
91                E::from(error),
92                Some(service),
93                Location::caller(),
94            )),
95        }
96    }
97
98    #[must_use]
99    /// Set response service
100    pub fn set_service(mut self, name: &'static str) -> Self
101    where
102        E: Clone,
103    {
104        if let Some(inner) = Arc::get_mut(&mut self.inner) {
105            inner.service = Some(name);
106            self
107        } else {
108            Error {
109                inner: Arc::new(ErrorRepr::new2(
110                    self.inner.error.clone(),
111                    Some(name),
112                    self.inner.backtrace.clone(),
113                )),
114            }
115        }
116    }
117
118    /// Map inner error to new error
119    ///
120    /// Keep same `service` and `location`
121    pub fn map<U, F>(self, f: F) -> Error<U>
122    where
123        E: Clone,
124        F: FnOnce(E) -> U,
125        U: ErrorDiagnostic,
126    {
127        match Arc::try_unwrap(self.inner) {
128            Ok(inner) => Error {
129                inner: Arc::new(ErrorRepr::new2(
130                    f(inner.error),
131                    inner.service,
132                    inner.backtrace,
133                )),
134            },
135            Err(inner) => Error {
136                inner: Arc::new(ErrorRepr::new2(
137                    f(inner.error.clone()),
138                    inner.service,
139                    inner.backtrace.clone(),
140                )),
141            },
142        }
143    }
144
145    /// Get inner error value
146    pub fn into_error(self) -> E
147    where
148        E: Clone,
149    {
150        Arc::try_unwrap(self.inner)
151            .map_or_else(|inner| inner.error.clone(), |inner| inner.error)
152    }
153}
154
155impl<E: ErrorDiagnostic + Clone> Clone for Error<E> {
156    fn clone(&self) -> Error<E> {
157        Error {
158            inner: self.inner.clone(),
159        }
160    }
161}
162
163impl<E: ErrorDiagnostic> From<E> for Error<E> {
164    #[track_caller]
165    fn from(error: E) -> Self {
166        Self {
167            inner: Arc::new(ErrorRepr::new(error, None, Location::caller())),
168        }
169    }
170}
171
172impl<E: ErrorDiagnostic> Eq for Error<E> where E: Eq {}
173
174impl<E: ErrorDiagnostic> PartialEq for Error<E>
175where
176    E: PartialEq,
177{
178    fn eq(&self, other: &Self) -> bool {
179        self.inner.error.eq(&other.inner.error) && self.inner.service == other.inner.service
180    }
181}
182
183impl<E: ErrorDiagnostic> PartialEq<E> for Error<E>
184where
185    E: PartialEq,
186{
187    fn eq(&self, other: &E) -> bool {
188        self.inner.error.eq(other)
189    }
190}
191
192impl<E: ErrorDiagnostic> ops::Deref for Error<E> {
193    type Target = E;
194
195    fn deref(&self) -> &E {
196        &self.inner.error
197    }
198}
199
200impl<E: ErrorDiagnostic> error::Error for Error<E> {
201    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
202        Some(&self.inner.error)
203    }
204}
205
206impl<E: ErrorDiagnostic> ErrorDiagnostic for Error<E> {
207    type Kind = E::Kind;
208
209    fn kind(&self) -> Self::Kind {
210        self.inner.kind()
211    }
212
213    fn service(&self) -> Option<&'static str> {
214        self.inner.service()
215    }
216
217    fn signature(&self) -> &'static str {
218        self.inner.signature()
219    }
220
221    fn backtrace(&self) -> Option<&Backtrace> {
222        self.inner.backtrace()
223    }
224}
225
226impl<E: ErrorDiagnostic> fmt::Display for Error<E> {
227    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
228        fmt::Display::fmt(&self.inner.error, f)
229    }
230}
231
232impl<E: ErrorDiagnostic> fmt::Debug for Error<E> {
233    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
234        f.debug_struct("Error")
235            .field("error", &self.inner.error)
236            .field("service", &self.inner.service)
237            .field("backtrace", &self.inner.backtrace)
238            .finish()
239    }
240}
241
242impl fmt::Display for ErrorType {
243    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
244        match self {
245            ErrorType::Client => write!(f, "ClientError"),
246            ErrorType::Service => write!(f, "ServiceError"),
247        }
248    }
249}
250
251#[allow(dead_code)]
252#[cfg(test)]
253mod tests {
254    use std::{error::Error as StdError, mem};
255
256    use super::*;
257
258    #[derive(Copy, Clone, Debug, PartialEq, Eq, thiserror::Error)]
259    enum TestKind {
260        #[error("Connect")]
261        Connect,
262        #[error("Disconnect")]
263        Disconnect,
264        #[error("ServiceError")]
265        ServiceError,
266    }
267
268    impl ErrorKind for TestKind {
269        fn error_type(&self) -> ErrorType {
270            match self {
271                TestKind::Connect | TestKind::Disconnect => ErrorType::Client,
272                TestKind::ServiceError => ErrorType::Service,
273            }
274        }
275    }
276
277    #[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
278    enum TestError {
279        #[error("Connect err: {0}")]
280        Connect(&'static str),
281        #[error("Disconnect")]
282        Disconnect,
283        #[error("InternalServiceError")]
284        Service(&'static str),
285    }
286
287    impl ErrorDiagnostic for TestError {
288        type Kind = TestKind;
289
290        fn kind(&self) -> Self::Kind {
291            match self {
292                TestError::Connect(_) => TestKind::Connect,
293                TestError::Disconnect => TestKind::Disconnect,
294                TestError::Service(_) => TestKind::ServiceError,
295            }
296        }
297
298        fn service(&self) -> Option<&'static str> {
299            Some("test")
300        }
301
302        fn signature(&self) -> &'static str {
303            match self {
304                TestError::Connect(_) => "Client-Connect",
305                TestError::Disconnect => "Client-Disconnect",
306                TestError::Service(_) => "Service-Internal",
307            }
308        }
309    }
310
311    #[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
312    #[error("TestError2")]
313    struct TestError2;
314    impl ErrorDiagnostic for TestError2 {
315        type Kind = ErrorType;
316
317        fn kind(&self) -> Self::Kind {
318            ErrorType::Client
319        }
320    }
321
322    #[ntex::test]
323    async fn test_error() {
324        let err: Error<TestError> = TestError::Service("409 Error").into();
325        let err = err.clone();
326        assert_eq!(err.kind(), TestKind::ServiceError);
327        assert_eq!((*err).kind(), TestKind::ServiceError);
328        assert_eq!(err.to_string(), "InternalServiceError");
329        assert_eq!(err.service(), Some("test"));
330        assert_eq!(err.signature(), "Service-Internal");
331        assert_eq!(
332            err,
333            Into::<Error<TestError>>::into(TestError::Service("409 Error"))
334        );
335        assert!(err.backtrace().is_some());
336        assert!(err.is_service());
337        assert!(
338            format!("{:?}", err.source()).contains("Service(\"409 Error\")"),
339            "{:?}",
340            err.source().unwrap()
341        );
342
343        let err = err.set_service("SVC");
344        assert_eq!(err.service(), Some("SVC"));
345
346        let err2: Error<TestError> = Error::new(TestError::Service("409 Error"), "TEST");
347        assert!(err != err2);
348        assert_eq!(err, TestError::Service("409 Error"));
349
350        let err2 = err2.set_service("SVC");
351        assert_eq!(err, err2);
352        let err2 = err2.map(|_| TestError::Disconnect);
353        assert!(err != err2);
354
355        assert_eq!(
356            TestError::Connect("").kind().error_type(),
357            ErrorType::Client
358        );
359        assert_eq!(TestError::Disconnect.kind().error_type(), ErrorType::Client);
360        assert_eq!(
361            TestError::Service("").kind().error_type(),
362            ErrorType::Service
363        );
364        assert_eq!(TestError::Connect("").to_string(), "Connect err: ");
365        assert_eq!(TestError::Disconnect.to_string(), "Disconnect");
366        assert_eq!(TestError::Disconnect.service(), Some("test"));
367        assert!(TestError::Disconnect.backtrace().is_none());
368
369        assert_eq!(ErrorType::Client.as_str(), "ClientError");
370        assert_eq!(ErrorType::Service.as_str(), "ServiceError");
371        assert_eq!(ErrorType::Client.error_type(), ErrorType::Client);
372        assert_eq!(ErrorType::Service.error_type(), ErrorType::Service);
373        assert_eq!(ErrorType::Client.to_string(), "ClientError");
374        assert_eq!(ErrorType::Service.to_string(), "ServiceError");
375
376        assert_eq!(TestKind::Connect.to_string(), "Connect");
377        assert_eq!(TestError::Connect("").signature(), "Client-Connect");
378        assert_eq!(TestKind::Disconnect.to_string(), "Disconnect");
379        assert_eq!(TestError::Disconnect.signature(), "Client-Disconnect");
380        assert_eq!(TestKind::ServiceError.to_string(), "ServiceError");
381        assert_eq!(TestError::Service("").signature(), "Service-Internal");
382
383        let err = err.into_error().chain();
384        assert_eq!(err.kind(), TestKind::ServiceError);
385        assert_eq!(err.kind(), TestError::Service("409 Error").kind());
386        assert_eq!(err.to_string(), "InternalServiceError");
387        assert!(format!("{err:?}").contains("Service(\"409 Error\")"));
388        assert!(
389            format!("{:?}", err.source()).contains("Service(\"409 Error\")"),
390            "{:?}",
391            err.source().unwrap()
392        );
393
394        let err: Error<TestError> = TestError::Service("404 Error").into();
395        assert!(
396            format!("{}", err.backtrace().unwrap())
397                .contains("ntex_error::tests::test_error"),
398            "{}",
399            err.backtrace().unwrap()
400        );
401        assert!(
402            err.backtrace()
403                .unwrap()
404                .repr()
405                .contains("ntex_error::tests::test_error"),
406            "{}",
407            err.backtrace().unwrap()
408        );
409
410        let err: ErrorChain<TestKind> = err.into();
411        assert_eq!(err.kind(), TestKind::ServiceError);
412        assert_eq!(err.kind(), TestError::Service("404 Error").kind());
413        assert_eq!(err.service(), Some("test"));
414        assert_eq!(err.signature(), "Service-Internal");
415        assert_eq!(err.to_string(), "InternalServiceError");
416        assert!(err.backtrace().is_some());
417        assert!(format!("{err:?}").contains("Service(\"404 Error\")"));
418
419        assert_eq!(24, mem::size_of::<TestError>());
420        assert_eq!(8, mem::size_of::<Error<TestError>>());
421
422        assert_eq!(TestError2.service(), None);
423        assert_eq!(TestError2.signature(), "ClientError");
424
425        // ErrorInformation
426        let err: Error<TestError> = TestError::Service("409 Error").into();
427        let err: ErrorInformation = err.set_service("SVC").into();
428        assert_eq!(err.error_type(), ErrorType::Service);
429        assert_eq!(err.error_signature(), "ServiceError");
430        assert_eq!(err.service(), Some("SVC"));
431        assert_eq!(err.signature(), "Service-Internal");
432        assert!(err.backtrace().is_some());
433    }
434}