diff --git a/packages/router-macro/src/lib.rs b/packages/router-macro/src/lib.rs index 9ea31c92d..2257571a2 100644 --- a/packages/router-macro/src/lib.rs +++ b/packages/router-macro/src/lib.rs @@ -364,10 +364,7 @@ impl RouteEnum { let parser = |input: ParseStream| { let bang: Option = input.parse().ok(); let exclude = bang.is_some(); - Ok(( - exclude, - Layout::parse(input, nest_stack.iter().rev().cloned().collect())?, - )) + Ok((exclude, Layout::parse(input, nest_stack.clone())?)) }; let (exclude, layout): (bool, Layout) = attr.parse_args_with(parser)?; @@ -389,19 +386,14 @@ impl RouteEnum { 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(), - ) + Redirect::parse(input, nest_stack.clone(), redirects.len()) }; let redirect = attr.parse_args_with(parser)?; redirects.push(redirect); } } - let mut active_nests = nest_stack.clone(); - active_nests.reverse(); + let active_nests = nest_stack.clone(); let mut active_layouts = layout_stack.clone(); active_layouts.retain(|&id| !excluded.contains(&id)); diff --git a/packages/router-macro/src/route.rs b/packages/router-macro/src/route.rs index 77199d04e..329a7c132 100644 --- a/packages/router-macro/src/route.rs +++ b/packages/router-macro/src/route.rs @@ -186,26 +186,37 @@ impl Route { pub fn display_match(&self, nests: &[Nest]) -> TokenStream2 { let name = &self.route_name; let dynamic_segments = self.dynamic_segments(); - let write_layouts = self.nests.iter().map(|id| nests[id.0].write()); - let write_segments = self.segments.iter().map(|s| s.write_segment()); let write_query = self.query.as_ref().map(|q| q.write()); match &self.ty { RouteType::Child(field) => { + let write_nests = self.nests.iter().map(|id| nests[id.0].write()); + let write_segments = self.segments.iter().map(|s| s.write_segment()); let child = field.ident.as_ref().unwrap(); quote! { Self::#name { #(#dynamic_segments,)* #child } => { use std::fmt::Display; - #(#write_layouts)* - #(#write_segments)* - #child.fmt(f); + use std::fmt::Write; + let mut route = String::new(); + { + let f = &mut route; + #(#write_nests)* + #(#write_segments)* + } + if route.ends_with('/') { + route.pop(); + } + f.write_str(&route)?; + #child.fmt(f)?; } } } RouteType::Leaf { .. } => { + let write_nests = self.nests.iter().map(|id| nests[id.0].write()); + let write_segments = self.segments.iter().map(|s| s.write_segment()); quote! { Self::#name { #(#dynamic_segments,)* } => { - #(#write_layouts)* + #(#write_nests)* #(#write_segments)* #write_query } diff --git a/packages/router-macro/src/route_tree.rs b/packages/router-macro/src/route_tree.rs index c531175cd..27b622919 100644 --- a/packages/router-macro/src/route_tree.rs +++ b/packages/router-macro/src/route_tree.rs @@ -314,7 +314,7 @@ impl<'a> RouteTreeSegmentData<'a> { #(#children)* } else { - errors.push(#error_enum_name::#enum_varient(#varient_parse_error::#error_ident)) + errors.push(#error_enum_name::#enum_varient(#varient_parse_error::#error_ident(segment.to_string()))) } } } diff --git a/packages/router-macro/src/segment.rs b/packages/router-macro/src/segment.rs index e59f7bba6..30b9e7e10 100644 --- a/packages/router-macro/src/segment.rs +++ b/packages/router-macro/src/segment.rs @@ -58,10 +58,11 @@ impl RouteSegment { quote! { { let mut segments = segments.clone(); - let parsed = if let Some(#segment) = segments.next() { + let segment = segments.next(); + let parsed = if let Some(#segment) = segment { Ok(()) } else { - Err(#error_enum_name::#error_enum_varient(#inner_parse_enum::#error_name)) + Err(#error_enum_name::#error_enum_varient(#inner_parse_enum::#error_name(segment.map(|s|s.to_string()).unwrap_or_default()))) }; match parsed { Ok(_) => { @@ -234,8 +235,8 @@ pub(crate) fn create_error_type( 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)? }); + error_variants.push(quote! { #error_name(String) }); + display_match.push(quote! { Self::#error_name(found) => write!(f, "Static segment '{}' did not match instead found '{found}'", #index)? }); } RouteSegment::Dynamic(ident, ty) => { let missing_error = segment.missing_error_name().unwrap(); diff --git a/packages/router/examples/simple_routes.rs b/packages/router/examples/simple_routes.rs index a226d221a..3036bdb9b 100644 --- a/packages/router/examples/simple_routes.rs +++ b/packages/router/examples/simple_routes.rs @@ -96,21 +96,28 @@ fn Route3(cx: Scope, dynamic: String) -> Element { p { "Site Map" } pre { "{site_map:#?}" } p { "Dynamic link" } - if let Ok(route) = parsed { - if route != current_route { + match parsed { + Ok(route) => { + if route != current_route { + render! { + Link { + target: route.clone(), + "{route}" + } + } + } + else { + None + } + } + Err(err) => { render! { - Link { - target: route.clone(), - "{route}" + pre { + color: "red", + "Invalid route:\n{err}" } } } - else { - None - } - } - else { - None } } } @@ -118,24 +125,26 @@ fn Route3(cx: Scope, dynamic: String) -> Element { #[rustfmt::skip] #[derive(Clone, Debug, PartialEq, Routable)] enum Route { - // Nests with parameters have types taken from child routes - #[nest("/user/:user_id")] - // Everything inside the nest has the added parameter `user_id: usize` - // UserFrame is a layout component that will receive the `user_id: usize` parameter - #[layout(UserFrame)] - #[route("/:dynamic?:query")] - Route1 { - // The type is taken from the first instance of the dynamic parameter - user_id: usize, - dynamic: usize, - query: String, - extra: String, - }, - #[route("/hello_world")] - // You can opt out of the layout by using the `!` prefix - #[layout(!UserFrame)] - Route2 { user_id: usize }, - #[end_layout] + #[nest("/test")] + // Nests with parameters have types taken from child routes + #[nest("/user/:user_id")] + // Everything inside the nest has the added parameter `user_id: usize` + // UserFrame is a layout component that will receive the `user_id: usize` parameter + #[layout(UserFrame)] + #[route("/:dynamic?:query")] + Route1 { + // The type is taken from the first instance of the dynamic parameter + user_id: usize, + dynamic: usize, + query: String, + extra: String, + }, + #[route("/hello_world")] + // You can opt out of the layout by using the `!` prefix + #[layout(!UserFrame)] + Route2 { user_id: usize }, + #[end_layout] + #[end_nest] #[end_nest] #[redirect("/:id/user", |id: usize| Route::Route3 { dynamic: id.to_string()})] #[route("/:dynamic")]