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        if type_name::<T>() != "()" {
91            write!(f, "{}:{:?} ", type_name::<T>(), self.code)?;
92        }
93
94        if self.file.is_some() && self.line.is_some() {
95            let file = self.file.as_ref().unwrap();
96            let file = if let Some(pos) = file.rfind("src") {
97                if pos == 0 {
98                    file
99                } else if let Some(pos) = &file[..pos-1].rfind(&['/', '\\']) {
100                    &file[pos+1..]
101                } else {
102                    file
103                }
104            } else {
105                file
106            };
107            write!(f, "at:[{}:{}] ", file, self.line.as_ref().unwrap())?;
108        }
109
110        if !self.msg.is_empty() {
111            write!(f, "{}", self.msg)?;
112        }
113        if let Some(backtrace) = &self.backtrace {
114            if let BacktraceStatus::Captured = backtrace.status() {
115                let mut backtrace = backtrace.to_string();
116                write!(f, "\n")?;
117                if backtrace.starts_with("stack backtrace:") {
118                    // Capitalize to match "Caused by:"
119                    backtrace.replace_range(0..1, "S");
120                } else {
121                    // "stack backtrace:" prefix was removed in
122                    // https://github.com/rust-lang/backtrace-rs/pull/286
123                    writeln!(f, "Stack backtrace:")?;
124                }
125                backtrace.truncate(backtrace.trim_end().len());
126                write!(f, "{}", backtrace)?;
127            }
128        }
129        if self.source.is_some() {
130            write!(f, "\nCaused by: {:?}", self.source.as_ref().unwrap())?;
131        }
132        Ok(())
133    }
134}
135
136impl<T: Debug> Display for Error<T> {
137    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
138        if type_name::<T>() != "()" {
139            write!(f, "{}:{:?} ", type_name::<T>(), self.code)?;
140        }
141
142        if self.file.is_some() && self.line.is_some() {
143            let file = self.file.as_ref().unwrap();
144            let file = if let Some(pos) = file.rfind("src") {
145                if pos == 0 {
146                    file
147                } else if let Some(pos) = &file[..pos-1].rfind(&['/', '\\']) {
148                    &file[pos+1..]
149                } else {
150                    file
151                }
152            } else {
153                file
154            };
155            write!(f, "at:[{}:{}] ", file, self.line.as_ref().unwrap())?;
156        }
157
158        if !self.msg.is_empty() {
159            write!(f, "{}", self.msg)?;
160        }
161        if let Some(backtrace) = &self.backtrace {
162            if let BacktraceStatus::Captured = backtrace.status() {
163                let mut backtrace = backtrace.to_string();
164                write!(f, "\n")?;
165                if backtrace.starts_with("stack backtrace:") {
166                    // Capitalize to match "Caused by:"
167                    backtrace.replace_range(0..1, "S");
168                } else {
169                    // "stack backtrace:" prefix was removed in
170                    // https://github.com/rust-lang/backtrace-rs/pull/286
171                    writeln!(f, "Stack backtrace:")?;
172                }
173                backtrace.truncate(backtrace.trim_end().len());
174                write!(f, "{}", backtrace)?;
175            }
176        }
177        if self.source.is_some() {
178            write!(f, "\nCaused by: {:?}", self.source.as_ref().unwrap())?;
179        }
180        Ok(())
181    }
182}
183
184impl<T: Default> From<String> for Error<T> {
185    fn from(value: String) -> Self {
186        #[cfg(feature = "backtrace")]
187            let backtrace = Some(Backtrace::force_capture());
188
189        #[cfg(not(feature = "backtrace"))]
190            let backtrace = None;
191        Self {
192            code: Default::default(),
193            msg: value,
194            source: None,
195            backtrace,
196            file: None,
197            line: None,
198        }
199    }
200}
201
202impl<T, E: std::error::Error + 'static + Send + Sync> From<(T, String, E)> for Error<T> {
203    fn from(value: (T, String, E)) -> Self {
204        #[cfg(feature = "backtrace")]
205            let backtrace = Some(Backtrace::force_capture());
206
207        #[cfg(not(feature = "backtrace"))]
208            let backtrace = None;
209
210        Self {
211            code: value.0,
212            msg: value.1,
213            source: Some(Box::new(value.2)),
214            backtrace,
215            file: None,
216            line: None,
217        }
218    }
219}
220
221impl<T: Default, E: std::error::Error + 'static + Send + Sync> From<(String, E)> for Error<T> {
222    fn from(value: (String, E)) -> Self {
223        #[cfg(feature = "backtrace")]
224        let backtrace = Some(Backtrace::force_capture());
225
226        #[cfg(not(feature = "backtrace"))]
227        let backtrace = None;
228
229        Self {
230            code: Default::default(),
231            msg: value.0,
232            source: Some(Box::new(value.1)),
233            backtrace,
234            file: None,
235            line: None,
236        }
237    }
238}
239
240impl<T, E: std::error::Error + 'static + Send + Sync> From<(T, String, E, &str, u32)> for Error<T> {
241    fn from(value: (T, String, E, &str, u32)) -> Self {
242        #[cfg(feature = "backtrace")]
243        let backtrace = Some(Backtrace::force_capture());
244
245        #[cfg(not(feature = "backtrace"))]
246        let backtrace = None;
247
248        Self {
249            code: value.0,
250            msg: value.1,
251            source: Some(Box::new(value.2)),
252            backtrace,
253            file: Some(value.3.to_string()),
254            line: Some(value.4),
255        }
256    }
257}
258
259impl<T, E: std::error::Error + 'static + Send + Sync> From<(T, &str, E, &str, u32)> for Error<T> {
260    fn from(value: (T, &str, E, &str, u32)) -> Self {
261        #[cfg(feature = "backtrace")]
262            let backtrace = Some(Backtrace::force_capture());
263
264        #[cfg(not(feature = "backtrace"))]
265            let backtrace = None;
266        Self {
267            code: value.0,
268            msg: value.1.to_string(),
269            source: Some(Box::new(value.2)),
270            backtrace,
271            file: Some(value.3.to_string()),
272            line: Some(value.4),
273        }
274    }
275}
276
277#[cfg(feature = "log")]
278pub use log::error as serror;
279
280#[cfg(feature = "log")]
281#[macro_export]
282macro_rules! error {
283    // error!(target: "my_target", key1 = 42, key2 = true; "a {} event", "log")
284    // error!(target: "my_target", "a {} event", "log")
285    (target: $target:expr, $($arg:tt)+) => ($crate::serror!($target, $($arg)+));
286
287    // error!("a {} event", "log")
288    ($($arg:tt)+) => ($crate::serror!($($arg)+))
289}
290#[cfg(not(feature = "log"))]
291#[macro_export]
292macro_rules! error {
293    // error!(target: "my_target", key1 = 42, key2 = true; "a {} event", "log")
294    // error!(target: "my_target", "a {} event", "log")
295    (target: $target:expr, $($arg:tt)+) => ();
296
297    // error!("a {} event", "log")
298    ($($arg:tt)+) => ()
299}
300
301#[macro_export]
302macro_rules! err {
303    ( $fmt:literal ) => {{
304        $crate::error!($fmt);
305        $crate::Error::new2(
306            ::std::default::Default::default(),
307            format!($fmt),
308            file!(),
309            line!()
310        )
311    }};
312    ( $fmt:literal, $($arg:tt)* ) => {{
313        $crate::error!($fmt, $($arg)*);
314        $crate::Error::new2(
315            ::std::default::Default::default(),
316            format!($fmt, $($arg)*),
317            file!(),
318            line!()
319        )
320    }};
321    ( $err: path) => {
322        {
323            $crate::error!("{:?}", $err);
324            $crate::Error::new2($err, "".to_string(), file!(), line!())
325        }
326    };
327    ( $err:path, $($arg:tt)*) => {{
328        $crate::error!($($arg)*);
329        $crate::Error::new2($err, format!($($arg)*), file!(), line!())
330    }};
331}
332
333#[macro_export]
334macro_rules! into_err {
335    ( $fmt:literal ) => {
336        |e| {
337            $crate::error!("{} err:{:?}", format!($fmt), e);
338            let msg = format!($fmt);
339            $crate::Error::from((
340                ::std::default::Default::default(),
341                msg,
342                e,
343                file!(),
344                line!()))
345        }
346    };
347    ( $fmt:literal, $($arg:tt)* ) => {
348        |e| {
349            $crate::error!("{} err:{:?}", format!($fmt, $($arg)*), e);
350            let msg = format!($fmt, $($arg)*);
351            $crate::Error::from((
352                ::std::default::Default::default(),
353                msg,
354                e,
355                file!(),
356                line!()))
357        }
358    };
359    ($err: path) => {
360        |e| {
361            $crate::error!("err:{:?}", e);
362            $crate::Error::from(($err, "".to_string(), e, file!(), line!()))
363        }
364    };
365    ($err: path, $($arg:tt)*) => {
366        |e| {
367            $crate::error!("{} err:{:?}", format!($($arg)*), e);
368            $crate::Error::from(($err, format!($($arg)*), e, file!(), line!()))
369        }
370    };
371}
372
373#[cfg(test)]
374mod test {
375    use crate::test::TestCode::Test1;
376
377    #[derive(Copy, Clone, Debug, Eq, PartialEq, Default)]
378    pub enum TestCode {
379        #[default]
380        Test1,
381        Test2,
382    }
383    pub type Error = super::Error<TestCode>;
384    pub type TestResult<T> = super::Result<T, TestCode>;
385    pub type TestResult2<T> = super::Result<T, ()>;
386
387    fn test2() -> TestResult2<()> {
388        // let error: super::Error<()> = err!("test {}", 1);
389        Err(err!("test {}", 2))
390    }
391
392    #[derive(Debug)]
393    struct Test {
394        name: String,
395    }
396
397    impl Test {
398        pub fn new(name: String) -> Self {
399            Self { name }
400        }
401
402        pub fn test(&self) -> TestResult<()> {
403            let error = err!("test {:?}", self.name);
404            println!("{:?}", error);
405            Err(error)
406        }
407    }
408
409    #[test]
410    fn test() {
411        use crate as sfo_result;
412        let error = sfo_result::Error::new2(TestCode::Test2, "test".to_string(), file!(), line!());
413        println!("{:?}", error);
414
415        let error = err!(Test1, "test");
416        println!("{:?}", error);
417
418        let error = Error::from((TestCode::Test1, "test".to_string(), error));
419        println!("{:?}", error);
420
421        let error: Error = err!("test {}", 1);
422        println!("{:?}", error);
423        let error: Error = err!("test");
424        println!("{:?}", error);
425        let error: super::Error<()> = err!("test {}", 1);
426        println!("{:?}", error);
427
428        let ret: TestResult<()> = test2().map_err(into_err!(Test1, "test333"));
429        println!("{:?}", ret);
430        let ret: TestResult<()> = test2().map_err(into_err!(TestCode::Test1, "test333 {val}", val=1+1));
431        println!("{:?}", ret);
432        let t = "test333".to_string();
433        let ret: TestResult<()> = test2().map_err(into_err!("test333 {v}", v=t));
434        println!("{:?}", ret);
435        let test = Test::new("test".to_string());
436        let ret: TestResult<()> = test.test().map_err(into_err!("test333 {}", 1));
437        println!("{:?}", ret);
438        let ret: TestResult<()> = test.test().map_err(into_err!("test333"));
439        println!("{:?}", ret);
440        // assert_eq!(format!("{:?}", error), "Error: 1, msg: test");
441        // assert_eq!(format!("{}", error), "Error: 1, msg: test");
442    }
443}