1#![doc(html_root_url = "https://docs.rs/btparse/0.1.1")]
4#![allow(clippy::try_err)]
5use std::fmt;
6
7mod deser;
8
9#[derive(Debug)]
23#[non_exhaustive]
24pub struct Backtrace {
25 pub frames: Vec<Frame>,
26}
27
28#[derive(Debug, PartialEq)]
30pub struct Frame {
31 pub function: String,
32 pub file: Option<String>,
33 pub line: Option<usize>,
34}
35
36#[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
83pub 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}