瀏覽代碼

implement layouts and outlets

Evan Almloff 2 年之前
父節點
當前提交
fbd333e334

+ 112 - 32
packages/router-core/src/router.rs

@@ -1,7 +1,8 @@
+#![allow(non_snake_case)]
 use crate::history::HistoryProvider;
 use dioxus::prelude::*;
 
-use std::str::FromStr;
+use std::{cell::RefCell, rc::Rc, str::FromStr, sync::Arc};
 
 #[derive(Debug, PartialEq)]
 pub struct RouteParseError<E: std::fmt::Display> {
@@ -18,27 +19,38 @@ impl<E: std::fmt::Display> std::fmt::Display for RouteParseError<E> {
     }
 }
 
-struct Router<R: Routable, H: HistoryProvider>
-where
-    <R as FromStr>::Err: std::fmt::Display,
-{
-    history: H,
-    route: R,
+#[derive(Clone)]
+pub struct Router {
+    subscribers: Rc<RefCell<Vec<ScopeId>>>,
+    update_any: Arc<dyn Fn(ScopeId)>,
+    history: Rc<dyn HistoryProvider>,
+    route: Rc<RefCell<Option<Rc<dyn RouteRenderable>>>>,
 }
 
-impl<R: Routable, H: HistoryProvider> Router<R, H>
-where
-    <R as FromStr>::Err: std::fmt::Display,
-{
-    fn new(history: H) -> Result<Self, R::Err> {
-        let path = history.current_path();
-        Ok(Self {
-            history,
-            route: R::from_str(path.as_str())?,
-        })
+impl Router {
+    fn set_route<R: Routable + 'static>(&self, route: R)
+    where
+        R::Err: std::fmt::Display,
+    {
+        *self.route.borrow_mut() = Some(Rc::new(route));
+        for subscriber in self.subscribers.borrow().iter() {
+            (self.update_any)(*subscriber);
+        }
     }
 }
 
+fn use_router(cx: &ScopeState) -> &Router {
+    use_context(cx).unwrap()
+}
+
+fn use_route(cx: &ScopeState) -> Rc<dyn RouteRenderable> {
+    let router = use_router(cx);
+    cx.use_hook(|| {
+        router.subscribers.borrow_mut().push(cx.scope_id());
+    });
+    router.route.borrow().clone().unwrap()
+}
+
 pub trait FromQuery {
     fn from_query(query: &str) -> Self;
 }
@@ -104,25 +116,93 @@ impl<I: std::iter::FromIterator<String>> FromRouteSegments for I {
 pub struct RouterProps {
     pub current_route: String,
 }
-
-pub trait Routable: FromStr + std::fmt::Display + Clone
+pub trait Routable: std::fmt::Display + std::str::FromStr + 'static
 where
     <Self as FromStr>::Err: std::fmt::Display,
 {
-    fn render(self, cx: &ScopeState) -> Element;
+    fn render<'a>(&self, cx: &'a ScopeState, level: usize) -> Element<'a>;
+}
 
-    fn comp(cx: Scope<RouterProps>) -> Element
-    where
-        Self: 'static,
-    {
-        let router = Self::from_str(&cx.props.current_route);
-        match router {
-            Ok(router) => router.render(cx),
-            Err(err) => {
-                render! {
-                    pre {
-                        "{err}"
-                    }
+trait RoutableFactory {
+    type Err: std::fmt::Display;
+    type Routable: Routable + FromStr<Err = Self::Err>;
+}
+
+impl<R: Routable + FromStr> RoutableFactory for R
+where
+    <R as FromStr>::Err: std::fmt::Display,
+{
+    type Err = <R as FromStr>::Err;
+    type Routable = R;
+}
+
+trait RouteRenderable: std::fmt::Display + 'static {
+    fn render<'a>(&self, cx: &'a ScopeState, level: usize) -> Element<'a>;
+}
+
+impl<R: Routable> RouteRenderable for R
+where
+    <R as FromStr>::Err: std::fmt::Display,
+{
+    fn render<'a>(&self, cx: &'a ScopeState, level: usize) -> Element<'a> {
+        self.render(cx, level)
+    }
+}
+
+#[derive(Clone)]
+struct OutletContext {
+    current_level: usize,
+}
+
+fn use_outlet_context(cx: &ScopeState) -> &OutletContext {
+    let outlet_context = use_context(cx).unwrap();
+    outlet_context
+}
+
+impl OutletContext {
+    fn render(cx: &ScopeState) -> Element<'_> {
+        let outlet = use_outlet_context(cx);
+        let current_level = outlet.current_level;
+        cx.provide_context({
+            OutletContext {
+                current_level: current_level + 1,
+            }
+        });
+
+        use_route(cx).render(cx, current_level)
+    }
+}
+
+pub fn Outlet(cx: Scope) -> Element {
+    OutletContext::render(cx)
+}
+
+pub fn Router<R: Routable, H: HistoryProvider + Default + 'static>(
+    cx: Scope<RouterProps>,
+) -> Element
+where
+    <R as FromStr>::Err: std::fmt::Display,
+{
+    let current_route = R::from_str(&cx.props.current_route);
+    let router = use_context_provider(cx, || Router {
+        subscribers: Rc::default(),
+        update_any: cx.schedule_update_any(),
+        history: Rc::<H>::default(),
+        route: Rc::new(RefCell::new(None)),
+    });
+
+    use_context_provider(cx, || OutletContext { current_level: 1 });
+
+    match current_route {
+        Ok(current_route) => {
+            router.set_route(current_route);
+
+            router.route.borrow().as_ref().unwrap().render(cx, 0)
+        }
+        Err(err) => {
+            render! {
+                pre {
+                    "{err}"
                 }
             }
         }

+ 12 - 2
packages/router-core/tests/macro.rs

@@ -59,12 +59,22 @@ fn Route6(cx: Scope, extra: Vec<String>) -> Element {
     }
 }
 
+#[inline_props]
+fn Nested(cx: Scope, nested: String) -> Element {
+    render! {
+        div{
+            "Nested: {nested:?}"
+        }
+    }
+}
+
 #[rustfmt::skip]
-#[derive(Routable, Clone, Debug, PartialEq)]
+#[routable]
+#[derive(Clone, Debug, PartialEq)]
 enum Route {
     #[route("/(dynamic)" Route1)]
     Route1 { dynamic: String },
-    #[nest("/hello_world")]
+    #[nest("/(nested)" nested { nested: String } Nested)]
         #[route("/" Route2)]
         Route2 {},
         // #[redirect("/(dynamic)/hello_world")]

+ 35 - 0
packages/router-core/tests/nested.rs

@@ -0,0 +1,35 @@
+#![allow(non_snake_case)]
+
+use dioxus::prelude::*;
+use dioxus_router_core::*;
+use dioxus_router_macro::*;
+
+#[inline_props]
+fn Route1(cx: Scope, dynamic: String) -> Element {
+    render! {
+        div{
+            "Route1: {dynamic}"
+        }
+    }
+}
+
+#[inline_props]
+fn Nested(cx: Scope, nested: String) -> Element {
+    render! {
+        div{
+            "Nested: {nested:?}"
+        }
+    }
+}
+
+#[rustfmt::skip]
+#[routable]
+#[derive(Clone, Debug, PartialEq)]
+enum Route {
+    #[nest("/(nested)" nested { nested: String } Nested)]
+        #[route("/(dynamic)" Route1)]
+        Route1 { dynamic: String },
+    #[end_nest]
+    #[route("/(dynamic)" Route1)]
+    Route2 { dynamic: String },
+}

+ 1 - 0
packages/router-macro/Cargo.toml

@@ -18,6 +18,7 @@ proc-macro = true
 syn = { version = "1.0.11", features = ["extra-traits", "full"] }
 quote = "1.0"
 proc-macro2 = "1.0.56"
+slab = "0.4"
 
 [features]
 default = []

+ 154 - 59
packages/router-macro/src/lib.rs

@@ -1,23 +1,25 @@
 extern crate proc_macro;
 
-use nest::Nest;
+use nest::{Layout, Nest};
 use proc_macro::TokenStream;
 use quote::{__private::Span, format_ident, quote, ToTokens};
 use route::Route;
-use route_tree::RouteTreeSegment;
 use syn::{parse_macro_input, Ident};
 
 use proc_macro2::TokenStream as TokenStream2;
 
+use crate::{nest::LayoutId, route_tree::RouteTree};
+
 mod nest;
 mod query;
 mod route;
 mod route_tree;
 mod segment;
 
-#[proc_macro_derive(Routable, attributes(route, nest, end_nest))]
-pub fn derive_routable(input: TokenStream) -> TokenStream {
-    let routes_enum = parse_macro_input!(input as syn::DeriveInput);
+// #[proc_macro_derive(Routable, attributes(route, nest, end_nest))]
+#[proc_macro_attribute]
+pub fn routable(_: TokenStream, input: TokenStream) -> TokenStream {
+    let routes_enum = parse_macro_input!(input as syn::ItemEnum);
 
     let route_enum = match RouteEnum::parse(routes_enum) {
         Ok(route_enum) => route_enum,
@@ -44,59 +46,104 @@ pub fn derive_routable(input: TokenStream) -> TokenStream {
 }
 
 struct RouteEnum {
-    route_name: Ident,
+    vis: syn::Visibility,
+    attrs: Vec<syn::Attribute>,
+    name: Ident,
     routes: Vec<Route>,
+    layouts: Vec<Layout>,
 }
 
 impl RouteEnum {
-    fn parse(input: syn::DeriveInput) -> syn::Result<Self> {
-        let name = &input.ident;
-
-        if let syn::Data::Enum(data) = input.data {
-            let mut routes = Vec::new();
-
-            let mut current_base_route = Vec::new();
-
-            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()?;
-                        match nest {
-                            Nest::Static(s) => current_base_route.push(s),
-                            _ => todo!(),
+    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 nest_stack = Vec::new();
+
+        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 id = layouts.len();
+                            layouts.push(l);
+                            NestRef::Dynamic { id: LayoutId(id) }
                         }
-                    } else if attr.path.is_ident("end_nest") {
-                        current_base_route.pop();
-                    }
+                    };
+                    nest_stack.push(nest_ref);
+                } else if attr.path.is_ident("end_nest") {
+                    nest_stack.pop();
                 }
-
-                let route = Route::parse(current_base_route.join("/"), variant)?;
-                routes.push(route);
             }
 
-            let myself = Self {
-                route_name: name.clone(),
-                routes,
-            };
-
-            Ok(myself)
-        } else {
-            Err(syn::Error::new_spanned(
-                input.clone(),
-                "Routable can only be derived for enums",
-            ))
+            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 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(),
+            routes,
+            layouts,
+        };
+
+        Ok(myself)
     }
 
     fn impl_display(&self) -> TokenStream2 {
         let mut display_match = Vec::new();
 
         for route in &self.routes {
-            display_match.push(route.display_match());
+            display_match.push(route.display_match(&self.layouts));
         }
 
-        let name = &self.route_name;
+        let name = &self.name;
 
         quote! {
             impl std::fmt::Display for #name {
@@ -111,13 +158,14 @@ impl RouteEnum {
     }
 
     fn parse_impl(&self) -> TokenStream2 {
-        let tree = RouteTreeSegment::build(&self.routes);
-        let name = &self.route_name;
+        let tree = RouteTree::new(&self.routes, &self.layouts);
+        let name = &self.name;
 
-        let error_name = format_ident!("{}MatchError", self.route_name);
-        let tokens = tree
-            .into_iter()
-            .map(|t| t.to_tokens(self.route_name.clone(), error_name.clone()));
+        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)
+        });
 
         quote! {
             impl<'a> TryFrom<&'a str> for #name {
@@ -148,10 +196,7 @@ impl RouteEnum {
     }
 
     fn error_name(&self) -> Ident {
-        Ident::new(
-            &(self.route_name.to_string() + "MatchError"),
-            Span::call_site(),
-        )
+        Ident::new(&(self.name.to_string() + "MatchError"), Span::call_site())
     }
 
     fn error_type(&self) -> TokenStream2 {
@@ -164,7 +209,7 @@ impl RouteEnum {
         for route in &self.routes {
             let route_name = &route.route_name;
 
-            let error_name = Ident::new(&format!("{}ParseError", route_name), Span::call_site());
+            let error_name = route.error_ident();
             let route_str = &route.route;
 
             error_variants.push(quote! { #route_name(#error_name) });
@@ -172,6 +217,17 @@ impl RouteEnum {
             type_defs.push(route.error_type());
         }
 
+        for layout in &self.layouts {
+            let layout_name = &layout.layout_name;
+
+            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());
+        }
+
         quote! {
             #(#type_defs)*
 
@@ -192,17 +248,47 @@ impl RouteEnum {
     }
 
     fn routable_impl(&self) -> TokenStream2 {
-        let mut routable_match = Vec::new();
+        let name = &self.name;
 
-        for route in &self.routes {
-            routable_match.push(route.routable_match());
+        let mut layers = Vec::new();
+
+        loop {
+            let index = layers.len();
+            let mut routable_match = Vec::new();
+
+            // Collect all routes that match the current layer
+            for route in &self.routes {
+                if let Some(matched) = route.routable_match(&self.layouts, index) {
+                    routable_match.push(matched);
+                }
+            }
+
+            // All routes are exhausted
+            if routable_match.is_empty() {
+                break;
+            }
+
+            layers.push(quote! {
+                #(#routable_match)*
+            });
         }
 
+        let index_iter = 0..layers.len();
+
         quote! {
-            impl Routable for Route {
-                fn render<'a>(self, cx: &'a ScopeState) -> Element<'a> {
-                    match self {
-                        #(#routable_match)*
+            impl Routable for #name where Self: Clone {
+                fn render<'a>(&self, cx: &'a ScopeState, level: usize) -> Element<'a> {
+                    let myself = self.clone();
+                    match level {
+                        #(
+                            #index_iter => {
+                                match myself {
+                                    #layers
+                                    _ => panic!("Route::render called with invalid level {}", level),
+                                }
+                            },
+                        )*
+                        _ => panic!("Route::render called with invalid level {}", level),
                     }
                 }
             }
@@ -213,8 +299,17 @@ impl RouteEnum {
 impl ToTokens for RouteEnum {
     fn to_tokens(&self, tokens: &mut quote::__private::TokenStream) {
         let routes = &self.routes;
+        let vis = &self.vis;
+        let name = &self.name;
+        let attrs = &self.attrs;
+        let variants = routes.iter().map(|r| r.variant(&self.layouts));
 
         tokens.extend(quote!(
+            #(#attrs)*
+            #vis enum #name {
+                #(#variants),*
+            }
+
             #[path = "pages"]
             mod pages {
                 #(#routes)*

+ 129 - 40
packages/router-macro/src/nest.rs

@@ -1,7 +1,8 @@
-use quote::format_ident;
-use syn::{parse::Parse, Ident, LitStr, Variant};
+use proc_macro2::TokenStream;
+use quote::{format_ident, quote};
+use syn::{parse::Parse, Ident, LitStr};
 
-use crate::segment::RouteSegment;
+use crate::segment::{parse_route_segments, RouteSegment};
 
 pub enum Nest {
     Static(String),
@@ -12,28 +13,45 @@ 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 route.value().contains('(') {
+        if !input.is_empty() || is_dynamic {
             // Then parse the layout name
             let _ = input.parse::<syn::Token![,]>();
-            let layout_name: Ident = input.parse()?;
+            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: Variant = input.parse()?;
+            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.ident.to_string()));
+                .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(),
-                route_segments: Vec::new(),
+                segments: route_segments,
                 layout_name,
                 comp,
                 props_name,
+                layout_fields,
             }))
         } else {
             Ok(Self::Static(route.value()))
@@ -41,40 +59,111 @@ impl Parse for Nest {
     }
 }
 
-struct Layout {
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct LayoutId(pub usize);
+
+#[derive(Debug)]
+pub struct Layout {
     pub route: String,
-    pub route_segments: Vec<RouteSegment>,
+    pub segments: Vec<RouteSegment>,
     pub layout_name: Ident,
-    pub comp: Variant,
+    pub layout_fields: syn::FieldsNamed,
+    pub comp: Ident,
     pub props_name: Ident,
 }
 
-// #[derive(Clone, Debug, PartialEq, Routable)]
-// enum Route {
-//     // Each Variant is a route with a linked component, dynamic segments are defined with the syntax: (name) and the type is inferred from the field type. The type must implement FromStr
-//     #[route("/(dynamic)" Component1)]
-//     Route1 { dynamic: usize },
-//     // You can nest routes which makes all routes in the block relative to a parent route. Nested routes are flattened into the parent enum
-//     // Nest accepts a optional layout component. The layout component that wraps all children and renders them where the Outlet component is found. It can accept parameters from the nested route, just like a normal route
-//     #[nest("/(dynamic)" root_dynamic_segment Component { dynamic: String })]
-//         // If the component is not specified, the component is assumed to be at the path of the route (in this case /pages/hello_world.rs or /pages/hello_world/index.rs)
-//         #[route("/")]
-//         // You can opt out of a parent Layout
-//         #[layout(!root_dynamic_segment)]
-//         Route2 {
-//             // implicitly adds
-//             // root_dynamic_segment: ComponentProps,
-//         },
-//     #[end_nest]
-//     // Queries are defined with the syntax: ?(name) and the type is inferred from the field type. The type must implement From<&str> (not FromStr because the query parsing must be infallible). The query part of the url is not included in the route path for file based routing. (in this case /pages/takes_query.rs or /pages/takes_query/index.rs)
-//     #[route("/takes_query?(dynamic)")]
-//     Route3 { dynamic: u32 },
-//     // Redirects are defined with the redirect attribute
-//     #[redirect("/old_hello_world/(dynamic)")]
-//     #[route("/hello_world/(dynamic)")]
-//     Route4 { dynamic: u32 },
-//     // members that can be parsed from all trailing segments are defined with the syntax: (...name) and the type is inferred from the field type. The type must implement FromSegments.
-//     // Because this route is defined after Route3, it will only be matched if Route3 does not match and it will act as a fallback
-//     #[route("/(...number2)")]
-//     Route5 { number1: u32, number2: u32 },
-// }
+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()));
+    }
+
+    pub fn dynamic_segments(&self) -> impl Iterator<Item = TokenStream> + '_ {
+        self.segments
+            .iter()
+            .filter_map(|seg| seg.name())
+            .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());
+
+        quote! {
+            {
+                #(#write_segments)*
+            }
+        }
+    }
+
+    pub fn error_ident(&self) -> Ident {
+        format_ident!("{}LayoutParseError", self.layout_name)
+    }
+
+    pub fn error_type(&self) -> TokenStream {
+        let error_name = self.error_ident();
+
+        let mut error_variants = Vec::new();
+        let mut display_match = Vec::new();
+
+        for (i, segment) in self.segments.iter().enumerate() {
+            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)? });
+                }
+                RouteSegment::Dynamic(ident, ty) => {
+                    let missing_error = segment.missing_error_name().unwrap();
+                    error_variants.push(quote! { #error_name(<#ty as dioxus_router_core::router::FromRouteSegment>::Err) });
+                    display_match.push(quote! { Self::#error_name(err) => write!(f, "Dynamic segment '({}:{})' did not match: {}", stringify!(#ident), stringify!(#ty), err)? });
+                    error_variants.push(quote! { #missing_error });
+                    display_match.push(quote! { Self::#missing_error => write!(f, "Dynamic segment '({}:{})' was missing", stringify!(#ident), stringify!(#ty))? });
+                }
+                _ => todo!(),
+            }
+        }
+
+        quote! {
+            #[allow(non_camel_case_types)]
+            #[derive(Debug, PartialEq)]
+            pub enum #error_name {
+                #(#error_variants,)*
+            }
+
+            impl std::fmt::Display for #error_name {
+                fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+                    match self {
+                        #(#display_match,)*
+                    }
+                    Ok(())
+                }
+            }
+        }
+    }
+
+    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)
+        }
+    }
+}

+ 4 - 0
packages/router-macro/src/query.rs

@@ -28,4 +28,8 @@ impl QuerySegment {
     pub fn name(&self) -> Ident {
         self.ident.clone()
     }
+
+    pub fn ty(&self) -> &Type {
+        &self.ty
+    }
 }

+ 120 - 27
packages/router-macro/src/route.rs

@@ -5,6 +5,8 @@ use syn::{Ident, LitStr};
 
 use proc_macro2::TokenStream as TokenStream2;
 
+use crate::nest::Layout;
+use crate::nest::LayoutId;
 use crate::query::QuerySegment;
 use crate::segment::parse_route_segments;
 use crate::segment::RouteSegment;
@@ -40,24 +42,30 @@ pub struct Route {
     pub comp_name: Ident,
     pub props_name: Ident,
     pub route: String,
-    pub route_segments: Vec<RouteSegment>,
+    pub segments: Vec<RouteSegment>,
     pub query: Option<QuerySegment>,
+    pub layouts: Vec<LayoutId>,
+    pub variant: syn::Variant,
 }
 
 impl Route {
-    pub fn parse(root_route: String, input: syn::Variant) -> syn::Result<Self> {
-        let route_attr = input
+    pub fn parse(
+        root_route: String,
+        layouts: Vec<LayoutId>,
+        variant: syn::Variant,
+    ) -> syn::Result<Self> {
+        let route_attr = variant
             .attrs
             .iter()
             .find(|attr| attr.path.is_ident("route"))
             .ok_or_else(|| {
                 syn::Error::new_spanned(
-                    input.clone(),
+                    variant.clone(),
                     "Routable variants must have a #[route(...)] attribute",
                 )
             })?;
 
-        let route_name = input.ident.clone();
+        let route_name = variant.ident.clone();
         let args = route_attr.parse_args::<RouteArgs>()?;
         let route = root_route + &args.route.value();
         let file_based = args.comp_name.is_none();
@@ -68,53 +76,115 @@ impl Route {
             .props_name
             .unwrap_or_else(|| format_ident!("{}Props", comp_name));
 
-        let (route_segments, query) = parse_route_segments(&input, &route)?;
+        let named_fields = match &variant.fields {
+            syn::Fields::Named(fields) => fields,
+            _ => {
+                return Err(syn::Error::new_spanned(
+                    variant.clone(),
+                    "Routable variants must have named fields",
+                ))
+            }
+        };
+
+        let (route_segments, query) = parse_route_segments(&variant.ident, named_fields, &route)?;
 
         Ok(Self {
             comp_name,
             props_name,
             route_name,
-            route_segments,
+            segments: route_segments,
             route,
             file_based,
             query,
+            layouts,
+            variant,
         })
     }
 
-    pub fn display_match(&self) -> TokenStream2 {
+    pub fn display_match(&self, layouts: &[Layout]) -> TokenStream2 {
         let name = &self.route_name;
-        let dynamic_segments = self.dynamic_segments();
-        let write_segments = self.route_segments.iter().map(|s| s.write_segment());
+        let dynamic_segments = self.dynamic_segments(layouts);
+        let write_layouts = self.layouts.iter().map(|id| layouts[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());
 
         quote! {
             Self::#name { #(#dynamic_segments,)* } => {
+                #(#write_layouts)*
                 #(#write_segments)*
                 #write_query
             }
         }
     }
 
-    pub fn routable_match(&self) -> TokenStream2 {
+    pub fn routable_match(&self, layouts: &[Layout], index: usize) -> Option<TokenStream2> {
         let name = &self.route_name;
-        let dynamic_segments: Vec<_> = self.dynamic_segments().collect();
-        let props_name = &self.props_name;
-        let comp_name = &self.comp_name;
+        let dynamic_segments = self.dynamic_segments(layouts);
 
-        quote! {
-            Self::#name { #(#dynamic_segments,)* } => {
-                let comp = #props_name { #(#dynamic_segments,)* };
-                let cx = cx.bump().alloc(Scoped {
-                    props: cx.bump().alloc(comp),
-                    scope: cx,
-                });
-                #comp_name(cx)
+        match index.cmp(&self.layouts.len()) {
+            std::cmp::Ordering::Less => {
+                let layout = self.layouts[index];
+                let render_layout = layouts[layout.0].routable_match();
+                // This is a layout
+                Some(quote! {
+                    #[allow(unused)]
+                    Self::#name { #(#dynamic_segments,)* } => {
+                        #render_layout
+                    }
+                })
+            }
+            std::cmp::Ordering::Equal => {
+                let dynamic_segments_from_route = self.dynamic_segments_from_route();
+                let props_name = &self.props_name;
+                let comp_name = &self.comp_name;
+                // This is the final route
+                Some(quote! {
+                    #[allow(unused)]
+                    Self::#name { #(#dynamic_segments,)* } => {
+                        let comp = #props_name { #(#dynamic_segments_from_route,)* };
+                        let cx = cx.bump().alloc(Scoped {
+                            props: cx.bump().alloc(comp),
+                            scope: cx,
+                        });
+                        #comp_name(cx)
+                    }
+                })
             }
+            _ => None,
         }
     }
 
-    fn dynamic_segments(&self) -> impl Iterator<Item = TokenStream2> + '_ {
-        let segments = self.route_segments.iter().filter_map(|seg| {
+    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_from_route(&self) -> impl Iterator<Item = TokenStream2> + '_ {
+        let segments = self.segments.iter().filter_map(|seg| {
             seg.name().map(|name| {
                 quote! {
                     #name
@@ -135,8 +205,21 @@ impl Route {
         segments.chain(query)
     }
 
-    pub fn construct(&self, enum_name: Ident) -> TokenStream2 {
-        let segments = self.dynamic_segments();
+    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);
         let name = &self.route_name;
 
         quote! {
@@ -156,7 +239,7 @@ impl Route {
         let mut error_variants = Vec::new();
         let mut display_match = Vec::new();
 
-        for (i, segment) in self.route_segments.iter().enumerate() {
+        for (i, segment) in self.segments.iter().enumerate() {
             let error_name = segment.error_name(i);
             match segment {
                 RouteSegment::Static(index) => {
@@ -205,6 +288,16 @@ impl Route {
             None => quote! {},
         }
     }
+
+    pub fn variant(&self, layouts: &[Layout]) -> TokenStream2 {
+        let name = &self.route_name;
+        let segments = self.dynamic_segments(layouts);
+        let types = self.dynamic_segment_types(layouts);
+
+        quote! {
+            #name { #(#segments: #types,)* }
+        }
+    }
 }
 
 impl ToTokens for Route {

+ 321 - 86
packages/router-macro/src/route_tree.rs

@@ -1,94 +1,283 @@
 use proc_macro2::TokenStream;
 use quote::quote;
+use slab::Slab;
 use syn::Ident;
 
 use crate::{
+    nest::Layout,
     route::Route,
     segment::{static_segment_idx, RouteSegment},
 };
 
-// First deduplicate the routes by the static part of the route
-#[derive(Debug)]
-pub enum RouteTreeSegment<'a> {
-    Static {
-        index: usize,
-        segment: &'a str,
-        children: Vec<RouteTreeSegment<'a>>,
-        from_route: &'a Route,
-    },
-    Dynamic(&'a Route),
+#[derive(Debug, Clone, Default)]
+pub struct RouteTree<'a> {
+    pub roots: Vec<usize>,
+    entries: Slab<RouteTreeSegmentData<'a>>,
 }
 
-impl<'a> RouteTreeSegment<'a> {
-    pub fn build(routes: &'a [Route]) -> Vec<RouteTreeSegment<'a>> {
-        let routes = routes.iter().map(PartialRoute::new).collect();
-        Self::construct(routes)
+impl<'a> RouteTree<'a> {
+    pub fn get(&self, index: usize) -> Option<&RouteTreeSegmentData<'a>> {
+        self.entries.get(index)
+    }
+
+    pub fn get_mut(&mut self, element: usize) -> Option<&mut RouteTreeSegmentData<'a>> {
+        self.entries.get_mut(element)
+    }
+
+    fn sort_children(&mut self) {
+        let mut old_roots = self.roots.clone();
+        self.sort_ids(&mut old_roots);
+        self.roots = old_roots;
+
+        for id in self.roots.clone() {
+            self.sort_children_of_id(id);
+        }
+    }
+
+    fn sort_ids(&self, ids: &mut [usize]) {
+        ids.sort_by_key(|&seg| {
+            let seg = self.get(seg).unwrap();
+            match seg {
+                RouteTreeSegmentData::Static { .. } => 0,
+                RouteTreeSegmentData::Layout { .. } => 1,
+                RouteTreeSegmentData::Route(_) => 1,
+            }
+        });
     }
 
-    fn construct(routes: Vec<PartialRoute<'a>>) -> Vec<RouteTreeSegment<'a>> {
-        let mut static_segments = Vec::new();
-        let mut dyn_segments = Vec::new();
+    fn sort_children_of_id(&mut self, id: usize) {
+        // Sort segments so that all static routes are checked before dynamic routes
+        let mut children = self.children(id);
+
+        self.sort_ids(&mut children);
+
+        if let Some(old) = self.try_children_mut(id) {
+            old.clone_from(&children)
+        }
+
+        for id in children {
+            self.sort_children_of_id(id);
+        }
+    }
 
-        // Add all routes we can to the tree
+    fn children(&self, element: usize) -> Vec<usize> {
+        let element = self.entries.get(element).unwrap();
+        match element {
+            RouteTreeSegmentData::Static { children, .. } => children.clone(),
+            RouteTreeSegmentData::Layout { children, .. } => children.clone(),
+            _ => Vec::new(),
+        }
+    }
+
+    fn try_children_mut(&mut self, element: usize) -> Option<&mut Vec<usize>> {
+        let element = self.entries.get_mut(element).unwrap();
+        match element {
+            RouteTreeSegmentData::Static { children, .. } => Some(children),
+            RouteTreeSegmentData::Layout { 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")
+    }
+
+    pub fn new(routes: &'a [Route], layouts: &'a [Layout]) -> Self {
+        let routes = routes
+            .iter()
+            .map(|route| RouteIter::new(route, layouts))
+            .collect::<Vec<_>>();
+
+        let mut myself = Self::default();
+        myself.roots = myself.construct(routes);
+        myself.sort_children();
+
+        myself
+    }
+
+    pub fn construct(&mut self, routes: Vec<RouteIter<'a>>) -> Vec<usize> {
+        let mut segments = Vec::new();
+
+        // Add all routes to the tree
         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();
+
+                // Add all static segments of the layout
+                'o: for (index, segment) in segments_iter.enumerate() {
+                    match segment {
+                        RouteSegment::Static(segment) => {
+                            // Check if the segment already exists
+                            {
+                                // Either look for the segment in the current route or in the static segments
+                                let segments = current_route
+                                    .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
+                                    {
+                                        if s == segment {
+                                            // If it does, just update the current route
+                                            current_route = children.last().cloned();
+                                            continue 'o;
+                                        }
+                                    }
+                                }
+                            }
+
+                            let static_segment = RouteTreeSegmentData::Static {
+                                segment,
+                                children: Vec::new(),
+                                error_variant: route.error_variant(),
+                                index,
+                            };
+
+                            // If it doesn't, add the segment to the current route
+                            let static_segment = self.entries.insert(static_segment);
+
+                            let current_children = current_route
+                                .map(|id| self.children_mut(id))
+                                .unwrap_or_else(|| &mut segments);
+                            current_children.push(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")
+                        }
+                    }
+                }
+
+                // Add the layout to the current route
+                let layout = RouteTreeSegmentData::Layout {
+                    layout,
+                    children: Vec::new(),
+                };
+
+                let layout = self.entries.insert(layout);
+                let segments = match current_route.and_then(|id| self.get_mut(id)) {
+                    Some(RouteTreeSegmentData::Static { children, .. }) => children,
+                    Some(_) => unreachable!(),
+                    None => &mut segments,
+                };
+                segments.push(layout);
+
+                // Update the current route
+                current_route = segments.last().cloned();
+            }
+
             match route.next_static_segment() {
                 // If there is a static segment, check if it already exists in the tree
                 Some((i, segment)) => {
-                    let found = static_segments.iter_mut().find_map(|seg| match seg {
-                        RouteTreeSegment::Static {
-                            segment: s,
-                            children,
-                            ..
-                        } => (s == &segment).then_some(children),
-                        _ => None,
+                    let current_children = current_route
+                        .map(|id| self.children(id))
+                        .unwrap_or_else(|| segments.clone());
+                    let found = current_children.iter().find_map(|&id| {
+                        let seg = self.get(id).unwrap();
+                        match seg {
+                            RouteTreeSegmentData::Static { segment: s, .. } => {
+                                (s == &segment).then_some(id)
+                            }
+                            _ => None,
+                        }
                     });
 
                     match found {
-                        Some(children) => {
-                            // If it does, add the route to the children of the segment
-                            children.append(&mut RouteTreeSegment::construct(vec![route]))
+                        Some(id) => {
+                            // If it exists, add the route to the children of the segment
+                            let new_children = self.construct(vec![route]);
+                            self.children_mut(id).extend(new_children.into_iter());
                         }
                         None => {
-                            // If it doesn't, add the route as a new segment
-                            static_segments.push(RouteTreeSegment::Static {
+                            // If it doesn't exist, add the route as a new segment
+                            let data = RouteTreeSegmentData::Static {
                                 segment,
-                                from_route: route.route,
-                                children: RouteTreeSegment::construct(vec![route]),
+                                error_variant: route.error_variant(),
+                                children: self.construct(vec![route]),
                                 index: i,
-                            })
+                            };
+                            let id = self.entries.insert(data);
+                            let current_children_mut = current_route
+                                .map(|id| self.children_mut(id))
+                                .unwrap_or_else(|| &mut segments);
+                            current_children_mut.push(id);
                         }
                     }
                 }
-                // If there is no static segment, add the route to the dynamic routes
+                // If there is no static segment, add the route to the current_route
                 None => {
-                    dyn_segments.push(RouteTreeSegment::Dynamic(route.route));
+                    let id = self
+                        .entries
+                        .insert(RouteTreeSegmentData::Route(route.route));
+                    let current_children_mut = current_route
+                        .map(|id| self.children_mut(id))
+                        .unwrap_or_else(|| &mut segments);
+                    current_children_mut.push(id);
                 }
             }
         }
 
-        // All static routes are checked before dynamic routes
-        static_segments.append(&mut dyn_segments);
-
-        static_segments
+        segments
     }
 }
 
-impl<'a> RouteTreeSegment<'a> {
-    pub fn to_tokens(&self, enum_name: syn::Ident, error_enum_name: syn::Ident) -> TokenStream {
+#[derive(Debug, Clone)]
+pub struct StaticErrorVariant {
+    varient_parse_error: Ident,
+    enum_varient: Ident,
+}
+
+// First deduplicate the routes by the static part of the route
+#[derive(Debug, Clone)]
+pub enum RouteTreeSegmentData<'a> {
+    Static {
+        segment: &'a str,
+        error_variant: StaticErrorVariant,
+        index: usize,
+        children: Vec<usize>,
+    },
+    Layout {
+        layout: &'a Layout,
+        children: Vec<usize>,
+    },
+    Route(&'a Route),
+}
+
+impl<'a> RouteTreeSegmentData<'a> {
+    pub fn to_tokens(
+        &self,
+        tree: &RouteTree,
+        enum_name: syn::Ident,
+        error_enum_name: syn::Ident,
+        layouts: &[Layout],
+    ) -> TokenStream {
         match self {
-            RouteTreeSegment::Static {
+            RouteTreeSegmentData::Static {
                 segment,
                 children,
                 index,
-                from_route,
+                error_variant:
+                    StaticErrorVariant {
+                        varient_parse_error,
+                        enum_varient,
+                    },
             } => {
-                let varient_parse_error = from_route.error_ident();
-                let enum_varient = &from_route.route_name;
                 let error_ident = static_segment_idx(*index);
 
-                let children = children
-                    .iter()
-                    .map(|child| child.to_tokens(enum_name.clone(), error_enum_name.clone()));
+                let children = children.iter().map(|child| {
+                    let child = tree.get(*child).unwrap();
+                    child.to_tokens(tree, enum_name.clone(), error_enum_name.clone(), layouts)
+                });
 
                 quote! {
                     {
@@ -104,52 +293,22 @@ impl<'a> RouteTreeSegment<'a> {
                     }
                 }
             }
-            RouteTreeSegment::Dynamic(route) => {
+            RouteTreeSegmentData::Route(route) => {
                 // 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 = route.error_ident();
                 let enum_varient = &route.route_name;
 
                 let route_segments = route
-                    .route_segments
+                    .segments
                     .iter()
                     .enumerate()
                     .skip_while(|(_, seg)| matches!(seg, RouteSegment::Static(_)));
 
-                fn print_route_segment<'a, I: Iterator<Item = (usize, &'a RouteSegment)>>(
-                    mut s: std::iter::Peekable<I>,
-                    sucess_tokens: TokenStream,
-                    error_enum_name: &Ident,
-                    enum_varient: &Ident,
-                    varient_parse_error: &Ident,
-                ) -> TokenStream {
-                    if let Some((i, route)) = s.next() {
-                        let children = print_route_segment(
-                            s,
-                            sucess_tokens,
-                            error_enum_name,
-                            enum_varient,
-                            varient_parse_error,
-                        );
-
-                        route.try_parse(
-                            i,
-                            error_enum_name,
-                            enum_varient,
-                            varient_parse_error,
-                            children,
-                        )
-                    } else {
-                        quote! {
-                            #sucess_tokens
-                        }
-                    }
-                }
-
-                let construct_variant = route.construct(enum_name);
+                let construct_variant = route.construct(enum_name, layouts);
                 let parse_query = route.parse_query();
 
                 let insure_not_trailing = route
-                    .route_segments
+                    .segments
                     .last()
                     .map(|seg| !matches!(seg, RouteSegment::CatchAll(_, _)))
                     .unwrap_or(true);
@@ -169,6 +328,63 @@ impl<'a> RouteTreeSegment<'a> {
                     &varient_parse_error,
                 )
             }
+            Self::Layout { layout, 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 route_segments = layout
+                    .segments
+                    .iter()
+                    .enumerate()
+                    .skip_while(|(_, seg)| matches!(seg, RouteSegment::Static(_)));
+
+                let parse_children = children
+                    .iter()
+                    .map(|child| {
+                        let child = tree.get(*child).unwrap();
+                        child.to_tokens(tree, enum_name.clone(), error_enum_name.clone(), layouts)
+                    })
+                    .collect();
+
+                print_route_segment(
+                    route_segments.peekable(),
+                    parse_children,
+                    &error_enum_name,
+                    enum_varient,
+                    &varient_parse_error,
+                )
+            }
+        }
+    }
+}
+
+fn print_route_segment<'a, I: Iterator<Item = (usize, &'a RouteSegment)>>(
+    mut s: std::iter::Peekable<I>,
+    sucess_tokens: TokenStream,
+    error_enum_name: &Ident,
+    enum_varient: &Ident,
+    varient_parse_error: &Ident,
+) -> TokenStream {
+    if let Some((i, route)) = s.next() {
+        let children = print_route_segment(
+            s,
+            sucess_tokens,
+            error_enum_name,
+            enum_varient,
+            varient_parse_error,
+        );
+
+        route.try_parse(
+            i,
+            error_enum_name,
+            enum_varient,
+            varient_parse_error,
+            children,
+        )
+    } else {
+        quote! {
+            #sucess_tokens
         }
     }
 }
@@ -212,22 +428,34 @@ fn return_constructed(
     }
 }
 
-struct PartialRoute<'a> {
+pub struct RouteIter<'a> {
     route: &'a Route,
+    layouts: &'a [Layout],
+    layout_index: usize,
     static_segment_index: usize,
 }
 
-impl<'a> PartialRoute<'a> {
-    fn new(route: &'a Route) -> Self {
+impl<'a> RouteIter<'a> {
+    fn new(route: &'a Route, layouts: &'a [Layout]) -> Self {
         Self {
             route,
+            layouts,
+            layout_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_static_segment(&mut self) -> Option<(usize, &'a str)> {
         let idx = self.static_segment_index;
-        let segment = self.route.route_segments.get(idx)?;
+        let segment = self.route.segments.get(idx)?;
         match segment {
             RouteSegment::Static(segment) => {
                 self.static_segment_index += 1;
@@ -236,4 +464,11 @@ impl<'a> PartialRoute<'a> {
             _ => None,
         }
     }
+
+    fn error_variant(&self) -> StaticErrorVariant {
+        StaticErrorVariant {
+            varient_parse_error: self.route.error_ident(),
+            enum_varient: self.route.route_name.clone(),
+        }
+    }
 }

+ 18 - 9
packages/router-macro/src/segment.rs

@@ -1,5 +1,5 @@
 use quote::{format_ident, quote};
-use syn::{Ident, Type, Variant};
+use syn::{Ident, Type};
 
 use proc_macro2::{Span, TokenStream as TokenStream2};
 
@@ -21,6 +21,14 @@ 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)?; },
@@ -123,7 +131,8 @@ pub fn static_segment_idx(idx: usize) -> Ident {
 }
 
 pub fn parse_route_segments(
-    varient: &Variant,
+    route_name: &Ident,
+    fields: &syn::FieldsNamed,
     route: &str,
 ) -> syn::Result<(Vec<RouteSegment>, Option<QuerySegment>)> {
     let mut route_segments = Vec::new();
@@ -138,7 +147,7 @@ pub fn parse_route_segments(
     let first = iterator.next();
     if first != Some("") {
         return Err(syn::Error::new_spanned(
-            varient,
+            route_name,
             format!(
                 "Routes should start with /. Error found in the route '{}'",
                 route
@@ -156,7 +165,7 @@ pub fn parse_route_segments(
                 segment[1..segment.len() - 1].to_string()
             };
 
-            let field = varient.fields.iter().find(|field| match field.ident {
+            let field = fields.named.iter().find(|field| match field.ident {
                 Some(ref field_ident) => *field_ident == ident,
                 None => false,
             });
@@ -165,10 +174,10 @@ pub fn parse_route_segments(
                 field.ty.clone()
             } else {
                 return Err(syn::Error::new_spanned(
-                    varient,
+                    route_name,
                     format!(
                         "Could not find a field with the name '{}' in the variant '{}'",
-                        ident, varient.ident
+                        ident, route_name
                     ),
                 ));
             };
@@ -202,7 +211,7 @@ pub fn parse_route_segments(
         Some(query) => {
             if query.starts_with('(') && query.ends_with(')') {
                 let query_ident = Ident::new(&query[1..query.len() - 1], Span::call_site());
-                let field = varient.fields.iter().find(|field| match field.ident {
+                let field = fields.named.iter().find(|field| match field.ident {
                     Some(ref field_ident) => field_ident == &query_ident,
                     None => false,
                 });
@@ -211,10 +220,10 @@ pub fn parse_route_segments(
                     field.ty.clone()
                 } else {
                     return Err(syn::Error::new_spanned(
-                        varient,
+                        route_name,
                         format!(
                             "Could not find a field with the name '{}' in the variant '{}'",
-                            query_ident, varient.ident
+                            query_ident, route_name
                         ),
                     ));
                 };