html_to_markdown_rs/
safety.rs1use std::any::Any;
9use std::panic::{self, AssertUnwindSafe, UnwindSafe};
10
11use crate::error::{ConversionError, Result};
12
13pub fn guard_panic<F, T>(f: F) -> Result<T>
18where
19 F: FnOnce() -> Result<T>,
20 F: UnwindSafe,
21{
22 match panic::catch_unwind(AssertUnwindSafe(f)) {
23 Ok(result) => result,
24 Err(payload) => Err(ConversionError::Panic(panic_message(payload))),
25 }
26}
27
28fn panic_message(payload: Box<dyn Any + Send>) -> String {
29 if let Some(msg) = payload.downcast_ref::<&str>() {
30 (*msg).to_string()
31 } else if let Some(msg) = payload.downcast_ref::<String>() {
32 msg.clone()
33 } else {
34 "unexpected panic without message".to_string()
35 }
36}
37
38#[cfg(test)]
39mod tests {
40 use super::*;
41
42 #[test]
43 fn guard_panic_converts_panic_to_error() {
44 let err = guard_panic::<_, ()>(|| -> Result<()> {
45 panic!("boom");
46 })
47 .unwrap_err();
48
49 match err {
50 ConversionError::Panic(message) => assert_eq!(message, "boom"),
51 other => panic!("expected panic error, got {:?}", other),
52 }
53 }
54
55 #[test]
56 fn guard_panic_forwards_ok() {
57 let value = guard_panic(|| Ok::<_, ConversionError>(42)).unwrap();
58 assert_eq!(value, 42);
59 }
60}