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