segment.rs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. use quote::{format_ident, quote};
  2. use syn::{Ident, Type};
  3. use proc_macro2::{Span, TokenStream as TokenStream2};
  4. use crate::query::QuerySegment;
  5. #[derive(Debug, Clone)]
  6. pub enum RouteSegment {
  7. Static(String),
  8. Dynamic(Ident, Type),
  9. CatchAll(Ident, Type),
  10. }
  11. impl RouteSegment {
  12. pub fn name(&self) -> Option<Ident> {
  13. match self {
  14. Self::Static(_) => None,
  15. Self::Dynamic(ident, _) => Some(ident.clone()),
  16. Self::CatchAll(ident, _) => Some(ident.clone()),
  17. }
  18. }
  19. pub fn write_segment(&self) -> TokenStream2 {
  20. match self {
  21. Self::Static(segment) => quote! { write!(f, "/{}", #segment)?; },
  22. Self::Dynamic(ident, _) => quote! { write!(f, "/{}", #ident)?; },
  23. Self::CatchAll(ident, _) => quote! { #ident.display_route_segements(f)?; },
  24. }
  25. }
  26. pub fn error_name(&self, idx: usize) -> Ident {
  27. match self {
  28. Self::Static(_) => static_segment_idx(idx),
  29. Self::Dynamic(ident, _) => format_ident!("{}ParseError", ident),
  30. Self::CatchAll(ident, _) => format_ident!("{}ParseError", ident),
  31. }
  32. }
  33. pub fn missing_error_name(&self) -> Option<Ident> {
  34. match self {
  35. Self::Dynamic(ident, _) => Some(format_ident!("{}MissingError", ident)),
  36. _ => None,
  37. }
  38. }
  39. pub fn try_parse(
  40. &self,
  41. idx: usize,
  42. error_enum_name: &Ident,
  43. error_enum_varient: &Ident,
  44. inner_parse_enum: &Ident,
  45. parse_children: TokenStream2,
  46. ) -> TokenStream2 {
  47. let error_name = self.error_name(idx);
  48. match self {
  49. Self::Static(segment) => {
  50. quote! {
  51. {
  52. let mut segments = segments.clone();
  53. let segment = segments.next();
  54. let parsed = if let Some(#segment) = segment {
  55. Ok(())
  56. } else {
  57. Err(#error_enum_name::#error_enum_varient(#inner_parse_enum::#error_name(segment.map(|s|s.to_string()).unwrap_or_default())))
  58. };
  59. match parsed {
  60. Ok(_) => {
  61. #parse_children
  62. }
  63. Err(err) => {
  64. errors.push(err);
  65. }
  66. }
  67. }
  68. }
  69. }
  70. Self::Dynamic(name, ty) => {
  71. let missing_error_name = self.missing_error_name().unwrap();
  72. quote! {
  73. {
  74. let mut segments = segments.clone();
  75. let parsed = if let Some(segment) = segments.next() {
  76. <#ty as dioxus_router::routable::FromRouteSegment>::from_route_segment(segment).map_err(|err| #error_enum_name::#error_enum_varient(#inner_parse_enum::#error_name(err)))
  77. } else {
  78. Err(#error_enum_name::#error_enum_varient(#inner_parse_enum::#missing_error_name))
  79. };
  80. match parsed {
  81. Ok(#name) => {
  82. #parse_children
  83. }
  84. Err(err) => {
  85. errors.push(err);
  86. }
  87. }
  88. }
  89. }
  90. }
  91. Self::CatchAll(name, ty) => {
  92. quote! {
  93. {
  94. let parsed = {
  95. let mut segments = segments.clone();
  96. let segments: Vec<_> = segments.collect();
  97. <#ty as dioxus_router::routable::FromRouteSegments>::from_route_segments(&segments).map_err(|err| #error_enum_name::#error_enum_varient(#inner_parse_enum::#error_name(err)))
  98. };
  99. match parsed {
  100. Ok(#name) => {
  101. #parse_children
  102. }
  103. Err(err) => {
  104. errors.push(err);
  105. }
  106. }
  107. }
  108. }
  109. }
  110. }
  111. }
  112. }
  113. pub fn static_segment_idx(idx: usize) -> Ident {
  114. format_ident!("StaticSegment{}ParseError", idx)
  115. }
  116. pub fn parse_route_segments<'a>(
  117. route_span: Span,
  118. mut fields: impl Iterator<Item = (&'a Ident, &'a Type)>,
  119. route: &str,
  120. ) -> syn::Result<(Vec<RouteSegment>, Option<QuerySegment>)> {
  121. let mut route_segments = Vec::new();
  122. let (route_string, query) = match route.rsplit_once('?') {
  123. Some((route, query)) => (route, Some(query)),
  124. None => (route, None),
  125. };
  126. let mut iterator = route_string.split('/');
  127. // skip the first empty segment
  128. let first = iterator.next();
  129. if first != Some("") {
  130. return Err(syn::Error::new(
  131. route_span,
  132. format!(
  133. "Routes should start with /. Error found in the route '{}'",
  134. route
  135. ),
  136. ));
  137. }
  138. while let Some(segment) = iterator.next() {
  139. if let Some(segment) = segment.strip_prefix(':') {
  140. let spread = segment.starts_with("..");
  141. let ident = if spread {
  142. segment[2..].to_string()
  143. } else {
  144. segment.to_string()
  145. };
  146. let field = fields.find(|(name, _)| **name == ident);
  147. let ty = if let Some(field) = field {
  148. field.1.clone()
  149. } else {
  150. return Err(syn::Error::new(
  151. route_span,
  152. format!("Could not find a field with the name '{}'", ident,),
  153. ));
  154. };
  155. if spread {
  156. route_segments.push(RouteSegment::CatchAll(
  157. Ident::new(&ident, Span::call_site()),
  158. ty,
  159. ));
  160. if iterator.next().is_some() {
  161. return Err(syn::Error::new(
  162. route_span,
  163. "Catch-all route segments must be the last segment in a route. The route segments after the catch-all segment will never be matched.",
  164. ));
  165. } else {
  166. break;
  167. }
  168. } else {
  169. route_segments.push(RouteSegment::Dynamic(
  170. Ident::new(&ident, Span::call_site()),
  171. ty,
  172. ));
  173. }
  174. } else {
  175. route_segments.push(RouteSegment::Static(segment.to_string()));
  176. }
  177. }
  178. // check if the route has a query string
  179. let parsed_query = match query {
  180. Some(query) => {
  181. if let Some(query) = query.strip_prefix(':') {
  182. let query_ident = Ident::new(query, Span::call_site());
  183. let field = fields.find(|(name, _)| *name == &query_ident);
  184. let ty = if let Some((_, ty)) = field {
  185. ty.clone()
  186. } else {
  187. return Err(syn::Error::new(
  188. route_span,
  189. format!("Could not find a field with the name '{}'", query_ident),
  190. ));
  191. };
  192. Some(QuerySegment {
  193. ident: query_ident,
  194. ty,
  195. })
  196. } else {
  197. None
  198. }
  199. }
  200. None => None,
  201. };
  202. Ok((route_segments, parsed_query))
  203. }
  204. pub(crate) fn create_error_type(
  205. error_name: Ident,
  206. segments: &[RouteSegment],
  207. child_type: Option<&Type>,
  208. ) -> TokenStream2 {
  209. let mut error_variants = Vec::new();
  210. let mut display_match = Vec::new();
  211. for (i, segment) in segments.iter().enumerate() {
  212. let error_name = segment.error_name(i);
  213. match segment {
  214. RouteSegment::Static(index) => {
  215. error_variants.push(quote! { #error_name(String) });
  216. display_match.push(quote! { Self::#error_name(found) => write!(f, "Static segment '{}' did not match instead found '{}'", #index, found)? });
  217. }
  218. RouteSegment::Dynamic(ident, ty) => {
  219. let missing_error = segment.missing_error_name().unwrap();
  220. error_variants.push(
  221. quote! { #error_name(<#ty as dioxus_router::routable::FromRouteSegment>::Err) },
  222. );
  223. display_match.push(quote! { Self::#error_name(err) => write!(f, "Dynamic segment '({}:{})' did not match: {}", stringify!(#ident), stringify!(#ty), err)? });
  224. error_variants.push(quote! { #missing_error });
  225. display_match.push(quote! { Self::#missing_error => write!(f, "Dynamic segment '({}:{})' was missing", stringify!(#ident), stringify!(#ty))? });
  226. }
  227. RouteSegment::CatchAll(ident, ty) => {
  228. error_variants.push(quote! { #error_name(<#ty as dioxus_router::routable::FromRouteSegments>::Err) });
  229. display_match.push(quote! { Self::#error_name(err) => write!(f, "Catch-all segment '({}:{})' did not match: {}", stringify!(#ident), stringify!(#ty), err)? });
  230. }
  231. }
  232. }
  233. let child_type_variant = child_type
  234. .map(|child_type| {
  235. quote! { ChildRoute(<#child_type as std::str::FromStr>::Err) }
  236. })
  237. .into_iter();
  238. let child_type_error = child_type
  239. .map(|_| {
  240. quote! {
  241. Self::ChildRoute(error) => {
  242. write!(f, "{}", error)?
  243. }
  244. }
  245. })
  246. .into_iter();
  247. quote! {
  248. #[allow(non_camel_case_types)]
  249. #[derive(Debug, PartialEq)]
  250. pub enum #error_name {
  251. ExtraSegments(String),
  252. #(#child_type_variant,)*
  253. #(#error_variants,)*
  254. }
  255. impl std::fmt::Display for #error_name {
  256. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  257. match self {
  258. Self::ExtraSegments(segments) => {
  259. write!(f, "Found additional trailing segments: {}", segments)?
  260. },
  261. #(#child_type_error,)*
  262. #(#display_match,)*
  263. }
  264. Ok(())
  265. }
  266. }
  267. }
  268. }