dioxus_router_macro/
lib.rs

1#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
2#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
3
4extern crate proc_macro;
5
6use layout::Layout;
7use nest::{Nest, NestId};
8use proc_macro::TokenStream;
9use proc_macro2::Span;
10use quote::{format_ident, quote, ToTokens};
11use redirect::Redirect;
12use route::{Route, RouteType};
13use segment::RouteSegment;
14use syn::{parse::ParseStream, parse_macro_input, Ident, Token, Type};
15
16use proc_macro2::TokenStream as TokenStream2;
17
18use crate::{layout::LayoutId, route_tree::ParseRouteTree};
19
20mod hash;
21mod layout;
22mod nest;
23mod query;
24mod redirect;
25mod route;
26mod route_tree;
27mod segment;
28
29/// Derives the Routable trait for an enum of routes
30///
31/// Each variant must:
32/// 1. Be struct-like with {}'s
33/// 2. Contain all of the dynamic parameters of the current and nested routes
34/// 3. Have a `#[route("route")]` attribute
35///
36/// Route Segments:
37/// 1. Static Segments: "/static"
38/// 2. Dynamic Segments: "/:dynamic" (where dynamic has a type that is FromStr in all child Variants)
39/// 3. Catch all Segments: "/:..segments" (where segments has a type that is FromSegments in all child Variants)
40/// 4. Query Segments: "/?:..query" (where query has a type that is FromQuery in all child Variants) or "/?:query&:other_query" (where query and other_query has a type that is FromQueryArgument in all child Variants)
41///
42/// Routes are matched:
43/// 1. By there specificity this order: Query Routes ("/?:query"), Static Routes ("/route"), Dynamic Routes ("/:route"), Catch All Routes ("/:..route")
44/// 2. By the order they are defined in the enum
45///
46/// All features:
47/// ```rust
48/// use dioxus::prelude::*;
49///
50/// #[rustfmt::skip]
51/// #[derive(Clone, Debug, PartialEq, Routable)]
52/// enum Route {
53///     // Define routes with the route macro. If the name of the component is not the same as the variant, you can specify it as the second parameter
54///     #[route("/", IndexComponent)]
55///     Index {},
56///     // Nests with parameters have types taken from child routes
57///     // Everything inside the nest has the added parameter `user_id: usize`
58///     #[nest("/user/:user_id")]
59///         // All children of layouts will be rendered inside the Outlet in the layout component
60///         // Creates a Layout UserFrame that has the parameter `user_id: usize`
61///         #[layout(UserFrame)]
62///             // If there is a component with the name Route1, you do not need to pass in the component name
63///             #[route("/:dynamic?:query")]
64///             Route1 {
65///                 // The type is taken from the first instance of the dynamic parameter
66///                 user_id: usize,
67///                 dynamic: usize,
68///                 query: String,
69///             },
70///             #[route("/hello_world")]
71///             // You can opt out of the layout by using the `!` prefix
72///             #[layout(!UserFrame)]
73///             Route2 { user_id: usize },
74///         // End layouts with #[end_layout]
75///         #[end_layout]
76///     // End nests with #[end_nest]
77///     #[end_nest]
78///     // Redirects take a path and a function that takes the parameters from the path and returns a new route
79///     #[redirect("/:id/user", |id: usize| Route::Route3 { dynamic: id.to_string()})]
80///     #[route("/:dynamic")]
81///     Route3 { dynamic: String },
82/// }
83/// # #[component]
84/// # fn Route1(user_id: usize, dynamic: usize, query: String) -> Element { VNode::empty() }
85/// # #[component]
86/// # fn Route2(user_id: usize) -> Element { VNode::empty() }
87/// # #[component]
88/// # fn Route3(dynamic: String) -> Element { VNode::empty() }
89/// # #[component]
90/// # fn UserFrame(user_id: usize) -> Element { VNode::empty() }
91/// # #[component]
92/// # fn IndexComponent() -> Element { VNode::empty() }
93/// ```
94///
95/// # `#[route("path", component)]`
96///
97/// The `#[route]` attribute is used to define a route. It takes up to 2 parameters:
98/// - `path`: The path to the enum variant (relative to the parent nest)
99/// - (optional) `component`: The component to render when the route is matched. If not specified, the name of the variant is used
100///
101/// Routes are the most basic attribute. They allow you to define a route and the component to render when the route is matched. The component must take all dynamic parameters of the route and all parent nests.
102/// The next variant will be tied to the component. If you link to that variant, the component will be rendered.
103///
104/// ```rust
105/// use dioxus::prelude::*;
106///
107/// #[derive(Clone, Debug, PartialEq, Routable)]
108/// enum Route {
109///     // Define routes that renders the IndexComponent
110///     // The Index component will be rendered when the route is matched (e.g. when the user navigates to /)
111///     #[route("/", Index)]
112///     Index {},
113/// }
114/// # #[component]
115/// # fn Index() -> Element { VNode::empty() }
116/// ```
117///
118/// # `#[redirect("path", function)]`
119///
120/// The `#[redirect]` attribute is used to define a redirect. It takes 2 parameters:
121/// - `path`: The path to the enum variant (relative to the parent nest)
122/// - `function`: A function that takes the parameters from the path and returns a new route
123///
124/// ```rust
125/// use dioxus::prelude::*;
126///
127/// #[derive(Clone, Debug, PartialEq, Routable)]
128/// enum Route {
129///     // Redirects the /:id route to the Index route
130///     #[redirect("/:id", |id: usize| Route::Index {})]
131///     #[route("/", Index)]
132///     Index {},
133/// }
134/// # #[component]
135/// # fn Index() -> Element { VNode::empty() }
136/// ```
137///
138/// Redirects allow you to redirect a route to another route. The function must take all dynamic parameters of the route and all parent nests.
139///
140/// # `#[nest("path")]`
141///
142/// The `#[nest]` attribute is used to define a nest. It takes 1 parameter:
143/// - `path`: The path to the nest (relative to the parent nest)
144///
145/// Nests effect all nests, routes and redirects defined until the next `#[end_nest]` attribute. All children of nests are relative to the nest route and must include all dynamic parameters of the nest.
146///
147/// ```rust
148/// use dioxus::prelude::*;
149///
150/// #[derive(Clone, Debug, PartialEq, Routable)]
151/// enum Route {
152///     // Nests all child routes in the /blog route
153///     #[nest("/blog")]
154///         // This is at /blog/:id
155///         #[redirect("/:id", |id: usize| Route::Index {})]
156///         // This is at /blog
157///         #[route("/", Index)]
158///         Index {},
159/// }
160/// # #[component]
161/// # fn Index() -> Element { VNode::empty() }
162/// ```
163///
164/// # `#[end_nest]`
165///
166/// The `#[end_nest]` attribute is used to end a nest. It takes no parameters.
167///
168/// ```rust
169/// use dioxus::prelude::*;
170///
171/// #[derive(Clone, Debug, PartialEq, Routable)]
172/// enum Route {
173///     #[nest("/blog")]
174///         // This is at /blog/:id
175///         #[redirect("/:id", |id: usize| Route::Index {})]
176///         // This is at /blog
177///         #[route("/", Index)]
178///         Index {},
179///     // Ends the nest
180///     #[end_nest]
181///     // This is at /
182///     #[route("/")]
183///     Home {},
184/// }
185/// # #[component]
186/// # fn Index() -> Element { VNode::empty() }
187/// # #[component]
188/// # fn Home() -> Element { VNode::empty() }
189/// ```
190///
191/// # `#[layout(component)]`
192///
193/// The `#[layout]` attribute is used to define a layout. It takes 1 parameter:
194/// - `component`: The component to render when the route is matched. If not specified, the name of the variant is used
195///
196/// The layout component allows you to wrap all children of the layout in a component. The child routes are rendered in the Outlet of the layout component. The layout component must take all dynamic parameters of the nests it is nested in.
197///
198/// ```rust
199/// use dioxus::prelude::*;
200///
201/// #[derive(Clone, Debug, PartialEq, Routable)]
202/// enum Route {
203///     #[layout(BlogFrame)]
204///         #[redirect("/:id", |id: usize| Route::Index {})]
205///         // Index will be rendered in the Outlet of the BlogFrame component
206///         #[route("/", Index)]
207///         Index {},
208/// }
209/// # #[component]
210/// # fn Index() -> Element { VNode::empty() }
211/// # #[component]
212/// # fn BlogFrame() -> Element { VNode::empty() }
213/// ```
214///
215/// # `#[end_layout]`
216///
217/// The `#[end_layout]` attribute is used to end a layout. It takes no parameters.
218///
219/// ```rust
220/// use dioxus::prelude::*;
221///
222/// #[derive(Clone, Debug, PartialEq, Routable)]
223/// enum Route {
224///     #[layout(BlogFrame)]
225///         #[redirect("/:id", |id: usize| Route::Index {})]
226///         // Index will be rendered in the Outlet of the BlogFrame component
227///         #[route("/", Index)]
228///         Index {},
229///     // Ends the layout
230///     #[end_layout]
231///     // This will be rendered standalone
232///     #[route("/")]
233///     Home {},
234/// }
235/// # #[component]
236/// # fn Index() -> Element { VNode::empty() }
237/// # #[component]
238/// # fn BlogFrame() -> Element { VNode::empty() }
239/// # #[component]
240/// # fn Home() -> Element { VNode::empty() }
241/// ```
242#[doc(alias = "route")]
243#[proc_macro_derive(
244    Routable,
245    attributes(route, nest, end_nest, layout, end_layout, redirect, child)
246)]
247pub fn routable(input: TokenStream) -> TokenStream {
248    let routes_enum = parse_macro_input!(input as syn::ItemEnum);
249
250    let route_enum = match RouteEnum::parse(routes_enum) {
251        Ok(route_enum) => route_enum,
252        Err(err) => return err.to_compile_error().into(),
253    };
254
255    let error_type = route_enum.error_type();
256    let parse_impl = route_enum.parse_impl();
257    let display_impl = route_enum.impl_display();
258    let routable_impl = route_enum.routable_impl();
259
260    (quote! {
261        const _: () = {
262            #error_type
263
264            #display_impl
265
266            #routable_impl
267
268            #parse_impl
269        };
270    })
271    .into()
272}
273
274struct RouteEnum {
275    name: Ident,
276    endpoints: Vec<RouteEndpoint>,
277    nests: Vec<Nest>,
278    layouts: Vec<Layout>,
279    site_map: Vec<SiteMapSegment>,
280}
281
282impl RouteEnum {
283    fn parse(data: syn::ItemEnum) -> syn::Result<Self> {
284        let name = &data.ident;
285
286        let mut site_map = Vec::new();
287        let mut site_map_stack: Vec<Vec<SiteMapSegment>> = Vec::new();
288
289        let mut endpoints = Vec::new();
290
291        let mut layouts: Vec<Layout> = Vec::new();
292        let mut layout_stack = Vec::new();
293
294        let mut nests = Vec::new();
295        let mut nest_stack = Vec::new();
296
297        for variant in &data.variants {
298            let mut excluded = Vec::new();
299            // Apply the any nesting attributes in order
300            for attr in &variant.attrs {
301                if attr.path().is_ident("nest") {
302                    let mut children_routes = Vec::new();
303                    {
304                        // add all of the variants of the enum to the children_routes until we hit an end_nest
305                        let mut level = 0;
306                        'o: for variant in &data.variants {
307                            children_routes.push(variant.fields.clone());
308                            for attr in &variant.attrs {
309                                if attr.path().is_ident("nest") {
310                                    level += 1;
311                                } else if attr.path().is_ident("end_nest") {
312                                    level -= 1;
313                                    if level < 0 {
314                                        break 'o;
315                                    }
316                                }
317                            }
318                        }
319                    }
320
321                    let nest_index = nests.len();
322
323                    let parser = |input: ParseStream| {
324                        Nest::parse(
325                            input,
326                            children_routes
327                                .iter()
328                                .filter_map(|f: &syn::Fields| match f {
329                                    syn::Fields::Named(fields) => Some(fields.clone()),
330                                    _ => None,
331                                })
332                                .collect(),
333                            nest_index,
334                        )
335                    };
336                    let nest = attr.parse_args_with(parser)?;
337
338                    // add the current segment to the site map stack
339                    let segments: Vec<_> = nest
340                        .segments
341                        .iter()
342                        .map(|seg| {
343                            let segment_type = seg.into();
344                            SiteMapSegment {
345                                segment_type,
346                                children: Vec::new(),
347                            }
348                        })
349                        .collect();
350                    if !segments.is_empty() {
351                        site_map_stack.push(segments);
352                    }
353
354                    nests.push(nest);
355                    nest_stack.push(NestId(nest_index));
356                } else if attr.path().is_ident("end_nest") {
357                    nest_stack.pop();
358                    // pop the current nest segment off the stack and add it to the parent or the site map
359                    if let Some(segment) = site_map_stack.pop() {
360                        let children = site_map_stack
361                            .last_mut()
362                            .map(|seg| &mut seg.last_mut().unwrap().children)
363                            .unwrap_or(&mut site_map);
364
365                        // Turn the list of segments in the segments stack into a tree
366                        let mut iter = segment.into_iter().rev();
367                        let mut current = iter.next().unwrap();
368                        for mut segment in iter {
369                            segment.children.push(current);
370                            current = segment;
371                        }
372
373                        children.push(current);
374                    }
375                } else if attr.path().is_ident("layout") {
376                    let parser = |input: ParseStream| {
377                        let bang: Option<Token![!]> = input.parse().ok();
378                        let exclude = bang.is_some();
379                        Ok((exclude, Layout::parse(input, nest_stack.clone())?))
380                    };
381                    let (exclude, layout): (bool, Layout) = attr.parse_args_with(parser)?;
382
383                    if exclude {
384                        let Some(layout_index) = layouts.iter().position(|l| l.comp == layout.comp)
385                        else {
386                            return Err(syn::Error::new(
387                                Span::call_site(),
388                                "Attempted to exclude a layout that does not exist",
389                            ));
390                        };
391                        excluded.push(LayoutId(layout_index));
392                    } else {
393                        let layout_index = layouts.len();
394                        layouts.push(layout);
395                        layout_stack.push(LayoutId(layout_index));
396                    }
397                } else if attr.path().is_ident("end_layout") {
398                    layout_stack.pop();
399                } else if attr.path().is_ident("redirect") {
400                    let parser = |input: ParseStream| {
401                        Redirect::parse(input, nest_stack.clone(), endpoints.len())
402                    };
403                    let redirect = attr.parse_args_with(parser)?;
404                    endpoints.push(RouteEndpoint::Redirect(redirect));
405                }
406            }
407
408            let active_nests = nest_stack.clone();
409            let mut active_layouts = layout_stack.clone();
410            active_layouts.retain(|&id| !excluded.contains(&id));
411
412            let route = Route::parse(active_nests, active_layouts, variant.clone())?;
413
414            // add the route to the site map
415            let mut segment = SiteMapSegment::new(&route.segments);
416            if let RouteType::Child(child) = &route.ty {
417                let new_segment = SiteMapSegment {
418                    segment_type: SegmentType::Child(child.ty.clone()),
419                    children: Vec::new(),
420                };
421                match &mut segment {
422                    Some(segment) => {
423                        fn set_last_child_to(
424                            segment: &mut SiteMapSegment,
425                            new_segment: SiteMapSegment,
426                        ) {
427                            if let Some(last) = segment.children.last_mut() {
428                                set_last_child_to(last, new_segment);
429                            } else {
430                                segment.children = vec![new_segment];
431                            }
432                        }
433                        set_last_child_to(segment, new_segment);
434                    }
435                    None => {
436                        segment = Some(new_segment);
437                    }
438                }
439            }
440
441            if let Some(segment) = segment {
442                let parent = site_map_stack.last_mut();
443                let children = match parent {
444                    Some(parent) => &mut parent.last_mut().unwrap().children,
445                    None => &mut site_map,
446                };
447                children.push(segment);
448            }
449
450            endpoints.push(RouteEndpoint::Route(route));
451        }
452
453        // pop any remaining site map segments
454        while let Some(segment) = site_map_stack.pop() {
455            let children = site_map_stack
456                .last_mut()
457                .map(|seg| &mut seg.last_mut().unwrap().children)
458                .unwrap_or(&mut site_map);
459
460            // Turn the list of segments in the segments stack into a tree
461            let mut iter = segment.into_iter().rev();
462            let mut current = iter.next().unwrap();
463            for mut segment in iter {
464                segment.children.push(current);
465                current = segment;
466            }
467
468            children.push(current);
469        }
470
471        let myself = Self {
472            name: name.clone(),
473            endpoints,
474            nests,
475            layouts,
476            site_map,
477        };
478
479        Ok(myself)
480    }
481
482    fn impl_display(&self) -> TokenStream2 {
483        let mut display_match = Vec::new();
484
485        for route in &self.endpoints {
486            if let RouteEndpoint::Route(route) = route {
487                display_match.push(route.display_match(&self.nests));
488            }
489        }
490
491        let name = &self.name;
492
493        quote! {
494            impl std::fmt::Display for #name {
495                fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
496                    #[allow(unused)]
497                    match self {
498                        #(#display_match)*
499                    }
500                    Ok(())
501                }
502            }
503        }
504    }
505
506    fn parse_impl(&self) -> TokenStream2 {
507        let tree = ParseRouteTree::new(&self.endpoints, &self.nests);
508        let name = &self.name;
509
510        let error_name = format_ident!("{}MatchError", self.name);
511        let tokens = tree.roots.iter().map(|&id| {
512            let route = tree.get(id).unwrap();
513            route.to_tokens(&self.nests, &tree, self.name.clone(), error_name.clone())
514        });
515
516        quote! {
517            impl<'a> ::core::convert::TryFrom<&'a str> for #name {
518                type Error = <Self as ::std::str::FromStr>::Err;
519
520                fn try_from(s: &'a str) -> ::std::result::Result<Self, Self::Error> {
521                    s.parse()
522                }
523            }
524
525            impl ::std::str::FromStr for #name {
526                type Err = dioxus_router::routable::RouteParseError<#error_name>;
527
528                fn from_str(s: &str) -> ::std::result::Result<Self, Self::Err> {
529                    let route = s;
530                    let (route, hash) = route.split_once('#').unwrap_or((route, ""));
531                    let (route, query) = route.split_once('?').unwrap_or((route, ""));
532                    // Remove any trailing slashes. We parse /route/ and /route in the same way
533                    // Note: we don't use trim because it includes more code
534                    let route = route.strip_suffix('/').unwrap_or(route);
535                    let query = dioxus_router::exports::percent_encoding::percent_decode_str(query)
536                        .decode_utf8()
537                        .unwrap_or(query.into());
538                    let hash = dioxus_router::exports::percent_encoding::percent_decode_str(hash)
539                        .decode_utf8()
540                        .unwrap_or(hash.into());
541                    let mut segments = route.split('/').map(|s| {
542                        dioxus_router::exports::percent_encoding::percent_decode_str(s)
543                            .decode_utf8()
544                            .unwrap_or(s.into())
545                    });
546                    // skip the first empty segment
547                    if s.starts_with('/') {
548                        let _ = segments.next();
549                    } else {
550                        // if this route does not start with a slash, it is not a valid route
551                        return Err(dioxus_router::routable::RouteParseError {
552                            attempted_routes: Vec::new(),
553                        });
554                    }
555                    let mut errors = Vec::new();
556
557                    #(#tokens)*
558
559                    Err(dioxus_router::routable::RouteParseError {
560                        attempted_routes: errors,
561                    })
562                }
563            }
564        }
565    }
566
567    fn error_name(&self) -> Ident {
568        Ident::new(&(self.name.to_string() + "MatchError"), Span::call_site())
569    }
570
571    fn error_type(&self) -> TokenStream2 {
572        let match_error_name = self.error_name();
573
574        let mut type_defs = Vec::new();
575        let mut error_variants = Vec::new();
576        let mut display_match = Vec::new();
577
578        for endpoint in &self.endpoints {
579            match endpoint {
580                RouteEndpoint::Route(route) => {
581                    let route_name = &route.route_name;
582
583                    let error_name = route.error_ident();
584                    let route_str = &route.route;
585                    let comment = format!(
586                        " An error that can occur when trying to parse the route [`{}::{}`] ('{}').",
587                        self.name,
588                        route_name,
589                        route_str
590                    );
591
592                    error_variants.push(quote! {
593                        #[doc = #comment]
594                        #route_name(#error_name)
595                    });
596                    display_match.push(quote! { Self::#route_name(err) => write!(f, "Route '{}' ('{}') did not match:\n{}", stringify!(#route_name), #route_str, err)? });
597                    type_defs.push(route.error_type());
598                }
599                RouteEndpoint::Redirect(redirect) => {
600                    let error_variant = redirect.error_variant();
601                    let error_name = redirect.error_ident();
602                    let route_str = &redirect.route;
603                    let comment = format!(
604                        " An error that can occur when trying to parse the redirect '{}'.",
605                        route_str.value()
606                    );
607
608                    error_variants.push(quote! {
609                        #[doc = #comment]
610                        #error_variant(#error_name)
611                    });
612                    display_match.push(quote! { Self::#error_variant(err) => write!(f, "Redirect '{}' ('{}') did not match:\n{}", stringify!(#error_name), #route_str, err)? });
613                    type_defs.push(redirect.error_type());
614                }
615            }
616        }
617
618        for nest in &self.nests {
619            let error_variant = nest.error_variant();
620            let error_name = nest.error_ident();
621            let route_str = &nest.route;
622            let comment = format!(
623                " An error that can occur when trying to parse the nested segment {} ('{}').",
624                error_name, route_str
625            );
626
627            error_variants.push(quote! {
628                #[doc = #comment]
629                #error_variant(#error_name)
630            });
631            display_match.push(quote! { Self::#error_variant(err) => write!(f, "Nest '{}' ('{}') did not match:\n{}", stringify!(#error_name), #route_str, err)? });
632            type_defs.push(nest.error_type());
633        }
634
635        let comment = format!(
636            " An error that can occur when trying to parse the route enum [`{}`].",
637            self.name
638        );
639
640        quote! {
641            #(#type_defs)*
642
643            #[doc = #comment]
644            #[allow(non_camel_case_types)]
645            #[allow(clippy::derive_partial_eq_without_eq)]
646            pub enum #match_error_name {
647                #(#error_variants),*
648            }
649
650            impl ::std::fmt::Debug for #match_error_name {
651                fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
652                    write!(f, "{}({})", stringify!(#match_error_name), self)
653                }
654            }
655
656            impl ::std::fmt::Display for #match_error_name {
657                fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
658                    match self {
659                        #(#display_match),*
660                    }
661                    Ok(())
662                }
663            }
664        }
665    }
666
667    fn routable_impl(&self) -> TokenStream2 {
668        let name = &self.name;
669        let site_map = &self.site_map;
670
671        let mut matches = Vec::new();
672
673        // Collect all routes matches
674        for route in &self.endpoints {
675            if let RouteEndpoint::Route(route) = route {
676                matches.push(route.routable_match(&self.layouts, &self.nests, name));
677            }
678        }
679
680        quote! {
681            impl dioxus_router::routable::Routable for #name where Self: Clone {
682                const SITE_MAP: &'static [dioxus_router::routable::SiteMapSegment] = &[
683                    #(#site_map,)*
684                ];
685
686                fn render(&self, level: usize) -> dioxus_core::Element {
687                    let myself = self.clone();
688                    match (level, myself) {
689                        #(#matches)*
690                        _ => VNode::empty()
691                    }
692                }
693            }
694        }
695    }
696}
697
698#[allow(clippy::large_enum_variant)]
699enum RouteEndpoint {
700    Route(Route),
701    Redirect(Redirect),
702}
703
704struct SiteMapSegment {
705    pub segment_type: SegmentType,
706    pub children: Vec<SiteMapSegment>,
707}
708
709impl SiteMapSegment {
710    fn new(segments: &[RouteSegment]) -> Option<Self> {
711        let mut current = None;
712        // walk backwards through the new segments, adding children as we go
713        for segment in segments.iter().rev() {
714            let segment_type = segment.into();
715            let mut segment = SiteMapSegment {
716                segment_type,
717                children: Vec::new(),
718            };
719            // if we have a current segment, add it as a child
720            if let Some(current) = current.take() {
721                segment.children.push(current)
722            }
723            current = Some(segment);
724        }
725        current
726    }
727}
728
729impl ToTokens for SiteMapSegment {
730    fn to_tokens(&self, tokens: &mut TokenStream2) {
731        let segment_type = &self.segment_type;
732        let children = if let SegmentType::Child(ty) = &self.segment_type {
733            quote! { #ty::SITE_MAP }
734        } else {
735            let children = self
736                .children
737                .iter()
738                .map(|child| child.to_token_stream())
739                .collect::<Vec<_>>();
740            quote! {
741                &[
742                    #(#children,)*
743                ]
744            }
745        };
746
747        tokens.extend(quote! {
748            dioxus_router::routable::SiteMapSegment {
749                segment_type: #segment_type,
750                children: #children,
751            }
752        });
753    }
754}
755
756#[allow(clippy::large_enum_variant)]
757enum SegmentType {
758    Static(String),
759    Dynamic(String),
760    CatchAll(String),
761    Child(Type),
762}
763
764impl ToTokens for SegmentType {
765    fn to_tokens(&self, tokens: &mut TokenStream2) {
766        match self {
767            SegmentType::Static(s) => {
768                tokens.extend(quote! { dioxus_router::routable::SegmentType::Static(#s) })
769            }
770            SegmentType::Dynamic(s) => {
771                tokens.extend(quote! { dioxus_router::routable::SegmentType::Dynamic(#s) })
772            }
773            SegmentType::CatchAll(s) => {
774                tokens.extend(quote! { dioxus_router::routable::SegmentType::CatchAll(#s) })
775            }
776            SegmentType::Child(_) => {
777                tokens.extend(quote! { dioxus_router::routable::SegmentType::Child })
778            }
779        }
780    }
781}
782
783impl<'a> From<&'a RouteSegment> for SegmentType {
784    fn from(value: &'a RouteSegment) -> Self {
785        match value {
786            RouteSegment::Static(s) => SegmentType::Static(s.to_string()),
787            RouteSegment::Dynamic(s, _) => SegmentType::Dynamic(s.to_string()),
788            RouteSegment::CatchAll(s, _) => SegmentType::CatchAll(s.to_string()),
789        }
790    }
791}