sfo_result/
lib.rs

1use std::any::{type_name};
2use std::backtrace::{Backtrace, BacktraceStatus};
3use std::fmt::{Debug, Display};
4
5#[cfg(feature = "serde")]
6use serde::{Deserialize, Serialize};
7
8#[cfg(feature = "serde")]
9#[derive(Serialize, Deserialize)]
10pub struct Error<T> {
11    code: T,
12    msg: String,
13    #[serde(skip)]
14    source: Option<Box<(dyn std::error::Error + 'static + Send + Sync)>>,
15    #[serde(skip)]
16    backtrace: Option<Backtrace>,
17    file: Option<String>,
18    line: Option<u32>,
19}
20
21#[cfg(not(feature = "serde"))]
22pub struct Error<T> {
23    code: T,
24    msg: String,
25    source: Option<Box<(dyn std::error::Error + 'static + Send + Sync)>>,
26    backtrace: Option<Backtrace>,
27    file: Option<String>,
28    line: Option<u32>,
29}
30
31pub type Result<T, C> = std::result::Result<T, Error<C>>;
32
33impl<T: Debug + Copy + Sync + Send + 'static> Error<T> {
34    pub fn new(code: T, msg: String) -> Self {
35        #[cfg(feature = "backtrace")]
36        let backtrace = Some(Backtrace::force_capture());
37
38        #[cfg(not(feature = "backtrace"))]
39        let backtrace = None;
40
41        Self {
42            code,
43            msg,
44            source: None,
45            backtrace,
46            file: None,
47            line: None,
48        }
49    }
50
51    pub fn new2(code: T, msg: String, file: &str, line: u32) -> Self {
52        #[cfg(feature = "backtrace")]
53        let backtrace = Some(Backtrace::force_capture());
54
55        #[cfg(not(feature = "backtrace"))]
56        let backtrace = None;
57
58        Self {
59            code,
60            msg,
61            source: None,
62            backtrace,
63            file: Some(file.to_string()),
64            line: Some(line),
65        }
66    }
67
68    pub fn code(&self) -> T {
69        self.code
70    }
71
72    pub fn msg(&self) -> &str {
73        &self.msg
74    }
75
76    #[cfg(feature = "backtrace")]
77    pub fn backtrace(&self) -> Option<&Backtrace> {
78        self.backtrace.as_ref()
79    }
80}
81
82impl<T: Debug + Clone + Copy> std::error::Error for Error<T> {
83    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
84        self.source.as_ref().map(|e| e.as_ref() as _)
85    }
86}
87
88impl<T: Debug> Debug for Error<T> {
89    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90        write!(f, "{}:{:?}", type_name::<T>(), self.code)?;
91
92        if self.file.is_some() && self.line.is_some() {
93            let file = self.file.as_ref().unwrap();
94            let file = if let Some(pos) = file.rfind("src") {
95                if pos == 0 {
96                    file
97                } else if let Some(pos) = &file[..pos-1].rfind(&['/', '\\']) {
98                    &file[pos+1..]
99                } else {
100                    file
101                }
102            } else {
103                file
104            };
105            write!(f, " at:[{}:{}]", file, self.line.as_ref().unwrap())?;
106        }
107
108        if !self.msg.is_empty() {
109            write!(f, ", msg:{}", self.msg)?;
110        }
111        if let Some(backtrace) = &self.backtrace {
112            if let BacktraceStatus::Captured = backtrace.status() {
113                let mut backtrace = backtrace.to_string();
114                write!(f, "\n")?;
115                if backtrace.starts_with("stack backtrace:") {
116                    // Capitalize to match "Caused by:"
117                    backtrace.replace_range(0..1, "S");
118                } else {
119                    // "stack backtrace:" prefix was removed in
120                    // https://github.com/rust-lang/backtrace-rs/pull/286
121                    writeln!(f, "Stack backtrace:")?;
122                }
123                backtrace.truncate(backtrace.trim_end().len());
124                write!(f, "{}", backtrace)?;
125            }
126        }
127        if self.source.is_some() {
128            write!(f, "\nCaused by: {:?}", self.source.as_ref().unwrap())?;
129        }
130        Ok(())
131    }
132}
133
134impl<T: Debug> Display for Error<T> {
135    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
136        write!(f, "{}:{:?}", type_name::<T>(), self.code)?;
137
138        if self.file.is_some() && self.line.is_some() {
139            let file = self.file.as_ref().unwrap();
140            let file = if let Some(pos) = file.rfind("src") {
141                if pos == 0 {
142                    file
143                } else if let Some(pos) = &file[..pos-1].rfind(&['/', '\\']) {
144                    &file[pos+1..]
145                } else {
146                    file
147                }
148            } else {
149                file
150            };
151            write!(f, " at:[{}:{}]", file, self.line.as_ref().unwrap())?;
152        }
153
154        if !self.msg.is_empty() {
155            write!(f, ", msg:{}", self.msg)?;
156        }
157        if let Some(backtrace) = &self.backtrace {
158            if let BacktraceStatus::Captured = backtrace.status() {
159                let mut backtrace = backtrace.to_string();
160                write!(f, "\n")?;
161                if backtrace.starts_with("stack backtrace:") {
162                    // Capitalize to match "Caused by:"
163                    backtrace.replace_range(0..1, "S");
164                } else {
165                    // "stack backtrace:" prefix was removed in
166                    // https://github.com/rust-lang/backtrace-rs/pull/286
167                    writeln!(f, "Stack backtrace:")?;
168                }
169                backtrace.truncate(backtrace.trim_end().len());
170                write!(f, "{}", backtrace)?;
171            }
172        }
173        if self.source.is_some() {
174            write!(f, "\nCaused by: {:?}", self.source.as_ref().unwrap())?;
175        }
176        Ok(())
177    }
178}
179
180impl<T: Default> From<String> for Error<T> {
181    fn from(value: String) -> Self {
182        #[cfg(feature = "backtrace")]
183            let backtrace = Some(Backtrace::force_capture());
184
185        #[cfg(not(feature = "backtrace"))]
186            let backtrace = None;
187        Self {
188            code: Default::default(),
189            msg: value,
190            source: None,
191            backtrace,
192            file: None,
193            line: None,
194        }
195    }
196}
197
198impl<T, E: std::error::Error + 'static + Send + Sync> From<(T, String, E)> for Error<T> {
199    fn from(value: (T, String, E)) -> Self {
200        #[cfg(feature = "backtrace")]
201            let backtrace = Some(Backtrace::force_capture());
202
203        #[cfg(not(feature = "backtrace"))]
204            let backtrace = None;
205
206        Self {
207            code: value.0,
208            msg: value.1,
209            source: Some(Box::new(value.2)),
210            backtrace,
211            file: None,
212            line: None,
213        }
214    }
215}
216
217impl<T, E: std::error::Error + 'static + Send + Sync> From<(T, String, E, &str, u32)> for Error<T> {
218    fn from(value: (T, String, E, &str, u32)) -> Self {
219        #[cfg(feature = "backtrace")]
220        let backtrace = Some(Backtrace::force_capture());
221
222        #[cfg(not(feature = "backtrace"))]
223        let backtrace = None;
224
225        Self {
226            code: value.0,
227            msg: value.1,
228            source: Some(Box::new(value.2)),
229            backtrace,
230            file: Some(value.3.to_string()),
231            line: Some(value.4),
232        }
233    }
234}
235
236impl<T, E: std::error::Error + 'static + Send + Sync> From<(T, &str, E, &str, u32)> for Error<T> {
237    fn from(value: (T, &str, E, &str, u32)) -> Self {
238        #[cfg(feature = "backtrace")]
239            let backtrace = Some(Backtrace::force_capture());
240
241        #[cfg(not(feature = "backtrace"))]
242            let backtrace = None;
243        Self {
244            code: value.0,
245            msg: value.1.to_string(),
246            source: Some(Box::new(value.2)),
247            backtrace,
248            file: Some(value.3.to_string()),
249            line: Some(value.4),
250        }
251    }
252}
253
254#[cfg(feature = "log")]
255pub use log::error as serror;
256
257#[cfg(feature = "log")]
258#[macro_export]
259macro_rules! error {
260    // error!(target: "my_target", key1 = 42, key2 = true; "a {} event", "log")
261    // error!(target: "my_target", "a {} event", "log")
262    (target: $target:expr, $($arg:tt)+) => ($crate::serror!($target, $($arg)+));
263
264    // error!("a {} event", "log")
265    ($($arg:tt)+) => ($crate::serror!($($arg)+))
266}
267#[cfg(not(feature = "log"))]
268#[macro_export]
269macro_rules! error {
270    // error!(target: "my_target", key1 = 42, key2 = true; "a {} event", "log")
271    // error!(target: "my_target", "a {} event", "log")
272    (target: $target:expr, $($arg:tt)+) => ();
273
274    // error!("a {} event", "log")
275    ($($arg:tt)+) => ()
276}
277
278#[macro_export]
279macro_rules! err {
280    ( $err: expr) => {
281        {
282            $crate::error!("{:?}", $err);
283            $crate::Error::new2($err, "".to_string(), file!(), line!())
284        }
285    };
286    ( $err: expr, $($arg:tt)*) => {
287        {
288            $crate::error!("{}", format!($($arg)*));
289            $crate::Error::new2($err, format!("{}", format!($($arg)*)), file!(), line!())
290        }
291    };
292}
293
294#[macro_export]
295macro_rules! into_err {
296    ($err: expr) => {
297        |e| {
298            $crate::error!("err:{:?}", e);
299            $crate::Error::from(($err, "".to_string(), e, file!(), line!()))
300        }
301    };
302    ($err: expr, $($arg:tt)*) => {
303        |e| {
304            $crate::error!("{} err:{:?}", format!($($arg)*), e);
305            $crate::Error::from(($err, format!($($arg)*), e, file!(), line!()))
306        }
307    };
308}
309
310#[cfg(test)]
311mod test {
312    #[derive(Copy, Clone, Debug, Eq, PartialEq, Default)]
313    pub enum TestCode {
314        #[default]
315        Test1,
316        Test2,
317    }
318    pub type Error = super::Error<TestCode>;
319
320    #[test]
321    fn test() {
322        use crate as sfo_result;
323        let error = sfo_result::Error::new2(1, "test".to_string(), file!(), line!());
324        println!("{:?}", error);
325
326        let error = err!(1, "test");
327        println!("{:?}", error);
328
329        let error = Error::from((TestCode::Test1, "test".to_string(), error));
330        println!("{:?}", error);
331
332        // assert_eq!(format!("{:?}", error), "Error: 1, msg: test");
333        // assert_eq!(format!("{}", error), "Error: 1, msg: test");
334    }
335}