1use std::{error::Error as StdError, fmt, fmt::Write, rc::Rc};
2
3use ntex_bytes::ByteString;
4
5use crate::{ErrorDiagnostic, ResultKind, ResultType};
6
7struct Wrt<'a> {
8 written: usize,
9 fmt: &'a mut dyn fmt::Write,
10}
11
12impl<'a> Wrt<'a> {
13 fn new(fmt: &'a mut dyn fmt::Write) -> Self {
14 Wrt { fmt, written: 0 }
15 }
16
17 fn wrote(&mut self) -> bool {
18 let res = self.written != 0;
19 self.written = 0;
20 res
21 }
22}
23
24impl fmt::Write for Wrt<'_> {
25 fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> {
26 self.written += s.len();
27 self.fmt.write_str(s)
28 }
29
30 fn write_char(&mut self, c: char) -> Result<(), fmt::Error> {
31 self.written += 1;
32 self.fmt.write_char(c)
33 }
34}
35
36pub fn fmt_err_string(e: &dyn StdError) -> String {
37 let mut buf = String::new();
38 _ = fmt_err(&mut buf, e);
39 buf
40}
41
42pub fn fmt_err(f: &mut dyn fmt::Write, e: &dyn StdError) -> fmt::Result {
43 let mut wrt = Wrt::new(f);
44 let mut current = Some(e);
45 while let Some(std_err) = current {
46 write!(&mut wrt, "{std_err}")?;
47 if wrt.wrote() {
48 writeln!(wrt.fmt)?;
49 }
50 current = std_err.source();
51 }
52 Ok(())
53}
54
55pub fn fmt_diag_string<K: ResultKind>(e: &dyn ErrorDiagnostic<Kind = K>) -> String {
56 let mut buf = String::new();
57 _ = fmt_diag(&mut buf, e);
58 buf
59}
60
61pub fn fmt_diag<K>(f: &mut dyn fmt::Write, e: &dyn ErrorDiagnostic<Kind = K>) -> fmt::Result
62where
63 K: ResultKind,
64{
65 let k = e.kind();
66 let tp = k.tp();
67
68 writeln!(f, "err: {e}")?;
69 writeln!(f, "type: {}", tp.as_str())?;
70 writeln!(f, "signature: {}", k.signature())?;
71
72 if let Some(svc) = e.service() {
73 writeln!(f, "service: {svc}")?;
74 }
75 writeln!(f)?;
76
77 let mut wrt = Wrt::new(f);
78 write!(&mut wrt, "{e:?}")?;
79 if wrt.wrote() {
80 writeln!(wrt.fmt)?;
81 }
82
83 let mut current = e.source();
84 while let Some(err) = current {
85 write!(&mut wrt, "{err:?}")?;
86 if wrt.wrote() {
87 writeln!(wrt.fmt)?;
88 }
89 current = err.source();
90 }
91
92 if tp == ResultType::ServiceError
93 && let Some(bt) = e.backtrace()
94 {
95 writeln!(wrt.fmt, "{bt}")?;
96 }
97
98 Ok(())
99}
100
101#[derive(Clone, PartialEq, Eq, thiserror::Error)]
102pub struct ErrorMessage(ByteString);
103
104#[derive(Clone)]
105pub struct ErrorMessageChained {
106 msg: ByteString,
107 source: Option<Rc<dyn StdError>>,
108}
109
110impl ErrorMessageChained {
111 pub fn new<M, E>(ctx: M, source: E) -> Self
112 where
113 M: Into<ErrorMessage>,
114 E: StdError + 'static,
115 {
116 ErrorMessageChained {
117 msg: ctx.into().into_string(),
118 source: Some(Rc::new(source)),
119 }
120 }
121
122 pub const fn from_bstr(msg: ByteString) -> Self {
124 Self { msg, source: None }
125 }
126
127 pub fn msg(&self) -> &ByteString {
128 &self.msg
129 }
130}
131
132impl ErrorMessage {
133 pub const fn empty() -> Self {
135 Self(ByteString::from_static(""))
136 }
137
138 pub const fn from_bstr(msg: ByteString) -> ErrorMessage {
140 ErrorMessage(msg)
141 }
142
143 pub const fn from_static(msg: &'static str) -> Self {
145 ErrorMessage(ByteString::from_static(msg))
146 }
147
148 pub fn is_empty(&self) -> bool {
149 self.0.is_empty()
150 }
151
152 pub fn as_str(&self) -> &str {
153 &self.0
154 }
155
156 pub fn as_bstr(&self) -> &ByteString {
157 &self.0
158 }
159
160 pub fn into_string(self) -> ByteString {
161 self.0
162 }
163
164 pub fn with_source<E: StdError + 'static>(self, source: E) -> ErrorMessageChained {
165 ErrorMessageChained::new(self, source)
166 }
167}
168
169impl From<String> for ErrorMessage {
170 fn from(value: String) -> Self {
171 Self(ByteString::from(value))
172 }
173}
174
175impl From<ByteString> for ErrorMessage {
176 fn from(value: ByteString) -> Self {
177 Self(value)
178 }
179}
180
181impl From<&'static str> for ErrorMessage {
182 fn from(value: &'static str) -> Self {
183 Self(ByteString::from_static(value))
184 }
185}
186
187impl fmt::Debug for ErrorMessage {
188 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
189 fmt::Display::fmt(&self.0, f)
190 }
191}
192
193impl fmt::Display for ErrorMessage {
194 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
195 fmt::Display::fmt(&self.0, f)
196 }
197}
198
199impl From<ErrorMessage> for ByteString {
200 fn from(msg: ErrorMessage) -> Self {
201 msg.0
202 }
203}
204
205impl<'a> From<&'a ErrorMessage> for ByteString {
206 fn from(msg: &'a ErrorMessage) -> Self {
207 msg.0.clone()
208 }
209}
210
211impl<M: Into<ErrorMessage>> From<M> for ErrorMessageChained {
212 fn from(value: M) -> Self {
213 ErrorMessageChained {
214 msg: value.into().0,
215 source: None,
216 }
217 }
218}
219
220impl StdError for ErrorMessageChained {
221 fn source(&self) -> Option<&(dyn StdError + 'static)> {
222 self.source.as_ref().map(AsRef::as_ref)
223 }
224}
225
226impl fmt::Debug for ErrorMessageChained {
227 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
228 fmt::Display::fmt(&self, f)
229 }
230}
231
232impl fmt::Display for ErrorMessageChained {
233 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
234 if self.msg.is_empty() {
235 Ok(())
236 } else {
237 fmt::Display::fmt(&self.msg, f)
238 }
239 }
240}
241
242#[cfg(test)]
243#[allow(dead_code)]
244mod tests {
245 use std::io;
246
247 use super::*;
248
249 #[test]
250 fn error_message() {
251 let msg = ErrorMessage::empty();
252 assert!(msg.is_empty());
253 assert_eq!(msg.as_str(), "");
254 assert_eq!(msg.as_bstr(), ByteString::new());
255 assert_eq!(ByteString::new(), msg.as_bstr());
256 assert_eq!(ByteString::new(), msg.into_string());
257
258 let msg = ErrorMessage::from("test");
259 assert!(!msg.is_empty());
260 assert_eq!(format!("{msg}"), "test");
261 assert_eq!(msg.as_str(), "test");
262 assert_eq!(msg.as_bstr(), ByteString::from("test"));
263
264 let msg = ErrorMessage::from("test".to_string());
265 assert!(!msg.is_empty());
266 assert_eq!(msg.as_str(), "test");
267 assert_eq!(msg.as_bstr(), ByteString::from("test"));
268
269 let msg = ErrorMessage::from_bstr(ByteString::from("test"));
270 assert!(!msg.is_empty());
271 assert_eq!(msg.as_str(), "test");
272 assert_eq!(msg.as_bstr(), ByteString::from("test"));
273
274 let msg = ErrorMessage::from(ByteString::from("test"));
275 assert!(!msg.is_empty());
276 assert_eq!(msg.as_str(), "test");
277 assert_eq!(msg.as_bstr(), ByteString::from("test"));
278
279 let msg = ErrorMessage::from_static("test");
280 assert!(!msg.is_empty());
281 assert_eq!(msg.as_str(), "test");
282 assert_eq!(msg.as_bstr(), ByteString::from("test"));
283
284 assert_eq!(ByteString::from(&msg), "test");
285 assert_eq!(ByteString::from(msg), "test");
286 }
287
288 #[test]
289 fn error_message_chained() {
290 let chained = ErrorMessageChained::from(ByteString::from("test"));
291 assert_eq!(chained.msg(), "test");
292 assert!(chained.source().is_none());
293
294 let chained = ErrorMessageChained::from_bstr(ByteString::from("test"));
295 assert_eq!(chained.msg(), "test");
296 assert!(chained.source().is_none());
297 assert_eq!(format!("{chained}"), "test");
298 assert_eq!(format!("{chained:?}"), "test");
299
300 let err = ErrorMessageChained::new("test", io::Error::other("io-test"));
301 let msg = fmt_err_string(&err);
302 assert_eq!(msg, "test\nio-test\n");
303 }
304
305 #[derive(thiserror::Error, derive_more::Debug)]
306 enum TestError {
307 #[error("Disconnect")]
308 #[debug("")]
309 Disconnect(#[source] io::Error),
310 #[error("InternalServiceError")]
311 #[debug("InternalServiceError {_0}")]
312 Service(&'static str),
313 }
314
315 impl Clone for TestError {
316 fn clone(&self) -> Self {
317 match self {
318 TestError::Service(msg) => TestError::Service(msg),
319 TestError::Disconnect(err) => {
320 TestError::Disconnect(io::Error::other(format!("{err:?}")))
321 }
322 }
323 }
324 }
325
326 impl ErrorDiagnostic for TestError {
327 type Kind = ResultType;
328
329 fn kind(&self) -> Self::Kind {
330 match self {
331 TestError::Service(_) => ResultType::ServiceError,
332 TestError::Disconnect(_) => ResultType::ClientError,
333 }
334 }
335 }
336
337 #[test]
338 fn fmt_diag() {
339 let err = TestError::Service("409 Error");
340
341 let msg = fmt_err_string(&err);
342 assert_eq!(msg, "InternalServiceError\n");
343
344 let err = TestError::Disconnect(io::Error::other("Test io error"));
345 let msg = fmt_diag_string(&err);
346 assert!(msg.contains("Test io error"));
347
348 assert!(
349 format!("{:?}", err.source()).contains("Test io erro"),
350 "{:?}",
351 err.source().unwrap()
352 );
353 }
354}