error_boundary.rs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488
  1. use crate::{
  2. scope_context::{consume_context, current_scope_id, schedule_update_any},
  3. Element, IntoDynNode, LazyNodes, Properties, Scope, ScopeId, ScopeState, Template,
  4. TemplateAttribute, TemplateNode, VNode,
  5. };
  6. use std::{
  7. any::{Any, TypeId},
  8. backtrace::Backtrace,
  9. cell::{Cell, RefCell},
  10. error::Error,
  11. fmt::{Debug, Display},
  12. rc::Rc,
  13. sync::Arc,
  14. };
  15. /// Provide an error boundary to catch errors from child components
  16. pub fn use_error_boundary(cx: &ScopeState) -> &ErrorBoundary {
  17. cx.use_hook(|| cx.provide_context(ErrorBoundary::new()))
  18. }
  19. /// A boundary that will capture any errors from child components
  20. #[derive(Debug, Clone, Default)]
  21. pub struct ErrorBoundary {
  22. inner: Rc<ErrorBoundaryInner>,
  23. }
  24. /// A boundary that will capture any errors from child components
  25. pub struct ErrorBoundaryInner {
  26. error: RefCell<Option<CapturedError>>,
  27. _id: ScopeId,
  28. rerun_boundary: Arc<dyn Fn(ScopeId) + Send + Sync>,
  29. }
  30. impl Debug for ErrorBoundaryInner {
  31. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  32. f.debug_struct("ErrorBoundaryInner")
  33. .field("error", &self.error)
  34. .finish()
  35. }
  36. }
  37. #[derive(Debug)]
  38. /// An instance of an error captured by a descendant component.
  39. pub struct CapturedError {
  40. /// The error captured by the error boundary
  41. pub error: Box<dyn Debug + 'static>,
  42. /// The backtrace of the error
  43. pub backtrace: Backtrace,
  44. /// The scope that threw the error
  45. pub scope: ScopeId,
  46. }
  47. impl Display for CapturedError {
  48. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  49. f.write_fmt(format_args!(
  50. "Encountered error: {:?}\nIn scope: {:?}\nBacktrace: {}",
  51. self.error, self.scope, self.backtrace
  52. ))
  53. }
  54. }
  55. impl Error for CapturedError {}
  56. impl CapturedError {
  57. /// Downcast the error type into a concrete error type
  58. pub fn downcast<T: 'static>(&self) -> Option<&T> {
  59. if TypeId::of::<T>() == self.error.type_id() {
  60. let raw = self.error.as_ref() as *const _ as *const T;
  61. Some(unsafe { &*raw })
  62. } else {
  63. None
  64. }
  65. }
  66. }
  67. impl Default for ErrorBoundaryInner {
  68. fn default() -> Self {
  69. Self {
  70. error: RefCell::new(None),
  71. _id: current_scope_id()
  72. .expect("Cannot create an error boundary outside of a component's scope."),
  73. rerun_boundary: schedule_update_any().unwrap(),
  74. }
  75. }
  76. }
  77. impl ErrorBoundary {
  78. /// Create a new error boundary
  79. pub fn new() -> Self {
  80. Self::default()
  81. }
  82. /// Create a new error boundary in the current scope
  83. pub(crate) fn new_in_scope(
  84. scope: ScopeId,
  85. rerun_boundary: Arc<dyn Fn(ScopeId) + Send + Sync>,
  86. ) -> Self {
  87. Self {
  88. inner: Rc::new(ErrorBoundaryInner {
  89. error: RefCell::new(None),
  90. _id: scope,
  91. rerun_boundary,
  92. }),
  93. }
  94. }
  95. /// Push an error into this Error Boundary
  96. pub fn insert_error(
  97. &self,
  98. scope: ScopeId,
  99. error: Box<dyn Debug + 'static>,
  100. backtrace: Backtrace,
  101. ) {
  102. println!("{:?} {:?}", error, self.inner._id);
  103. self.inner.error.replace(Some(CapturedError {
  104. error,
  105. scope,
  106. backtrace,
  107. }));
  108. (self.inner.rerun_boundary)(self.inner._id);
  109. }
  110. /// Take any error that has been captured by this error boundary
  111. pub fn take_error(&self) -> Option<CapturedError> {
  112. self.inner.error.take()
  113. }
  114. }
  115. /// A trait to allow results to be thrown upwards to the nearest Error Boundary
  116. ///
  117. /// The canonical way of using this trait is to throw results from hooks, aborting rendering
  118. /// through question mark syntax. The throw method returns an option that evaluates to None
  119. /// if there is an error, injecting the error to the nearest error boundary.
  120. ///
  121. /// If the value is `Ok`, then throw returns the value, not aborting the rendering process.
  122. ///
  123. /// The call stack is saved for this component and provided to the error boundary
  124. ///
  125. /// ```rust, ignore
  126. /// #[component]
  127. /// fn App(cx: Scope, count: String) -> Element {
  128. /// let id: i32 = count.parse().throw()?;
  129. ///
  130. /// cx.render(rsx! {
  131. /// div { "Count {}" }
  132. /// })
  133. /// }
  134. /// ```
  135. pub trait Throw<S = ()>: Sized {
  136. /// The value that will be returned in if the given value is `Ok`.
  137. type Out;
  138. /// Returns an option that evaluates to None if there is an error, injecting the error to the nearest error boundary.
  139. ///
  140. /// If the value is `Ok`, then throw returns the value, not aborting the rendering process.
  141. ///
  142. /// The call stack is saved for this component and provided to the error boundary
  143. ///
  144. ///
  145. /// Note that you can also manually throw errors using the throw method on `ScopeState` directly,
  146. /// which is what this trait shells out to.
  147. ///
  148. ///
  149. /// ```rust, ignore
  150. /// #[component]
  151. /// fn App(cx: Scope, count: String) -> Element {
  152. /// let id: i32 = count.parse().throw()?;
  153. ///
  154. /// cx.render(rsx! {
  155. /// div { "Count {}" }
  156. /// })
  157. /// }
  158. /// ```
  159. fn throw(self) -> Option<Self::Out>;
  160. /// Returns an option that evaluates to None if there is an error, injecting the error to the nearest error boundary.
  161. ///
  162. /// If the value is `Ok`, then throw returns the value, not aborting the rendering process.
  163. ///
  164. /// The call stack is saved for this component and provided to the error boundary
  165. ///
  166. ///
  167. /// Note that you can also manually throw errors using the throw method on `ScopeState` directly,
  168. /// which is what this trait shells out to.
  169. ///
  170. ///
  171. /// ```rust, ignore
  172. /// #[component]
  173. /// fn App(cx: Scope, count: String) -> Element {
  174. /// let id: i32 = count.parse().throw()?;
  175. ///
  176. /// cx.render(rsx! {
  177. /// div { "Count {}" }
  178. /// })
  179. /// }
  180. /// ```
  181. fn throw_with<D: Debug + 'static>(self, e: impl FnOnce() -> D) -> Option<Self::Out> {
  182. self.throw().or_else(|| throw_error(e()))
  183. }
  184. }
  185. fn throw_error<T>(e: impl Debug + 'static) -> Option<T> {
  186. if let Some(cx) = consume_context::<ErrorBoundary>() {
  187. match current_scope_id() {
  188. Some(id) => cx.insert_error(id, Box::new(e), Backtrace::capture()),
  189. None => {
  190. tracing::error!("Cannot throw error outside of a component's scope.")
  191. }
  192. }
  193. }
  194. None
  195. }
  196. /// We call clone on any errors that can be owned out of a reference
  197. impl<'a, T, O: Debug + 'static, E: ToOwned<Owned = O>> Throw for &'a Result<T, E> {
  198. type Out = &'a T;
  199. fn throw(self) -> Option<Self::Out> {
  200. match self {
  201. Ok(t) => Some(t),
  202. Err(e) => throw_error(e.to_owned()),
  203. }
  204. }
  205. fn throw_with<D: Debug + 'static>(self, err: impl FnOnce() -> D) -> Option<Self::Out> {
  206. match self {
  207. Ok(t) => Some(t),
  208. Err(_e) => throw_error(err()),
  209. }
  210. }
  211. }
  212. /// Or just throw errors we know about
  213. impl<T, E: Debug + 'static> Throw for Result<T, E> {
  214. type Out = T;
  215. fn throw(self) -> Option<T> {
  216. match self {
  217. Ok(t) => Some(t),
  218. Err(e) => throw_error(e),
  219. }
  220. }
  221. fn throw_with<D: Debug + 'static>(self, error: impl FnOnce() -> D) -> Option<Self::Out> {
  222. self.ok().or_else(|| throw_error(error()))
  223. }
  224. }
  225. /// Or just throw errors we know about
  226. impl<T> Throw for Option<T> {
  227. type Out = T;
  228. fn throw(self) -> Option<T> {
  229. self.or_else(|| throw_error("Attempted to unwrap a None value."))
  230. }
  231. fn throw_with<D: Debug + 'static>(self, error: impl FnOnce() -> D) -> Option<Self::Out> {
  232. self.or_else(|| throw_error(error()))
  233. }
  234. }
  235. pub struct ErrorHandler<'a>(Box<dyn Fn(CapturedError) -> LazyNodes<'a, 'a> + 'a>);
  236. impl<'a, F: Fn(CapturedError) -> LazyNodes<'a, 'a> + 'a> From<F> for ErrorHandler<'a> {
  237. fn from(value: F) -> Self {
  238. Self(Box::new(value))
  239. }
  240. }
  241. fn default_handler<'a>(error: CapturedError) -> LazyNodes<'a, 'a> {
  242. LazyNodes::new(move |__cx: &ScopeState| -> VNode {
  243. static TEMPLATE: Template = Template {
  244. name: "error_handle.rs:42:5:884",
  245. roots: &[TemplateNode::Element {
  246. tag: "pre",
  247. namespace: None,
  248. attrs: &[TemplateAttribute::Static {
  249. name: "color",
  250. namespace: Some("style"),
  251. value: "red",
  252. }],
  253. children: &[TemplateNode::DynamicText { id: 0usize }],
  254. }],
  255. node_paths: &[&[0u8, 0u8]],
  256. attr_paths: &[],
  257. };
  258. VNode {
  259. parent: Default::default(),
  260. stable_id: Default::default(),
  261. key: None,
  262. template: std::cell::Cell::new(TEMPLATE),
  263. root_ids: bumpalo::collections::Vec::with_capacity_in(1usize, __cx.bump()).into(),
  264. dynamic_nodes: __cx
  265. .bump()
  266. .alloc([__cx.text_node(format_args!("{0}", error))]),
  267. dynamic_attrs: __cx.bump().alloc([]),
  268. }
  269. })
  270. }
  271. pub struct ErrorBoundaryProps<'a> {
  272. children: Element<'a>,
  273. handle_error: ErrorHandler<'a>,
  274. }
  275. impl<'a> ErrorBoundaryProps<'a> {
  276. /**
  277. Create a builder for building `ErrorBoundaryProps`.
  278. On the builder, call `.children(...)`(optional), `.handle_error(...)`(optional) to set the values of the fields.
  279. Finally, call `.build()` to create the instance of `ErrorBoundaryProps`.
  280. */
  281. #[allow(dead_code)]
  282. pub fn builder() -> ErrorBoundaryPropsBuilder<'a, ((), ())> {
  283. ErrorBoundaryPropsBuilder {
  284. fields: ((), ()),
  285. _phantom: ::core::default::Default::default(),
  286. }
  287. }
  288. }
  289. #[must_use]
  290. #[doc(hidden)]
  291. #[allow(dead_code, non_camel_case_types, non_snake_case)]
  292. pub struct ErrorBoundaryPropsBuilder<'a, TypedBuilderFields> {
  293. fields: TypedBuilderFields,
  294. _phantom: ::core::marker::PhantomData<&'a ()>,
  295. }
  296. impl<'a, TypedBuilderFields> Clone for ErrorBoundaryPropsBuilder<'a, TypedBuilderFields>
  297. where
  298. TypedBuilderFields: Clone,
  299. {
  300. fn clone(&self) -> Self {
  301. Self {
  302. fields: self.fields.clone(),
  303. _phantom: ::core::default::Default::default(),
  304. }
  305. }
  306. }
  307. impl<'a> Properties<'a> for ErrorBoundaryProps<'a> {
  308. type Builder = ErrorBoundaryPropsBuilder<'a, ((), ())>;
  309. const IS_STATIC: bool = false;
  310. fn builder(_: &'a ScopeState) -> Self::Builder {
  311. ErrorBoundaryProps::builder()
  312. }
  313. unsafe fn memoize(&self, _: &Self) -> bool {
  314. false
  315. }
  316. }
  317. #[doc(hidden)]
  318. #[allow(dead_code, non_camel_case_types, non_snake_case)]
  319. pub trait ErrorBoundaryPropsBuilder_Optional<T> {
  320. fn into_value<F: FnOnce() -> T>(self, default: F) -> T;
  321. }
  322. impl<T> ErrorBoundaryPropsBuilder_Optional<T> for () {
  323. fn into_value<F: FnOnce() -> T>(self, default: F) -> T {
  324. default()
  325. }
  326. }
  327. impl<T> ErrorBoundaryPropsBuilder_Optional<T> for (T,) {
  328. fn into_value<F: FnOnce() -> T>(self, _: F) -> T {
  329. self.0
  330. }
  331. }
  332. #[allow(dead_code, non_camel_case_types, missing_docs)]
  333. impl<'a, __handle_error> ErrorBoundaryPropsBuilder<'a, ((), __handle_error)> {
  334. pub fn children(
  335. self,
  336. children: Element<'a>,
  337. ) -> ErrorBoundaryPropsBuilder<'a, ((Element<'a>,), __handle_error)> {
  338. let children = (children,);
  339. let (_, handle_error) = self.fields;
  340. ErrorBoundaryPropsBuilder {
  341. fields: (children, handle_error),
  342. _phantom: self._phantom,
  343. }
  344. }
  345. }
  346. #[doc(hidden)]
  347. #[allow(dead_code, non_camel_case_types, non_snake_case)]
  348. pub enum ErrorBoundaryPropsBuilder_Error_Repeated_field_children {}
  349. #[doc(hidden)]
  350. #[allow(dead_code, non_camel_case_types, missing_docs)]
  351. impl<'a, __handle_error> ErrorBoundaryPropsBuilder<'a, ((Element<'a>,), __handle_error)> {
  352. #[deprecated(note = "Repeated field children")]
  353. pub fn children(
  354. self,
  355. _: ErrorBoundaryPropsBuilder_Error_Repeated_field_children,
  356. ) -> ErrorBoundaryPropsBuilder<'a, ((Element<'a>,), __handle_error)> {
  357. self
  358. }
  359. }
  360. #[allow(dead_code, non_camel_case_types, missing_docs)]
  361. impl<'a, __children> ErrorBoundaryPropsBuilder<'a, (__children, ())> {
  362. pub fn handle_error(
  363. self,
  364. handle_error: impl ::core::convert::Into<ErrorHandler<'a>>,
  365. ) -> ErrorBoundaryPropsBuilder<'a, (__children, (ErrorHandler<'a>,))> {
  366. let handle_error = (handle_error.into(),);
  367. let (children, _) = self.fields;
  368. ErrorBoundaryPropsBuilder {
  369. fields: (children, handle_error),
  370. _phantom: self._phantom,
  371. }
  372. }
  373. }
  374. #[doc(hidden)]
  375. #[allow(dead_code, non_camel_case_types, non_snake_case)]
  376. pub enum ErrorBoundaryPropsBuilder_Error_Repeated_field_handle_error {}
  377. #[doc(hidden)]
  378. #[allow(dead_code, non_camel_case_types, missing_docs)]
  379. impl<'a, __children> ErrorBoundaryPropsBuilder<'a, (__children, (ErrorHandler<'a>,))> {
  380. #[deprecated(note = "Repeated field handle_error")]
  381. pub fn handle_error(
  382. self,
  383. _: ErrorBoundaryPropsBuilder_Error_Repeated_field_handle_error,
  384. ) -> ErrorBoundaryPropsBuilder<'a, (__children, (ErrorHandler<'a>,))> {
  385. self
  386. }
  387. }
  388. #[allow(dead_code, non_camel_case_types, missing_docs)]
  389. impl<
  390. 'a,
  391. __handle_error: ErrorBoundaryPropsBuilder_Optional<ErrorHandler<'a>>,
  392. __children: ErrorBoundaryPropsBuilder_Optional<Element<'a>>,
  393. > ErrorBoundaryPropsBuilder<'a, (__children, __handle_error)>
  394. {
  395. pub fn build(self) -> ErrorBoundaryProps<'a> {
  396. let (children, handle_error) = self.fields;
  397. let children = ErrorBoundaryPropsBuilder_Optional::into_value(children, || {
  398. ::core::default::Default::default()
  399. });
  400. let handle_error = ErrorBoundaryPropsBuilder_Optional::into_value(handle_error, || {
  401. ErrorHandler(Box::new(default_handler))
  402. });
  403. ErrorBoundaryProps {
  404. children,
  405. handle_error,
  406. }
  407. }
  408. }
  409. /// Create a new error boundary component.
  410. ///
  411. /// ## Details
  412. ///
  413. /// Error boundaries handle errors within a specific part of your application. Any errors passed in a child with [`Throw`] will be caught by the nearest error boundary.
  414. ///
  415. /// ## Example
  416. ///
  417. /// ```rust, ignore
  418. /// rsx!{
  419. /// ErrorBoundary {
  420. /// handle_error: |error| rsx! { "Oops, we encountered an error. Please report {error} to the developer of this application" }
  421. /// ThrowsError {}
  422. /// }
  423. /// }
  424. /// ```
  425. ///
  426. /// ## Usage
  427. ///
  428. /// Error boundaries are an easy way to handle errors in your application.
  429. /// They are similar to `try/catch` in JavaScript, but they only catch errors in the tree below them.
  430. /// Error boundaries are quick to implement, but it can be useful to individually handle errors in your components to provide a better user experience when you know that an error is likely to occur.
  431. #[allow(non_upper_case_globals, non_snake_case)]
  432. pub fn ErrorBoundary<'a>(cx: Scope<'a, ErrorBoundaryProps<'a>>) -> Element {
  433. let error_boundary = use_error_boundary(cx);
  434. match error_boundary.take_error() {
  435. Some(error) => cx.render((cx.props.handle_error.0)(error)),
  436. None => Some({
  437. let __cx = cx;
  438. static TEMPLATE: Template = Template {
  439. name: "examples/error_handle.rs:81:17:2342",
  440. roots: &[TemplateNode::Dynamic { id: 0usize }],
  441. node_paths: &[&[0u8]],
  442. attr_paths: &[],
  443. };
  444. VNode {
  445. parent: Cell::new(None),
  446. stable_id: Cell::new(None),
  447. key: None,
  448. template: std::cell::Cell::new(TEMPLATE),
  449. root_ids: bumpalo::collections::Vec::with_capacity_in(1usize, __cx.bump()).into(),
  450. dynamic_nodes: __cx.bump().alloc([{
  451. let ___nodes = (&cx.props.children).into_dyn_node(__cx);
  452. ___nodes
  453. }]),
  454. dynamic_attrs: __cx.bump().alloc([]),
  455. }
  456. }),
  457. }
  458. }