1#![allow(non_snake_case)]
13
14use crate::{View, remember_state, request_frame};
15use std::cell::RefCell;
16use std::panic::{AssertUnwindSafe, catch_unwind};
17use std::rc::Rc;
18
19#[derive(Clone, Debug)]
21pub struct ErrorInfo {
22 pub message: String,
23 pub component: String,
24}
25
26thread_local! {
27 static BOUNDARY_THROW: RefCell<Option<ErrorInfo>> = const { RefCell::new(None) };
29}
30
31pub fn throw_boundary(message: impl Into<String>) {
34 BOUNDARY_THROW.with(|t| {
35 *t.borrow_mut() = Some(ErrorInfo {
36 message: message.into(),
37 component: "Unknown".into(),
38 });
39 });
40}
41
42fn take_throw() -> Option<ErrorInfo> {
43 BOUNDARY_THROW.with(|t| t.borrow_mut().take())
44}
45
46pub fn ErrorBoundary(
52 fallback: impl Fn(ErrorInfo, Rc<dyn Fn()>) -> View + 'static,
53 content: impl Fn() -> View + 'static,
54) -> View {
55 let error: Rc<RefCell<Option<ErrorInfo>>> = remember_state(|| None);
57 let generation = remember_state(|| 0u64);
58
59 let reset = {
60 let error = error.clone();
61 let generation = generation.clone();
62 Rc::new(move || {
63 *error.borrow_mut() = None;
64 *generation.borrow_mut() += 1;
65 request_frame();
66 }) as Rc<dyn Fn()>
67 };
68
69 if let Some(info) = error.borrow().clone() {
70 return fallback(info, reset);
71 }
72
73 let _g = *generation.borrow();
75
76 BOUNDARY_THROW.with(|t| *t.borrow_mut() = None);
77
78 let result = catch_unwind(AssertUnwindSafe(|| content()));
79
80 if let Some(info) = take_throw() {
82 *error.borrow_mut() = Some(info.clone());
83 request_frame();
84 return fallback(info, reset);
85 }
86
87 match result {
88 Ok(view) => view,
89 Err(err) => {
90 let message = if let Some(s) = err.downcast_ref::<String>() {
91 s.clone()
92 } else if let Some(s) = err.downcast_ref::<&str>() {
93 (*s).to_string()
94 } else {
95 "Unknown panic".to_string()
96 };
97 let info = ErrorInfo {
98 message,
99 component: "Unknown".into(),
100 };
101 *error.borrow_mut() = Some(info.clone());
102 request_frame();
103 fallback(info, reset)
104 }
105 }
106}