diff --git a/packages/core/src/error_boundary.rs b/packages/core/src/error_boundary.rs index 91ac3b61d..4d9848dd8 100644 --- a/packages/core/src/error_boundary.rs +++ b/packages/core/src/error_boundary.rs @@ -67,7 +67,7 @@ impl ErrorBoundary { /// }) /// } /// ``` -pub trait Throw { +pub trait Throw: Sized { /// The value that will be returned in if the given value is `Ok`. type Out; @@ -94,6 +94,34 @@ pub trait Throw { /// } /// ``` 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 @@ -109,6 +137,20 @@ impl<'a, T, O: Debug + 'static, E: ToOwned> Throw for &'a Result( + 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 @@ -124,6 +166,17 @@ impl Throw for Result { } } } + + 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 @@ -131,12 +184,20 @@ impl Throw for Option { type Out = T; fn throw(self, cx: &ScopeState) -> Option { - match self { - Some(t) => Some(t), - None => { - cx.throw("None error."); - None - } - } + 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 + }) } } diff --git a/packages/router/src/hooks/use_route.rs b/packages/router/src/hooks/use_route.rs index 2e6b2cd4e..146de3109 100644 --- a/packages/router/src/hooks/use_route.rs +++ b/packages/router/src/hooks/use_route.rs @@ -1,4 +1,4 @@ -use crate::{ParsedRoute, RouteContext, RouterContext}; +use crate::{Error as RouterError, ParsedRoute, RouteContext, RouterContext}; use dioxus::core::{ScopeId, ScopeState}; use std::{borrow::Cow, str::FromStr, sync::Arc}; use url::Url; @@ -107,6 +107,23 @@ impl UseRoute { { self.segment(name).map(|value| value.parse::()) } + + /// Get the named parameter from the path, as defined in your router. The + /// value will be parsed into the type specified by `T` by calling + /// `value.parse::()`. This method returns `None` if the named + /// parameter does not exist in the current path. + pub fn parse_segment_or_404(&self, name: &str) -> Option + where + T: FromStr, + { + match self.parse_segment(name) { + Some(Ok(val)) => Some(val), + _ => { + // todo: throw a 404 + None + } + } + } } // The entire purpose of this struct is to unubscribe this component when it is unmounted. diff --git a/packages/router/src/lib.rs b/packages/router/src/lib.rs index 5aaac6ad3..266d40d50 100644 --- a/packages/router/src/lib.rs +++ b/packages/router/src/lib.rs @@ -30,3 +30,8 @@ mod service; pub use routecontext::*; pub use service::*; + +#[derive(Debug)] +pub enum Error { + NotFound, +}