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, file: &str, line: u32) -> 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: Some(file.to_string()),
47            line: Some(line),
48        }
49    }
50
51    pub fn code(&self) -> T {
52        self.code
53    }
54
55    pub fn msg(&self) -> &str {
56        &self.msg
57    }
58
59    #[cfg(feature = "backtrace")]
60    pub fn backtrace(&self) -> Option<&Backtrace> {
61        self.backtrace.as_ref()
62    }
63}
64
65impl<T: Debug + Clone + Copy> std::error::Error for Error<T> {
66    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
67        self.source.as_ref().map(|e| e.as_ref() as _)
68    }
69}
70
71impl<T: Debug> Debug for Error<T> {
72    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73        write!(f, "{}:{:?}", type_name::<T>(), self.code)?;
74
75        if self.file.is_some() && self.line.is_some() {
76            write!(f, " at:[{}:{}]", self.file.as_ref().unwrap(), self.line.as_ref().unwrap())?;
77        }
78
79        if !self.msg.is_empty() {
80            write!(f, ", msg:{}", self.msg)?;
81        }
82        if let Some(backtrace) = &self.backtrace {
83            if let BacktraceStatus::Captured = backtrace.status() {
84                let mut backtrace = backtrace.to_string();
85                write!(f, "\n")?;
86                if backtrace.starts_with("stack backtrace:") {
87                    // Capitalize to match "Caused by:"
88                    backtrace.replace_range(0..1, "S");
89                } else {
90                    // "stack backtrace:" prefix was removed in
91                    // https://github.com/rust-lang/backtrace-rs/pull/286
92                    writeln!(f, "Stack backtrace:")?;
93                }
94                backtrace.truncate(backtrace.trim_end().len());
95                write!(f, "{}", backtrace)?;
96            }
97        }
98        if self.source.is_some() {
99            write!(f, "\nCaused by: {:?}", self.source.as_ref().unwrap())?;
100        }
101        Ok(())
102    }
103}
104
105impl<T: Debug> Display for Error<T> {
106    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107        write!(f, "{}:{:?}", type_name::<T>(), self.code)?;
108
109        if self.file.is_some() && self.line.is_some() {
110            write!(f, " at:[{}:{}]", self.file.as_ref().unwrap(), self.line.as_ref().unwrap())?;
111        }
112
113        if !self.msg.is_empty() {
114            write!(f, ", msg:{}", self.msg)?;
115        }
116        if let Some(backtrace) = &self.backtrace {
117            if let BacktraceStatus::Captured = backtrace.status() {
118                let mut backtrace = backtrace.to_string();
119                write!(f, "\n")?;
120                if backtrace.starts_with("stack backtrace:") {
121                    // Capitalize to match "Caused by:"
122                    backtrace.replace_range(0..1, "S");
123                } else {
124                    // "stack backtrace:" prefix was removed in
125                    // https://github.com/rust-lang/backtrace-rs/pull/286
126                    writeln!(f, "Stack backtrace:")?;
127                }
128                backtrace.truncate(backtrace.trim_end().len());
129                write!(f, "{}", backtrace)?;
130            }
131        }
132        if self.source.is_some() {
133            write!(f, "\nCaused by: {:?}", self.source.as_ref().unwrap())?;
134        }
135        Ok(())
136    }
137}
138
139impl<T: Default> From<String> for Error<T> {
140    fn from(value: String) -> 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: Default::default(),
148            msg: value,
149            source: None,
150            backtrace,
151            file: None,
152            line: None,
153        }
154    }
155}
156
157impl<T, E: std::error::Error + 'static + Send + Sync> From<(T, String, E)> for Error<T> {
158    fn from(value: (T, String, E)) -> Self {
159        #[cfg(feature = "backtrace")]
160            let backtrace = Some(Backtrace::force_capture());
161
162        #[cfg(not(feature = "backtrace"))]
163            let backtrace = None;
164
165        Self {
166            code: value.0,
167            msg: value.1,
168            source: Some(Box::new(value.2)),
169            backtrace,
170            file: None,
171            line: None,
172        }
173    }
174}
175
176impl<T, E: std::error::Error + 'static + Send + Sync> From<(T, String, E, &str, u32)> for Error<T> {
177    fn from(value: (T, String, E, &str, u32)) -> Self {
178        #[cfg(feature = "backtrace")]
179        let backtrace = Some(Backtrace::force_capture());
180
181        #[cfg(not(feature = "backtrace"))]
182        let backtrace = None;
183
184        Self {
185            code: value.0,
186            msg: value.1,
187            source: Some(Box::new(value.2)),
188            backtrace,
189            file: Some(value.3.to_string()),
190            line: Some(value.4),
191        }
192    }
193}
194
195impl<T, E: std::error::Error + 'static + Send + Sync> From<(T, &str, E, &str, u32)> for Error<T> {
196    fn from(value: (T, &str, E, &str, u32)) -> Self {
197        #[cfg(feature = "backtrace")]
198            let backtrace = Some(Backtrace::force_capture());
199
200        #[cfg(not(feature = "backtrace"))]
201            let backtrace = None;
202        Self {
203            code: value.0,
204            msg: value.1.to_string(),
205            source: Some(Box::new(value.2)),
206            backtrace,
207            file: Some(value.3.to_string()),
208            line: Some(value.4),
209        }
210    }
211}
212
213#[cfg(feature = "log")]
214pub use log::error as serror;
215
216#[cfg(feature = "log")]
217#[macro_export]
218macro_rules! error {
219    // error!(target: "my_target", key1 = 42, key2 = true; "a {} event", "log")
220    // error!(target: "my_target", "a {} event", "log")
221    (target: $target:expr, $($arg:tt)+) => ($crate::serror!($target, $($arg)+));
222
223    // error!("a {} event", "log")
224    ($($arg:tt)+) => ($crate::serror!($($arg)+))
225}
226#[cfg(not(feature = "log"))]
227#[macro_export]
228macro_rules! error {
229    // error!(target: "my_target", key1 = 42, key2 = true; "a {} event", "log")
230    // error!(target: "my_target", "a {} event", "log")
231    (target: $target:expr, $($arg:tt)+) => ();
232
233    // error!("a {} event", "log")
234    ($($arg:tt)+) => ()
235}
236
237#[macro_export]
238macro_rules! err {
239    ( $err: expr) => {
240        {
241            $crate::error!("{:?}", $err);
242            $crate::Error::new($err, "".to_string(), file!(), line!())
243        }
244    };
245    ( $err: expr, $($arg:tt)*) => {
246        {
247            $crate::error!("{}", format!($($arg)*));
248            $crate::Error::new($err, format!("{}", format!($($arg)*)), file!(), line!())
249        }
250    };
251}
252
253#[macro_export]
254macro_rules! into_err {
255    ($err: expr) => {
256        |e| {
257            $crate::error!("err:{:?}", e);
258            $crate::Error::from(($err, "".to_string(), e, file!(), line!()))
259        }
260    };
261    ($err: expr, $($arg:tt)*) => {
262        |e| {
263            $crate::error!("{} err:{:?}", format!($($arg)*), e);
264            $crate::Error::from(($err, format!($($arg)*), e, file!(), line!()))
265        }
266    };
267}
268
269#[cfg(test)]
270mod test {
271    #[derive(Copy, Clone, Debug, Eq, PartialEq, Default)]
272    pub enum TestCode {
273        #[default]
274        Test1,
275        Test2,
276    }
277    pub type Error = super::Error<TestCode>;
278
279    #[test]
280    fn test() {
281        use crate as sfo_result;
282        let error = sfo_result::Error::new(1, "test".to_string(), file!(), line!());
283        println!("{:?}", error);
284
285        let error = err!(1, "test");
286        println!("{:?}", error);
287
288        let error = Error::from((TestCode::Test1, "test".to_string(), error));
289        println!("{:?}", error);
290
291        // assert_eq!(format!("{:?}", error), "Error: 1, msg: test");
292        // assert_eq!(format!("{}", error), "Error: 1, msg: test");
293    }
294}