allow child routers to have fields

This commit is contained in:
Evan Almloff committed 2023-06-27 15:56:47 -07:00
1 parent 4fba279291
commit 914767892c
2 files changed
+79 -48

No files matched your search

+1 -1
View File
@@ -293,7 +293,7 @@ impl RouteEnum {
let mut segment = SiteMapSegment::new(&route.segments); let mut segment = SiteMapSegment::new(&route.segments);
if let RouteType::Child(child) = &route.ty { if let RouteType::Child(child) = &route.ty {
let new_segment = SiteMapSegment { let new_segment = SiteMapSegment {
segment_type: SegmentType::Child(child.clone()), segment_type: SegmentType::Child(child.ty.clone()),
children: Vec::new(), children: Vec::new(),
}; };
match &mut segment { match &mut segment {
+78 -47
View File
@@ -2,6 +2,7 @@ use quote::{format_ident, quote};
use syn::parse::Parse; use syn::parse::Parse;
use syn::parse::ParseStream; use syn::parse::ParseStream;
use syn::parse_quote; use syn::parse_quote;
use syn::Field;
use syn::Path; use syn::Path;
use syn::Type; use syn::Type;
use syn::{Ident, LitStr}; use syn::{Ident, LitStr};
@@ -110,19 +111,30 @@ impl Route {
let args = route_attr.parse_args::<ChildArgs>()?; let args = route_attr.parse_args::<ChildArgs>()?;
route = args.route.value(); route = args.route.value();
match &variant.fields { match &variant.fields {
syn::Fields::Unnamed(fields) => { syn::Fields::Named(fields) => {
if fields.unnamed.len() != 1 { // find either a field with #[child] or a field named "child"
return Err(syn::Error::new_spanned( let child_field = fields.named.iter().find(|f| {
variant.clone(), f.attrs
"Routable variants with a #[child(...)] attribute must have exactly one field", .iter()
)); .any(|attr| attr.path.is_ident("child"))
|| f.ident.as_ref().unwrap().to_string() == "child"
});
match child_field{
Some(child) => {
ty = RouteType::Child(child.clone());
}
None => {
return Err(syn::Error::new_spanned(
variant.clone(),
"Routable variants with a #[child(...)] attribute must have a field named \"child\" or a field with a #[child] attribute",
));
}
} }
ty = RouteType::Child(fields.unnamed[0].ty.clone());
} }
_ => { _ => {
return Err(syn::Error::new_spanned( return Err(syn::Error::new_spanned(
variant.clone(), variant.clone(),
"Routable variants with a #[child(...)] attribute must have exactly one field", "Routable variants with a #[child(...)] attribute must have named fields",
)) ))
} }
} }
@@ -139,7 +151,14 @@ impl Route {
syn::Fields::Named(fields) => fields syn::Fields::Named(fields) => fields
.named .named
.iter() .iter()
.map(|f| (f.ident.clone().unwrap(), f.ty.clone())) .filter_map(|f| {
if let RouteType::Child(child) = &ty {
if f.ident == child.ident {
return None;
}
}
Some((f.ident.clone().unwrap(), f.ty.clone()))
})
.collect(), .collect(),
_ => Vec::new(), _ => Vec::new(),
}; };
@@ -172,13 +191,14 @@ impl Route {
let write_query = self.query.as_ref().map(|q| q.write()); let write_query = self.query.as_ref().map(|q| q.write());
match &self.ty { match &self.ty {
RouteType::Child(_) => { RouteType::Child(field) => {
let child = field.ident.as_ref().unwrap();
quote! { quote! {
Self::#name(child) => { Self::#name { #(#dynamic_segments,)* #child } => {
use std::fmt::Display; use std::fmt::Display;
#(#write_layouts)* #(#write_layouts)*
#(#write_segments)* #(#write_segments)*
child.fmt(f); #child.fmt(f);
} }
} }
} }
@@ -204,10 +224,15 @@ impl Route {
for (idx, layout_id) in self.layouts.iter().copied().enumerate() { for (idx, layout_id) in self.layouts.iter().copied().enumerate() {
let render_layout = layouts[layout_id.0].routable_match(nests); let render_layout = layouts[layout_id.0].routable_match(nests);
let dynamic_segments = self.dynamic_segments(); let dynamic_segments = self.dynamic_segments();
let mut field_name = None;
if let RouteType::Child(field) = &self.ty {
field_name = field.ident.as_ref();
}
let field_name = field_name.map(|f| quote!(#f,));
// This is a layout // This is a layout
tokens.extend(quote! { tokens.extend(quote! {
#[allow(unused)] #[allow(unused)]
(#idx, Self::#name { #(#dynamic_segments,)* }) => { (#idx, Self::#name { #(#dynamic_segments,)* #field_name .. }) => {
#render_layout #render_layout
} }
}); });
@@ -216,11 +241,12 @@ impl Route {
// Then match the route // Then match the route
let last_index = self.layouts.len(); let last_index = self.layouts.len();
tokens.extend(match &self.ty { tokens.extend(match &self.ty {
RouteType::Child(_) => { RouteType::Child(field) => {
let field_name = field.ident.as_ref().unwrap();
quote! { quote! {
#[allow(unused)] #[allow(unused)]
(#last_index.., Self::#name(child_route)) => { (#last_index.., Self::#name { #field_name, .. }) => {
child_route.render(cx, level - #last_index) #field_name.render(cx, level - #last_index)
} }
} }
} }
@@ -250,9 +276,39 @@ impl Route {
} }
pub fn construct(&self, nests: &[Nest], enum_name: Ident) -> TokenStream2 { pub fn construct(&self, nests: &[Nest], enum_name: Ident) -> TokenStream2 {
let segments = self.fields.iter().map(|(name, _)| {
let mut from_route = false;
for id in &self.nests {
let nest = &nests[id.0];
if nest.dynamic_segments_names().any(|i| &i == name) {
from_route = true
}
}
for segment in &self.segments {
if let RouteSegment::Dynamic(other, ..) = segment {
if other == name {
from_route = true
}
}
}
if let Some(query) = &self.query {
if &query.ident == name {
from_route = true
}
}
if from_route {
quote! {#name}
} else {
quote! {#name: Default::default()}
}
});
match &self.ty { match &self.ty {
RouteType::Child(ty) => { RouteType::Child(field) => {
let name = &self.route_name; let name = &self.route_name;
let child_name = field.ident.as_ref().unwrap();
let ty = &field.ty;
quote! { quote! {
{ {
@@ -262,39 +318,14 @@ impl Route {
trailing += "/"; trailing += "/";
} }
trailing.pop(); trailing.pop();
#enum_name::#name(#ty::from_str(&trailing).unwrap()) #enum_name::#name {
#child_name: #ty::from_str(&trailing).unwrap(),
#(#segments,)*
}
} }
} }
} }
RouteType::Leaf { .. } => { RouteType::Leaf { .. } => {
let segments = self.fields.iter().map(|(name, _)| {
let mut from_route = false;
for id in &self.nests {
let nest = &nests[id.0];
if nest.dynamic_segments_names().any(|i| &i == name) {
from_route = true
}
}
for segment in &self.segments {
if let RouteSegment::Dynamic(other, ..) = segment {
if other == name {
from_route = true
}
}
}
if let Some(query) = &self.query {
if &query.ident == name {
from_route = true
}
}
if from_route {
quote! {#name}
} else {
quote! {#name: Default::default()}
}
});
let name = &self.route_name; let name = &self.route_name;
quote! { quote! {
@@ -326,6 +357,6 @@ impl Route {
#[derive(Debug)] #[derive(Debug)]
pub(crate) enum RouteType { pub(crate) enum RouteType {
Child(Type), Child(Field),
Leaf { component: Path, props: Path }, Leaf { component: Path, props: Path },
} }