btparse/
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    Unsupported,
46    UnexpectedInput(String),
47    InvalidInput { expected: String, found: String },
48    LineParse(String, std::num::ParseIntError),
49}
50
51impl fmt::Display for Error {
52    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53        writeln!(f, "{}", self.kind)
54    }
55}
56
57impl fmt::Display for Kind {
58    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59        match self {
60            Self::Disabled => write!(f, "backtrace capture disabled"),
61            Self::Unsupported => write!(f, "backtrace capture unsupported on this platform"),
62            Self::UnexpectedInput(input) => write!(f, "encountered unexpected input: {:?}", input),
63            Self::InvalidInput { expected, found } => write!(
64                f,
65                "invalid input, expected: {:?}, found: {:?}",
66                expected, found
67            ),
68            Self::LineParse(input, e) => {
69                write!(f, "invalid line input for line number: {:?} ({:?})", input, e)
70            }
71        }
72    }
73}
74
75impl std::error::Error for Error {}
76
77impl From<Kind> for Error {
78    fn from(kind: Kind) -> Self {
79        Self { kind }
80    }
81}
82
83/// Deserialize a backtrace based on its debug format and return a parsed
84/// representation containing a vector of frames.
85pub fn deserialize(bt: &std::backtrace::Backtrace) -> Result<Backtrace, Error> {
86    let bt_str = format!("{:?}", bt);
87    deserialize_str(&bt_str)
88}
89
90fn deserialize_str(bt: &str) -> Result<Backtrace, Error> {
91    let mut frames = vec![];
92    let mut bt = deser::header(bt)?;
93
94    loop {
95        let (bt_next, frame) = deser::frame(bt)?;
96        bt = bt_next;
97        frames.push(frame);
98
99        let (bt_next, had_comma) = deser::trailing_comma(bt);
100        bt = bt_next;
101
102        if !had_comma {
103            break;
104        }
105    }
106
107    let bt = deser::close_bracket(bt)?;
108
109    if !bt.is_empty() {
110        Err(Kind::UnexpectedInput(bt.into()))?;
111    }
112
113    Ok(Backtrace { frames })
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119
120    #[test]
121    fn backtrace_deserialize_enabled() -> eyre::Result<()> {
122        let bt = std::backtrace::Backtrace::force_capture();
123        let bt_parsed = super::deserialize(&bt)?;
124        dbg!(bt_parsed);
125
126        Ok(())
127    }
128
129    #[test]
130    fn backtrace_deserialize_disabled() -> eyre::Result<()> {
131        let bt = std::backtrace::Backtrace::capture();
132        let bt_parsed = super::deserialize(&bt);
133        match bt_parsed {
134            Ok(_) => panic!("this should not parse"),
135            Err(Error {
136                kind: Kind::Disabled,
137                ..
138            }) => (),
139            Err(e) => Err(e)?,
140        }
141
142        Ok(())
143    }
144
145    #[test]
146    fn deserialize_simple() -> eyre::Result<()> {
147        let backtrace = r#"Backtrace [{fn: "fn1", file: "fi"le1", line: 1}, {fn: "fn2", line: 2}, {fn: "fn3", file: "file3"}, {fn: "fn4"}]"#;
148        let expected = Backtrace {
149            frames: vec![
150                Frame {
151                    function: "fn1".into(),
152                    file: Some("fi\"le1".into()),
153                    line: Some(1),
154                },
155                Frame {
156                    function: "fn2".into(),
157                    file: None,
158                    line: Some(2),
159                },
160                Frame {
161                    function: "fn3".into(),
162                    file: Some("file3".into()),
163                    line: None,
164                },
165                Frame {
166                    function: "fn4".into(),
167                    file: None,
168                    line: None,
169                },
170            ],
171        };
172        let bt_parsed = super::deserialize_str(backtrace)?;
173        dbg!(&bt_parsed);
174
175        assert_eq!(expected.frames, bt_parsed.frames);
176
177        Ok(())
178    }
179}