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>, _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, /// The scope that threw the error pub scope: ScopeId, } impl CapturedError { /// Downcast the error type into a concrete error type pub fn downcast(&self) -> Option<&T> { if TypeId::of::() == 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) { 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: 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; /// 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( self, cx: &ScopeState, e: impl FnOnce() -> D, ) -> Option; } /// We call clone on any errors that can be owned out of a reference impl<'a, T, O: Debug + 'static, E: ToOwned> Throw for &'a Result { type Out = &'a T; fn throw(self, cx: &ScopeState) -> Option { match self { Ok(t) => Some(t), Err(e) => { cx.throw(e.to_owned()); None } } } fn throw_with( self, cx: &ScopeState, err: impl FnOnce() -> D, ) -> Option { match self { Ok(t) => Some(t), Err(_e) => { cx.throw(err()); None } } } } /// Or just throw errors we know about impl Throw for Result { type Out = T; fn throw(self, cx: &ScopeState) -> Option { match self { Ok(t) => Some(t), Err(e) => { cx.throw(e); None } } } fn throw_with( self, cx: &ScopeState, error: impl FnOnce() -> D, ) -> Option { self.ok().or_else(|| { cx.throw(error()); None }) } } /// Or just throw errors we know about impl Throw for Option { type Out = T; fn throw(self, cx: &ScopeState) -> Option { self.or_else(|| { cx.throw("None error."); None }) } fn throw_with( self, cx: &ScopeState, error: impl FnOnce() -> D, ) -> Option { self.or_else(|| { cx.throw(error()); None }) } }