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}
18
19#[cfg(not(feature = "serde"))]
20pub struct Error<T> {
21    code: T,
22    msg: String,
23    source: Option<Box<(dyn std::error::Error + 'static + Send + Sync)>>,
24    backtrace: Option<Backtrace>,
25}
26
27pub type Result<T, C> = std::result::Result<T, Error<C>>;
28
29impl<T: Debug + Copy + Sync + Send + 'static> Error<T> {
30    pub fn new(code: T, msg: String) -> Self {
31        #[cfg(feature = "backtrace")]
32        let backtrace = Some(Backtrace::force_capture());
33
34        #[cfg(not(feature = "backtrace"))]
35        let backtrace = None;
36
37        Self {
38            code,
39            msg,
40            source: None,
41            backtrace,
42        }
43    }
44
45    pub fn code(&self) -> T {
46        self.code
47    }
48
49    pub fn msg(&self) -> &str {
50        &self.msg
51    }
52
53    #[cfg(feature = "backtrace")]
54    pub fn backtrace(&self) -> Option<&Backtrace> {
55        self.backtrace.as_ref()
56    }
57}
58
59impl<T: Debug + Clone + Copy> std::error::Error for Error<T> {
60    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
61        self.source.as_ref().map(|e| e.as_ref() as _)
62    }
63}
64
65impl<T: Debug> Debug for Error<T> {
66    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67        write!(f, "{}:{:?}", type_name::<T>(), self.code)?;
68        if !self.msg.is_empty() {
69            write!(f, ", msg:{}", self.msg)?;
70        }
71        if self.source.is_some() {
72            write!(f, "\nCaused by: {:?}", self.source.as_ref().unwrap())?;
73        }
74        if let Some(backtrace) = &self.backtrace {
75            if let BacktraceStatus::Captured = backtrace.status() {
76                let mut backtrace = backtrace.to_string();
77                write!(f, "\n")?;
78                if backtrace.starts_with("stack backtrace:") {
79                    // Capitalize to match "Caused by:"
80                    backtrace.replace_range(0..1, "S");
81                } else {
82                    // "stack backtrace:" prefix was removed in
83                    // https://github.com/rust-lang/backtrace-rs/pull/286
84                    writeln!(f, "Stack backtrace:")?;
85                }
86                backtrace.truncate(backtrace.trim_end().len());
87                write!(f, "{}", backtrace)?;
88            }
89        }
90        Ok(())
91    }
92}
93
94impl<T: Debug> Display for Error<T> {
95    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96        write!(f, "{}:{:?}", type_name::<T>(), self.code)?;
97        if !self.msg.is_empty() {
98            write!(f, ", msg:{}", self.msg)?;
99        }
100        if self.source.is_some() {
101            write!(f, "\nCaused by: {:?}", self.source.as_ref().unwrap())?;
102        }
103        if let Some(backtrace) = &self.backtrace {
104            if let BacktraceStatus::Captured = backtrace.status() {
105                let mut backtrace = backtrace.to_string();
106                write!(f, "\n")?;
107                if backtrace.starts_with("stack backtrace:") {
108                    // Capitalize to match "Caused by:"
109                    backtrace.replace_range(0..1, "S");
110                } else {
111                    // "stack backtrace:" prefix was removed in
112                    // https://github.com/rust-lang/backtrace-rs/pull/286
113                    writeln!(f, "Stack backtrace:")?;
114                }
115                backtrace.truncate(backtrace.trim_end().len());
116                write!(f, "{}", backtrace)?;
117            }
118        }
119        Ok(())
120    }
121}
122
123impl<T: Default> From<String> for Error<T> {
124    fn from(value: String) -> Self {
125        #[cfg(feature = "backtrace")]
126            let backtrace = Some(Backtrace::force_capture());
127
128        #[cfg(not(feature = "backtrace"))]
129            let backtrace = None;
130        Self {
131            code: Default::default(),
132            msg: value,
133            source: None,
134            backtrace,
135        }
136    }
137}
138
139impl<T, E: std::error::Error + 'static + Send + Sync> From<(T, String, E)> for Error<T> {
140    fn from(value: (T, String, E)) -> Self {
141        #[cfg(feature = "backtrace")]
142            let backtrace = Some(Backtrace::force_capture());
143
144        #[cfg(not(feature = "backtrace"))]
145            let backtrace = None;
146        Self {
147            code: value.0,
148            msg: value.1,
149            source: Some(Box::new(value.2)),
150            backtrace,
151        }
152    }
153}
154
155impl<T, E: std::error::Error + 'static + Send + Sync> From<(T, &str, E)> for Error<T> {
156    fn from(value: (T, &str, E)) -> Self {
157        #[cfg(feature = "backtrace")]
158            let backtrace = Some(Backtrace::force_capture());
159
160        #[cfg(not(feature = "backtrace"))]
161            let backtrace = None;
162        Self {
163            code: value.0,
164            msg: value.1.to_string(),
165            source: Some(Box::new(value.2)),
166            backtrace,
167        }
168    }
169}
170
171#[macro_export]
172macro_rules! err {
173    ( $err: expr) => {
174        {
175            log::error!("{:?}", $err);
176            sfo_result::Error::new($err, "".to_string())
177        }
178    };
179    ( $err: expr, $($arg:tt)*) => {
180        {
181            log::error!("{}", format!($($arg)*));
182            sfo_result::Error::new($err, format!("{}", format!($($arg)*)))
183        }
184    };
185}
186
187#[macro_export]
188macro_rules! into_err {
189    ($err: expr) => {
190        |e| {
191            log::error!("err:{:?}", e);
192            sfo_result::Error::from(($err, "".to_string(), e))
193        }
194    };
195    ($err: expr, $($arg:tt)*) => {
196        |e| {
197            log::error!("{} err:{:?}", format!($($arg)*), e);
198            sfo_result::Error::from(($err, format!($($arg)*), e))
199        }
200    };
201}
202
203#[cfg(test)]
204mod test {
205    #[derive(Copy, Clone, Debug, Eq, PartialEq, Default)]
206    pub enum TestCode {
207        #[default]
208        Test1,
209        Test2,
210    }
211    pub type Error = super::Error<TestCode>;
212
213    #[test]
214    fn test() {
215        use crate as sfo_result;
216        let error = sfo_result::Error::new(1, "test".to_string());
217        println!("{:?}", error);
218
219        let error = err!(1, "test");
220        println!("{:?}", error);
221
222        let error = Error::from((TestCode::Test1, "test".to_string(), error));
223        println!("{:?}", error);
224
225        // assert_eq!(format!("{:?}", error), "Error: 1, msg: test");
226        // assert_eq!(format!("{}", error), "Error: 1, msg: test");
227    }
228}