error_boundary.rs 14 KB

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