Browse Source

create redirects

Evan Almloff 2 năm trước cách đây
mục cha
commit
58b74c1155

+ 31 - 3
packages/router-macro/src/lib.rs

@@ -4,6 +4,7 @@ use layout::Layout;
 use nest::{Nest, NestId};
 use proc_macro::TokenStream;
 use quote::{__private::Span, format_ident, quote, ToTokens};
+use redirect::Redirect;
 use route::Route;
 use segment::RouteSegment;
 use syn::{parse::ParseStream, parse_macro_input, Ident, Token};
@@ -13,14 +14,17 @@ use proc_macro2::TokenStream as TokenStream2;
 use crate::{layout::LayoutId, route_tree::RouteTree};
 
 mod layout;
-mod macro2;
 mod nest;
 mod query;
+mod redirect;
 mod route;
 mod route_tree;
 mod segment;
 
-#[proc_macro_derive(Routable)]
+#[proc_macro_derive(
+    Routable,
+    attributes(route, nest, end_nest, layout, end_layout, redirect)
+)]
 pub fn routable(input: TokenStream) -> TokenStream {
     let routes_enum = parse_macro_input!(input as syn::ItemEnum);
 
@@ -69,6 +73,7 @@ pub fn routable(input: TokenStream) -> TokenStream {
 struct RouteEnum {
     vis: syn::Visibility,
     name: Ident,
+    redirects: Vec<Redirect>,
     routes: Vec<Route>,
     nests: Vec<Nest>,
     layouts: Vec<Layout>,
@@ -85,6 +90,8 @@ impl RouteEnum {
 
         let mut routes = Vec::new();
 
+        let mut redirects = Vec::new();
+
         let mut layouts: Vec<Layout> = Vec::new();
         let mut layout_stack = Vec::new();
 
@@ -197,6 +204,16 @@ impl RouteEnum {
                     }
                 } else if attr.path.is_ident("end_layout") {
                     layout_stack.pop();
+                } else if attr.path.is_ident("redirect") {
+                    let parser = |input: ParseStream| {
+                        Redirect::parse(
+                            input,
+                            nest_stack.iter().rev().cloned().collect(),
+                            redirects.len(),
+                        )
+                    };
+                    let redirect = attr.parse_args_with(parser)?;
+                    redirects.push(redirect);
                 }
             }
 
@@ -225,6 +242,7 @@ impl RouteEnum {
             vis: vis.clone(),
             name: name.clone(),
             routes,
+            redirects,
             nests,
             layouts,
             site_map,
@@ -256,7 +274,7 @@ impl RouteEnum {
     }
 
     fn parse_impl(&self) -> TokenStream2 {
-        let tree = RouteTree::new(&self.routes, &self.nests);
+        let tree = RouteTree::new(&self.routes, &self.nests, &self.redirects);
         let name = &self.name;
 
         let error_name = format_ident!("{}MatchError", self.name);
@@ -325,6 +343,16 @@ impl RouteEnum {
             type_defs.push(nest.error_type());
         }
 
+        for redirect in &self.redirects {
+            let error_variant = redirect.error_variant();
+            let error_name = redirect.error_ident();
+            let route_str = &redirect.route;
+
+            error_variants.push(quote! { #error_variant(#error_name) });
+            display_match.push(quote! { Self::#error_variant(err) => write!(f, "Redirect '{}' ('{}') did not match:\n{}", stringify!(#error_name), #route_str, err)? });
+            type_defs.push(redirect.error_type());
+        }
+
         quote! {
             #(#type_defs)*
 

+ 0 - 286
packages/router-macro/src/macro2.rs

@@ -1,286 +0,0 @@
-use syn::{braced, parenthesized, parse::Parse, Expr, Ident, LitStr, Path, Token};
-
-#[test]
-fn parses() {
-    use quote::quote;
-
-    let tokens = quote! {
-        // The name of the enum
-        Route,
-        // All nests that have dynamic segments must have a name used to generate the enum
-        route(User, "user" / user_id: usize) {
-            route(Product, "product" / product_id: usize / dynamic: usize ) {
-                // Render creates a new route (that will be included in the enum) and is rendered with the given component
-                // The component uses the struct of the parent route as a prop (in this case, Product)
-                render(Other)
-            }
-
-            // You can nest routes inside a layout to wrap them in a component that accepts the struct of the parent route as a prop (in this case, User)
-            layout(UserFrame) {
-                route(Route1Props, "hello_world" / dynamic: usize ) {
-                    // (Accepts Route1Props as a prop)
-                    render(Route1)
-                }
-
-                // You can opt out of the layout by using !layout
-                !layout(UserFrame) {
-                    route(Route2Props, "hello_world" / dynamic: usize ) {
-                        // (Accepts Route2Props as a prop)
-                        render(Route2)
-                    }
-                }
-            }
-        }
-
-        route(Route3Props, "hello_world" / dynamic: usize ) {
-            // (Accepts Route3Props as a prop)
-            render(Route3)
-        }
-
-        route(RedirectData, dynamic: usize / extra: String) {
-            // Redirects accept a function that receives the struct of the parent route and returns the new route
-            redirect(|data: RedirectData| todo!() )
-        }
-    };
-
-    let _ = syn::parse2::<RouteTree>(tokens).unwrap();
-}
-
-struct RouteTree {
-    name: Ident,
-    roots: Vec<RouteSegment>,
-}
-
-impl Parse for RouteTree {
-    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
-        let name = input.parse()?;
-        let _ = input.parse::<Token![,]>();
-
-        let mut roots = Vec::new();
-        while !input.is_empty() {
-            roots.push(input.parse()?);
-        }
-        Ok(Self { name, roots })
-    }
-}
-
-enum RouteSegment {
-    Route(Route),
-    Layout(Layout),
-    Render(Render),
-    Redirect(Redirect),
-}
-
-impl Parse for RouteSegment {
-    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
-        let lookahead = input.lookahead1();
-        if lookahead.peek(syn::Token![!]) {
-            input.parse::<Token![!]>()?;
-            let ident: Ident = input.parse()?;
-            if ident == "layout" {
-                let mut layout: Layout = input.parse()?;
-                layout.opt_out = true;
-                Ok(RouteSegment::Layout(layout))
-            } else {
-                Err(lookahead.error())
-            }
-        } else if lookahead.peek(syn::Ident) {
-            let ident: Ident = input.parse()?;
-            if ident == "route" {
-                let route = input.parse()?;
-                Ok(RouteSegment::Route(route))
-            } else if ident == "layout" {
-                let layout = input.parse()?;
-                Ok(RouteSegment::Layout(layout))
-            } else if ident == "render" {
-                let render = input.parse()?;
-                Ok(RouteSegment::Render(render))
-            } else if ident == "redirect" {
-                let redirect = input.parse()?;
-                Ok(RouteSegment::Redirect(redirect))
-            } else {
-                Err(lookahead.error())
-            }
-        } else {
-            Err(lookahead.error())
-        }
-    }
-}
-
-struct Render {
-    component: Path,
-}
-
-impl Parse for Render {
-    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
-        let inner;
-        parenthesized!(inner in input);
-        let component = inner.parse()?;
-
-        Ok(Self { component })
-    }
-}
-
-struct Layout {
-    opt_out: bool,
-    component: Path,
-    children: Vec<RouteSegment>,
-}
-
-impl Parse for Layout {
-    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
-        let inner;
-        parenthesized!(inner in input);
-        let component = inner.parse()?;
-
-        let content;
-        braced!(content in input);
-        let mut children = Vec::new();
-        while !content.is_empty() {
-            children.push(content.parse()?);
-        }
-
-        Ok(Self {
-            opt_out: false,
-            component,
-            children,
-        })
-    }
-}
-
-struct Route {
-    name: Ident,
-    path: RoutePath,
-    children: Vec<RouteSegment>,
-}
-
-impl Parse for Route {
-    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
-        let inner;
-        parenthesized!(inner in input);
-        let name = inner.parse()?;
-        inner.parse::<Token![,]>()?;
-        let path = inner.parse()?;
-
-        let content;
-        braced!(content in input);
-        let mut children = Vec::new();
-        while !content.is_empty() {
-            children.push(content.parse()?);
-        }
-
-        Ok(Self {
-            name,
-            path,
-            children,
-        })
-    }
-}
-
-struct RoutePath {
-    segments: Vec<RoutePathSegment>,
-    query: Option<QuerySegment>,
-}
-
-impl Parse for RoutePath {
-    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
-        // parse all segments first
-        let mut segments = Vec::new();
-        // remove any leading slash
-        if input.peek(syn::Token![/]) {
-            input.parse::<syn::Token![/]>()?;
-        }
-
-        while !input.is_empty() {
-            let peak = input.lookahead1();
-            // check if the next segment is a query
-            if peak.peek(syn::Token![?]) {
-                break;
-            } else if peak.peek(syn::Token![/]) {
-                input.parse::<syn::Token![/]>()?;
-            } else if peak.peek(syn::Ident) || peak.peek(syn::Token![...]) || peak.peek(syn::LitStr)
-            {
-                // parse the segment
-                segments.push(input.parse()?);
-            } else {
-                return Err(peak.error());
-            }
-        }
-        // then parse the query
-        let query = if input.peek(syn::Token![?]) {
-            Some(input.parse()?)
-        } else {
-            None
-        };
-        Ok(Self { segments, query })
-    }
-}
-
-enum RoutePathSegment {
-    Static(String),
-    Dynamic(Ident, Path),
-    CatchAll(Ident, Path),
-}
-
-impl Parse for RoutePathSegment {
-    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
-        let lookahead = input.lookahead1();
-        if lookahead.peek(Token![...]) {
-            input.parse::<Token![...]>()?;
-            let name: Ident = input.parse()?;
-            input.parse::<Token![:]>()?;
-            let type_: Path = input.parse()?;
-
-            // parse the /
-            let _ = input.parse::<Token![/]>();
-
-            Ok(RoutePathSegment::CatchAll(name, type_))
-        } else if lookahead.peek(LitStr) {
-            let lit: LitStr = input.parse()?;
-
-            // parse the /
-            let _ = input.parse::<Token![/]>();
-
-            Ok(RoutePathSegment::Static(lit.value()))
-        } else if lookahead.peek(Ident) {
-            let ident: Ident = input.parse()?;
-            input.parse::<Token![:]>()?;
-            let type_: Path = input.parse()?;
-
-            // parse the /
-            let _ = input.parse::<Token![/]>();
-
-            Ok(RoutePathSegment::Dynamic(ident, type_))
-        } else {
-            Err(lookahead.error())
-        }
-    }
-}
-
-struct QuerySegment {
-    name: Ident,
-    type_: Path,
-}
-
-impl Parse for QuerySegment {
-    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
-        input.parse::<syn::Token![?]>()?;
-        let name = input.parse()?;
-        input.parse::<syn::Token![:]>()?;
-        let type_ = input.parse()?;
-        Ok(Self { name, type_ })
-    }
-}
-
-struct Redirect {
-    function: Expr,
-}
-
-impl Parse for Redirect {
-    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
-        let inner;
-        parenthesized!(inner in input);
-        let function = inner.parse()?;
-        Ok(Self { function })
-    }
-}

+ 6 - 39
packages/router-macro/src/nest.rs

@@ -2,7 +2,7 @@ use proc_macro2::TokenStream;
 use quote::{format_ident, quote};
 use syn::{Ident, LitStr};
 
-use crate::segment::{parse_route_segments, RouteSegment};
+use crate::segment::{create_error_type, parse_route_segments, RouteSegment};
 
 #[derive(Debug, Clone, Copy)]
 pub struct NestId(pub usize);
@@ -25,7 +25,10 @@ impl Nest {
 
         let route_segments = parse_route_segments(
             route.span(),
-            children_routes.iter().flat_map(|f| f.named.iter()),
+            children_routes
+                .iter()
+                .flat_map(|f| f.named.iter())
+                .map(|f| (f.ident.as_ref().unwrap(), &f.ty)),
             &route.value(),
         )?
         .0;
@@ -79,42 +82,6 @@ impl Nest {
     pub fn error_type(&self) -> TokenStream {
         let error_name = self.error_ident();
 
-        let mut error_variants = Vec::new();
-        let mut display_match = Vec::new();
-
-        for (i, segment) in self.segments.iter().enumerate() {
-            let error_name = segment.error_name(i);
-            match segment {
-                RouteSegment::Static(index) => {
-                    error_variants.push(quote! { #error_name });
-                    display_match.push(quote! { Self::#error_name => write!(f, "Static segment '{}' did not match", #index)? });
-                }
-                RouteSegment::Dynamic(ident, ty) => {
-                    let missing_error = segment.missing_error_name().unwrap();
-                    error_variants.push(quote! { #error_name(<#ty as dioxus_router::routable::FromRouteSegment>::Err) });
-                    display_match.push(quote! { Self::#error_name(err) => write!(f, "Dynamic segment '({}:{})' did not match: {}", stringify!(#ident), stringify!(#ty), err)? });
-                    error_variants.push(quote! { #missing_error });
-                    display_match.push(quote! { Self::#missing_error => write!(f, "Dynamic segment '({}:{})' was missing", stringify!(#ident), stringify!(#ty))? });
-                }
-                _ => todo!(),
-            }
-        }
-
-        quote! {
-            #[allow(non_camel_case_types)]
-            #[derive(Debug, PartialEq)]
-            pub enum #error_name {
-                #(#error_variants,)*
-            }
-
-            impl std::fmt::Display for #error_name {
-                fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-                    match self {
-                        #(#display_match,)*
-                    }
-                    Ok(())
-                }
-            }
-        }
+        create_error_type(error_name, &self.segments)
     }
 }

+ 91 - 0
packages/router-macro/src/redirect.rs

@@ -0,0 +1,91 @@
+use proc_macro2::{Ident, TokenStream};
+use quote::{format_ident, quote};
+use syn::LitStr;
+
+use crate::{
+    nest::NestId,
+    query::QuerySegment,
+    segment::{create_error_type, parse_route_segments, RouteSegment},
+};
+
+#[derive(Debug)]
+pub(crate) struct Redirect {
+    pub route: LitStr,
+    pub nests: Vec<NestId>,
+    pub segments: Vec<RouteSegment>,
+    pub query: Option<QuerySegment>,
+    pub function: syn::ExprClosure,
+    pub index: usize,
+}
+
+impl Redirect {
+    pub fn error_ident(&self) -> Ident {
+        format_ident!("Redirect{}ParseError", self.index)
+    }
+
+    pub fn error_variant(&self) -> Ident {
+        format_ident!("Redirect{}", self.index)
+    }
+
+    pub fn error_type(&self) -> TokenStream {
+        let error_name = self.error_ident();
+
+        create_error_type(error_name, &self.segments)
+    }
+
+    pub fn parse_query(&self) -> TokenStream {
+        match &self.query {
+            Some(query) => query.parse(),
+            None => quote! {},
+        }
+    }
+
+    pub fn parse(
+        input: syn::parse::ParseStream,
+        active_nests: Vec<NestId>,
+        index: usize,
+    ) -> syn::Result<Self> {
+        let path = input.parse::<syn::LitStr>()?;
+
+        let _ = input.parse::<syn::Token![,]>();
+        let function = input.parse::<syn::ExprClosure>()?;
+
+        let mut closure_arguments = Vec::new();
+        for arg in function.inputs.iter() {
+            match arg {
+                syn::Pat::Type(pat) => match &*pat.pat {
+                    syn::Pat::Ident(ident) => {
+                        closure_arguments.push((ident.ident.clone(), (*pat.ty).clone()));
+                    }
+                    _ => {
+                        return Err(syn::Error::new_spanned(
+                            arg,
+                            "Expected closure argument to be a typed pattern",
+                        ))
+                    }
+                },
+                _ => {
+                    return Err(syn::Error::new_spanned(
+                        arg,
+                        "Expected closure argument to be a typed pattern",
+                    ))
+                }
+            }
+        }
+
+        let (segments, query) = parse_route_segments(
+            path.span(),
+            closure_arguments.iter().map(|(name, ty)| (name, ty)),
+            &path.value(),
+        )?;
+
+        Ok(Redirect {
+            route: path,
+            nests: active_nests,
+            segments,
+            query,
+            function,
+            index,
+        })
+    }
+}

+ 10 - 46
packages/router-macro/src/route.rs

@@ -10,6 +10,7 @@ use crate::layout::LayoutId;
 use crate::nest::Nest;
 use crate::nest::NestId;
 use crate::query::QuerySegment;
+use crate::segment::create_error_type;
 use crate::segment::parse_route_segments;
 use crate::segment::RouteSegment;
 
@@ -90,8 +91,14 @@ impl Route {
             }
         };
 
-        let (route_segments, query) =
-            parse_route_segments(variant.ident.span(), named_fields.named.iter(), &route)?;
+        let (route_segments, query) = parse_route_segments(
+            variant.ident.span(),
+            named_fields
+                .named
+                .iter()
+                .map(|f| (f.ident.as_ref().unwrap(), &f.ty)),
+            &route,
+        )?;
 
         Ok(Self {
             comp_name,
@@ -217,50 +224,7 @@ impl Route {
     pub fn error_type(&self) -> TokenStream2 {
         let error_name = self.error_ident();
 
-        let mut error_variants = Vec::new();
-        let mut display_match = Vec::new();
-
-        for (i, segment) in self.segments.iter().enumerate() {
-            let error_name = segment.error_name(i);
-            match segment {
-                RouteSegment::Static(index) => {
-                    error_variants.push(quote! { #error_name });
-                    display_match.push(quote! { Self::#error_name => write!(f, "Static segment '{}' did not match", #index)? });
-                }
-                RouteSegment::Dynamic(ident, ty) => {
-                    let missing_error = segment.missing_error_name().unwrap();
-                    error_variants.push(quote! { #error_name(<#ty as dioxus_router::routable::FromRouteSegment>::Err) });
-                    display_match.push(quote! { Self::#error_name(err) => write!(f, "Dynamic segment '({}:{})' did not match: {}", stringify!(#ident), stringify!(#ty), err)? });
-                    error_variants.push(quote! { #missing_error });
-                    display_match.push(quote! { Self::#missing_error => write!(f, "Dynamic segment '({}:{})' was missing", stringify!(#ident), stringify!(#ty))? });
-                }
-                RouteSegment::CatchAll(ident, ty) => {
-                    error_variants.push(quote! { #error_name(<#ty as dioxus_router::routable::FromRouteSegments>::Err) });
-                    display_match.push(quote! { Self::#error_name(err) => write!(f, "Catch-all segment '({}:{})' did not match: {}", stringify!(#ident), stringify!(#ty), err)? });
-                }
-            }
-        }
-
-        quote! {
-            #[allow(non_camel_case_types)]
-            #[derive(Debug, PartialEq)]
-            pub enum #error_name {
-                ExtraSegments(String),
-                #(#error_variants,)*
-            }
-
-            impl std::fmt::Display for #error_name {
-                fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-                    match self {
-                        Self::ExtraSegments(segments) => {
-                            write!(f, "Found additional trailing segments: {}", segments)?
-                        }
-                        #(#display_match,)*
-                    }
-                    Ok(())
-                }
-            }
-        }
+        create_error_type(error_name, &self.segments)
     }
 
     pub fn parse_query(&self) -> TokenStream2 {

+ 101 - 22
packages/router-macro/src/route_tree.rs

@@ -4,13 +4,14 @@ use slab::Slab;
 use syn::Ident;
 
 use crate::{
-    nest::Nest,
+    nest::{Nest, NestId},
+    redirect::Redirect,
     route::Route,
     segment::{static_segment_idx, RouteSegment},
 };
 
 #[derive(Debug, Clone, Default)]
-pub struct RouteTree<'a> {
+pub(crate) struct RouteTree<'a> {
     pub roots: Vec<usize>,
     entries: Slab<RouteTreeSegmentData<'a>>,
 }
@@ -47,6 +48,13 @@ impl<'a> RouteTree<'a> {
                         _ => 1,
                     }
                 }
+                RouteTreeSegmentData::Redirect(redirect) => {
+                    // Routes that end in a catch all segment should be checked last
+                    match redirect.segments.last() {
+                        Some(RouteSegment::CatchAll(..)) => 2,
+                        _ => 1,
+                    }
+                }
             }
         });
     }
@@ -89,10 +97,15 @@ impl<'a> RouteTree<'a> {
             .expect("Cannot get children of non static or nest segment")
     }
 
-    pub fn new(routes: &'a [Route], nests: &'a [Nest]) -> Self {
+    pub(crate) fn new(routes: &'a [Route], nests: &'a [Nest], redirects: &'a [Redirect]) -> Self {
         let routes = routes
             .iter()
-            .map(|route| RouteIter::new(route, nests))
+            .map(|route| PathIter::new_route(route, nests))
+            .chain(
+                redirects
+                    .iter()
+                    .map(|redirect| PathIter::new_redirect(redirect, nests)),
+            )
             .collect::<Vec<_>>();
 
         let mut myself = Self::default();
@@ -102,7 +115,7 @@ impl<'a> RouteTree<'a> {
         myself
     }
 
-    pub fn construct(&mut self, routes: Vec<RouteIter<'a>>) -> Vec<usize> {
+    pub fn construct(&mut self, routes: Vec<PathIter<'a>>) -> Vec<usize> {
         let mut segments = Vec::new();
 
         // Add all routes to the tree
@@ -127,7 +140,7 @@ impl<'a> RouteTree<'a> {
                                 for &seg_id in segments.iter() {
                                     let seg = self.get(seg_id).unwrap();
                                     if let RouteTreeSegmentData::Static { segment: s, .. } = seg {
-                                        if s == segment {
+                                        if *s == segment {
                                             // If it does, just update the current route
                                             current_route = Some(seg_id);
                                             continue 'o;
@@ -223,9 +236,7 @@ impl<'a> RouteTree<'a> {
                 }
                 // If there is no static segment, add the route to the current_route
                 None => {
-                    let id = self
-                        .entries
-                        .insert(RouteTreeSegmentData::Route(route.route));
+                    let id = self.entries.insert(route.final_segment);
                     let current_children_mut = current_route
                         .map(|id| self.children_mut(id))
                         .unwrap_or_else(|| &mut segments);
@@ -246,7 +257,7 @@ pub struct StaticErrorVariant {
 
 // First deduplicate the routes by the static part of the route
 #[derive(Debug, Clone)]
-pub enum RouteTreeSegmentData<'a> {
+pub(crate) enum RouteTreeSegmentData<'a> {
     Static {
         segment: &'a str,
         error_variant: StaticErrorVariant,
@@ -258,6 +269,7 @@ pub enum RouteTreeSegmentData<'a> {
         children: Vec<usize>,
     },
     Route(&'a Route),
+    Redirect(&'a Redirect),
 }
 
 impl<'a> RouteTreeSegmentData<'a> {
@@ -362,6 +374,52 @@ impl<'a> RouteTreeSegmentData<'a> {
                     &varient_parse_error,
                 )
             }
+            Self::Redirect(redirect) => {
+                // At this point, we have matched all static segments, so we can just check if the remaining segments match the route
+                let varient_parse_error = redirect.error_ident();
+                let enum_varient = &redirect.error_variant();
+
+                let route_segments = redirect
+                    .segments
+                    .iter()
+                    .enumerate()
+                    .skip_while(|(_, seg)| matches!(seg, RouteSegment::Static(_)));
+
+                let parse_query = redirect.parse_query();
+
+                let insure_not_trailing = redirect
+                    .segments
+                    .last()
+                    .map(|seg| !matches!(seg, RouteSegment::CatchAll(_, _)))
+                    .unwrap_or(true);
+
+                let redirect_function = &redirect.function;
+                let args = redirect_function.inputs.iter().map(|pat| match pat {
+                    syn::Pat::Type(ident) => {
+                        let name = &ident.pat;
+                        quote! {#name}
+                    }
+                    _ => panic!("Expected closure argument to be a typed pattern"),
+                });
+                let return_redirect = quote! {
+                    (#redirect_function)(#(#args,)*)
+                };
+
+                print_route_segment(
+                    route_segments.peekable(),
+                    return_constructed(
+                        insure_not_trailing,
+                        return_redirect,
+                        &error_enum_name,
+                        enum_varient,
+                        &varient_parse_error,
+                        parse_query,
+                    ),
+                    &error_enum_name,
+                    enum_varient,
+                    &varient_parse_error,
+                )
+            }
         }
     }
 }
@@ -435,18 +493,39 @@ fn return_constructed(
     }
 }
 
-pub struct RouteIter<'a> {
-    route: &'a Route,
-    nests: &'a [Nest],
+pub struct PathIter<'a> {
+    final_segment: RouteTreeSegmentData<'a>,
+    active_nests: &'a [NestId],
+    all_nests: &'a [Nest],
+    segments: &'a [RouteSegment],
+    error_ident: Ident,
+    error_variant: Ident,
     nest_index: usize,
     static_segment_index: usize,
 }
 
-impl<'a> RouteIter<'a> {
-    fn new(route: &'a Route, nests: &'a [Nest]) -> Self {
+impl<'a> PathIter<'a> {
+    fn new_route(route: &'a Route, nests: &'a [Nest]) -> Self {
+        Self {
+            final_segment: RouteTreeSegmentData::Route(route),
+            active_nests: &*route.nests,
+            segments: &*route.segments,
+            error_ident: route.error_ident(),
+            error_variant: route.route_name.clone(),
+            all_nests: nests,
+            nest_index: 0,
+            static_segment_index: 0,
+        }
+    }
+
+    fn new_redirect(redirect: &'a Redirect, nests: &'a [Nest]) -> Self {
         Self {
-            route,
-            nests,
+            final_segment: RouteTreeSegmentData::Redirect(redirect),
+            active_nests: &*redirect.nests,
+            segments: &*redirect.segments,
+            error_ident: redirect.error_ident(),
+            error_variant: redirect.error_variant(),
+            all_nests: nests,
             nest_index: 0,
             static_segment_index: 0,
         }
@@ -454,15 +533,15 @@ impl<'a> RouteIter<'a> {
 
     fn next_nest(&mut self) -> Option<&'a Nest> {
         let idx = self.nest_index;
-        let nest_index = self.route.nests.get(idx)?;
-        let nest = &self.nests[nest_index.0];
+        let nest_index = self.active_nests.get(idx)?;
+        let nest = &self.all_nests[nest_index.0];
         self.nest_index += 1;
         Some(nest)
     }
 
     fn next_static_segment(&mut self) -> Option<(usize, &'a str)> {
         let idx = self.static_segment_index;
-        let segment = self.route.segments.get(idx)?;
+        let segment = self.segments.get(idx)?;
         match segment {
             RouteSegment::Static(segment) => {
                 self.static_segment_index += 1;
@@ -474,8 +553,8 @@ impl<'a> RouteIter<'a> {
 
     fn error_variant(&self) -> StaticErrorVariant {
         StaticErrorVariant {
-            varient_parse_error: self.route.error_ident(),
-            enum_varient: self.route.route_name.clone(),
+            varient_parse_error: self.error_ident.clone(),
+            enum_varient: self.error_variant.clone(),
         }
     }
 }

+ 55 - 12
packages/router-macro/src/segment.rs

@@ -124,7 +124,7 @@ pub fn static_segment_idx(idx: usize) -> Ident {
 
 pub fn parse_route_segments<'a>(
     route_span: Span,
-    mut fields: impl Iterator<Item = &'a syn::Field>,
+    mut fields: impl Iterator<Item = (&'a Ident, &'a Type)>,
     route: &str,
 ) -> syn::Result<(Vec<RouteSegment>, Option<QuerySegment>)> {
     let mut route_segments = Vec::new();
@@ -157,13 +157,10 @@ pub fn parse_route_segments<'a>(
                 segment.to_string()
             };
 
-            let field = fields.find(|field| match field.ident {
-                Some(ref field_ident) => *field_ident == ident,
-                None => false,
-            });
+            let field = fields.find(|(name, _)| **name == ident);
 
             let ty = if let Some(field) = field {
-                field.ty.clone()
+                field.1.clone()
             } else {
                 return Err(syn::Error::new(
                     route_span,
@@ -200,13 +197,10 @@ pub fn parse_route_segments<'a>(
         Some(query) => {
             if let Some(query) = query.strip_prefix(':') {
                 let query_ident = Ident::new(query, Span::call_site());
-                let field = fields.find(|field| match field.ident {
-                    Some(ref field_ident) => field_ident == &query_ident,
-                    None => false,
-                });
+                let field = fields.find(|(name, _)| *name == &query_ident);
 
-                let ty = if let Some(field) = field {
-                    field.ty.clone()
+                let ty = if let Some((_, ty)) = field {
+                    ty.clone()
                 } else {
                     return Err(syn::Error::new(
                         route_span,
@@ -227,3 +221,52 @@ pub fn parse_route_segments<'a>(
 
     Ok((route_segments, parsed_query))
 }
+
+pub(crate) fn create_error_type(error_name: Ident, segments: &[RouteSegment]) -> TokenStream2 {
+    let mut error_variants = Vec::new();
+    let mut display_match = Vec::new();
+
+    for (i, segment) in segments.iter().enumerate() {
+        let error_name = segment.error_name(i);
+        match segment {
+            RouteSegment::Static(index) => {
+                error_variants.push(quote! { #error_name });
+                display_match.push(quote! { Self::#error_name => write!(f, "Static segment '{}' did not match", #index)? });
+            }
+            RouteSegment::Dynamic(ident, ty) => {
+                let missing_error = segment.missing_error_name().unwrap();
+                error_variants.push(
+                    quote! { #error_name(<#ty as dioxus_router::routable::FromRouteSegment>::Err) },
+                );
+                display_match.push(quote! { Self::#error_name(err) => write!(f, "Dynamic segment '({}:{})' did not match: {}", stringify!(#ident), stringify!(#ty), err)? });
+                error_variants.push(quote! { #missing_error });
+                display_match.push(quote! { Self::#missing_error => write!(f, "Dynamic segment '({}:{})' was missing", stringify!(#ident), stringify!(#ty))? });
+            }
+            RouteSegment::CatchAll(ident, ty) => {
+                error_variants.push(quote! { #error_name(<#ty as dioxus_router::routable::FromRouteSegments>::Err) });
+                display_match.push(quote! { Self::#error_name(err) => write!(f, "Catch-all segment '({}:{})' did not match: {}", stringify!(#ident), stringify!(#ty), err)? });
+            }
+        }
+    }
+
+    quote! {
+        #[allow(non_camel_case_types)]
+        #[derive(Debug, PartialEq)]
+        pub enum #error_name {
+            ExtraSegments(String),
+            #(#error_variants,)*
+        }
+
+        impl std::fmt::Display for #error_name {
+            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+                match self {
+                    Self::ExtraSegments(segments) => {
+                        write!(f, "Found additional trailing segments: {}", segments)?
+                    }
+                    #(#display_match,)*
+                }
+                Ok(())
+            }
+        }
+    }
+}

+ 7 - 5
packages/router/examples/simple_routes.rs

@@ -45,13 +45,13 @@ fn UserFrame(cx: Scope, user_id: usize) -> Element {
 }
 
 #[inline_props]
-fn Route1(cx: Scope, user_id: usize, dynamic: usize, extra: String) -> Element {
+fn Route1(cx: Scope, user_id: usize, dynamic: usize, query: String, extra: String) -> Element {
     render! {
         pre {
-            "Route1{{\n\tuser_id:{user_id},\n\tdynamic:{dynamic},\n\textra:{extra}\n}}"
+            "Route1{{\n\tuser_id:{user_id},\n\tdynamic:{dynamic},\n\tquery:{query},\n\textra:{extra}\n}}"
         }
         Link {
-            target: Route::Route1 { user_id: *user_id, dynamic: *dynamic, extra: extra.clone() + "." },
+            target: Route::Route1 { user_id: *user_id, dynamic: *dynamic, query: String::new(), extra: extra.clone() + "." },
             "Route1 with extra+\".\""
         }
         p { "Footer" }
@@ -131,12 +131,13 @@ enum Route {
         // Everything inside the nest has the added parameter `user_id: String`
         // UserFrame is a layout component that will receive the `user_id: String` parameter
         #[layout(UserFrame)]
-        // Route1 is a non-layout component that will receive the `user_id: String` and `dynamic: String` parameters
-        #[route("/:dynamic", Route1)]
+            // Route1 is a non-layout component that will receive the `user_id: String` and `dynamic: String` parameters
+            #[route("/:dynamic?:query", Route1)]
             Route1 {
                 // The type is taken from the first instance of the dynamic parameter
                 user_id: usize,
                 dynamic: usize,
+                query: String,
                 extra: String,
             },
             // Route2 is a non-layout component that will receive the `user_id: String` parameter
@@ -146,6 +147,7 @@ enum Route {
             Route2 { user_id: usize },
         #[end_layout]
     #[end_nest]
+    #[redirect("/:id/user", |id: usize| Route::Route3 { dynamic: id.to_string()})]
     #[route("/:dynamic", Route3)]
     Route3 { dynamic: String },
 }

+ 6 - 6
packages/router/src/history/web.rs

@@ -12,7 +12,7 @@ use super::{
     HistoryProvider,
 };
 
-fn update_scroll<R: Serialize + DeserializeOwned>(window: &Window, history: &History) {
+fn update_scroll<R: Serialize + DeserializeOwned + Routable>(window: &Window, history: &History) {
     if let Some(WebHistoryState { state, .. }) = get_current::<WebHistoryState<R>>(history) {
         let scroll = ScrollPosition::of_window(window);
         let state = WebHistoryState { state, scroll };
@@ -42,7 +42,7 @@ struct WebHistoryState<R> {
 /// in the URL. Otherwise, if a router navigation is triggered, the prefix will be added.
 ///
 /// [History API]: https://developer.mozilla.org/en-US/docs/Web/API/History_API
-pub struct WebHistory<R: Serialize + DeserializeOwned> {
+pub struct WebHistory<R: Serialize + DeserializeOwned + Routable> {
     do_scroll_restoration: bool,
     history: History,
     listener_navigation: Option<EventListener>,
@@ -102,10 +102,10 @@ impl<R: Serialize + DeserializeOwned + Routable> WebHistory<R> {
             phantom: Default::default(),
         };
 
-        let state = myself.current_route();
-
-        let state = myself.create_state(state);
-        let _ = replace_state_with_url(&myself.history, &state, None);
+        let current_route = myself.current_route();
+        let current_url = current_route.to_string();
+        let state = myself.create_state(current_route);
+        let _ = replace_state_with_url(&myself.history, &state, Some(&current_url));
 
         myself
     }