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#[cfg(feature = "log")]
172pub use log::error as serror;
173
174#[cfg(feature = "log")]
175#[macro_export]
176macro_rules! error {
177    // error!(target: "my_target", key1 = 42, key2 = true; "a {} event", "log")
178    // error!(target: "my_target", "a {} event", "log")
179    (target: $target:expr, $($arg:tt)+) => ($crate::serror!($target, $($arg)+));
180
181    // error!("a {} event", "log")
182    ($($arg:tt)+) => ($crate::serror!($($arg)+))
183}
184#[cfg(not(feature = "log"))]
185#[macro_export]
186macro_rules! error {
187    // error!(target: "my_target", key1 = 42, key2 = true; "a {} event", "log")
188    // error!(target: "my_target", "a {} event", "log")
189    (target: $target:expr, $($arg:tt)+) => ();
190
191    // error!("a {} event", "log")
192    ($($arg:tt)+) => ()
193}
194
195#[macro_export]
196macro_rules! err {
197    ( $err: expr) => {
198        {
199            $crate::error!("{:?}", $err);
200            $crate::Error::new($err, "".to_string())
201        }
202    };
203    ( $err: expr, $($arg:tt)*) => {
204        {
205            $crate::error!("{}", format!($($arg)*));
206            $crate::Error::new($err, format!("{}", format!($($arg)*)))
207        }
208    };
209}
210
211#[macro_export]
212macro_rules! into_err {
213    ($err: expr) => {
214        |e| {
215            $crate::error!("err:{:?}", e);
216            $crate::Error::from(($err, "".to_string(), e))
217        }
218    };
219    ($err: expr, $($arg:tt)*) => {
220        |e| {
221            $crate::error!("{} err:{:?}", format!($($arg)*), e);
222            $crate::Error::from(($err, format!($($arg)*), e))
223        }
224    };
225}
226
227#[cfg(test)]
228mod test {
229    #[derive(Copy, Clone, Debug, Eq, PartialEq, Default)]
230    pub enum TestCode {
231        #[default]
232        Test1,
233        Test2,
234    }
235    pub type Error = super::Error<TestCode>;
236
237    #[test]
238    fn test() {
239        use crate as sfo_result;
240        let error = sfo_result::Error::new(1, "test".to_string());
241        println!("{:?}", error);
242
243        let error = err!(1, "test");
244        println!("{:?}", error);
245
246        let error = Error::from((TestCode::Test1, "test".to_string(), error));
247        println!("{:?}", error);
248
249        // assert_eq!(format!("{:?}", error), "Error: 1, msg: test");
250        // assert_eq!(format!("{}", error), "Error: 1, msg: test");
251    }
252}