#![allow(non_snake_case)]
use crate::{View, remember_state, request_frame};
use std::cell::RefCell;
use std::panic::{AssertUnwindSafe, catch_unwind};
use std::rc::Rc;
#[derive(Clone, Debug)]
pub struct ErrorInfo {
pub message: String,
pub component: String,
}
thread_local! {
static BOUNDARY_THROW: RefCell<Option<ErrorInfo>> = const { RefCell::new(None) };
}
pub fn throw_boundary(message: impl Into<String>) {
BOUNDARY_THROW.with(|t| {
*t.borrow_mut() = Some(ErrorInfo {
message: message.into(),
component: "Unknown".into(),
});
});
}
fn take_throw() -> Option<ErrorInfo> {
BOUNDARY_THROW.with(|t| t.borrow_mut().take())
}
pub fn ErrorBoundary(
fallback: impl Fn(ErrorInfo, Rc<dyn Fn()>) -> View + 'static,
content: impl Fn() -> View + 'static,
) -> View {
let error: Rc<RefCell<Option<ErrorInfo>>> = remember_state(|| None);
let generation = remember_state(|| 0u64);
let reset = {
let error = error.clone();
let generation = generation.clone();
Rc::new(move || {
*error.borrow_mut() = None;
*generation.borrow_mut() += 1;
request_frame();
}) as Rc<dyn Fn()>
};
if let Some(info) = error.borrow().clone() {
return fallback(info, reset);
}
let _g = *generation.borrow();
BOUNDARY_THROW.with(|t| *t.borrow_mut() = None);
let result = catch_unwind(AssertUnwindSafe(|| content()));
if let Some(info) = take_throw() {
*error.borrow_mut() = Some(info.clone());
request_frame();
return fallback(info, reset);
}
match result {
Ok(view) => view,
Err(err) => {
let message = if let Some(s) = err.downcast_ref::<String>() {
s.clone()
} else if let Some(s) = err.downcast_ref::<&str>() {
(*s).to_string()
} else {
"Unknown panic".to_string()
};
let info = ErrorInfo {
message,
component: "Unknown".into(),
};
*error.borrow_mut() = Some(info.clone());
request_frame();
fallback(info, reset)
}
}
}