Merge pull request #1618 from ealmloff/polish-throw
Document and improve the throw trait
This commit is contained in:
9 files changed
+377
-120
No files matched your search
@@ -133,4 +133,8 @@ fern = { version = "0.6.0", features = ["colored"] }
|
|||||||
env_logger = "0.10.0"
|
env_logger = "0.10.0"
|
||||||
simple_logger = "4.0.0"
|
simple_logger = "4.0.0"
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
|
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
tracing-subscriber = "0.3.17"
|
||||||
http-range = "0.1.5"
|
http-range = "0.1.5"
|
||||||
+11
-16
@@ -1,4 +1,4 @@
|
|||||||
use dioxus::prelude::*;
|
use dioxus::{core::CapturedError, prelude::*};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
dioxus_desktop::launch(App);
|
dioxus_desktop::launch(App);
|
||||||
@@ -6,30 +6,25 @@ fn main() {
|
|||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
fn App(cx: Scope) -> Element {
|
fn App(cx: Scope) -> Element {
|
||||||
let val = use_state(cx, || "0.0001");
|
|
||||||
|
|
||||||
let num = match val.parse::<f32>() {
|
|
||||||
Err(_) => return cx.render(rsx!("Parsing failed")),
|
|
||||||
Ok(num) => num,
|
|
||||||
};
|
|
||||||
|
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
h1 { "The parsed value is {num}" }
|
ErrorBoundary {
|
||||||
button {
|
handle_error: |error: CapturedError| rsx! {"Found error {error}"},
|
||||||
onclick: move |_| val.set("invalid"),
|
DemoC {
|
||||||
"Set an invalid number"
|
x: 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
(0..5).map(|i| rsx! {
|
|
||||||
DemoC { x: i }
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
fn DemoC(cx: Scope, x: i32) -> Element {
|
fn DemoC(cx: Scope, x: i32) -> Element {
|
||||||
|
let result = Err("Error");
|
||||||
|
|
||||||
|
result.throw()?;
|
||||||
|
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
h1 {
|
h1 {
|
||||||
"asdasdasdasd {x}"
|
"{x}"
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -602,7 +602,7 @@ impl<'b> VirtualDom {
|
|||||||
// If none of the old keys are reused by the new children, then we remove all the remaining old children and
|
// If none of the old keys are reused by the new children, then we remove all the remaining old children and
|
||||||
// create the new children afresh.
|
// create the new children afresh.
|
||||||
if shared_keys.is_empty() {
|
if shared_keys.is_empty() {
|
||||||
if old.first().is_some() {
|
if !old.is_empty() {
|
||||||
self.remove_nodes(&old[1..]);
|
self.remove_nodes(&old[1..]);
|
||||||
self.replace(&old[0], new, Some(parent));
|
self.replace(&old[0], new, Some(parent));
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,25 +1,68 @@
|
|||||||
use crate::{ScopeId, ScopeState};
|
use crate::{
|
||||||
|
scope_context::{consume_context, current_scope_id, schedule_update_any},
|
||||||
|
Element, IntoDynNode, LazyNodes, Properties, Scope, ScopeId, ScopeState, Template,
|
||||||
|
TemplateAttribute, TemplateNode, VNode,
|
||||||
|
};
|
||||||
use std::{
|
use std::{
|
||||||
any::{Any, TypeId},
|
any::{Any, TypeId},
|
||||||
|
backtrace::Backtrace,
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
fmt::Debug,
|
error::Error,
|
||||||
|
fmt::{Debug, Display},
|
||||||
|
rc::Rc,
|
||||||
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A boundary that will capture any errors from child components
|
/// Provide an error boundary to catch errors from child components
|
||||||
pub struct ErrorBoundary {
|
pub fn use_error_boundary(cx: &ScopeState) -> &ErrorBoundary {
|
||||||
error: RefCell<Option<CapturedError>>,
|
cx.use_hook(|| cx.provide_context(ErrorBoundary::new()))
|
||||||
_id: ScopeId,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A boundary that will capture any errors from child components
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct ErrorBoundary {
|
||||||
|
inner: Rc<ErrorBoundaryInner>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A boundary that will capture any errors from child components
|
||||||
|
pub struct ErrorBoundaryInner {
|
||||||
|
error: RefCell<Option<CapturedError>>,
|
||||||
|
_id: ScopeId,
|
||||||
|
rerun_boundary: Arc<dyn Fn(ScopeId) + Send + Sync>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for ErrorBoundaryInner {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("ErrorBoundaryInner")
|
||||||
|
.field("error", &self.error)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
/// An instance of an error captured by a descendant component.
|
/// An instance of an error captured by a descendant component.
|
||||||
pub struct CapturedError {
|
pub struct CapturedError {
|
||||||
/// The error captured by the error boundary
|
/// The error captured by the error boundary
|
||||||
pub error: Box<dyn Debug + 'static>,
|
pub error: Box<dyn Debug + 'static>,
|
||||||
|
|
||||||
|
/// The backtrace of the error
|
||||||
|
pub backtrace: Backtrace,
|
||||||
|
|
||||||
/// The scope that threw the error
|
/// The scope that threw the error
|
||||||
pub scope: ScopeId,
|
pub scope: ScopeId,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Display for CapturedError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_fmt(format_args!(
|
||||||
|
"Encountered error: {:?}\nIn scope: {:?}\nBacktrace: {}",
|
||||||
|
self.error, self.scope, self.backtrace
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for CapturedError {}
|
||||||
|
|
||||||
impl CapturedError {
|
impl CapturedError {
|
||||||
/// Downcast the error type into a concrete error type
|
/// Downcast the error type into a concrete error type
|
||||||
pub fn downcast<T: 'static>(&self) -> Option<&T> {
|
pub fn downcast<T: 'static>(&self) -> Option<&T> {
|
||||||
@@ -32,17 +75,56 @@ impl CapturedError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ErrorBoundary {
|
impl Default for ErrorBoundaryInner {
|
||||||
pub fn new(id: ScopeId) -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
error: RefCell::new(None),
|
error: RefCell::new(None),
|
||||||
_id: id,
|
_id: current_scope_id()
|
||||||
|
.expect("Cannot create an error boundary outside of a component's scope."),
|
||||||
|
rerun_boundary: schedule_update_any().unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ErrorBoundary {
|
||||||
|
/// Create a new error boundary
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new error boundary in the current scope
|
||||||
|
pub(crate) fn new_in_scope(
|
||||||
|
scope: ScopeId,
|
||||||
|
rerun_boundary: Arc<dyn Fn(ScopeId) + Send + Sync>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: Rc::new(ErrorBoundaryInner {
|
||||||
|
error: RefCell::new(None),
|
||||||
|
_id: scope,
|
||||||
|
rerun_boundary,
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Push an error into this Error Boundary
|
/// Push an error into this Error Boundary
|
||||||
pub fn insert_error(&self, scope: ScopeId, error: Box<dyn Debug + 'static>) {
|
pub fn insert_error(
|
||||||
self.error.replace(Some(CapturedError { error, scope }));
|
&self,
|
||||||
|
scope: ScopeId,
|
||||||
|
error: Box<dyn Debug + 'static>,
|
||||||
|
backtrace: Backtrace,
|
||||||
|
) {
|
||||||
|
println!("{:?} {:?}", error, self.inner._id);
|
||||||
|
self.inner.error.replace(Some(CapturedError {
|
||||||
|
error,
|
||||||
|
scope,
|
||||||
|
backtrace,
|
||||||
|
}));
|
||||||
|
(self.inner.rerun_boundary)(self.inner._id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Take any error that has been captured by this error boundary
|
||||||
|
pub fn take_error(&self) -> Option<CapturedError> {
|
||||||
|
self.inner.error.take()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,7 +141,7 @@ impl ErrorBoundary {
|
|||||||
/// ```rust, ignore
|
/// ```rust, ignore
|
||||||
/// #[component]
|
/// #[component]
|
||||||
/// fn App(cx: Scope, count: String) -> Element {
|
/// fn App(cx: Scope, count: String) -> Element {
|
||||||
/// let id: i32 = count.parse().throw(cx)?;
|
/// let id: i32 = count.parse().throw()?;
|
||||||
///
|
///
|
||||||
/// cx.render(rsx! {
|
/// cx.render(rsx! {
|
||||||
/// div { "Count {}" }
|
/// div { "Count {}" }
|
||||||
@@ -84,14 +166,14 @@ pub trait Throw<S = ()>: Sized {
|
|||||||
/// ```rust, ignore
|
/// ```rust, ignore
|
||||||
/// #[component]
|
/// #[component]
|
||||||
/// fn App(cx: Scope, count: String) -> Element {
|
/// fn App(cx: Scope, count: String) -> Element {
|
||||||
/// let id: i32 = count.parse().throw(cx)?;
|
/// let id: i32 = count.parse().throw()?;
|
||||||
///
|
///
|
||||||
/// cx.render(rsx! {
|
/// cx.render(rsx! {
|
||||||
/// div { "Count {}" }
|
/// div { "Count {}" }
|
||||||
/// })
|
/// })
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
fn throw(self, cx: &ScopeState) -> Option<Self::Out>;
|
fn throw(self) -> Option<Self::Out>;
|
||||||
|
|
||||||
/// Returns an option that evaluates to None if there is an error, injecting the error to the nearest error boundary.
|
/// Returns an option that evaluates to None if there is an error, injecting the error to the nearest error boundary.
|
||||||
///
|
///
|
||||||
@@ -107,45 +189,46 @@ pub trait Throw<S = ()>: Sized {
|
|||||||
/// ```rust, ignore
|
/// ```rust, ignore
|
||||||
/// #[component]
|
/// #[component]
|
||||||
/// fn App(cx: Scope, count: String) -> Element {
|
/// fn App(cx: Scope, count: String) -> Element {
|
||||||
/// let id: i32 = count.parse().throw(cx)?;
|
/// let id: i32 = count.parse().throw()?;
|
||||||
///
|
///
|
||||||
/// cx.render(rsx! {
|
/// cx.render(rsx! {
|
||||||
/// div { "Count {}" }
|
/// div { "Count {}" }
|
||||||
/// })
|
/// })
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
fn throw_with<D: Debug + 'static>(
|
fn throw_with<D: Debug + 'static>(self, e: impl FnOnce() -> D) -> Option<Self::Out> {
|
||||||
self,
|
self.throw().or_else(|| throw_error(e()))
|
||||||
cx: &ScopeState,
|
}
|
||||||
e: impl FnOnce() -> D,
|
}
|
||||||
) -> Option<Self::Out>;
|
|
||||||
|
fn throw_error<T>(e: impl Debug + 'static) -> Option<T> {
|
||||||
|
if let Some(cx) = consume_context::<ErrorBoundary>() {
|
||||||
|
match current_scope_id() {
|
||||||
|
Some(id) => cx.insert_error(id, Box::new(e), Backtrace::capture()),
|
||||||
|
None => {
|
||||||
|
tracing::error!("Cannot throw error outside of a component's scope.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// We call clone on any errors that can be owned out of a reference
|
/// 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> {
|
impl<'a, T, O: Debug + 'static, E: ToOwned<Owned = O>> Throw for &'a Result<T, E> {
|
||||||
type Out = &'a T;
|
type Out = &'a T;
|
||||||
|
|
||||||
fn throw(self, cx: &ScopeState) -> Option<Self::Out> {
|
fn throw(self) -> Option<Self::Out> {
|
||||||
match self {
|
match self {
|
||||||
Ok(t) => Some(t),
|
Ok(t) => Some(t),
|
||||||
Err(e) => {
|
Err(e) => throw_error(e.to_owned()),
|
||||||
cx.throw(e.to_owned());
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn throw_with<D: Debug + 'static>(
|
fn throw_with<D: Debug + 'static>(self, err: impl FnOnce() -> D) -> Option<Self::Out> {
|
||||||
self,
|
|
||||||
cx: &ScopeState,
|
|
||||||
err: impl FnOnce() -> D,
|
|
||||||
) -> Option<Self::Out> {
|
|
||||||
match self {
|
match self {
|
||||||
Ok(t) => Some(t),
|
Ok(t) => Some(t),
|
||||||
Err(_e) => {
|
Err(_e) => throw_error(err()),
|
||||||
cx.throw(err());
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -154,25 +237,15 @@ impl<'a, T, O: Debug + 'static, E: ToOwned<Owned = O>> Throw for &'a Result<T, E
|
|||||||
impl<T, E: Debug + 'static> Throw for Result<T, E> {
|
impl<T, E: Debug + 'static> Throw for Result<T, E> {
|
||||||
type Out = T;
|
type Out = T;
|
||||||
|
|
||||||
fn throw(self, cx: &ScopeState) -> Option<T> {
|
fn throw(self) -> Option<T> {
|
||||||
match self {
|
match self {
|
||||||
Ok(t) => Some(t),
|
Ok(t) => Some(t),
|
||||||
Err(e) => {
|
Err(e) => throw_error(e),
|
||||||
cx.throw(e);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn throw_with<D: Debug + 'static>(
|
fn throw_with<D: Debug + 'static>(self, error: impl FnOnce() -> D) -> Option<Self::Out> {
|
||||||
self,
|
self.ok().or_else(|| throw_error(error()))
|
||||||
cx: &ScopeState,
|
|
||||||
error: impl FnOnce() -> D,
|
|
||||||
) -> Option<Self::Out> {
|
|
||||||
self.ok().or_else(|| {
|
|
||||||
cx.throw(error());
|
|
||||||
None
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,21 +253,234 @@ impl<T, E: Debug + 'static> Throw for Result<T, E> {
|
|||||||
impl<T> Throw for Option<T> {
|
impl<T> Throw for Option<T> {
|
||||||
type Out = T;
|
type Out = T;
|
||||||
|
|
||||||
fn throw(self, cx: &ScopeState) -> Option<T> {
|
fn throw(self) -> Option<T> {
|
||||||
self.or_else(|| {
|
self.or_else(|| throw_error("Attempted to unwrap a None value."))
|
||||||
cx.throw("None error.");
|
|
||||||
None
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn throw_with<D: Debug + 'static>(
|
fn throw_with<D: Debug + 'static>(self, error: impl FnOnce() -> D) -> Option<Self::Out> {
|
||||||
self,
|
self.or_else(|| throw_error(error()))
|
||||||
cx: &ScopeState,
|
}
|
||||||
error: impl FnOnce() -> D,
|
}
|
||||||
) -> Option<Self::Out> {
|
|
||||||
self.or_else(|| {
|
pub struct ErrorHandler<'a>(Box<dyn Fn(CapturedError) -> LazyNodes<'a, 'a> + 'a>);
|
||||||
cx.throw(error());
|
impl<'a, F: Fn(CapturedError) -> LazyNodes<'a, 'a> + 'a> From<F> for ErrorHandler<'a> {
|
||||||
None
|
fn from(value: F) -> Self {
|
||||||
})
|
Self(Box::new(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn default_handler<'a>(error: CapturedError) -> LazyNodes<'a, 'a> {
|
||||||
|
LazyNodes::new(move |__cx: &ScopeState| -> VNode {
|
||||||
|
static TEMPLATE: Template = Template {
|
||||||
|
name: "error_handle.rs:42:5:884",
|
||||||
|
roots: &[TemplateNode::Element {
|
||||||
|
tag: "pre",
|
||||||
|
namespace: None,
|
||||||
|
attrs: &[TemplateAttribute::Static {
|
||||||
|
name: "color",
|
||||||
|
namespace: Some("style"),
|
||||||
|
value: "red",
|
||||||
|
}],
|
||||||
|
children: &[TemplateNode::DynamicText { id: 0usize }],
|
||||||
|
}],
|
||||||
|
node_paths: &[&[0u8, 0u8]],
|
||||||
|
attr_paths: &[],
|
||||||
|
};
|
||||||
|
VNode {
|
||||||
|
parent: None,
|
||||||
|
key: None,
|
||||||
|
template: std::cell::Cell::new(TEMPLATE),
|
||||||
|
root_ids: bumpalo::collections::Vec::with_capacity_in(1usize, __cx.bump()).into(),
|
||||||
|
dynamic_nodes: __cx
|
||||||
|
.bump()
|
||||||
|
.alloc([__cx.text_node(format_args!("{0}", error))]),
|
||||||
|
dynamic_attrs: __cx.bump().alloc([]),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
pub struct ErrorBoundaryProps<'a> {
|
||||||
|
children: Element<'a>,
|
||||||
|
handle_error: ErrorHandler<'a>,
|
||||||
|
}
|
||||||
|
impl<'a> ErrorBoundaryProps<'a> {
|
||||||
|
/**
|
||||||
|
Create a builder for building `ErrorBoundaryProps`.
|
||||||
|
On the builder, call `.children(...)`(optional), `.handle_error(...)`(optional) to set the values of the fields.
|
||||||
|
Finally, call `.build()` to create the instance of `ErrorBoundaryProps`.
|
||||||
|
*/
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn builder() -> ErrorBoundaryPropsBuilder<'a, ((), ())> {
|
||||||
|
ErrorBoundaryPropsBuilder {
|
||||||
|
fields: ((), ()),
|
||||||
|
_phantom: ::core::default::Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[must_use]
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[allow(dead_code, non_camel_case_types, non_snake_case)]
|
||||||
|
pub struct ErrorBoundaryPropsBuilder<'a, TypedBuilderFields> {
|
||||||
|
fields: TypedBuilderFields,
|
||||||
|
_phantom: ::core::marker::PhantomData<&'a ()>,
|
||||||
|
}
|
||||||
|
impl<'a, TypedBuilderFields> Clone for ErrorBoundaryPropsBuilder<'a, TypedBuilderFields>
|
||||||
|
where
|
||||||
|
TypedBuilderFields: Clone,
|
||||||
|
{
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
fields: self.fields.clone(),
|
||||||
|
_phantom: ::core::default::Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a> Properties for ErrorBoundaryProps<'a> {
|
||||||
|
type Builder = ErrorBoundaryPropsBuilder<'a, ((), ())>;
|
||||||
|
const IS_STATIC: bool = false;
|
||||||
|
fn builder() -> Self::Builder {
|
||||||
|
ErrorBoundaryProps::builder()
|
||||||
|
}
|
||||||
|
unsafe fn memoize(&self, _: &Self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[allow(dead_code, non_camel_case_types, non_snake_case)]
|
||||||
|
pub trait ErrorBoundaryPropsBuilder_Optional<T> {
|
||||||
|
fn into_value<F: FnOnce() -> T>(self, default: F) -> T;
|
||||||
|
}
|
||||||
|
impl<T> ErrorBoundaryPropsBuilder_Optional<T> for () {
|
||||||
|
fn into_value<F: FnOnce() -> T>(self, default: F) -> T {
|
||||||
|
default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T> ErrorBoundaryPropsBuilder_Optional<T> for (T,) {
|
||||||
|
fn into_value<F: FnOnce() -> T>(self, _: F) -> T {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[allow(dead_code, non_camel_case_types, missing_docs)]
|
||||||
|
impl<'a, __handle_error> ErrorBoundaryPropsBuilder<'a, ((), __handle_error)> {
|
||||||
|
pub fn children(
|
||||||
|
self,
|
||||||
|
children: Element<'a>,
|
||||||
|
) -> ErrorBoundaryPropsBuilder<'a, ((Element<'a>,), __handle_error)> {
|
||||||
|
let children = (children,);
|
||||||
|
let (_, handle_error) = self.fields;
|
||||||
|
ErrorBoundaryPropsBuilder {
|
||||||
|
fields: (children, handle_error),
|
||||||
|
_phantom: self._phantom,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[allow(dead_code, non_camel_case_types, non_snake_case)]
|
||||||
|
pub enum ErrorBoundaryPropsBuilder_Error_Repeated_field_children {}
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[allow(dead_code, non_camel_case_types, missing_docs)]
|
||||||
|
impl<'a, __handle_error> ErrorBoundaryPropsBuilder<'a, ((Element<'a>,), __handle_error)> {
|
||||||
|
#[deprecated(note = "Repeated field children")]
|
||||||
|
pub fn children(
|
||||||
|
self,
|
||||||
|
_: ErrorBoundaryPropsBuilder_Error_Repeated_field_children,
|
||||||
|
) -> ErrorBoundaryPropsBuilder<'a, ((Element<'a>,), __handle_error)> {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[allow(dead_code, non_camel_case_types, missing_docs)]
|
||||||
|
impl<'a, __children> ErrorBoundaryPropsBuilder<'a, (__children, ())> {
|
||||||
|
pub fn handle_error(
|
||||||
|
self,
|
||||||
|
handle_error: impl ::core::convert::Into<ErrorHandler<'a>>,
|
||||||
|
) -> ErrorBoundaryPropsBuilder<'a, (__children, (ErrorHandler<'a>,))> {
|
||||||
|
let handle_error = (handle_error.into(),);
|
||||||
|
let (children, _) = self.fields;
|
||||||
|
ErrorBoundaryPropsBuilder {
|
||||||
|
fields: (children, handle_error),
|
||||||
|
_phantom: self._phantom,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[allow(dead_code, non_camel_case_types, non_snake_case)]
|
||||||
|
pub enum ErrorBoundaryPropsBuilder_Error_Repeated_field_handle_error {}
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[allow(dead_code, non_camel_case_types, missing_docs)]
|
||||||
|
impl<'a, __children> ErrorBoundaryPropsBuilder<'a, (__children, (ErrorHandler<'a>,))> {
|
||||||
|
#[deprecated(note = "Repeated field handle_error")]
|
||||||
|
pub fn handle_error(
|
||||||
|
self,
|
||||||
|
_: ErrorBoundaryPropsBuilder_Error_Repeated_field_handle_error,
|
||||||
|
) -> ErrorBoundaryPropsBuilder<'a, (__children, (ErrorHandler<'a>,))> {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[allow(dead_code, non_camel_case_types, missing_docs)]
|
||||||
|
impl<
|
||||||
|
'a,
|
||||||
|
__handle_error: ErrorBoundaryPropsBuilder_Optional<ErrorHandler<'a>>,
|
||||||
|
__children: ErrorBoundaryPropsBuilder_Optional<Element<'a>>,
|
||||||
|
> ErrorBoundaryPropsBuilder<'a, (__children, __handle_error)>
|
||||||
|
{
|
||||||
|
pub fn build(self) -> ErrorBoundaryProps<'a> {
|
||||||
|
let (children, handle_error) = self.fields;
|
||||||
|
let children = ErrorBoundaryPropsBuilder_Optional::into_value(children, || {
|
||||||
|
::core::default::Default::default()
|
||||||
|
});
|
||||||
|
let handle_error = ErrorBoundaryPropsBuilder_Optional::into_value(handle_error, || {
|
||||||
|
ErrorHandler(Box::new(default_handler))
|
||||||
|
});
|
||||||
|
ErrorBoundaryProps {
|
||||||
|
children,
|
||||||
|
handle_error,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Create a new error boundary component.
|
||||||
|
///
|
||||||
|
/// ## Details
|
||||||
|
///
|
||||||
|
/// 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.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
///
|
||||||
|
/// ```rust, ignore
|
||||||
|
/// rsx!{
|
||||||
|
/// ErrorBoundary {
|
||||||
|
/// handle_error: |error| rsx! { "Oops, we encountered an error. Please report {error} to the developer of this application" }
|
||||||
|
/// ThrowsError {}
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Usage
|
||||||
|
///
|
||||||
|
/// Error boundaries are an easy way to handle errors in your application.
|
||||||
|
/// They are similar to `try/catch` in JavaScript, but they only catch errors in the tree below them.
|
||||||
|
/// 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.
|
||||||
|
#[allow(non_upper_case_globals, non_snake_case)]
|
||||||
|
pub fn ErrorBoundary<'a>(cx: Scope<'a, ErrorBoundaryProps<'a>>) -> Element {
|
||||||
|
let error_boundary = use_error_boundary(cx);
|
||||||
|
match error_boundary.take_error() {
|
||||||
|
Some(error) => cx.render((cx.props.handle_error.0)(error)),
|
||||||
|
None => Some({
|
||||||
|
let __cx = cx;
|
||||||
|
static TEMPLATE: Template = Template {
|
||||||
|
name: "examples/error_handle.rs:81:17:2342",
|
||||||
|
roots: &[TemplateNode::Dynamic { id: 0usize }],
|
||||||
|
node_paths: &[&[0u8]],
|
||||||
|
attr_paths: &[],
|
||||||
|
};
|
||||||
|
VNode {
|
||||||
|
parent: None,
|
||||||
|
key: None,
|
||||||
|
template: std::cell::Cell::new(TEMPLATE),
|
||||||
|
root_ids: bumpalo::collections::Vec::with_capacity_in(1usize, __cx.bump()).into(),
|
||||||
|
dynamic_nodes: __cx.bump().alloc([{
|
||||||
|
let ___nodes = (&cx.props.children).into_vnode(__cx);
|
||||||
|
___nodes
|
||||||
|
}]),
|
||||||
|
dynamic_attrs: __cx.bump().alloc([]),
|
||||||
|
}
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -90,10 +90,11 @@ pub mod prelude {
|
|||||||
pub use crate::innerlude::{
|
pub use crate::innerlude::{
|
||||||
consume_context, consume_context_from_scope, current_scope_id, fc_to_builder, has_context,
|
consume_context, consume_context_from_scope, current_scope_id, fc_to_builder, has_context,
|
||||||
provide_context, provide_context_to_scope, provide_root_context, push_future,
|
provide_context, provide_context_to_scope, provide_root_context, push_future,
|
||||||
remove_future, schedule_update_any, spawn, spawn_forever, suspend, throw, AnyValue,
|
remove_future, schedule_update_any, spawn, spawn_forever, suspend, use_error_boundary,
|
||||||
Component, Element, Event, EventHandler, Fragment, IntoAttributeValue, IntoDynNode,
|
AnyValue, Component, Element, ErrorBoundary, Event, EventHandler, Fragment,
|
||||||
LazyNodes, Properties, Runtime, RuntimeGuard, Scope, ScopeId, ScopeState, Scoped, TaskId,
|
IntoAttributeValue, IntoDynNode, LazyNodes, Properties, Runtime, RuntimeGuard, Scope,
|
||||||
Template, TemplateAttribute, TemplateNode, Throw, VNode, VirtualDom,
|
ScopeId, ScopeState, Scoped, TaskId, Template, TemplateAttribute, TemplateNode, Throw,
|
||||||
|
VNode, VirtualDom,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
innerlude::{ErrorBoundary, Scheduler, SchedulerMsg},
|
innerlude::{Scheduler, SchedulerMsg},
|
||||||
runtime::{with_current_scope, with_runtime},
|
runtime::{with_current_scope, with_runtime},
|
||||||
Element, ScopeId, TaskId,
|
Element, ScopeId, TaskId,
|
||||||
};
|
};
|
||||||
@@ -7,7 +7,6 @@ use rustc_hash::FxHashSet;
|
|||||||
use std::{
|
use std::{
|
||||||
any::Any,
|
any::Any,
|
||||||
cell::{Cell, RefCell},
|
cell::{Cell, RefCell},
|
||||||
fmt::Debug,
|
|
||||||
future::Future,
|
future::Future,
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
@@ -240,19 +239,6 @@ impl ScopeContext {
|
|||||||
self.tasks.remove(id);
|
self.tasks.remove(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inject an error into the nearest error boundary and quit rendering
|
|
||||||
///
|
|
||||||
/// The error doesn't need to implement Error or any specific traits since the boundary
|
|
||||||
/// itself will downcast the error into a trait object.
|
|
||||||
pub fn throw(&self, error: impl Debug + 'static) -> Option<()> {
|
|
||||||
if let Some(cx) = self.consume_context::<Rc<ErrorBoundary>>() {
|
|
||||||
cx.insert_error(self.scope_id(), Box::new(error));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Always return none during a throw
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Mark this component as suspended and then return None
|
/// Mark this component as suspended and then return None
|
||||||
pub fn suspend(&self) -> Option<Element> {
|
pub fn suspend(&self) -> Option<Element> {
|
||||||
self.suspended.set(true);
|
self.suspended.set(true);
|
||||||
@@ -322,11 +308,6 @@ pub fn suspend() -> Option<Element<'static>> {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Throw an error into the nearest error boundary
|
|
||||||
pub fn throw(error: impl Debug + 'static) -> Option<()> {
|
|
||||||
with_current_scope(|cx| cx.throw(error)).flatten()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Pushes the future onto the poll queue to be polled after the component renders.
|
/// Pushes the future onto the poll queue to be polled after the component renders.
|
||||||
pub fn push_future(fut: impl Future<Output = ()> + 'static) -> Option<TaskId> {
|
pub fn push_future(fut: impl Future<Output = ()> + 'static) -> Option<TaskId> {
|
||||||
with_current_scope(|cx| cx.push_future(fut))
|
with_current_scope(|cx| cx.push_future(fut))
|
||||||
|
|||||||
@@ -518,19 +518,6 @@ impl<'src> ScopeState {
|
|||||||
AttributeValue::Any(RefCell::new(Some(boxed)))
|
AttributeValue::Any(RefCell::new(Some(boxed)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inject an error into the nearest error boundary and quit rendering
|
|
||||||
///
|
|
||||||
/// The error doesn't need to implement Error or any specific traits since the boundary
|
|
||||||
/// itself will downcast the error into a trait object.
|
|
||||||
pub fn throw(&self, error: impl Debug + 'static) -> Option<()> {
|
|
||||||
if let Some(cx) = self.consume_context::<Rc<ErrorBoundary>>() {
|
|
||||||
cx.insert_error(self.scope_id(), Box::new(error));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Always return none during a throw
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Mark this component as suspended and then return None
|
/// Mark this component as suspended and then return None
|
||||||
pub fn suspend(&self) -> Option<Element> {
|
pub fn suspend(&self) -> Option<Element> {
|
||||||
let cx = self.context();
|
let cx = self.context();
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ use crate::{
|
|||||||
use futures_util::{pin_mut, StreamExt};
|
use futures_util::{pin_mut, StreamExt};
|
||||||
use rustc_hash::{FxHashMap, FxHashSet};
|
use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
use slab::Slab;
|
use slab::Slab;
|
||||||
use std::{any::Any, cell::Cell, collections::BTreeSet, future::Future, ptr::NonNull, rc::Rc};
|
use std::{any::Any, cell::Cell, collections::BTreeSet, future::Future, ptr::NonNull, rc::Rc, sync::Arc};
|
||||||
|
|
||||||
/// A virtual node system that progresses user events and diffs UI trees.
|
/// A virtual node system that progresses user events and diffs UI trees.
|
||||||
///
|
///
|
||||||
@@ -277,7 +277,10 @@ impl VirtualDom {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Unlike react, we provide a default error boundary that just renders the error as a string
|
// Unlike react, we provide a default error boundary that just renders the error as a string
|
||||||
root.provide_context(Rc::new(ErrorBoundary::new(ScopeId::ROOT)));
|
root.provide_context(Rc::new(ErrorBoundary::new_in_scope(
|
||||||
|
ScopeId::ROOT,
|
||||||
|
Arc::new(|_| {}),
|
||||||
|
)));
|
||||||
|
|
||||||
// the root element is always given element ID 0 since it's the container for the entire tree
|
// the root element is always given element ID 0 since it's the container for the entire tree
|
||||||
dom.elements.insert(None);
|
dom.elements.insert(None);
|
||||||
|
|||||||
@@ -24,9 +24,9 @@ fn NoneChild(_cx: Scope) -> Element {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn ThrowChild(cx: Scope) -> Element {
|
fn ThrowChild(cx: Scope) -> Element {
|
||||||
cx.throw(std::io::Error::new(std::io::ErrorKind::AddrInUse, "asd"))?;
|
Err(std::io::Error::new(std::io::ErrorKind::AddrInUse, "asd")).throw()?;
|
||||||
|
|
||||||
let _g: i32 = "123123".parse().throw(cx)?;
|
let _g: i32 = "123123".parse().throw()?;
|
||||||
|
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
div {}
|
div {}
|
||||||
|
|||||||
Reference in new issue
Block a user