From ae5dca8f43df9507a8ebb690b5c895b498658949 Mon Sep 17 00:00:00 2001 From: Leonard Date: Wed, 13 Sep 2023 18:55:26 +0200 Subject: [PATCH] `Routable` improvements (#1461) * Fix `SegmentType::CatchAll` `Display` * Rename `display_route_segements` to `display_route_segments` and add dots to docs * Fix `Routeable::static_routes` and add more route retrieval methods * Fix Clippy and extract duplication to function * Return route strings instead of instances * Remove some methods * Clippy --- packages/router-macro/src/lib.rs | 10 +- packages/router-macro/src/segment.rs | 2 +- packages/router/src/routable.rs | 148 +++++++++++++++------------ 3 files changed, 91 insertions(+), 69 deletions(-) diff --git a/packages/router-macro/src/lib.rs b/packages/router-macro/src/lib.rs index 1078e5b9a..b1ff7b0fe 100644 --- a/packages/router-macro/src/lib.rs +++ b/packages/router-macro/src/lib.rs @@ -210,7 +210,7 @@ pub fn routable(input: TokenStream) -> TokenStream { let display_impl = route_enum.impl_display(); let routable_impl = route_enum.routable_impl(); - quote! { + (quote! { #error_type #display_impl @@ -218,7 +218,7 @@ pub fn routable(input: TokenStream) -> TokenStream { #routable_impl #parse_impl - } + }) .into() } @@ -678,9 +678,9 @@ impl ToTokens for SegmentType { impl<'a> From<&'a RouteSegment> for SegmentType { fn from(value: &'a RouteSegment) -> Self { match value { - segment::RouteSegment::Static(s) => SegmentType::Static(s.to_string()), - segment::RouteSegment::Dynamic(s, _) => SegmentType::Dynamic(s.to_string()), - segment::RouteSegment::CatchAll(s, _) => SegmentType::CatchAll(s.to_string()), + RouteSegment::Static(s) => SegmentType::Static(s.to_string()), + RouteSegment::Dynamic(s, _) => SegmentType::Dynamic(s.to_string()), + RouteSegment::CatchAll(s, _) => SegmentType::CatchAll(s.to_string()), } } } diff --git a/packages/router-macro/src/segment.rs b/packages/router-macro/src/segment.rs index e13141dc0..7e4e40662 100644 --- a/packages/router-macro/src/segment.rs +++ b/packages/router-macro/src/segment.rs @@ -25,7 +25,7 @@ impl RouteSegment { match self { Self::Static(segment) => quote! { write!(f, "/{}", #segment)?; }, Self::Dynamic(ident, _) => quote! { write!(f, "/{}", #ident)?; }, - Self::CatchAll(ident, _) => quote! { #ident.display_route_segements(f)?; }, + Self::CatchAll(ident, _) => quote! { #ident.display_route_segments(f)?; }, } } diff --git a/packages/router/src/routable.rs b/packages/router/src/routable.rs index 39a5245a9..d074ab210 100644 --- a/packages/router/src/routable.rs +++ b/packages/router/src/routable.rs @@ -3,16 +3,18 @@ #![allow(non_snake_case)] use dioxus::prelude::*; +use std::iter::FlatMap; +use std::slice::Iter; use std::{fmt::Display, str::FromStr}; -/// An error that occurs when parsing a route +/// An error that occurs when parsing a route. #[derive(Debug, PartialEq)] -pub struct RouteParseError { - /// The attempted routes that failed to match +pub struct RouteParseError { + /// The attempted routes that failed to match. pub attempted_routes: Vec, } -impl std::fmt::Display for RouteParseError { +impl Display for RouteParseError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "Route did not match:\nAttempted Matches:\n")?; for (i, route) in self.attempted_routes.iter().enumerate() { @@ -26,9 +28,9 @@ impl std::fmt::Display for RouteParseError { /// /// This trait needs to be implemented if you want to turn a query string into a struct. /// -/// A working example can be found in the `examples` folder in the root package under `query_segments_demo` +/// A working example can be found in the `examples` folder in the root package under `query_segments_demo`. pub trait FromQuery { - /// Create an instance of `Self` from a query string + /// Create an instance of `Self` from a query string. fn from_query(query: &str) -> Self; } @@ -38,18 +40,18 @@ impl From<&'a str>> FromQuery for T { } } -/// Something that can be created from a route segment +/// Something that can be created from a route segment. pub trait FromRouteSegment: Sized { - /// The error that can occur when parsing a route segment + /// The error that can occur when parsing a route segment. type Err; - /// Create an instance of `Self` from a route segment + /// Create an instance of `Self` from a route segment. fn from_route_segment(route: &str) -> Result; } impl FromRouteSegment for T where - ::Err: std::fmt::Display, + ::Err: Display, { type Err = ::Err; @@ -70,17 +72,17 @@ fn full_circle() { assert_eq!(String::from_route_segment(route).unwrap(), route); } -/// Something that can be converted to route segments +/// Something that can be converted to route segments. pub trait ToRouteSegments { - /// Display the route segments - fn display_route_segements(self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result; + /// Display the route segments. + fn display_route_segments(self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result; } -impl ToRouteSegments for I +impl ToRouteSegments for I where I: IntoIterator, { - fn display_route_segements(self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn display_route_segments(self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { for segment in self { write!(f, "/")?; let segment = segment.to_string(); @@ -100,22 +102,22 @@ where fn to_route_segments() { struct DisplaysRoute; - impl std::fmt::Display for DisplaysRoute { + impl Display for DisplaysRoute { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let segments = vec!["hello", "world"]; - segments.display_route_segements(f) + segments.display_route_segments(f) } } assert_eq!(DisplaysRoute.to_string(), "/hello/world"); } -/// Something that can be created from route segments +/// Something that can be created from route segments. pub trait FromRouteSegments: Sized { - /// The error that can occur when parsing route segments + /// The error that can occur when parsing route segments. type Err; - /// Create an instance of `Self` from route segments + /// Create an instance of `Self` from route segments. fn from_route_segments(segments: &[&str]) -> Result; } @@ -130,20 +132,45 @@ impl> FromRouteSegments for I { } } +/// A flattened version of [`Routable::SITE_MAP`]. +/// This essentially represents a `Vec>`, which you can collect it into. +type SiteMapFlattened<'a> = FlatMap< + Iter<'a, SiteMapSegment>, + Vec>, + fn(&SiteMapSegment) -> Vec>, +>; + +fn seg_strs_to_route(segs_maybe: &Option>) -> Option +where + T: Routable, +{ + if let Some(str) = seg_strs_to_str(segs_maybe) { + T::from_str(&str).ok() + } else { + None + } +} + +fn seg_strs_to_str(segs_maybe: &Option>) -> Option { + segs_maybe + .as_ref() + .map(|segs| String::from('/') + &segs.join("/")) +} + /// Something that can be: -/// 1. Converted from a route -/// 2. Converted to a route -/// 3. Rendered as a component +/// 1. Converted from a route. +/// 2. Converted to a route. +/// 3. Rendered as a component. /// -/// This trait can be derived using the `#[derive(Routable)]` macro -pub trait Routable: std::fmt::Display + std::str::FromStr + Clone + 'static { - /// The error that can occur when parsing a route +/// This trait can be derived using the `#[derive(Routable)]` macro. +pub trait Routable: FromStr + Display + Clone + 'static { + /// The error that can occur when parsing a route. const SITE_MAP: &'static [SiteMapSegment]; /// Render the route at the given level fn render<'a>(&self, cx: &'a ScopeState, level: usize) -> Element<'a>; - /// Checks if this route is a child of the given route + /// Checks if this route is a child of the given route. /// /// # Example /// ```rust @@ -185,7 +212,7 @@ pub trait Routable: std::fmt::Display + std::str::FromStr + Clone + 'static { true } - /// Get the parent route of this route + /// Get the parent route of this route. /// /// # Example /// ```rust @@ -225,72 +252,67 @@ pub trait Routable: std::fmt::Display + std::str::FromStr + Clone + 'static { Self::from_str(&new_route).ok() } - /// Gets a list of all static routes + /// Returns a flattened version of [`Self::SITE_MAP`]. + fn flatten_site_map<'a>() -> SiteMapFlattened<'a> { + Self::SITE_MAP.iter().flat_map(SiteMapSegment::flatten) + } + + /// Gets a list of all the static routes. + /// Example static route: `#[route("/static/route")]` fn static_routes() -> Vec { - Self::SITE_MAP - .iter() - .flat_map(|segment| segment.flatten()) + Self::flatten_site_map() .filter_map(|route| { - if route + let route_if_static = &route .iter() - .all(|segment| matches!(segment, SegmentType::Static(_))) - { - Self::from_str( - &route - .iter() - .map(|segment| match segment { - SegmentType::Static(s) => s.to_string(), - _ => unreachable!(), - }) - .collect::>() - .join("/"), - ) - .ok() - } else { - None - } + .map(|segment| match segment { + SegmentType::Static(s) => Some(*s), + _ => None, + }) + .collect::>>(); + + seg_strs_to_route(route_if_static) }) .collect() } } trait RoutableFactory { - type Err: std::fmt::Display; + type Err: Display; type Routable: Routable + FromStr; } impl RoutableFactory for R where - ::Err: std::fmt::Display, + ::Err: Display, { type Err = ::Err; type Routable = R; } -trait RouteRenderable: std::fmt::Display + 'static { +trait RouteRenderable: Display + 'static { fn render<'a>(&self, cx: &'a ScopeState, level: usize) -> Element<'a>; } impl RouteRenderable for R where - ::Err: std::fmt::Display, + ::Err: Display, { fn render<'a>(&self, cx: &'a ScopeState, level: usize) -> Element<'a> { self.render(cx, level) } } -/// A type erased map of the site structurens +/// A type erased map of the site structure. #[derive(Debug, Clone, PartialEq)] pub struct SiteMapSegment { - /// The type of the route segment + /// The type of the route segment. pub segment_type: SegmentType, - /// The children of the route segment + /// The children of the route segment. pub children: &'static [SiteMapSegment], } impl SiteMapSegment { - /// Take a map of the site structure and flatten it into a vector of routes + /// Take a map of the site structure and flatten it into a vector of routes. pub fn flatten(&self) -> Vec> { let mut routes = Vec::new(); self.flatten_inner(&mut routes, Vec::new()); @@ -310,16 +332,16 @@ impl SiteMapSegment { } } -/// The type of a route segment +/// The type of a route segment. #[derive(Debug, Clone, PartialEq)] pub enum SegmentType { - /// A static route segment + /// A static route segment. Static(&'static str), - /// A dynamic route segment + /// A dynamic route segment. Dynamic(&'static str), - /// A catch all route segment + /// A catch all route segment. CatchAll(&'static str), - /// A child router + /// A child router. Child, } @@ -329,7 +351,7 @@ impl Display for SegmentType { SegmentType::Static(s) => write!(f, "/{}", s), SegmentType::Child => Ok(()), SegmentType::Dynamic(s) => write!(f, "/:{}", s), - SegmentType::CatchAll(s) => write!(f, "/:...{}", s), + SegmentType::CatchAll(s) => write!(f, "/:..{}", s), } } }