diff --git a/packages/router-core/tests/macro.rs b/packages/router-core/tests/macro.rs index def7e3e6d..f0709435c 100644 --- a/packages/router-core/tests/macro.rs +++ b/packages/router-core/tests/macro.rs @@ -1,3 +1,5 @@ +#![allow(non_snake_case)] + use dioxus::prelude::*; use dioxus_router_core::*; use dioxus_router_macro::*; @@ -57,6 +59,7 @@ fn Route6(cx: Scope, extra: Vec) -> Element { } } +#[rustfmt::skip] #[derive(Routable, Clone, Debug, PartialEq)] enum Route { #[route("/(dynamic)" Route1)] diff --git a/packages/router-macro/src/lib.rs b/packages/router-macro/src/lib.rs index 5549abf53..fb63a745a 100644 --- a/packages/router-macro/src/lib.rs +++ b/packages/router-macro/src/lib.rs @@ -8,8 +8,11 @@ use syn::{parse_macro_input, Ident}; use proc_macro2::TokenStream as TokenStream2; +mod nest; +mod query; mod route; mod route_tree; +mod segment; #[proc_macro_derive(Routable, attributes(route))] pub fn derive_routable(input: TokenStream) -> TokenStream { @@ -51,8 +54,15 @@ impl RouteEnum { if let syn::Data::Enum(data) = input.data { let mut routes = Vec::new(); + let mut current_base_route = String::new(); + for variant in data.variants { - let route = Route::parse(variant)?; + // Apply the any nesting attributes in order + for attr in &variant.attrs { + if attr.path.is_ident("nest") {} + } + + let route = Route::parse(current_base_route.clone(), variant)?; routes.push(route); } diff --git a/packages/router-macro/src/nest.rs b/packages/router-macro/src/nest.rs new file mode 100644 index 000000000..882b1d715 --- /dev/null +++ b/packages/router-macro/src/nest.rs @@ -0,0 +1 @@ +struct Nest {} diff --git a/packages/router-macro/src/query.rs b/packages/router-macro/src/query.rs new file mode 100644 index 000000000..e9683c553 --- /dev/null +++ b/packages/router-macro/src/query.rs @@ -0,0 +1,31 @@ +use quote::quote; +use syn::{Ident, Type}; + +use proc_macro2::TokenStream as TokenStream2; + +#[derive(Debug)] +pub struct QuerySegment { + pub ident: Ident, + pub ty: Type, +} + +impl QuerySegment { + pub fn parse(&self) -> TokenStream2 { + let ident = &self.ident; + let ty = &self.ty; + quote! { + let #ident = <#ty as dioxus_router_core::router::FromQuery>::from_query(query); + } + } + + pub fn write(&self) -> TokenStream2 { + let ident = &self.ident; + quote! { + write!(f, "?{}", #ident)?; + } + } + + pub fn name(&self) -> Ident { + self.ident.clone() + } +} diff --git a/packages/router-macro/src/route.rs b/packages/router-macro/src/route.rs index b86827f45..0b956942e 100644 --- a/packages/router-macro/src/route.rs +++ b/packages/router-macro/src/route.rs @@ -1,10 +1,13 @@ use quote::{__private::Span, format_ident, quote, ToTokens}; use syn::parse::Parse; use syn::parse::ParseStream; -use syn::{Ident, LitStr, Type, Variant}; +use syn::{Ident, LitStr, Variant}; use proc_macro2::TokenStream as TokenStream2; +use crate::query::QuerySegment; +use crate::segment::RouteSegment; + struct RouteArgs { route: LitStr, comp_name: Option, @@ -35,13 +38,13 @@ pub struct Route { pub route_name: Ident, pub comp_name: Ident, pub props_name: Ident, - pub route: LitStr, + pub route: String, pub route_segments: Vec, pub query: Option, } impl Route { - pub fn parse(input: syn::Variant) -> syn::Result { + pub fn parse(root_route: String, input: syn::Variant) -> syn::Result { let route_attr = input .attrs .iter() @@ -55,7 +58,7 @@ impl Route { let route_name = input.ident.clone(); let args = route_attr.parse_args::()?; - let route = args.route; + let route = root_route + &args.route.value(); let file_based = args.comp_name.is_none(); let comp_name = args .comp_name @@ -209,7 +212,7 @@ impl ToTokens for Route { return; } - let without_leading_slash = &self.route.value()[1..]; + let without_leading_slash = &self.route[1..]; let route_path = std::path::Path::new(without_leading_slash); let with_extension = route_path.with_extension("rs"); let dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); @@ -237,14 +240,13 @@ impl ToTokens for Route { fn parse_route_segments( varient: &Variant, - route: &LitStr, + route: &str, ) -> syn::Result<(Vec, Option)> { let mut route_segments = Vec::new(); - let route_string = route.value(); - let (route_string, query) = match route_string.rsplit_once('?') { + let (route_string, query) = match route.rsplit_once('?') { Some((route, query)) => (route, Some(query)), - None => (route_string.as_str(), None), + None => (route, None), }; let mut iterator = route_string.split('/'); @@ -255,7 +257,7 @@ fn parse_route_segments( varient, format!( "Routes should start with /. Error found in the route '{}'", - route.value() + route ), )); } @@ -346,147 +348,3 @@ fn parse_route_segments( Ok((route_segments, parsed_query)) } - -#[derive(Debug)] -pub enum RouteSegment { - Static(String), - Dynamic(Ident, Type), - CatchAll(Ident, Type), -} - -impl RouteSegment { - pub fn name(&self) -> Option { - match self { - Self::Static(_) => None, - Self::Dynamic(ident, _) => Some(ident.clone()), - Self::CatchAll(ident, _) => Some(ident.clone()), - } - } - - pub fn write_segment(&self) -> TokenStream2 { - match self { - Self::Static(segment) => quote! { write!(f, "/{}", #segment)?; }, - Self::Dynamic(ident, _) => quote! { write!(f, "/{}", #ident)?; }, - Self::CatchAll(ident, _) => quote! { #ident.display_route_segements(f)?; }, - } - } - - fn error_name(&self, idx: usize) -> Ident { - match self { - Self::Static(_) => static_segment_idx(idx), - Self::Dynamic(ident, _) => format_ident!("{}ParseError", ident), - Self::CatchAll(ident, _) => format_ident!("{}ParseError", ident), - } - } - - fn missing_error_name(&self) -> Option { - match self { - Self::Dynamic(ident, _) => Some(format_ident!("{}MissingError", ident)), - _ => None, - } - } - - pub fn try_parse( - &self, - idx: usize, - error_enum_name: &Ident, - error_enum_varient: &Ident, - inner_parse_enum: &Ident, - parse_children: TokenStream2, - ) -> TokenStream2 { - let error_name = self.error_name(idx); - match self { - Self::Static(segment) => { - quote! { - { - let mut segments = segments.clone(); - let parsed = if let Some(#segment) = segments.next() { - Ok(()) - } else { - Err(#error_enum_name::#error_enum_varient(#inner_parse_enum::#error_name)) - }; - match parsed { - Ok(_) => { - #parse_children - } - Err(err) => { - errors.push(err); - } - } - } - } - } - Self::Dynamic(name, ty) => { - let missing_error_name = self.missing_error_name().unwrap(); - quote! { - { - let mut segments = segments.clone(); - let parsed = if let Some(segment) = segments.next() { - <#ty as dioxus_router_core::router::FromRouteSegment>::from_route_segment(segment).map_err(|err| #error_enum_name::#error_enum_varient(#inner_parse_enum::#error_name(err))) - } else { - Err(#error_enum_name::#error_enum_varient(#inner_parse_enum::#missing_error_name)) - }; - match parsed { - Ok(#name) => { - #parse_children - } - Err(err) => { - errors.push(err); - } - } - } - } - } - Self::CatchAll(name, ty) => { - quote! { - { - let parsed = { - let mut segments = segments.clone(); - let segments: Vec<_> = segments.collect(); - <#ty as dioxus_router_core::router::FromRouteSegments>::from_route_segments(&segments).map_err(|err| #error_enum_name::#error_enum_varient(#inner_parse_enum::#error_name(err))) - }; - match parsed { - Ok(#name) => { - #parse_children - } - Err(err) => { - errors.push(err); - } - } - } - } - } - } - } -} - -pub fn static_segment_idx(idx: usize) -> Ident { - format_ident!("StaticSegment{}ParseError", idx) -} - -#[derive(Debug)] -pub struct QuerySegment { - ident: Ident, - ty: Type, -} - -impl QuerySegment { - pub fn parse(&self) -> TokenStream2 { - let ident = &self.ident; - let ty = &self.ty; - quote! { - let #ident = <#ty as dioxus_router_core::router::FromQuery>::from_query(query); - } - } - - pub fn write(&self) -> TokenStream2 { - let ident = &self.ident; - quote! { - write!(f, "?{}", #ident)?; - } - } - - pub fn name(&self) -> Ident { - self.ident.clone() - } -} diff --git a/packages/router-macro/src/route_tree.rs b/packages/router-macro/src/route_tree.rs index fcfa43a8d..b2a0358f6 100644 --- a/packages/router-macro/src/route_tree.rs +++ b/packages/router-macro/src/route_tree.rs @@ -2,7 +2,10 @@ use proc_macro2::TokenStream; use quote::quote; use syn::Ident; -use crate::route::{static_segment_idx, Route, RouteSegment}; +use crate::{ + route::Route, + segment::{static_segment_idx, RouteSegment}, +}; // First deduplicate the routes by the static part of the route #[derive(Debug)] diff --git a/packages/router-macro/src/segment.rs b/packages/router-macro/src/segment.rs new file mode 100644 index 000000000..e0aeda234 --- /dev/null +++ b/packages/router-macro/src/segment.rs @@ -0,0 +1,121 @@ +use quote::{format_ident, quote}; +use syn::{Ident, Type}; + +use proc_macro2::TokenStream as TokenStream2; + +#[derive(Debug)] +pub enum RouteSegment { + Static(String), + Dynamic(Ident, Type), + CatchAll(Ident, Type), +} + +impl RouteSegment { + pub fn name(&self) -> Option { + match self { + Self::Static(_) => None, + Self::Dynamic(ident, _) => Some(ident.clone()), + Self::CatchAll(ident, _) => Some(ident.clone()), + } + } + + pub fn write_segment(&self) -> TokenStream2 { + match self { + Self::Static(segment) => quote! { write!(f, "/{}", #segment)?; }, + Self::Dynamic(ident, _) => quote! { write!(f, "/{}", #ident)?; }, + Self::CatchAll(ident, _) => quote! { #ident.display_route_segements(f)?; }, + } + } + + pub fn error_name(&self, idx: usize) -> Ident { + match self { + Self::Static(_) => static_segment_idx(idx), + Self::Dynamic(ident, _) => format_ident!("{}ParseError", ident), + Self::CatchAll(ident, _) => format_ident!("{}ParseError", ident), + } + } + + pub fn missing_error_name(&self) -> Option { + match self { + Self::Dynamic(ident, _) => Some(format_ident!("{}MissingError", ident)), + _ => None, + } + } + + pub fn try_parse( + &self, + idx: usize, + error_enum_name: &Ident, + error_enum_varient: &Ident, + inner_parse_enum: &Ident, + parse_children: TokenStream2, + ) -> TokenStream2 { + let error_name = self.error_name(idx); + match self { + Self::Static(segment) => { + quote! { + { + let mut segments = segments.clone(); + let parsed = if let Some(#segment) = segments.next() { + Ok(()) + } else { + Err(#error_enum_name::#error_enum_varient(#inner_parse_enum::#error_name)) + }; + match parsed { + Ok(_) => { + #parse_children + } + Err(err) => { + errors.push(err); + } + } + } + } + } + Self::Dynamic(name, ty) => { + let missing_error_name = self.missing_error_name().unwrap(); + quote! { + { + let mut segments = segments.clone(); + let parsed = if let Some(segment) = segments.next() { + <#ty as dioxus_router_core::router::FromRouteSegment>::from_route_segment(segment).map_err(|err| #error_enum_name::#error_enum_varient(#inner_parse_enum::#error_name(err))) + } else { + Err(#error_enum_name::#error_enum_varient(#inner_parse_enum::#missing_error_name)) + }; + match parsed { + Ok(#name) => { + #parse_children + } + Err(err) => { + errors.push(err); + } + } + } + } + } + Self::CatchAll(name, ty) => { + quote! { + { + let parsed = { + let mut segments = segments.clone(); + let segments: Vec<_> = segments.collect(); + <#ty as dioxus_router_core::router::FromRouteSegments>::from_route_segments(&segments).map_err(|err| #error_enum_name::#error_enum_varient(#inner_parse_enum::#error_name(err))) + }; + match parsed { + Ok(#name) => { + #parse_children + } + Err(err) => { + errors.push(err); + } + } + } + } + } + } + } +} + +pub fn static_segment_idx(idx: usize) -> Ident { + format_ident!("StaticSegment{}ParseError", idx) +}