lib.rs 31 KB

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