Parse redirects in the same order they appear in (#2650)

* Parse redirects in the same order they appear in
This commit is contained in:
Evan Almloff 2024-07-26 02:47:38 +02:00 committed by GitHub
parent c6a2e5b6c8
commit 443b9a4af6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 120 additions and 51 deletions

View file

@ -270,8 +270,7 @@ pub fn routable(input: TokenStream) -> TokenStream {
struct RouteEnum { struct RouteEnum {
name: Ident, name: Ident,
redirects: Vec<Redirect>, endpoints: Vec<RouteEndpoint>,
routes: Vec<Route>,
nests: Vec<Nest>, nests: Vec<Nest>,
layouts: Vec<Layout>, layouts: Vec<Layout>,
site_map: Vec<SiteMapSegment>, site_map: Vec<SiteMapSegment>,
@ -284,9 +283,7 @@ impl RouteEnum {
let mut site_map = Vec::new(); let mut site_map = Vec::new();
let mut site_map_stack: Vec<Vec<SiteMapSegment>> = Vec::new(); let mut site_map_stack: Vec<Vec<SiteMapSegment>> = Vec::new();
let mut routes = Vec::new(); let mut endpoints = Vec::new();
let mut redirects = Vec::new();
let mut layouts: Vec<Layout> = Vec::new(); let mut layouts: Vec<Layout> = Vec::new();
let mut layout_stack = Vec::new(); let mut layout_stack = Vec::new();
@ -398,10 +395,10 @@ impl RouteEnum {
layout_stack.pop(); layout_stack.pop();
} else if attr.path().is_ident("redirect") { } else if attr.path().is_ident("redirect") {
let parser = |input: ParseStream| { let parser = |input: ParseStream| {
Redirect::parse(input, nest_stack.clone(), redirects.len()) Redirect::parse(input, nest_stack.clone(), endpoints.len())
}; };
let redirect = attr.parse_args_with(parser)?; let redirect = attr.parse_args_with(parser)?;
redirects.push(redirect); endpoints.push(RouteEndpoint::Redirect(redirect));
} }
} }
@ -447,7 +444,7 @@ impl RouteEnum {
children.push(segment); children.push(segment);
} }
routes.push(route); endpoints.push(RouteEndpoint::Route(route));
} }
// pop any remaining site map segments // pop any remaining site map segments
@ -470,8 +467,7 @@ impl RouteEnum {
let myself = Self { let myself = Self {
name: name.clone(), name: name.clone(),
routes, endpoints,
redirects,
nests, nests,
layouts, layouts,
site_map, site_map,
@ -502,27 +498,46 @@ impl RouteEnum {
from_route = true from_route = true
} }
} }
for route in &self.routes { for route in &self.endpoints {
match &route.ty { match route {
RouteType::Child(child) => { RouteEndpoint::Route(route) => match &route.ty {
if let Some(child) = child.ident.as_ref() { RouteType::Child(child) => {
if child == "child" { if let Some(child) = child.ident.as_ref() {
from_route = true if child == "child" {
from_route = true
}
} }
} }
} RouteType::Leaf { .. } => {
RouteType::Leaf { .. } => { for segment in &route.segments {
for segment in &route.segments { if segment.name().as_ref() == Some(field) {
from_route = true
}
}
if let Some(query) = &route.query {
if query.contains_ident(field) {
from_route = true
}
}
if let Some(hash) = &route.hash {
if hash.contains_ident(field) {
from_route = true
}
}
}
},
RouteEndpoint::Redirect(redirect) => {
for segment in &redirect.segments {
if segment.name().as_ref() == Some(field) { if segment.name().as_ref() == Some(field) {
from_route = true from_route = true
} }
} }
if let Some(query) = &route.query { if let Some(query) = &redirect.query {
if query.contains_ident(field) { if query.contains_ident(field) {
from_route = true from_route = true
} }
} }
if let Some(hash) = &route.hash { if let Some(hash) = &redirect.hash {
if hash.contains_ident(field) { if hash.contains_ident(field) {
from_route = true from_route = true
} }
@ -537,8 +552,10 @@ impl RouteEnum {
fn impl_display(&self) -> TokenStream2 { fn impl_display(&self) -> TokenStream2 {
let mut display_match = Vec::new(); let mut display_match = Vec::new();
for route in &self.routes { for route in &self.endpoints {
display_match.push(route.display_match(&self.nests)); if let RouteEndpoint::Route(route) = route {
display_match.push(route.display_match(&self.nests));
}
} }
let name = &self.name; let name = &self.name;
@ -557,7 +574,7 @@ impl RouteEnum {
} }
fn parse_impl(&self) -> TokenStream2 { fn parse_impl(&self) -> TokenStream2 {
let tree = RouteTree::new(&self.routes, &self.nests, &self.redirects); let tree = RouteTree::new(&self.endpoints, &self.nests);
let name = &self.name; let name = &self.name;
let error_name = format_ident!("{}MatchError", self.name); let error_name = format_ident!("{}MatchError", self.name);
@ -618,15 +635,28 @@ impl RouteEnum {
let mut error_variants = Vec::new(); let mut error_variants = Vec::new();
let mut display_match = Vec::new(); let mut display_match = Vec::new();
for route in &self.routes { for endpoint in &self.endpoints {
let route_name = &route.route_name; match endpoint {
RouteEndpoint::Route(route) => {
let route_name = &route.route_name;
let error_name = route.error_ident(); let error_name = route.error_ident();
let route_str = &route.route; let route_str = &route.route;
error_variants.push(quote! { #route_name(#error_name) }); error_variants.push(quote! { #route_name(#error_name) });
display_match.push(quote! { Self::#route_name(err) => write!(f, "Route '{}' ('{}') did not match:\n{}", stringify!(#route_name), #route_str, err)? }); display_match.push(quote! { Self::#route_name(err) => write!(f, "Route '{}' ('{}') did not match:\n{}", stringify!(#route_name), #route_str, err)? });
type_defs.push(route.error_type()); type_defs.push(route.error_type());
}
RouteEndpoint::Redirect(redirect) => {
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());
}
}
} }
for nest in &self.nests { for nest in &self.nests {
@ -639,16 +669,6 @@ impl RouteEnum {
type_defs.push(nest.error_type()); 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! { quote! {
#(#type_defs)* #(#type_defs)*
@ -682,8 +702,10 @@ impl RouteEnum {
let mut matches = Vec::new(); let mut matches = Vec::new();
// Collect all routes matches // Collect all routes matches
for route in &self.routes { for route in &self.endpoints {
matches.push(route.routable_match(&self.layouts, &self.nests)); if let RouteEndpoint::Route(route) = route {
matches.push(route.routable_match(&self.layouts, &self.nests));
}
} }
quote! { quote! {
@ -704,6 +726,11 @@ impl RouteEnum {
} }
} }
enum RouteEndpoint {
Route(Route),
Redirect(Redirect),
}
struct SiteMapSegment { struct SiteMapSegment {
pub segment_type: SegmentType, pub segment_type: SegmentType,
pub children: Vec<SiteMapSegment>, pub children: Vec<SiteMapSegment>,

View file

@ -8,6 +8,7 @@ use crate::{
redirect::Redirect, redirect::Redirect,
route::{Route, RouteType}, route::{Route, RouteType},
segment::{static_segment_idx, RouteSegment}, segment::{static_segment_idx, RouteSegment},
RouteEndpoint,
}; };
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
@ -97,15 +98,13 @@ impl<'a> RouteTree<'a> {
.expect("Cannot get children of non static or nest segment") .expect("Cannot get children of non static or nest segment")
} }
pub(crate) fn new(routes: &'a [Route], nests: &'a [Nest], redirects: &'a [Redirect]) -> Self { pub(crate) fn new(endpoints: &'a [RouteEndpoint], nests: &'a [Nest]) -> Self {
let routes = routes let routes = endpoints
.iter() .iter()
.map(|route| PathIter::new_route(route, nests)) .map(|endpoint| match endpoint {
.chain( RouteEndpoint::Route(route) => PathIter::new_route(route, nests),
redirects RouteEndpoint::Redirect(redirect) => PathIter::new_redirect(redirect, nests),
.iter() })
.map(|redirect| PathIter::new_redirect(redirect, nests)),
)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let mut myself = Self::default(); let mut myself = Self::default();

View file

@ -1,3 +1,4 @@
mod link; mod link;
mod outlet; mod outlet;
mod redirect;
mod without_index; mod without_index;

View file

@ -0,0 +1,42 @@
use dioxus::prelude::*;
use std::str::FromStr;
// Tests for regressions of <https://github.com/DioxusLabs/dioxus/issues/2549>
#[test]
fn redirects_apply_in_order() {
let path = Route::from_str("/").unwrap();
assert_eq!(
path,
Route::Home {
lang: "en".to_string()
}
);
let mut vdom = VirtualDom::new_with_props(App, AppProps { path });
vdom.rebuild_in_place();
let as_string = dioxus_ssr::render(&vdom);
assert_eq!(as_string, "en");
}
#[derive(Clone, Routable, Debug, PartialEq)]
enum Route {
// The redirect should try to parse first because it is placed first in the enum
#[redirect("/", || Route::Home { lang: "en".to_string() })]
#[route("/?:lang")]
Home { lang: String },
}
#[component]
fn Home(lang: String) -> Element {
rsx! { "{lang}" }
}
#[component]
fn App(path: Route) -> Element {
rsx! {
Router::<Route> {
config: {
move |_| RouterConfig::default().history(MemoryHistory::with_initial_path(path.clone()))
}
}
}
}