mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-23 12:43:08 +00:00
seperate dynamic nested segments and layouts
This commit is contained in:
parent
be3dae4b05
commit
ef6551a6cd
7 changed files with 294 additions and 330 deletions
61
packages/router-macro/src/layout.rs
Normal file
61
packages/router-macro/src/layout.rs
Normal file
|
@ -0,0 +1,61 @@
|
|||
use proc_macro2::TokenStream;
|
||||
use quote::{format_ident, quote};
|
||||
use syn::Ident;
|
||||
|
||||
use crate::nest::{Nest, NestId};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct LayoutId(pub usize);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Layout {
|
||||
pub layout_name: Ident,
|
||||
pub comp: Ident,
|
||||
pub props_name: Ident,
|
||||
pub active_nests: Vec<NestId>,
|
||||
}
|
||||
|
||||
impl Layout {
|
||||
pub fn routable_match(&self, nests: &[Nest]) -> TokenStream {
|
||||
let props_name = &self.props_name;
|
||||
let comp_name = &self.comp;
|
||||
let dynamic_segments = self
|
||||
.active_nests
|
||||
.iter()
|
||||
.flat_map(|id| nests[id.0].dynamic_segments());
|
||||
|
||||
quote! {
|
||||
let comp = #props_name { #(#dynamic_segments,)* };
|
||||
let cx = cx.bump().alloc(Scoped {
|
||||
props: cx.bump().alloc(comp),
|
||||
scope: cx,
|
||||
});
|
||||
#comp_name(cx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Layout {
|
||||
pub fn parse(input: syn::parse::ParseStream, active_nests: Vec<NestId>) -> syn::Result<Self> {
|
||||
// Then parse the layout name
|
||||
let _ = input.parse::<syn::Token![,]>();
|
||||
let layout_name: syn::Ident = input.parse()?;
|
||||
|
||||
// Then parse the component name
|
||||
let _ = input.parse::<syn::Token![,]>();
|
||||
let comp: Ident = input.parse()?;
|
||||
|
||||
// Then parse the props name
|
||||
let _ = input.parse::<syn::Token![,]>();
|
||||
let props_name: Ident = input
|
||||
.parse()
|
||||
.unwrap_or_else(|_| format_ident!("{}Props", comp.to_string()));
|
||||
|
||||
Ok(Self {
|
||||
layout_name,
|
||||
comp,
|
||||
props_name,
|
||||
active_nests,
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,15 +1,17 @@
|
|||
extern crate proc_macro;
|
||||
|
||||
use nest::{Layout, Nest};
|
||||
use layout::Layout;
|
||||
use nest::{Nest, NestId};
|
||||
use proc_macro::TokenStream;
|
||||
use quote::{__private::Span, format_ident, quote, ToTokens};
|
||||
use route::Route;
|
||||
use syn::{parse_macro_input, Ident};
|
||||
use syn::{parse::ParseStream, parse_macro_input, Ident};
|
||||
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
|
||||
use crate::{nest::LayoutId, route_tree::RouteTree};
|
||||
use crate::{layout::LayoutId, route_tree::RouteTree};
|
||||
|
||||
mod layout;
|
||||
mod nest;
|
||||
mod query;
|
||||
mod route;
|
||||
|
@ -46,10 +48,11 @@ pub fn routable(_: TokenStream, input: TokenStream) -> TokenStream {
|
|||
}
|
||||
|
||||
struct RouteEnum {
|
||||
vis: syn::Visibility,
|
||||
attrs: Vec<syn::Attribute>,
|
||||
vis: syn::Visibility,
|
||||
name: Ident,
|
||||
routes: Vec<Route>,
|
||||
nests: Vec<Nest>,
|
||||
layouts: Vec<Layout>,
|
||||
}
|
||||
|
||||
|
@ -57,79 +60,89 @@ impl RouteEnum {
|
|||
fn parse(data: syn::ItemEnum) -> syn::Result<Self> {
|
||||
let name = &data.ident;
|
||||
|
||||
enum NestRef {
|
||||
Static(String),
|
||||
Dynamic { id: LayoutId },
|
||||
}
|
||||
|
||||
let mut routes = Vec::new();
|
||||
|
||||
let mut layouts = Vec::new();
|
||||
let mut layout_stack = Vec::new();
|
||||
|
||||
let mut nests = Vec::new();
|
||||
let mut nest_stack = Vec::new();
|
||||
|
||||
for variant in data.variants {
|
||||
for variant in &data.variants {
|
||||
// Apply the any nesting attributes in order
|
||||
for attr in &variant.attrs {
|
||||
if attr.path.is_ident("nest") {
|
||||
let nest: Nest = attr.parse_args()?;
|
||||
let nest_ref = match nest {
|
||||
Nest::Static(s) => NestRef::Static(s),
|
||||
Nest::Layout(mut l) => {
|
||||
// if there is a static nest before this, add it to the layout
|
||||
let mut static_prefix = nest_stack
|
||||
.iter()
|
||||
// walk backwards and take all static nests
|
||||
.rev()
|
||||
.map_while(|nest| match nest {
|
||||
NestRef::Static(s) => Some(s.clone()),
|
||||
NestRef::Dynamic { .. } => None,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
// reverse the static prefix so it is in the correct order
|
||||
static_prefix.reverse();
|
||||
|
||||
if !static_prefix.is_empty() {
|
||||
l.add_static_prefix(&static_prefix.join("/"));
|
||||
let mut children_routes = Vec::new();
|
||||
{
|
||||
// add all of the variants of the enum to the children_routes until we hit an end_nest
|
||||
let mut level = 0;
|
||||
'o: for variant in &data.variants {
|
||||
children_routes.push(variant.fields.clone());
|
||||
for attr in &variant.attrs {
|
||||
if attr.path.is_ident("nest") {
|
||||
level += 1;
|
||||
} else if attr.path.is_ident("end_nest") {
|
||||
level -= 1;
|
||||
if level < 0 {
|
||||
break 'o;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let id = layouts.len();
|
||||
layouts.push(l);
|
||||
NestRef::Dynamic { id: LayoutId(id) }
|
||||
}
|
||||
}
|
||||
|
||||
let nest_index = nests.len();
|
||||
|
||||
let parser = |input: ParseStream| {
|
||||
Nest::parse(
|
||||
input,
|
||||
children_routes
|
||||
.iter()
|
||||
.filter_map(|f: &syn::Fields| match f {
|
||||
syn::Fields::Named(fields) => Some(fields.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.collect(),
|
||||
nest_index,
|
||||
)
|
||||
};
|
||||
nest_stack.push(nest_ref);
|
||||
let nest = attr.parse_args_with(parser)?;
|
||||
|
||||
nests.push(nest);
|
||||
nest_stack.push(NestId(nest_index));
|
||||
} else if attr.path.is_ident("end_nest") {
|
||||
nest_stack.pop();
|
||||
} else if attr.path.is_ident("layout") {
|
||||
let layout_index = layouts.len();
|
||||
|
||||
let parser = |input: ParseStream| {
|
||||
Layout::parse(input, nest_stack.iter().rev().cloned().collect())
|
||||
};
|
||||
let layout = attr.parse_args_with(parser)?;
|
||||
|
||||
layouts.push(layout);
|
||||
layout_stack.push(LayoutId(layout_index));
|
||||
} else if attr.path.is_ident("end_layout") {
|
||||
layout_stack.pop();
|
||||
}
|
||||
}
|
||||
|
||||
let mut trailing_static_route = nest_stack
|
||||
.iter()
|
||||
.rev()
|
||||
.map_while(|nest| match nest {
|
||||
NestRef::Static(s) => Some(s.clone()),
|
||||
NestRef::Dynamic { .. } => None,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
trailing_static_route.reverse();
|
||||
let active_layouts = nest_stack
|
||||
.iter()
|
||||
.filter_map(|nest| match nest {
|
||||
NestRef::Static(_) => None,
|
||||
NestRef::Dynamic { id } => Some(*id),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let mut active_nests = nest_stack.clone();
|
||||
active_nests.reverse();
|
||||
let mut active_layouts = layout_stack.clone();
|
||||
active_layouts.reverse();
|
||||
|
||||
let route = Route::parse(active_nests, active_layouts, variant.clone())?;
|
||||
|
||||
let route = Route::parse(trailing_static_route.join("/"), active_layouts, variant)?;
|
||||
routes.push(route);
|
||||
}
|
||||
|
||||
let myself = Self {
|
||||
vis: data.vis,
|
||||
attrs: data.attrs,
|
||||
name: name.clone(),
|
||||
attrs: data.attrs,
|
||||
vis: data.vis,
|
||||
routes,
|
||||
nests,
|
||||
layouts,
|
||||
};
|
||||
|
||||
|
@ -140,7 +153,7 @@ impl RouteEnum {
|
|||
let mut display_match = Vec::new();
|
||||
|
||||
for route in &self.routes {
|
||||
display_match.push(route.display_match(&self.layouts));
|
||||
display_match.push(route.display_match(&self.nests));
|
||||
}
|
||||
|
||||
let name = &self.name;
|
||||
|
@ -158,13 +171,13 @@ impl RouteEnum {
|
|||
}
|
||||
|
||||
fn parse_impl(&self) -> TokenStream2 {
|
||||
let tree = RouteTree::new(&self.routes, &self.layouts);
|
||||
let tree = RouteTree::new(&self.routes, &self.nests);
|
||||
let name = &self.name;
|
||||
|
||||
let error_name = format_ident!("{}MatchError", self.name);
|
||||
let tokens = tree.roots.iter().map(|&id| {
|
||||
let route = tree.get(id).unwrap();
|
||||
route.to_tokens(&tree, self.name.clone(), error_name.clone(), &self.layouts)
|
||||
route.to_tokens(&tree, self.name.clone(), error_name.clone())
|
||||
});
|
||||
|
||||
quote! {
|
||||
|
@ -217,15 +230,14 @@ impl RouteEnum {
|
|||
type_defs.push(route.error_type());
|
||||
}
|
||||
|
||||
for layout in &self.layouts {
|
||||
let layout_name = &layout.layout_name;
|
||||
for nest in &self.nests {
|
||||
let error_variant = nest.error_variant();
|
||||
let error_name = nest.error_ident();
|
||||
let route_str = &nest.route;
|
||||
|
||||
let error_name = layout.error_ident();
|
||||
let route_str = &layout.route;
|
||||
|
||||
error_variants.push(quote! { #layout_name(#error_name) });
|
||||
display_match.push(quote! { Self::#layout_name(err) => write!(f, "Layout '{}' ('{}') did not match:\n{}", stringify!(#layout_name), #route_str, err)? });
|
||||
type_defs.push(layout.error_type());
|
||||
error_variants.push(quote! { #error_variant(#error_name) });
|
||||
display_match.push(quote! { Self::#error_variant(err) => write!(f, "Nest '{}' ('{}') did not match:\n{}", stringify!(#error_name), #route_str, err)? });
|
||||
type_defs.push(nest.error_type());
|
||||
}
|
||||
|
||||
quote! {
|
||||
|
@ -259,7 +271,7 @@ impl RouteEnum {
|
|||
|
||||
// Collect all routes that match the current layer
|
||||
for route in &self.routes {
|
||||
if let Some(matched) = route.routable_match(&self.layouts, index) {
|
||||
if let Some(matched) = route.routable_match(&self.layouts, &self.nests, index) {
|
||||
routable_match.push(matched);
|
||||
}
|
||||
}
|
||||
|
@ -303,7 +315,7 @@ impl ToTokens for RouteEnum {
|
|||
let vis = &self.vis;
|
||||
let name = &self.name;
|
||||
let attrs = &self.attrs;
|
||||
let variants = routes.iter().map(|r| r.variant(&self.layouts));
|
||||
let variants = routes.iter().map(|r| r.variant());
|
||||
|
||||
tokens.extend(quote!(
|
||||
#(#attrs)*
|
||||
|
|
|
@ -1,83 +1,55 @@
|
|||
use proc_macro2::TokenStream;
|
||||
use quote::{format_ident, quote};
|
||||
use syn::{parse::Parse, Ident, LitStr};
|
||||
use syn::{Ident, LitStr};
|
||||
|
||||
use crate::segment::{parse_route_segments, RouteSegment};
|
||||
|
||||
pub enum Nest {
|
||||
Static(String),
|
||||
Layout(Layout),
|
||||
}
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct NestId(pub usize);
|
||||
|
||||
impl Parse for Nest {
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||
// First parse the route
|
||||
let route: LitStr = input.parse()?;
|
||||
let is_dynamic = route.value().contains('(');
|
||||
|
||||
if !input.is_empty() || is_dynamic {
|
||||
// Then parse the layout name
|
||||
let _ = input.parse::<syn::Token![,]>();
|
||||
let layout_name: syn::Ident = input.parse()?;
|
||||
let layout_fields: syn::FieldsNamed = input.parse()?;
|
||||
|
||||
// Then parse the component name
|
||||
let _ = input.parse::<syn::Token![,]>();
|
||||
let comp: Ident = input.parse()?;
|
||||
|
||||
// Then parse the props name
|
||||
let _ = input.parse::<syn::Token![,]>();
|
||||
let props_name: Ident = input
|
||||
.parse()
|
||||
.unwrap_or_else(|_| format_ident!("{}Props", comp.to_string()));
|
||||
|
||||
let route_segments =
|
||||
parse_route_segments(&layout_name, &layout_fields, &route.value())?.0;
|
||||
for seg in &route_segments {
|
||||
if let RouteSegment::CatchAll(name, _) = seg {
|
||||
return Err(syn::Error::new_spanned(
|
||||
name,
|
||||
format!(
|
||||
"Catch-all segments are not allowed in nested routes: {}",
|
||||
route.value()
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self::Layout(Layout {
|
||||
route: route.value(),
|
||||
segments: route_segments,
|
||||
layout_name,
|
||||
comp,
|
||||
props_name,
|
||||
layout_fields,
|
||||
}))
|
||||
} else {
|
||||
Ok(Self::Static(route.value()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct LayoutId(pub usize);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Layout {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Nest {
|
||||
pub route: String,
|
||||
pub segments: Vec<RouteSegment>,
|
||||
pub layout_name: Ident,
|
||||
pub layout_fields: syn::FieldsNamed,
|
||||
pub comp: Ident,
|
||||
pub props_name: Ident,
|
||||
index: usize,
|
||||
}
|
||||
|
||||
impl Layout {
|
||||
pub fn add_static_prefix(&mut self, prefix: &str) {
|
||||
self.route = format!("{}{}", prefix, self.route);
|
||||
self.segments.push(RouteSegment::Static(prefix.to_string()));
|
||||
}
|
||||
impl Nest {
|
||||
pub fn parse(
|
||||
input: syn::parse::ParseStream,
|
||||
children_routes: Vec<syn::FieldsNamed>,
|
||||
index: usize,
|
||||
) -> syn::Result<Self> {
|
||||
// Parse the route
|
||||
let route: LitStr = input.parse()?;
|
||||
|
||||
let route_segments = parse_route_segments(
|
||||
route.span(),
|
||||
children_routes.iter().flat_map(|f| f.named.iter()),
|
||||
&route.value(),
|
||||
)?
|
||||
.0;
|
||||
for seg in &route_segments {
|
||||
if let RouteSegment::CatchAll(name, _) = seg {
|
||||
return Err(syn::Error::new_spanned(
|
||||
name,
|
||||
format!(
|
||||
"Catch-all segments are not allowed in nested routes: {}",
|
||||
route.value()
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
route: route.value(),
|
||||
segments: route_segments,
|
||||
index,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Nest {
|
||||
pub fn dynamic_segments(&self) -> impl Iterator<Item = TokenStream> + '_ {
|
||||
self.segments
|
||||
.iter()
|
||||
|
@ -85,13 +57,6 @@ impl Layout {
|
|||
.map(|i| quote! {#i})
|
||||
}
|
||||
|
||||
pub fn dynamic_segment_types(&self) -> impl Iterator<Item = TokenStream> + '_ {
|
||||
self.segments
|
||||
.iter()
|
||||
.filter_map(|seg| seg.ty())
|
||||
.map(|ty| quote! {#ty})
|
||||
}
|
||||
|
||||
pub fn write(&self) -> TokenStream {
|
||||
let write_segments = self.segments.iter().map(|s| s.write_segment());
|
||||
|
||||
|
@ -103,7 +68,11 @@ impl Layout {
|
|||
}
|
||||
|
||||
pub fn error_ident(&self) -> Ident {
|
||||
format_ident!("{}LayoutParseError", self.layout_name)
|
||||
format_ident!("Nest{}ParseError", self.index)
|
||||
}
|
||||
|
||||
pub fn error_variant(&self) -> Ident {
|
||||
format_ident!("Nest{}", self.index)
|
||||
}
|
||||
|
||||
pub fn error_type(&self) -> TokenStream {
|
||||
|
@ -147,23 +116,4 @@ impl Layout {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn routable_match(&self) -> TokenStream {
|
||||
let props_name = &self.props_name;
|
||||
let comp_name = &self.comp;
|
||||
let dynamic_segments_from_route = self
|
||||
.segments
|
||||
.iter()
|
||||
.filter_map(|seg| seg.name())
|
||||
.map(|seg| quote! { #seg });
|
||||
|
||||
quote! {
|
||||
let comp = #props_name { #(#dynamic_segments_from_route,)* };
|
||||
let cx = cx.bump().alloc(Scoped {
|
||||
props: cx.bump().alloc(comp),
|
||||
scope: cx,
|
||||
});
|
||||
#comp_name(cx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,12 +24,4 @@ impl QuerySegment {
|
|||
write!(f, "?{}", #ident)?;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name(&self) -> Ident {
|
||||
self.ident.clone()
|
||||
}
|
||||
|
||||
pub fn ty(&self) -> &Type {
|
||||
&self.ty
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,8 +5,10 @@ use syn::{Ident, LitStr};
|
|||
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
|
||||
use crate::nest::Layout;
|
||||
use crate::nest::LayoutId;
|
||||
use crate::layout::Layout;
|
||||
use crate::layout::LayoutId;
|
||||
use crate::nest::Nest;
|
||||
use crate::nest::NestId;
|
||||
use crate::query::QuerySegment;
|
||||
use crate::segment::parse_route_segments;
|
||||
use crate::segment::RouteSegment;
|
||||
|
@ -44,13 +46,15 @@ pub struct Route {
|
|||
pub route: String,
|
||||
pub segments: Vec<RouteSegment>,
|
||||
pub query: Option<QuerySegment>,
|
||||
pub nests: Vec<NestId>,
|
||||
pub layouts: Vec<LayoutId>,
|
||||
pub variant: syn::Variant,
|
||||
fields: syn::FieldsNamed,
|
||||
}
|
||||
|
||||
impl Route {
|
||||
pub fn parse(
|
||||
root_route: String,
|
||||
nests: Vec<NestId>,
|
||||
layouts: Vec<LayoutId>,
|
||||
variant: syn::Variant,
|
||||
) -> syn::Result<Self> {
|
||||
|
@ -67,7 +71,7 @@ impl Route {
|
|||
|
||||
let route_name = variant.ident.clone();
|
||||
let args = route_attr.parse_args::<RouteArgs>()?;
|
||||
let route = root_route + &args.route.value();
|
||||
let route = args.route.value();
|
||||
let file_based = args.comp_name.is_none();
|
||||
let comp_name = args
|
||||
.comp_name
|
||||
|
@ -86,7 +90,8 @@ impl Route {
|
|||
}
|
||||
};
|
||||
|
||||
let (route_segments, query) = parse_route_segments(&variant.ident, named_fields, &route)?;
|
||||
let (route_segments, query) =
|
||||
parse_route_segments(variant.ident.span(), named_fields.named.iter(), &route)?;
|
||||
|
||||
Ok(Self {
|
||||
comp_name,
|
||||
|
@ -96,15 +101,17 @@ impl Route {
|
|||
route,
|
||||
file_based,
|
||||
query,
|
||||
nests,
|
||||
layouts,
|
||||
fields: named_fields.clone(),
|
||||
variant,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn display_match(&self, layouts: &[Layout]) -> TokenStream2 {
|
||||
pub fn display_match(&self, nests: &[Nest]) -> TokenStream2 {
|
||||
let name = &self.route_name;
|
||||
let dynamic_segments = self.dynamic_segments(layouts);
|
||||
let write_layouts = self.layouts.iter().map(|id| layouts[id.0].write());
|
||||
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());
|
||||
|
||||
|
@ -117,14 +124,19 @@ impl Route {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn routable_match(&self, layouts: &[Layout], index: usize) -> Option<TokenStream2> {
|
||||
pub fn routable_match(
|
||||
&self,
|
||||
layouts: &[Layout],
|
||||
nests: &[Nest],
|
||||
index: usize,
|
||||
) -> Option<TokenStream2> {
|
||||
let name = &self.route_name;
|
||||
let dynamic_segments = self.dynamic_segments(layouts);
|
||||
let dynamic_segments = self.dynamic_segments();
|
||||
|
||||
match index.cmp(&self.layouts.len()) {
|
||||
std::cmp::Ordering::Less => {
|
||||
let layout = self.layouts[index];
|
||||
let render_layout = layouts[layout.0].routable_match();
|
||||
let render_layout = layouts[layout.0].routable_match(nests);
|
||||
// This is a layout
|
||||
Some(quote! {
|
||||
#[allow(unused)]
|
||||
|
@ -134,7 +146,7 @@ impl Route {
|
|||
})
|
||||
}
|
||||
std::cmp::Ordering::Equal => {
|
||||
let dynamic_segments_from_route = self.dynamic_segments_from_route();
|
||||
let dynamic_segments_from_route = self.dynamic_segments();
|
||||
let props_name = &self.props_name;
|
||||
let comp_name = &self.comp_name;
|
||||
// This is the final route
|
||||
|
@ -154,72 +166,15 @@ impl Route {
|
|||
}
|
||||
}
|
||||
|
||||
fn dynamic_segment_types<'a>(
|
||||
&'a self,
|
||||
layouts: &'a [Layout],
|
||||
) -> impl Iterator<Item = TokenStream2> + 'a {
|
||||
let layouts = self
|
||||
.layouts
|
||||
.iter()
|
||||
.flat_map(|id| layouts[id.0].dynamic_segment_types());
|
||||
let segments = self.segments.iter().filter_map(|seg| {
|
||||
let ty = seg.ty()?;
|
||||
|
||||
Some(quote! {
|
||||
#ty
|
||||
})
|
||||
});
|
||||
let query = self
|
||||
.query
|
||||
.as_ref()
|
||||
.map(|q| {
|
||||
let ty = q.ty();
|
||||
quote! {
|
||||
#ty
|
||||
}
|
||||
})
|
||||
.into_iter();
|
||||
|
||||
layouts.chain(segments.chain(query))
|
||||
fn dynamic_segments(&self) -> impl Iterator<Item = TokenStream2> + '_ {
|
||||
self.fields.named.iter().map(|f| {
|
||||
let name = f.ident.as_ref().unwrap();
|
||||
quote! {#name}
|
||||
})
|
||||
}
|
||||
|
||||
fn dynamic_segments_from_route(&self) -> impl Iterator<Item = TokenStream2> + '_ {
|
||||
let segments = self.segments.iter().filter_map(|seg| {
|
||||
seg.name().map(|name| {
|
||||
quote! {
|
||||
#name
|
||||
}
|
||||
})
|
||||
});
|
||||
let query = self
|
||||
.query
|
||||
.as_ref()
|
||||
.map(|q| {
|
||||
let name = q.name();
|
||||
quote! {
|
||||
#name
|
||||
}
|
||||
})
|
||||
.into_iter();
|
||||
|
||||
segments.chain(query)
|
||||
}
|
||||
|
||||
fn dynamic_segments<'a>(
|
||||
&'a self,
|
||||
layouts: &'a [Layout],
|
||||
) -> impl Iterator<Item = TokenStream2> + 'a {
|
||||
let layouts = self
|
||||
.layouts
|
||||
.iter()
|
||||
.flat_map(|id| layouts[id.0].dynamic_segments());
|
||||
let dynamic_segments = self.dynamic_segments_from_route();
|
||||
|
||||
layouts.chain(dynamic_segments)
|
||||
}
|
||||
|
||||
pub fn construct(&self, enum_name: Ident, layouts: &[Layout]) -> TokenStream2 {
|
||||
let segments = self.dynamic_segments(layouts);
|
||||
pub fn construct(&self, enum_name: Ident) -> TokenStream2 {
|
||||
let segments = self.dynamic_segments();
|
||||
let name = &self.route_name;
|
||||
|
||||
quote! {
|
||||
|
@ -289,13 +244,21 @@ impl Route {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn variant(&self, layouts: &[Layout]) -> TokenStream2 {
|
||||
pub fn variant(&self) -> TokenStream2 {
|
||||
let name = &self.route_name;
|
||||
let segments = self.dynamic_segments(layouts);
|
||||
let types = self.dynamic_segment_types(layouts);
|
||||
let fields = self.fields.named.iter().map(|f| {
|
||||
let mut new = f.clone();
|
||||
new.attrs.retain(|a| {
|
||||
!a.path.is_ident("nest")
|
||||
&& !a.path.is_ident("end_nest")
|
||||
&& !a.path.is_ident("layout")
|
||||
&& !a.path.is_ident("end_layout")
|
||||
});
|
||||
new
|
||||
});
|
||||
|
||||
quote! {
|
||||
#name { #(#segments: #types,)* }
|
||||
#name { #(#fields,)* }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ use slab::Slab;
|
|||
use syn::Ident;
|
||||
|
||||
use crate::{
|
||||
nest::Layout,
|
||||
nest::Nest,
|
||||
route::Route,
|
||||
segment::{static_segment_idx, RouteSegment},
|
||||
};
|
||||
|
@ -39,7 +39,7 @@ impl<'a> RouteTree<'a> {
|
|||
let seg = self.get(seg).unwrap();
|
||||
match seg {
|
||||
RouteTreeSegmentData::Static { .. } => 0,
|
||||
RouteTreeSegmentData::Layout { .. } => 1,
|
||||
RouteTreeSegmentData::Nest { .. } => 1,
|
||||
RouteTreeSegmentData::Route(route) => {
|
||||
// Routes that end in a catch all segment should be checked last
|
||||
match route.segments.last() {
|
||||
|
@ -70,7 +70,7 @@ impl<'a> RouteTree<'a> {
|
|||
let element = self.entries.get(element).unwrap();
|
||||
match element {
|
||||
RouteTreeSegmentData::Static { children, .. } => children.clone(),
|
||||
RouteTreeSegmentData::Layout { children, .. } => children.clone(),
|
||||
RouteTreeSegmentData::Nest { children, .. } => children.clone(),
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
@ -79,20 +79,20 @@ impl<'a> RouteTree<'a> {
|
|||
let element = self.entries.get_mut(element).unwrap();
|
||||
match element {
|
||||
RouteTreeSegmentData::Static { children, .. } => Some(children),
|
||||
RouteTreeSegmentData::Layout { children, .. } => Some(children),
|
||||
RouteTreeSegmentData::Nest { children, .. } => Some(children),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn children_mut(&mut self, element: usize) -> &mut Vec<usize> {
|
||||
self.try_children_mut(element)
|
||||
.expect("Cannot get children of non static or layout segment")
|
||||
.expect("Cannot get children of non static or nest segment")
|
||||
}
|
||||
|
||||
pub fn new(routes: &'a [Route], layouts: &'a [Layout]) -> Self {
|
||||
pub fn new(routes: &'a [Route], nests: &'a [Nest]) -> Self {
|
||||
let routes = routes
|
||||
.iter()
|
||||
.map(|route| RouteIter::new(route, layouts))
|
||||
.map(|route| RouteIter::new(route, nests))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut myself = Self::default();
|
||||
|
@ -109,11 +109,11 @@ impl<'a> RouteTree<'a> {
|
|||
for mut route in routes {
|
||||
let mut current_route: Option<usize> = None;
|
||||
|
||||
// First add a layout if there is one
|
||||
while let Some(layout) = route.next_layout() {
|
||||
let segments_iter: std::slice::Iter<RouteSegment> = layout.segments.iter();
|
||||
// First add all nests
|
||||
while let Some(nest) = route.next_nest() {
|
||||
let segments_iter = nest.segments.iter();
|
||||
|
||||
// Add all static segments of the layout
|
||||
// Add all static segments of the nest
|
||||
'o: for (index, segment) in segments_iter.enumerate() {
|
||||
match segment {
|
||||
RouteSegment::Static(segment) => {
|
||||
|
@ -124,17 +124,12 @@ impl<'a> RouteTree<'a> {
|
|||
.map(|id| self.children(id))
|
||||
.unwrap_or_else(|| segments.clone());
|
||||
|
||||
for seg in segments.iter() {
|
||||
let seg = self.get(*seg).unwrap();
|
||||
if let RouteTreeSegmentData::Static {
|
||||
segment: s,
|
||||
children,
|
||||
..
|
||||
} = seg
|
||||
{
|
||||
for &seg_id in segments.iter() {
|
||||
let seg = self.get(seg_id).unwrap();
|
||||
if let RouteTreeSegmentData::Static { segment: s, .. } = seg {
|
||||
if s == segment {
|
||||
// If it does, just update the current route
|
||||
current_route = children.last().cloned();
|
||||
current_route = Some(seg_id);
|
||||
continue 'o;
|
||||
}
|
||||
}
|
||||
|
@ -144,7 +139,10 @@ impl<'a> RouteTree<'a> {
|
|||
let static_segment = RouteTreeSegmentData::Static {
|
||||
segment,
|
||||
children: Vec::new(),
|
||||
error_variant: route.error_variant(),
|
||||
error_variant: StaticErrorVariant {
|
||||
varient_parse_error: nest.error_ident(),
|
||||
enum_varient: nest.error_variant(),
|
||||
},
|
||||
index,
|
||||
};
|
||||
|
||||
|
@ -155,28 +153,31 @@ impl<'a> RouteTree<'a> {
|
|||
.map(|id| self.children_mut(id))
|
||||
.unwrap_or_else(|| &mut segments);
|
||||
current_children.push(static_segment);
|
||||
|
||||
// Update the current route
|
||||
current_route = Some(static_segment);
|
||||
}
|
||||
// If there is a dynamic segment, stop adding static segments
|
||||
RouteSegment::Dynamic(..) => break,
|
||||
RouteSegment::CatchAll(..) => {
|
||||
todo!("Catch all segments are not allowed in layouts")
|
||||
todo!("Catch all segments are not allowed in nests")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add the layout to the current route
|
||||
let layout = RouteTreeSegmentData::Layout {
|
||||
layout,
|
||||
// Add the nest to the current route
|
||||
let nest = RouteTreeSegmentData::Nest {
|
||||
nest,
|
||||
children: Vec::new(),
|
||||
};
|
||||
|
||||
let layout = self.entries.insert(layout);
|
||||
let nest = self.entries.insert(nest);
|
||||
let segments = match current_route.and_then(|id| self.get_mut(id)) {
|
||||
Some(RouteTreeSegmentData::Static { children, .. }) => children,
|
||||
Some(_) => unreachable!(),
|
||||
Some(r) => unreachable!("{r:?} is not a static segment"),
|
||||
None => &mut segments,
|
||||
};
|
||||
segments.push(layout);
|
||||
segments.push(nest);
|
||||
|
||||
// Update the current route
|
||||
current_route = segments.last().cloned();
|
||||
|
@ -252,8 +253,8 @@ pub enum RouteTreeSegmentData<'a> {
|
|||
index: usize,
|
||||
children: Vec<usize>,
|
||||
},
|
||||
Layout {
|
||||
layout: &'a Layout,
|
||||
Nest {
|
||||
nest: &'a Nest,
|
||||
children: Vec<usize>,
|
||||
},
|
||||
Route(&'a Route),
|
||||
|
@ -265,7 +266,6 @@ impl<'a> RouteTreeSegmentData<'a> {
|
|||
tree: &RouteTree,
|
||||
enum_name: syn::Ident,
|
||||
error_enum_name: syn::Ident,
|
||||
layouts: &[Layout],
|
||||
) -> TokenStream {
|
||||
match self {
|
||||
RouteTreeSegmentData::Static {
|
||||
|
@ -282,7 +282,7 @@ impl<'a> RouteTreeSegmentData<'a> {
|
|||
|
||||
let children = children.iter().map(|child| {
|
||||
let child = tree.get(*child).unwrap();
|
||||
child.to_tokens(tree, enum_name.clone(), error_enum_name.clone(), layouts)
|
||||
child.to_tokens(tree, enum_name.clone(), error_enum_name.clone())
|
||||
});
|
||||
|
||||
quote! {
|
||||
|
@ -310,7 +310,7 @@ impl<'a> RouteTreeSegmentData<'a> {
|
|||
.enumerate()
|
||||
.skip_while(|(_, seg)| matches!(seg, RouteSegment::Static(_)));
|
||||
|
||||
let construct_variant = route.construct(enum_name, layouts);
|
||||
let construct_variant = route.construct(enum_name);
|
||||
let parse_query = route.parse_query();
|
||||
|
||||
let insure_not_trailing = route
|
||||
|
@ -334,12 +334,12 @@ impl<'a> RouteTreeSegmentData<'a> {
|
|||
&varient_parse_error,
|
||||
)
|
||||
}
|
||||
Self::Layout { layout, children } => {
|
||||
Self::Nest { nest, children } => {
|
||||
// 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: Ident = layout.error_ident();
|
||||
let enum_varient = &layout.layout_name;
|
||||
let varient_parse_error: Ident = nest.error_ident();
|
||||
let enum_varient = nest.error_variant();
|
||||
|
||||
let route_segments = layout
|
||||
let route_segments = nest
|
||||
.segments
|
||||
.iter()
|
||||
.enumerate()
|
||||
|
@ -349,7 +349,7 @@ impl<'a> RouteTreeSegmentData<'a> {
|
|||
.iter()
|
||||
.map(|child| {
|
||||
let child = tree.get(*child).unwrap();
|
||||
child.to_tokens(tree, enum_name.clone(), error_enum_name.clone(), layouts)
|
||||
child.to_tokens(tree, enum_name.clone(), error_enum_name.clone())
|
||||
})
|
||||
.collect();
|
||||
|
||||
|
@ -357,7 +357,7 @@ impl<'a> RouteTreeSegmentData<'a> {
|
|||
route_segments.peekable(),
|
||||
parse_children,
|
||||
&error_enum_name,
|
||||
enum_varient,
|
||||
&enum_varient,
|
||||
&varient_parse_error,
|
||||
)
|
||||
}
|
||||
|
@ -436,27 +436,27 @@ fn return_constructed(
|
|||
|
||||
pub struct RouteIter<'a> {
|
||||
route: &'a Route,
|
||||
layouts: &'a [Layout],
|
||||
layout_index: usize,
|
||||
nests: &'a [Nest],
|
||||
nest_index: usize,
|
||||
static_segment_index: usize,
|
||||
}
|
||||
|
||||
impl<'a> RouteIter<'a> {
|
||||
fn new(route: &'a Route, layouts: &'a [Layout]) -> Self {
|
||||
fn new(route: &'a Route, nests: &'a [Nest]) -> Self {
|
||||
Self {
|
||||
route,
|
||||
layouts,
|
||||
layout_index: 0,
|
||||
nests,
|
||||
nest_index: 0,
|
||||
static_segment_index: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn next_layout(&mut self) -> Option<&'a Layout> {
|
||||
let idx = self.layout_index;
|
||||
let layout_index = self.route.layouts.get(idx)?;
|
||||
let layout = &self.layouts[layout_index.0];
|
||||
self.layout_index += 1;
|
||||
Some(layout)
|
||||
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];
|
||||
self.nest_index += 1;
|
||||
Some(nest)
|
||||
}
|
||||
|
||||
fn next_static_segment(&mut self) -> Option<(usize, &'a str)> {
|
||||
|
|
|
@ -5,7 +5,7 @@ use proc_macro2::{Span, TokenStream as TokenStream2};
|
|||
|
||||
use crate::query::QuerySegment;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum RouteSegment {
|
||||
Static(String),
|
||||
Dynamic(Ident, Type),
|
||||
|
@ -21,14 +21,6 @@ impl RouteSegment {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn ty(&self) -> Option<&Type> {
|
||||
match self {
|
||||
Self::Static(_) => None,
|
||||
Self::Dynamic(_, ty) => Some(ty),
|
||||
Self::CatchAll(_, ty) => Some(ty),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_segment(&self) -> TokenStream2 {
|
||||
match self {
|
||||
Self::Static(segment) => quote! { write!(f, "/{}", #segment)?; },
|
||||
|
@ -130,9 +122,9 @@ pub fn static_segment_idx(idx: usize) -> Ident {
|
|||
format_ident!("StaticSegment{}ParseError", idx)
|
||||
}
|
||||
|
||||
pub fn parse_route_segments(
|
||||
route_name: &Ident,
|
||||
fields: &syn::FieldsNamed,
|
||||
pub fn parse_route_segments<'a>(
|
||||
route_span: Span,
|
||||
mut fields: impl Iterator<Item = &'a syn::Field>,
|
||||
route: &str,
|
||||
) -> syn::Result<(Vec<RouteSegment>, Option<QuerySegment>)> {
|
||||
let mut route_segments = Vec::new();
|
||||
|
@ -146,8 +138,8 @@ pub fn parse_route_segments(
|
|||
// skip the first empty segment
|
||||
let first = iterator.next();
|
||||
if first != Some("") {
|
||||
return Err(syn::Error::new_spanned(
|
||||
route_name,
|
||||
return Err(syn::Error::new(
|
||||
route_span,
|
||||
format!(
|
||||
"Routes should start with /. Error found in the route '{}'",
|
||||
route
|
||||
|
@ -165,7 +157,7 @@ pub fn parse_route_segments(
|
|||
segment.to_string()
|
||||
};
|
||||
|
||||
let field = fields.named.iter().find(|field| match field.ident {
|
||||
let field = fields.find(|field| match field.ident {
|
||||
Some(ref field_ident) => *field_ident == ident,
|
||||
None => false,
|
||||
});
|
||||
|
@ -173,12 +165,9 @@ pub fn parse_route_segments(
|
|||
let ty = if let Some(field) = field {
|
||||
field.ty.clone()
|
||||
} else {
|
||||
return Err(syn::Error::new_spanned(
|
||||
route_name,
|
||||
format!(
|
||||
"Could not find a field with the name '{}' in the variant '{}'",
|
||||
ident, route_name
|
||||
),
|
||||
return Err(syn::Error::new(
|
||||
route_span,
|
||||
format!("Could not find a field with the name '{}'", ident,),
|
||||
));
|
||||
};
|
||||
if spread {
|
||||
|
@ -188,8 +177,8 @@ pub fn parse_route_segments(
|
|||
));
|
||||
|
||||
if iterator.next().is_some() {
|
||||
return Err(syn::Error::new_spanned(
|
||||
route,
|
||||
return Err(syn::Error::new(
|
||||
route_span,
|
||||
"Catch-all route segments must be the last segment in a route. The route segments after the catch-all segment will never be matched.",
|
||||
));
|
||||
} else {
|
||||
|
@ -211,7 +200,7 @@ pub fn parse_route_segments(
|
|||
Some(query) => {
|
||||
if let Some(query) = query.strip_prefix(':') {
|
||||
let query_ident = Ident::new(query, Span::call_site());
|
||||
let field = fields.named.iter().find(|field| match field.ident {
|
||||
let field = fields.find(|field| match field.ident {
|
||||
Some(ref field_ident) => field_ident == &query_ident,
|
||||
None => false,
|
||||
});
|
||||
|
@ -219,12 +208,9 @@ pub fn parse_route_segments(
|
|||
let ty = if let Some(field) = field {
|
||||
field.ty.clone()
|
||||
} else {
|
||||
return Err(syn::Error::new_spanned(
|
||||
route_name,
|
||||
format!(
|
||||
"Could not find a field with the name '{}' in the variant '{}'",
|
||||
query_ident, route_name
|
||||
),
|
||||
return Err(syn::Error::new(
|
||||
route_span,
|
||||
format!("Could not find a field with the name '{}'", query_ident),
|
||||
));
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in a new issue