Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Routable improvements #1461

Merged
merged 7 commits into from Sep 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 5 additions & 5 deletions packages/router-macro/src/lib.rs
Expand Up @@ -210,15 +210,15 @@ 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

#routable_impl

#parse_impl
}
})
.into()
}

Expand Down Expand Up @@ -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()),
}
}
}
2 changes: 1 addition & 1 deletion packages/router-macro/src/segment.rs
Expand Up @@ -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)?; },
}
}

Expand Down
148 changes: 85 additions & 63 deletions packages/router/src/routable.rs
Expand Up @@ -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() {
Expand All @@ -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;
}

Expand All @@ -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;

Expand All @@ -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();
Expand All @@ -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>;
}

Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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());
Expand All @@ -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,
}

Expand All @@ -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),
}
}
}