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