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
This commit is contained in:
Leonard authored and GitHub committed 2023-09-13 11:55:26 -05:00
1 parent 300cbf4fc6
commit ae5dca8f43
3 files changed
+91 -69

No files matched your search

+5 -5
View File
@@ -210,7 +210,7 @@ pub fn routable(input: TokenStream) -> TokenStream {
let display_impl = route_enum.impl_display(); let display_impl = route_enum.impl_display();
let routable_impl = route_enum.routable_impl(); let routable_impl = route_enum.routable_impl();
quote! { (quote! {
#error_type #error_type
#display_impl #display_impl
@@ -218,7 +218,7 @@ pub fn routable(input: TokenStream) -> TokenStream {
#routable_impl #routable_impl
#parse_impl #parse_impl
} })
.into() .into()
} }
@@ -678,9 +678,9 @@ impl ToTokens for SegmentType {
impl<'a> From<&'a RouteSegment> for SegmentType { impl<'a> From<&'a RouteSegment> for SegmentType {
fn from(value: &'a RouteSegment) -> Self { fn from(value: &'a RouteSegment) -> Self {
match value { match value {
segment::RouteSegment::Static(s) => SegmentType::Static(s.to_string()), RouteSegment::Static(s) => SegmentType::Static(s.to_string()),
segment::RouteSegment::Dynamic(s, _) => SegmentType::Dynamic(s.to_string()), RouteSegment::Dynamic(s, _) => SegmentType::Dynamic(s.to_string()),
segment::RouteSegment::CatchAll(s, _) => SegmentType::CatchAll(s.to_string()), RouteSegment::CatchAll(s, _) => SegmentType::CatchAll(s.to_string()),
} }
} }
} }
+1 -1
View File
@@ -25,7 +25,7 @@ impl RouteSegment {
match self { match self {
Self::Static(segment) => quote! { write!(f, "/{}", #segment)?; }, Self::Static(segment) => quote! { write!(f, "/{}", #segment)?; },
Self::Dynamic(ident, _) => quote! { write!(f, "/{}", #ident)?; }, 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
View File
@@ -3,16 +3,18 @@
#![allow(non_snake_case)] #![allow(non_snake_case)]
use dioxus::prelude::*; use dioxus::prelude::*;
use std::iter::FlatMap;
use std::slice::Iter;
use std::{fmt::Display, str::FromStr}; 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)] #[derive(Debug, PartialEq)]
pub struct RouteParseError<E: std::fmt::Display> { pub struct RouteParseError<E: Display> {
/// The attempted routes that failed to match /// The attempted routes that failed to match.
pub attempted_routes: Vec<E>, 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 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Route did not match:\nAttempted Matches:\n")?; write!(f, "Route did not match:\nAttempted Matches:\n")?;
for (i, route) in self.attempted_routes.iter().enumerate() { 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. /// 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 { 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; 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 { 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; 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>; fn from_route_segment(route: &str) -> Result<Self, Self::Err>;
} }
impl<T: FromStr> FromRouteSegment for T impl<T: FromStr> FromRouteSegment for T
where where
<T as FromStr>::Err: std::fmt::Display, <T as FromStr>::Err: Display,
{ {
type Err = <T as FromStr>::Err; type Err = <T as FromStr>::Err;
@@ -70,17 +72,17 @@ fn full_circle() {
assert_eq!(String::from_route_segment(route).unwrap(), route); 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 { pub trait ToRouteSegments {
/// Display the route segments /// Display the route segments.
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;
} }
impl<I, T: std::fmt::Display> ToRouteSegments for I impl<I, T: Display> ToRouteSegments for I
where where
I: IntoIterator<Item = T>, 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 { for segment in self {
write!(f, "/")?; write!(f, "/")?;
let segment = segment.to_string(); let segment = segment.to_string();
@@ -100,22 +102,22 @@ where
fn to_route_segments() { fn to_route_segments() {
struct DisplaysRoute; struct DisplaysRoute;
impl std::fmt::Display for DisplaysRoute { impl Display for DisplaysRoute {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let segments = vec!["hello", "world"]; let segments = vec!["hello", "world"];
segments.display_route_segements(f) segments.display_route_segments(f)
} }
} }
assert_eq!(DisplaysRoute.to_string(), "/hello/world"); 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 { pub trait FromRouteSegments: Sized {
/// The error that can occur when parsing route segments /// The error that can occur when parsing route segments.
type Err; 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>; 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: /// Something that can be:
/// 1. Converted from a route /// 1. Converted from a route.
/// 2. Converted to a route /// 2. Converted to a route.
/// 3. Rendered as a component /// 3. Rendered as a component.
/// ///
/// This trait can be derived using the `#[derive(Routable)]` macro /// This trait can be derived using the `#[derive(Routable)]` macro.
pub trait Routable: std::fmt::Display + std::str::FromStr + Clone + 'static { pub trait Routable: FromStr + Display + Clone + 'static {
/// The error that can occur when parsing a route /// The error that can occur when parsing a route.
const SITE_MAP: &'static [SiteMapSegment]; const SITE_MAP: &'static [SiteMapSegment];
/// Render the route at the given level /// Render the route at the given level
fn render<'a>(&self, cx: &'a ScopeState, level: usize) -> Element<'a>; 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 /// # Example
/// ```rust /// ```rust
@@ -185,7 +212,7 @@ pub trait Routable: std::fmt::Display + std::str::FromStr + Clone + 'static {
true true
} }
/// Get the parent route of this route /// Get the parent route of this route.
/// ///
/// # Example /// # Example
/// ```rust /// ```rust
@@ -225,72 +252,67 @@ pub trait Routable: std::fmt::Display + std::str::FromStr + Clone + 'static {
Self::from_str(&new_route).ok() 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> { fn static_routes() -> Vec<Self> {
Self::SITE_MAP Self::flatten_site_map()
.iter()
.flat_map(|segment| segment.flatten())
.filter_map(|route| { .filter_map(|route| {
if route let route_if_static = &route
.iter() .iter()
.all(|segment| matches!(segment, SegmentType::Static(_))) .map(|segment| match segment {
{ SegmentType::Static(s) => Some(*s),
Self::from_str( _ => None,
&route })
.iter() .collect::<Option<Vec<_>>>();
.map(|segment| match segment {
SegmentType::Static(s) => s.to_string(), seg_strs_to_route(route_if_static)
_ => unreachable!(),
})
.collect::<Vec<_>>()
.join("/"),
)
.ok()
} else {
None
}
}) })
.collect() .collect()
} }
} }
trait RoutableFactory { trait RoutableFactory {
type Err: std::fmt::Display; type Err: Display;
type Routable: Routable + FromStr<Err = Self::Err>; type Routable: Routable + FromStr<Err = Self::Err>;
} }
impl<R: Routable + FromStr> RoutableFactory for R impl<R: Routable + FromStr> RoutableFactory for R
where where
<R as FromStr>::Err: std::fmt::Display, <R as FromStr>::Err: Display,
{ {
type Err = <R as FromStr>::Err; type Err = <R as FromStr>::Err;
type Routable = R; type Routable = R;
} }
trait RouteRenderable: std::fmt::Display + 'static { trait RouteRenderable: Display + 'static {
fn render<'a>(&self, cx: &'a ScopeState, level: usize) -> Element<'a>; fn render<'a>(&self, cx: &'a ScopeState, level: usize) -> Element<'a>;
} }
impl<R: Routable> RouteRenderable for R impl<R: Routable> RouteRenderable for R
where where
<R as FromStr>::Err: std::fmt::Display, <R as FromStr>::Err: Display,
{ {
fn render<'a>(&self, cx: &'a ScopeState, level: usize) -> Element<'a> { fn render<'a>(&self, cx: &'a ScopeState, level: usize) -> Element<'a> {
self.render(cx, level) self.render(cx, level)
} }
} }
/// A type erased map of the site structurens /// A type erased map of the site structure.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct SiteMapSegment { pub struct SiteMapSegment {
/// The type of the route segment /// The type of the route segment.
pub segment_type: SegmentType, pub segment_type: SegmentType,
/// The children of the route segment /// The children of the route segment.
pub children: &'static [SiteMapSegment], pub children: &'static [SiteMapSegment],
} }
impl 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>> { pub fn flatten(&self) -> Vec<Vec<SegmentType>> {
let mut routes = Vec::new(); let mut routes = Vec::new();
self.flatten_inner(&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)] #[derive(Debug, Clone, PartialEq)]
pub enum SegmentType { pub enum SegmentType {
/// A static route segment /// A static route segment.
Static(&'static str), Static(&'static str),
/// A dynamic route segment /// A dynamic route segment.
Dynamic(&'static str), Dynamic(&'static str),
/// A catch all route segment /// A catch all route segment.
CatchAll(&'static str), CatchAll(&'static str),
/// A child router /// A child router.
Child, Child,
} }
@@ -329,7 +351,7 @@ impl Display for SegmentType {
SegmentType::Static(s) => write!(f, "/{}", s), SegmentType::Static(s) => write!(f, "/{}", s),
SegmentType::Child => Ok(()), SegmentType::Child => Ok(()),
SegmentType::Dynamic(s) => write!(f, "/:{}", s), SegmentType::Dynamic(s) => write!(f, "/:{}", s),
SegmentType::CatchAll(s) => write!(f, "/:...{}", s), SegmentType::CatchAll(s) => write!(f, "/:..{}", s),
} }
} }
} }