truce_rack_core/
wrapper.rs1use crate::error::Error;
18use std::any::type_name;
19use std::panic::{AssertUnwindSafe, catch_unwind};
20
21#[must_use]
29pub fn run_audio_block<P>(format: &str, body: impl FnOnce()) -> bool {
30 let result = catch_unwind(AssertUnwindSafe(body));
31 if let Err(payload) = result {
32 eprintln!(
33 "[truce-rack {format}] panic in process() for plugin {}: {}",
34 type_name::<P>(),
35 extract_panic_msg(&payload),
36 );
37 return false;
38 }
39 true
40}
41
42pub fn run_audio_block_with<P, R>(format: &str, fallback: R, body: impl FnOnce() -> R) -> R {
46 match catch_unwind(AssertUnwindSafe(body)) {
47 Ok(value) => value,
48 Err(payload) => {
49 eprintln!(
50 "[truce-rack {format}] panic in process() for plugin {}: {}",
51 type_name::<P>(),
52 extract_panic_msg(&payload),
53 );
54 fallback
55 }
56 }
57}
58
59pub fn run_extern_callback_with<P, R>(
64 format: &str,
65 action: &str,
66 fallback: R,
67 body: impl FnOnce() -> R,
68) -> R {
69 match catch_unwind(AssertUnwindSafe(body)) {
70 Ok(value) => value,
71 Err(payload) => {
72 eprintln!(
73 "[truce-rack {format}] panic in {action} for plugin {}: {}",
74 type_name::<P>(),
75 extract_panic_msg(&payload),
76 );
77 fallback
78 }
79 }
80}
81
82pub fn run_callable<P, T>(
96 action: &'static str,
97 body: impl FnOnce() -> crate::Result<T>,
98) -> crate::Result<T> {
99 match catch_unwind(AssertUnwindSafe(body)) {
100 Ok(result) => result,
101 Err(payload) => Err(Error::Panic {
102 action,
103 message: extract_panic_msg(&payload).to_string(),
104 }),
105 }
106}
107
108fn extract_panic_msg(payload: &Box<dyn std::any::Any + Send>) -> &str {
109 if let Some(s) = payload.downcast_ref::<&'static str>() {
110 s
111 } else if let Some(s) = payload.downcast_ref::<String>() {
112 s.as_str()
113 } else {
114 "<non-string panic payload>"
115 }
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121
122 struct Dummy;
123
124 #[test]
125 fn clean_audio_block_returns_true() {
126 let ran = std::cell::Cell::new(false);
127 let result = run_audio_block::<Dummy>("test", || ran.set(true));
128 assert!(result);
129 assert!(ran.get());
130 }
131
132 #[test]
133 fn panicking_audio_block_returns_false() {
134 let result = run_audio_block::<Dummy>("test", || panic!("boom"));
135 assert!(!result);
136 }
137
138 #[test]
139 fn callable_panic_becomes_error() {
140 let result: crate::Result<()> = run_callable::<Dummy, ()>("save_state", || panic!("oops"));
141 match result {
142 Err(Error::Panic { action, .. }) => assert_eq!(action, "save_state"),
143 _ => panic!("expected Error::Panic"),
144 }
145 }
146
147 #[test]
148 fn callable_error_propagates() {
149 let result: crate::Result<()> =
150 run_callable::<Dummy, ()>("save_state", || Err(Error::Other("plain error".into())));
151 assert!(matches!(result, Err(Error::Other(_))));
152 }
153}