204 lines
5.4 KiB
Rust
204 lines
5.4 KiB
Rust
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
|
|
})
|
|
}
|
|
}
|