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 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
85pub 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}