lib.rs 26 KB

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