route.rs 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. use quote::{format_ident, quote, ToTokens};
  2. use syn::parse::Parse;
  3. use syn::parse::ParseStream;
  4. use syn::{Ident, LitStr};
  5. use proc_macro2::TokenStream as TokenStream2;
  6. use crate::layout::Layout;
  7. use crate::layout::LayoutId;
  8. use crate::nest::Nest;
  9. use crate::nest::NestId;
  10. use crate::query::QuerySegment;
  11. use crate::segment::parse_route_segments;
  12. use crate::segment::RouteSegment;
  13. struct RouteArgs {
  14. route: LitStr,
  15. comp_name: Option<Ident>,
  16. props_name: Option<Ident>,
  17. }
  18. impl Parse for RouteArgs {
  19. fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
  20. let route = input.parse::<LitStr>()?;
  21. Ok(RouteArgs {
  22. route,
  23. comp_name: {
  24. let _ = input.parse::<syn::Token![,]>();
  25. input.parse().ok()
  26. },
  27. props_name: {
  28. let _ = input.parse::<syn::Token![,]>();
  29. input.parse().ok()
  30. },
  31. })
  32. }
  33. }
  34. #[derive(Debug)]
  35. pub struct Route {
  36. pub file_based: bool,
  37. pub route_name: Ident,
  38. pub comp_name: Ident,
  39. pub props_name: Ident,
  40. pub route: String,
  41. pub segments: Vec<RouteSegment>,
  42. pub query: Option<QuerySegment>,
  43. pub nests: Vec<NestId>,
  44. pub layouts: Vec<LayoutId>,
  45. pub variant: syn::Variant,
  46. fields: syn::FieldsNamed,
  47. }
  48. impl Route {
  49. pub fn parse(
  50. nests: Vec<NestId>,
  51. layouts: Vec<LayoutId>,
  52. variant: syn::Variant,
  53. ) -> syn::Result<Self> {
  54. let route_attr = variant
  55. .attrs
  56. .iter()
  57. .find(|attr| attr.path.is_ident("route"))
  58. .ok_or_else(|| {
  59. syn::Error::new_spanned(
  60. variant.clone(),
  61. "Routable variants must have a #[route(...)] attribute",
  62. )
  63. })?;
  64. let route_name = variant.ident.clone();
  65. let args = route_attr.parse_args::<RouteArgs>()?;
  66. let route = args.route.value();
  67. let file_based = args.comp_name.is_none();
  68. let comp_name = args
  69. .comp_name
  70. .unwrap_or_else(|| format_ident!("{}", route_name));
  71. let props_name = args
  72. .props_name
  73. .unwrap_or_else(|| format_ident!("{}Props", comp_name));
  74. let named_fields = match &variant.fields {
  75. syn::Fields::Named(fields) => fields,
  76. _ => {
  77. return Err(syn::Error::new_spanned(
  78. variant.clone(),
  79. "Routable variants must have named fields",
  80. ))
  81. }
  82. };
  83. let (route_segments, query) =
  84. parse_route_segments(variant.ident.span(), named_fields.named.iter(), &route)?;
  85. Ok(Self {
  86. comp_name,
  87. props_name,
  88. route_name,
  89. segments: route_segments,
  90. route,
  91. file_based,
  92. query,
  93. nests,
  94. layouts,
  95. fields: named_fields.clone(),
  96. variant,
  97. })
  98. }
  99. pub fn display_match(&self, nests: &[Nest]) -> TokenStream2 {
  100. let name = &self.route_name;
  101. let dynamic_segments = self.dynamic_segments();
  102. let write_layouts = self.nests.iter().map(|id| nests[id.0].write());
  103. let write_segments = self.segments.iter().map(|s| s.write_segment());
  104. let write_query = self.query.as_ref().map(|q| q.write());
  105. quote! {
  106. Self::#name { #(#dynamic_segments,)* } => {
  107. #(#write_layouts)*
  108. #(#write_segments)*
  109. #write_query
  110. }
  111. }
  112. }
  113. pub fn routable_match(
  114. &self,
  115. layouts: &[Layout],
  116. nests: &[Nest],
  117. index: usize,
  118. ) -> Option<TokenStream2> {
  119. let name = &self.route_name;
  120. let dynamic_segments = self.dynamic_segments();
  121. match index.cmp(&self.layouts.len()) {
  122. std::cmp::Ordering::Less => {
  123. let layout = self.layouts[index];
  124. let render_layout = layouts[layout.0].routable_match(nests);
  125. // This is a layout
  126. Some(quote! {
  127. #[allow(unused)]
  128. Self::#name { #(#dynamic_segments,)* } => {
  129. #render_layout
  130. }
  131. })
  132. }
  133. std::cmp::Ordering::Equal => {
  134. let dynamic_segments_from_route = self.dynamic_segments();
  135. let props_name = &self.props_name;
  136. let comp_name = &self.comp_name;
  137. // This is the final route
  138. Some(quote! {
  139. #[allow(unused)]
  140. Self::#name { #(#dynamic_segments,)* } => {
  141. let comp = #props_name { #(#dynamic_segments_from_route,)* };
  142. let cx = cx.bump().alloc(Scoped {
  143. props: cx.bump().alloc(comp),
  144. scope: cx,
  145. });
  146. #comp_name(cx)
  147. }
  148. })
  149. }
  150. _ => None,
  151. }
  152. }
  153. fn dynamic_segments(&self) -> impl Iterator<Item = TokenStream2> + '_ {
  154. self.fields.named.iter().map(|f| {
  155. let name = f.ident.as_ref().unwrap();
  156. quote! {#name}
  157. })
  158. }
  159. pub fn construct(&self, nests: &[Nest], enum_name: Ident) -> TokenStream2 {
  160. let segments = self.fields.named.iter().map(|f| {
  161. let mut from_route = false;
  162. for id in &self.nests {
  163. let nest = &nests[id.0];
  164. if nest
  165. .dynamic_segments_names()
  166. .any(|i| &i == f.ident.as_ref().unwrap())
  167. {
  168. from_route = true
  169. }
  170. }
  171. for segment in &self.segments {
  172. if let RouteSegment::Dynamic(name, ..) = segment {
  173. if name == f.ident.as_ref().unwrap() {
  174. from_route = true
  175. }
  176. }
  177. }
  178. let name = f.ident.as_ref().unwrap();
  179. if from_route {
  180. quote! {#name}
  181. } else {
  182. quote! {#name: Default::default()}
  183. }
  184. });
  185. let name = &self.route_name;
  186. quote! {
  187. #enum_name::#name {
  188. #(#segments,)*
  189. }
  190. }
  191. }
  192. pub fn error_ident(&self) -> Ident {
  193. format_ident!("{}ParseError", self.route_name)
  194. }
  195. pub fn error_type(&self) -> TokenStream2 {
  196. let error_name = self.error_ident();
  197. let mut error_variants = Vec::new();
  198. let mut display_match = Vec::new();
  199. for (i, segment) in self.segments.iter().enumerate() {
  200. let error_name = segment.error_name(i);
  201. match segment {
  202. RouteSegment::Static(index) => {
  203. error_variants.push(quote! { #error_name });
  204. display_match.push(quote! { Self::#error_name => write!(f, "Static segment '{}' did not match", #index)? });
  205. }
  206. RouteSegment::Dynamic(ident, ty) => {
  207. let missing_error = segment.missing_error_name().unwrap();
  208. error_variants.push(quote! { #error_name(<#ty as dioxus_router::routable::FromRouteSegment>::Err) });
  209. display_match.push(quote! { Self::#error_name(err) => write!(f, "Dynamic segment '({}:{})' did not match: {}", stringify!(#ident), stringify!(#ty), err)? });
  210. error_variants.push(quote! { #missing_error });
  211. display_match.push(quote! { Self::#missing_error => write!(f, "Dynamic segment '({}:{})' was missing", stringify!(#ident), stringify!(#ty))? });
  212. }
  213. RouteSegment::CatchAll(ident, ty) => {
  214. error_variants.push(quote! { #error_name(<#ty as dioxus_router::routable::FromRouteSegments>::Err) });
  215. display_match.push(quote! { Self::#error_name(err) => write!(f, "Catch-all segment '({}:{})' did not match: {}", stringify!(#ident), stringify!(#ty), err)? });
  216. }
  217. }
  218. }
  219. quote! {
  220. #[allow(non_camel_case_types)]
  221. #[derive(Debug, PartialEq)]
  222. pub enum #error_name {
  223. ExtraSegments(String),
  224. #(#error_variants,)*
  225. }
  226. impl std::fmt::Display for #error_name {
  227. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  228. match self {
  229. Self::ExtraSegments(segments) => {
  230. write!(f, "Found additional trailing segments: {segments}")?
  231. }
  232. #(#display_match,)*
  233. }
  234. Ok(())
  235. }
  236. }
  237. }
  238. }
  239. pub fn parse_query(&self) -> TokenStream2 {
  240. match &self.query {
  241. Some(query) => query.parse(),
  242. None => quote! {},
  243. }
  244. }
  245. }
  246. impl ToTokens for Route {
  247. fn to_tokens(&self, tokens: &mut quote::__private::TokenStream) {
  248. if !self.file_based {
  249. return;
  250. }
  251. let without_leading_slash = &self.route[1..];
  252. let route_path = std::path::Path::new(without_leading_slash);
  253. let with_extension = route_path.with_extension("rs");
  254. let dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
  255. let dir = std::path::Path::new(&dir);
  256. let route = dir.join("src").join("pages").join(with_extension.clone());
  257. // check if the route exists or if not use the index route
  258. let route = if route.exists() && !without_leading_slash.is_empty() {
  259. with_extension.to_str().unwrap().to_string()
  260. } else {
  261. route_path.join("index.rs").to_str().unwrap().to_string()
  262. };
  263. let route_name: Ident = self.route_name.clone();
  264. let prop_name = &self.props_name;
  265. tokens.extend(quote!(
  266. #[path = #route]
  267. #[allow(non_snake_case)]
  268. mod #route_name;
  269. pub use #route_name::{#prop_name, #route_name};
  270. ));
  271. }
  272. }