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}