btparse_stable/
lib.rs

1//! An error utility library for deserializing `std::backtrace::Backtrace`'s
2//! based on its `Debug` format.
3#![doc(html_root_url = "https://docs.rs/btparse/0.1.1")]
4#![allow(clippy::try_err)]
5use std::fmt;
6
7mod deser;
8
9/// A deserialized Backtrace.
10///
11/// # Example
12///
13/// ```rust
14/// #![feature(backtrace)]
15///
16/// let backtrace = std::backtrace::Backtrace::force_capture();
17/// let backtrace = btparse::deserialize(&backtrace).unwrap();
18/// for frame in &backtrace.frames {
19///     println!("{:?}", frame);
20/// }
21/// ```
22#[derive(Debug)]
23#[non_exhaustive]
24pub struct Backtrace {
25    pub frames: Vec<Frame>,
26}
27
28/// A backtrace frame.
29#[derive(Debug, PartialEq)]
30pub struct Frame {
31    pub function: String,
32    pub file: Option<String>,
33    pub line: Option<usize>,
34}
35
36/// An error that prevented a backtrace from being deserialized.
37#[derive(Debug)]
38pub struct Error {
39    kind: Kind,
40}
41
42#[derive(Debug)]
43enum Kind {
44    Disabled,
45    Empty,
46    Unsupported,
47    UnexpectedInput(String),
48    InvalidInput { expected: String, found: String },
49    LineParse(String, std::num::ParseIntError),
50}
51
52impl fmt::Display for Error {
53    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54        writeln!(f, "{}", self.kind)
55    }
56}
57
58impl fmt::Display for Kind {
59    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60        match self {
61            Self::Disabled => write!(f, "backtrace capture disabled"),
62            Self::Empty => write!(f, "input is empty"),
63            Self::Unsupported => write!(f, "backtrace capture unsupported on this platform"),
64            Self::UnexpectedInput(input) => write!(f, "encountered unexpected input: {:?}", input),
65            Self::InvalidInput { expected, found } => write!(
66                f,
67                "invalid input, expected: {:?}, found: {:?}",
68                expected, found
69            ),
70            Self::LineParse(input, _) => {
71                write!(f, "invalid line input for line number: {:?}", input)
72            }
73        }
74    }
75}
76
77impl std::error::Error for Error {}
78
79impl From<Kind> for Error {
80    fn from(kind: Kind) -> Self {
81        Self { kind }
82    }
83}
84
85/// Deserialize a backtrace based on its debug format and return a parsed
86/// representation containing a vector of frames.
87pub fn deserialize(bt: &std::backtrace::Backtrace) -> Result<Backtrace, Error> {
88    let bt_str = format!("{:?}", bt);
89    deserialize_str(&bt_str)
90}
91
92fn deserialize_str(bt: &str) -> Result<Backtrace, Error> {
93    let mut frames = vec![];
94    let mut bt = deser::header(bt)?;
95
96    loop {
97        let (bt_next, frame) = deser::frame(bt)?;
98        bt = bt_next;
99        frames.push(frame);
100
101        let (bt_next, had_comma) = deser::trailing_comma(bt);
102        bt = bt_next;
103
104        if !had_comma {
105            break;
106        }
107    }
108
109    let bt = deser::close_bracket(bt)?;
110
111    if !bt.is_empty() {
112        Err(Kind::UnexpectedInput(bt.into()))?;
113    }
114
115    Ok(Backtrace { frames })
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121
122    #[test]
123    fn backtrace_deserialize_enabled() -> eyre::Result<()> {
124        let bt = std::backtrace::Backtrace::force_capture();
125        let bt_parsed = super::deserialize(&bt)?;
126        dbg!(bt_parsed);
127
128        Ok(())
129    }
130
131    #[test]
132    fn backtrace_deserialize_disabled() -> eyre::Result<()> {
133        let bt = std::backtrace::Backtrace::capture();
134        let bt_parsed = super::deserialize(&bt);
135        match bt_parsed {
136            Ok(_) => panic!("this should not parse"),
137            Err(Error {
138                kind: Kind::Disabled,
139                ..
140            }) => (),
141            Err(e) => Err(e)?,
142        }
143
144        Ok(())
145    }
146
147    #[test]
148    fn deserialize_simple() -> eyre::Result<()> {
149        let backtrace = r#"Backtrace [{fn: "fn1", file: "fi"le1", line: 1}, {fn: "fn2", line: 2}, {fn: "fn3", file: "file3"}, {fn: "fn4"}]"#;
150        let expected = Backtrace {
151            frames: vec![
152                Frame {
153                    function: "fn1".into(),
154                    file: Some("fi\"le1".into()),
155                    line: Some(1),
156                },
157                Frame {
158                    function: "fn2".into(),
159                    file: None,
160                    line: Some(2),
161                },
162                Frame {
163                    function: "fn3".into(),
164                    file: Some("file3".into()),
165                    line: None,
166                },
167                Frame {
168                    function: "fn4".into(),
169                    file: None,
170                    line: None,
171                },
172            ],
173        };
174        let bt_parsed = super::deserialize_str(backtrace)?;
175        dbg!(&bt_parsed);
176
177        assert_eq!(expected.frames, bt_parsed.frames);
178
179        Ok(())
180    }
181}