123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203 |
- use crate::{ScopeId, ScopeState};
- use std::{
- any::{Any, TypeId},
- cell::RefCell,
- fmt::Debug,
- };
- /// A boundary that will capture any errors from child components
- pub struct ErrorBoundary {
- error: RefCell<Option<CapturedError>>,
- _id: ScopeId,
- }
- /// An instance of an error captured by a descendant component.
- pub struct CapturedError {
- /// The error captured by the error boundary
- pub error: Box<dyn Debug + 'static>,
- /// The scope that threw the error
- pub scope: ScopeId,
- }
- impl CapturedError {
- /// Downcast the error type into a concrete error type
- pub fn downcast<T: 'static>(&self) -> Option<&T> {
- if TypeId::of::<T>() == self.error.type_id() {
- let raw = self.error.as_ref() as *const _ as *const T;
- Some(unsafe { &*raw })
- } else {
- None
- }
- }
- }
- impl ErrorBoundary {
- pub fn new(id: ScopeId) -> Self {
- Self {
- error: RefCell::new(None),
- _id: id,
- }
- }
- /// Push an error into this Error Boundary
- pub fn insert_error(&self, scope: ScopeId, error: Box<dyn Debug + 'static>) {
- self.error.replace(Some(CapturedError { error, scope }));
- }
- }
- /// A trait to allow results to be thrown upwards to the nearest Error Boundary
- ///
- /// The canonical way of using this trait is to throw results from hooks, aborting rendering
- /// through question mark synax. The throw method returns an option that evalutes to None
- /// if there is an error, injecting the error to the nearest error boundary.
- ///
- /// If the value is `Ok`, then throw returns the value, not aborting the rendering preocess.
- ///
- /// The call stack is saved for this component and provided to the error boundary
- ///
- /// ```rust, ignore
- ///
- /// #[inline_props]
- /// fn app(cx: Scope, count: String) -> Element {
- /// let id: i32 = count.parse().throw(cx)?;
- ///
- /// cx.render(rsx! {
- /// div { "Count {}" }
- /// })
- /// }
- /// ```
- pub trait Throw<S = ()>: Sized {
- /// The value that will be returned in if the given value is `Ok`.
- type Out;
- /// Returns an option that evalutes to None if there is an error, injecting the error to the nearest error boundary.
- ///
- /// If the value is `Ok`, then throw returns the value, not aborting the rendering preocess.
- ///
- /// The call stack is saved for this component and provided to the error boundary
- ///
- ///
- /// Note that you can also manually throw errors using the throw method on `ScopeState` directly,
- /// which is what this trait shells out to.
- ///
- ///
- /// ```rust, ignore
- ///
- /// #[inline_props]
- /// fn app(cx: Scope, count: String) -> Element {
- /// let id: i32 = count.parse().throw(cx)?;
- ///
- /// cx.render(rsx! {
- /// div { "Count {}" }
- /// })
- /// }
- /// ```
- fn throw(self, cx: &ScopeState) -> Option<Self::Out>;
- /// Returns an option that evalutes to None if there is an error, injecting the error to the nearest error boundary.
- ///
- /// If the value is `Ok`, then throw returns the value, not aborting the rendering preocess.
- ///
- /// The call stack is saved for this component and provided to the error boundary
- ///
- ///
- /// Note that you can also manually throw errors using the throw method on `ScopeState` directly,
- /// which is what this trait shells out to.
- ///
- ///
- /// ```rust, ignore
- ///
- /// #[inline_props]
- /// fn app(cx: Scope, count: String) -> Element {
- /// let id: i32 = count.parse().throw(cx)?;
- ///
- /// cx.render(rsx! {
- /// div { "Count {}" }
- /// })
- /// }
- /// ```
- fn throw_with<D: Debug + 'static>(
- self,
- cx: &ScopeState,
- e: impl FnOnce() -> D,
- ) -> Option<Self::Out>;
- }
- /// We call clone on any errors that can be owned out of a reference
- impl<'a, T, O: Debug + 'static, E: ToOwned<Owned = O>> Throw for &'a Result<T, E> {
- type Out = &'a T;
- fn throw(self, cx: &ScopeState) -> Option<Self::Out> {
- match self {
- Ok(t) => Some(t),
- Err(e) => {
- cx.throw(e.to_owned());
- None
- }
- }
- }
- fn throw_with<D: Debug + 'static>(
- self,
- cx: &ScopeState,
- err: impl FnOnce() -> D,
- ) -> Option<Self::Out> {
- match self {
- Ok(t) => Some(t),
- Err(_e) => {
- cx.throw(err());
- None
- }
- }
- }
- }
- /// Or just throw errors we know about
- impl<T, E: Debug + 'static> Throw for Result<T, E> {
- type Out = T;
- fn throw(self, cx: &ScopeState) -> Option<T> {
- match self {
- Ok(t) => Some(t),
- Err(e) => {
- cx.throw(e);
- None
- }
- }
- }
- fn throw_with<D: Debug + 'static>(
- self,
- cx: &ScopeState,
- error: impl FnOnce() -> D,
- ) -> Option<Self::Out> {
- self.ok().or_else(|| {
- cx.throw(error());
- None
- })
- }
- }
- /// Or just throw errors we know about
- impl<T> Throw for Option<T> {
- type Out = T;
- fn throw(self, cx: &ScopeState) -> Option<T> {
- self.or_else(|| {
- cx.throw("None error.");
- None
- })
- }
- fn throw_with<D: Debug + 'static>(
- self,
- cx: &ScopeState,
- error: impl FnOnce() -> D,
- ) -> Option<Self::Out> {
- self.or_else(|| {
- cx.throw(error());
- None
- })
- }
- }
|