browser_panic_hook/
utils.rs

1use std::fmt::{Display, Formatter};
2use std::panic::PanicInfo;
3
4/// Extract the message of the panic info
5///
6/// We need to extract the message from the string. That can fail and lead to wrong results, but
7/// currently is the only wait to only get the message.
8///
9/// If we fail, we do return the full "to string" representation of the panic info, which might
10/// be better than nothing.
11///
12/// This is necessary until `panic_info_message` is stabilized, see rust-lang/rust#66745
13pub fn extract_message(info: &PanicInfo) -> String {
14    // first turn it into a string using the Display format
15    let display = info.to_string();
16
17    // try to strip away the prefix "panicked at", up until the first '
18    let s = match display.strip_prefix("panicked at '") {
19        Some(s) => s,
20        None => return display,
21    };
22
23    // if that worked, try to find the first `'` from the other side of the string
24    let s = match s.rsplit_once('\'') {
25        Some((s, _)) => s,
26        None => return display,
27    };
28
29    // we should have captured everything between the two outer most '
30
31    s.to_string()
32}
33
34pub fn escape_text(text: &str) -> String {
35    text.replace('&', "&")
36        .replace('<', "&lt;")
37        .replace('>', "&gt;")
38        .replace('\'', "&#39;")
39        .replace('"', "&quot;")
40}
41
42pub enum Unescaped {
43    Unsafe(String),
44    Safe(String),
45}
46
47impl Unescaped {
48    pub fn safe(s: impl Into<String>) -> Self {
49        Unescaped::Safe(s.into())
50    }
51}
52
53impl From<String> for Unescaped {
54    fn from(value: String) -> Self {
55        Self::Unsafe(value)
56    }
57}
58
59impl Display for Unescaped {
60    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
61        match self {
62            Self::Unsafe(s) => f.write_str(&escape_text(&s)),
63            Self::Safe(s) => f.write_str(s),
64        }
65    }
66}