route.rs 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  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, enum_name: Ident) -> TokenStream2 {
  160. let segments = self.dynamic_segments();
  161. let name = &self.route_name;
  162. quote! {
  163. #enum_name::#name {
  164. #(#segments,)*
  165. }
  166. }
  167. }
  168. pub fn error_ident(&self) -> Ident {
  169. format_ident!("{}ParseError", self.route_name)
  170. }
  171. pub fn error_type(&self) -> TokenStream2 {
  172. let error_name = self.error_ident();
  173. let mut error_variants = Vec::new();
  174. let mut display_match = Vec::new();
  175. for (i, segment) in self.segments.iter().enumerate() {
  176. let error_name = segment.error_name(i);
  177. match segment {
  178. RouteSegment::Static(index) => {
  179. error_variants.push(quote! { #error_name });
  180. display_match.push(quote! { Self::#error_name => write!(f, "Static segment '{}' did not match", #index)? });
  181. }
  182. RouteSegment::Dynamic(ident, ty) => {
  183. let missing_error = segment.missing_error_name().unwrap();
  184. error_variants.push(quote! { #error_name(<#ty as dioxus_router::routable::FromRouteSegment>::Err) });
  185. display_match.push(quote! { Self::#error_name(err) => write!(f, "Dynamic segment '({}:{})' did not match: {}", stringify!(#ident), stringify!(#ty), err)? });
  186. error_variants.push(quote! { #missing_error });
  187. display_match.push(quote! { Self::#missing_error => write!(f, "Dynamic segment '({}:{})' was missing", stringify!(#ident), stringify!(#ty))? });
  188. }
  189. RouteSegment::CatchAll(ident, ty) => {
  190. error_variants.push(quote! { #error_name(<#ty as dioxus_router::routable::FromRouteSegments>::Err) });
  191. display_match.push(quote! { Self::#error_name(err) => write!(f, "Catch-all segment '({}:{})' did not match: {}", stringify!(#ident), stringify!(#ty), err)? });
  192. }
  193. }
  194. }
  195. quote! {
  196. #[allow(non_camel_case_types)]
  197. #[derive(Debug, PartialEq)]
  198. pub enum #error_name {
  199. ExtraSegments(String),
  200. #(#error_variants,)*
  201. }
  202. impl std::fmt::Display for #error_name {
  203. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  204. match self {
  205. Self::ExtraSegments(segments) => {
  206. write!(f, "Found additional trailing segments: {segments}")?
  207. }
  208. #(#display_match,)*
  209. }
  210. Ok(())
  211. }
  212. }
  213. }
  214. }
  215. pub fn parse_query(&self) -> TokenStream2 {
  216. match &self.query {
  217. Some(query) => query.parse(),
  218. None => quote! {},
  219. }
  220. }
  221. pub fn variant(&self) -> TokenStream2 {
  222. let name = &self.route_name;
  223. let fields = self.fields.named.iter().map(|f| {
  224. let mut new = f.clone();
  225. new.attrs.retain(|a| {
  226. !a.path.is_ident("nest")
  227. && !a.path.is_ident("end_nest")
  228. && !a.path.is_ident("layout")
  229. && !a.path.is_ident("end_layout")
  230. });
  231. new
  232. });
  233. quote! {
  234. #name { #(#fields,)* }
  235. }
  236. }
  237. }
  238. impl ToTokens for Route {
  239. fn to_tokens(&self, tokens: &mut quote::__private::TokenStream) {
  240. if !self.file_based {
  241. return;
  242. }
  243. let without_leading_slash = &self.route[1..];
  244. let route_path = std::path::Path::new(without_leading_slash);
  245. let with_extension = route_path.with_extension("rs");
  246. let dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
  247. let dir = std::path::Path::new(&dir);
  248. let route = dir.join("src").join("pages").join(with_extension.clone());
  249. // check if the route exists or if not use the index route
  250. let route = if route.exists() && !without_leading_slash.is_empty() {
  251. with_extension.to_str().unwrap().to_string()
  252. } else {
  253. route_path.join("index.rs").to_str().unwrap().to_string()
  254. };
  255. let route_name: Ident = self.route_name.clone();
  256. let prop_name = &self.props_name;
  257. tokens.extend(quote!(
  258. #[path = #route]
  259. #[allow(non_snake_case)]
  260. mod #route_name;
  261. pub use #route_name::{#prop_name, #route_name};
  262. ));
  263. }
  264. }