error_boundary.rs 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. use crate::{ScopeId, ScopeState};
  2. use std::{
  3. any::{Any, TypeId},
  4. cell::RefCell,
  5. fmt::Debug,
  6. };
  7. /// A boundary that will capture any errors from child components
  8. pub struct ErrorBoundary {
  9. error: RefCell<Option<CapturedError>>,
  10. _id: ScopeId,
  11. }
  12. /// An instance of an error captured by a descendant component.
  13. pub struct CapturedError {
  14. /// The error captured by the error boundary
  15. pub error: Box<dyn Debug + 'static>,
  16. /// The scope that threw the error
  17. pub scope: ScopeId,
  18. }
  19. impl CapturedError {
  20. /// Downcast the error type into a concrete error type
  21. pub fn downcast<T: 'static>(&self) -> Option<&T> {
  22. if TypeId::of::<T>() == self.error.type_id() {
  23. let raw = self.error.as_ref() as *const _ as *const T;
  24. Some(unsafe { &*raw })
  25. } else {
  26. None
  27. }
  28. }
  29. }
  30. impl ErrorBoundary {
  31. pub fn new(id: ScopeId) -> Self {
  32. Self {
  33. error: RefCell::new(None),
  34. _id: id,
  35. }
  36. }
  37. /// Push an error into this Error Boundary
  38. pub fn insert_error(&self, scope: ScopeId, error: Box<dyn Debug + 'static>) {
  39. self.error.replace(Some(CapturedError { error, scope }));
  40. }
  41. }
  42. /// A trait to allow results to be thrown upwards to the nearest Error Boundary
  43. ///
  44. /// The canonical way of using this trait is to throw results from hooks, aborting rendering
  45. /// through question mark synax. The throw method returns an option that evalutes to None
  46. /// if there is an error, injecting the error to the nearest error boundary.
  47. ///
  48. /// If the value is `Ok`, then throw returns the value, not aborting the rendering preocess.
  49. ///
  50. /// The call stack is saved for this component and provided to the error boundary
  51. ///
  52. /// ```rust, ignore
  53. ///
  54. /// #[inline_props]
  55. /// fn app(cx: Scope, count: String) -> Element {
  56. /// let id: i32 = count.parse().throw(cx)?;
  57. ///
  58. /// cx.render(rsx! {
  59. /// div { "Count {}" }
  60. /// })
  61. /// }
  62. /// ```
  63. pub trait Throw<S = ()>: Sized {
  64. /// The value that will be returned in if the given value is `Ok`.
  65. type Out;
  66. /// Returns an option that evalutes to None if there is an error, injecting the error to the nearest error boundary.
  67. ///
  68. /// If the value is `Ok`, then throw returns the value, not aborting the rendering preocess.
  69. ///
  70. /// The call stack is saved for this component and provided to the error boundary
  71. ///
  72. ///
  73. /// Note that you can also manually throw errors using the throw method on `ScopeState` directly,
  74. /// which is what this trait shells out to.
  75. ///
  76. ///
  77. /// ```rust, ignore
  78. ///
  79. /// #[inline_props]
  80. /// fn app(cx: Scope, count: String) -> Element {
  81. /// let id: i32 = count.parse().throw(cx)?;
  82. ///
  83. /// cx.render(rsx! {
  84. /// div { "Count {}" }
  85. /// })
  86. /// }
  87. /// ```
  88. fn throw(self, cx: &ScopeState) -> Option<Self::Out>;
  89. /// Returns an option that evalutes to None if there is an error, injecting the error to the nearest error boundary.
  90. ///
  91. /// If the value is `Ok`, then throw returns the value, not aborting the rendering preocess.
  92. ///
  93. /// The call stack is saved for this component and provided to the error boundary
  94. ///
  95. ///
  96. /// Note that you can also manually throw errors using the throw method on `ScopeState` directly,
  97. /// which is what this trait shells out to.
  98. ///
  99. ///
  100. /// ```rust, ignore
  101. ///
  102. /// #[inline_props]
  103. /// fn app(cx: Scope, count: String) -> Element {
  104. /// let id: i32 = count.parse().throw(cx)?;
  105. ///
  106. /// cx.render(rsx! {
  107. /// div { "Count {}" }
  108. /// })
  109. /// }
  110. /// ```
  111. fn throw_with<D: Debug + 'static>(
  112. self,
  113. cx: &ScopeState,
  114. e: impl FnOnce() -> D,
  115. ) -> Option<Self::Out>;
  116. }
  117. /// We call clone on any errors that can be owned out of a reference
  118. impl<'a, T, O: Debug + 'static, E: ToOwned<Owned = O>> Throw for &'a Result<T, E> {
  119. type Out = &'a T;
  120. fn throw(self, cx: &ScopeState) -> Option<Self::Out> {
  121. match self {
  122. Ok(t) => Some(t),
  123. Err(e) => {
  124. cx.throw(e.to_owned());
  125. None
  126. }
  127. }
  128. }
  129. fn throw_with<D: Debug + 'static>(
  130. self,
  131. cx: &ScopeState,
  132. err: impl FnOnce() -> D,
  133. ) -> Option<Self::Out> {
  134. match self {
  135. Ok(t) => Some(t),
  136. Err(_e) => {
  137. cx.throw(err());
  138. None
  139. }
  140. }
  141. }
  142. }
  143. /// Or just throw errors we know about
  144. impl<T, E: Debug + 'static> Throw for Result<T, E> {
  145. type Out = T;
  146. fn throw(self, cx: &ScopeState) -> Option<T> {
  147. match self {
  148. Ok(t) => Some(t),
  149. Err(e) => {
  150. cx.throw(e);
  151. None
  152. }
  153. }
  154. }
  155. fn throw_with<D: Debug + 'static>(
  156. self,
  157. cx: &ScopeState,
  158. error: impl FnOnce() -> D,
  159. ) -> Option<Self::Out> {
  160. self.ok().or_else(|| {
  161. cx.throw(error());
  162. None
  163. })
  164. }
  165. }
  166. /// Or just throw errors we know about
  167. impl<T> Throw for Option<T> {
  168. type Out = T;
  169. fn throw(self, cx: &ScopeState) -> Option<T> {
  170. self.or_else(|| {
  171. cx.throw("None error.");
  172. None
  173. })
  174. }
  175. fn throw_with<D: Debug + 'static>(
  176. self,
  177. cx: &ScopeState,
  178. error: impl FnOnce() -> D,
  179. ) -> Option<Self::Out> {
  180. self.or_else(|| {
  181. cx.throw(error());
  182. None
  183. })
  184. }
  185. }