Browse Source

`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
Leonard 1 year ago
parent
commit
ae5dca8f43

+ 5 - 5
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()),
         }
     }
 }

+ 1 - 1
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)?; },
         }
     }
 

+ 85 - 63
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<E: std::fmt::Display> {
-    /// The attempted routes that failed to match
+pub struct RouteParseError<E: Display> {
+    /// The attempted routes that failed to match.
     pub attempted_routes: Vec<E>,
 }
 
-impl<E: std::fmt::Display> std::fmt::Display for RouteParseError<E> {
+impl<E: Display> Display for RouteParseError<E> {
     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<E: std::fmt::Display> std::fmt::Display for RouteParseError<E> {
 ///
 /// 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<T: for<'a> 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<Self, Self::Err>;
 }
 
 impl<T: FromStr> FromRouteSegment for T
 where
-    <T as FromStr>::Err: std::fmt::Display,
+    <T as FromStr>::Err: Display,
 {
     type Err = <T as FromStr>::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<I, T: std::fmt::Display> ToRouteSegments for I
+impl<I, T: Display> ToRouteSegments for I
 where
     I: IntoIterator<Item = T>,
 {
-    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<Self, Self::Err>;
 }
 
@@ -130,20 +132,45 @@ impl<I: std::iter::FromIterator<String>> FromRouteSegments for I {
     }
 }
 
+/// A flattened version of [`Routable::SITE_MAP`].
+/// This essentially represents a `Vec<Vec<SegmentType>>`, which you can collect it into.
+type SiteMapFlattened<'a> = FlatMap<
+    Iter<'a, SiteMapSegment>,
+    Vec<Vec<SegmentType>>,
+    fn(&SiteMapSegment) -> Vec<Vec<SegmentType>>,
+>;
+
+fn seg_strs_to_route<T>(segs_maybe: &Option<Vec<&str>>) -> Option<T>
+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<Vec<&str>>) -> Option<String> {
+    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> {
-        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::<Vec<_>>()
-                            .join("/"),
-                    )
-                    .ok()
-                } else {
-                    None
-                }
+                    .map(|segment| match segment {
+                        SegmentType::Static(s) => Some(*s),
+                        _ => None,
+                    })
+                    .collect::<Option<Vec<_>>>();
+
+                seg_strs_to_route(route_if_static)
             })
             .collect()
     }
 }
 
 trait RoutableFactory {
-    type Err: std::fmt::Display;
+    type Err: Display;
     type Routable: Routable + FromStr<Err = Self::Err>;
 }
 
 impl<R: Routable + FromStr> RoutableFactory for R
 where
-    <R as FromStr>::Err: std::fmt::Display,
+    <R as FromStr>::Err: Display,
 {
     type Err = <R as FromStr>::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<R: Routable> RouteRenderable for R
 where
-    <R as FromStr>::Err: std::fmt::Display,
+    <R as FromStr>::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<Vec<SegmentType>> {
         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),
         }
     }
 }