123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272 |
- use quote::{format_ident, quote};
- use syn::{Ident, Type};
- use proc_macro2::{Span, TokenStream as TokenStream2};
- use crate::query::QuerySegment;
- #[derive(Debug, Clone)]
- pub enum RouteSegment {
- Static(String),
- Dynamic(Ident, Type),
- CatchAll(Ident, Type),
- }
- impl RouteSegment {
- pub fn name(&self) -> Option<Ident> {
- match self {
- Self::Static(_) => None,
- Self::Dynamic(ident, _) => Some(ident.clone()),
- Self::CatchAll(ident, _) => Some(ident.clone()),
- }
- }
- pub fn write_segment(&self) -> TokenStream2 {
- match self {
- Self::Static(segment) => quote! { write!(f, "/{}", #segment)?; },
- Self::Dynamic(ident, _) => quote! { write!(f, "/{}", #ident)?; },
- Self::CatchAll(ident, _) => quote! { #ident.display_route_segements(f)?; },
- }
- }
- pub fn error_name(&self, idx: usize) -> Ident {
- match self {
- Self::Static(_) => static_segment_idx(idx),
- Self::Dynamic(ident, _) => format_ident!("{}ParseError", ident),
- Self::CatchAll(ident, _) => format_ident!("{}ParseError", ident),
- }
- }
- pub fn missing_error_name(&self) -> Option<Ident> {
- match self {
- Self::Dynamic(ident, _) => Some(format_ident!("{}MissingError", ident)),
- _ => None,
- }
- }
- pub fn try_parse(
- &self,
- idx: usize,
- error_enum_name: &Ident,
- error_enum_varient: &Ident,
- inner_parse_enum: &Ident,
- parse_children: TokenStream2,
- ) -> TokenStream2 {
- let error_name = self.error_name(idx);
- match self {
- Self::Static(segment) => {
- quote! {
- {
- let mut segments = segments.clone();
- let parsed = if let Some(#segment) = segments.next() {
- Ok(())
- } else {
- Err(#error_enum_name::#error_enum_varient(#inner_parse_enum::#error_name))
- };
- match parsed {
- Ok(_) => {
- #parse_children
- }
- Err(err) => {
- errors.push(err);
- }
- }
- }
- }
- }
- Self::Dynamic(name, ty) => {
- let missing_error_name = self.missing_error_name().unwrap();
- quote! {
- {
- let mut segments = segments.clone();
- let parsed = if let Some(segment) = segments.next() {
- <#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)))
- } else {
- Err(#error_enum_name::#error_enum_varient(#inner_parse_enum::#missing_error_name))
- };
- match parsed {
- Ok(#name) => {
- #parse_children
- }
- Err(err) => {
- errors.push(err);
- }
- }
- }
- }
- }
- Self::CatchAll(name, ty) => {
- quote! {
- {
- let parsed = {
- let mut segments = segments.clone();
- let segments: Vec<_> = segments.collect();
- <#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)))
- };
- match parsed {
- Ok(#name) => {
- #parse_children
- }
- Err(err) => {
- errors.push(err);
- }
- }
- }
- }
- }
- }
- }
- }
- pub fn static_segment_idx(idx: usize) -> Ident {
- format_ident!("StaticSegment{}ParseError", idx)
- }
- pub fn parse_route_segments<'a>(
- route_span: Span,
- mut fields: impl Iterator<Item = (&'a Ident, &'a Type)>,
- route: &str,
- ) -> syn::Result<(Vec<RouteSegment>, Option<QuerySegment>)> {
- let mut route_segments = Vec::new();
- let (route_string, query) = match route.rsplit_once('?') {
- Some((route, query)) => (route, Some(query)),
- None => (route, None),
- };
- let mut iterator = route_string.split('/');
- // skip the first empty segment
- let first = iterator.next();
- if first != Some("") {
- return Err(syn::Error::new(
- route_span,
- format!(
- "Routes should start with /. Error found in the route '{}'",
- route
- ),
- ));
- }
- while let Some(segment) = iterator.next() {
- if let Some(segment) = segment.strip_prefix(':') {
- let spread = segment.starts_with("...");
- let ident = if spread {
- segment[3..].to_string()
- } else {
- segment.to_string()
- };
- let field = fields.find(|(name, _)| **name == ident);
- let ty = if let Some(field) = field {
- field.1.clone()
- } else {
- return Err(syn::Error::new(
- route_span,
- format!("Could not find a field with the name '{}'", ident,),
- ));
- };
- if spread {
- route_segments.push(RouteSegment::CatchAll(
- Ident::new(&ident, Span::call_site()),
- ty,
- ));
- if iterator.next().is_some() {
- return Err(syn::Error::new(
- route_span,
- "Catch-all route segments must be the last segment in a route. The route segments after the catch-all segment will never be matched.",
- ));
- } else {
- break;
- }
- } else {
- route_segments.push(RouteSegment::Dynamic(
- Ident::new(&ident, Span::call_site()),
- ty,
- ));
- }
- } else {
- route_segments.push(RouteSegment::Static(segment.to_string()));
- }
- }
- // check if the route has a query string
- let parsed_query = match query {
- Some(query) => {
- if let Some(query) = query.strip_prefix(':') {
- let query_ident = Ident::new(query, Span::call_site());
- let field = fields.find(|(name, _)| *name == &query_ident);
- let ty = if let Some((_, ty)) = field {
- ty.clone()
- } else {
- return Err(syn::Error::new(
- route_span,
- format!("Could not find a field with the name '{}'", query_ident),
- ));
- };
- Some(QuerySegment {
- ident: query_ident,
- ty,
- })
- } else {
- None
- }
- }
- None => None,
- };
- Ok((route_segments, parsed_query))
- }
- pub(crate) fn create_error_type(error_name: Ident, segments: &[RouteSegment]) -> TokenStream2 {
- let mut error_variants = Vec::new();
- let mut display_match = Vec::new();
- for (i, segment) in segments.iter().enumerate() {
- let error_name = segment.error_name(i);
- match segment {
- RouteSegment::Static(index) => {
- error_variants.push(quote! { #error_name });
- display_match.push(quote! { Self::#error_name => write!(f, "Static segment '{}' did not match", #index)? });
- }
- RouteSegment::Dynamic(ident, ty) => {
- let missing_error = segment.missing_error_name().unwrap();
- error_variants.push(
- quote! { #error_name(<#ty as dioxus_router::routable::FromRouteSegment>::Err) },
- );
- display_match.push(quote! { Self::#error_name(err) => write!(f, "Dynamic segment '({}:{})' did not match: {}", stringify!(#ident), stringify!(#ty), err)? });
- error_variants.push(quote! { #missing_error });
- display_match.push(quote! { Self::#missing_error => write!(f, "Dynamic segment '({}:{})' was missing", stringify!(#ident), stringify!(#ty))? });
- }
- RouteSegment::CatchAll(ident, ty) => {
- error_variants.push(quote! { #error_name(<#ty as dioxus_router::routable::FromRouteSegments>::Err) });
- display_match.push(quote! { Self::#error_name(err) => write!(f, "Catch-all segment '({}:{})' did not match: {}", stringify!(#ident), stringify!(#ty), err)? });
- }
- }
- }
- quote! {
- #[allow(non_camel_case_types)]
- #[derive(Debug, PartialEq)]
- pub enum #error_name {
- ExtraSegments(String),
- #(#error_variants,)*
- }
- impl std::fmt::Display for #error_name {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- match self {
- Self::ExtraSegments(segments) => {
- write!(f, "Found additional trailing segments: {}", segments)?
- }
- #(#display_match,)*
- }
- Ok(())
- }
- }
- }
- }
|