1#![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 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 fn kind(&self) -> Self::Kind;
48
49 fn service(&self) -> Option<&'static str> {
51 None
52 }
53
54 fn is_service(&self) -> bool {
56 self.kind().error_type() == ErrorType::Service
57 }
58
59 fn signature(&self) -> &'static str {
61 self.kind().error_type().as_str()
62 }
63
64 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 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 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 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 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}