2023-05-13 23:14:30 +00:00
|
|
|
use quote::{format_ident, quote};
|
2023-05-20 21:32:48 +00:00
|
|
|
use syn::{Ident, Type};
|
2023-05-13 23:14:30 +00:00
|
|
|
|
2023-05-14 22:39:42 +00:00
|
|
|
use proc_macro2::{Span, TokenStream as TokenStream2};
|
|
|
|
|
2024-04-25 18:30:25 +00:00
|
|
|
use crate::{hash::HashFragment, query::QuerySegment};
|
2023-05-13 23:14:30 +00:00
|
|
|
|
2023-05-22 16:44:24 +00:00
|
|
|
#[derive(Debug, Clone)]
|
2023-05-13 23:14:30 +00:00
|
|
|
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)?; },
|
2024-04-25 18:30:25 +00:00
|
|
|
Self::Dynamic(ident, _) => quote! {
|
|
|
|
{
|
|
|
|
let as_string = #ident.to_string();
|
|
|
|
write!(f, "/{}", dioxus_router::exports::urlencoding::encode(&as_string))?;
|
|
|
|
}
|
|
|
|
},
|
2023-09-13 16:55:26 +00:00
|
|
|
Self::CatchAll(ident, _) => quote! { #ident.display_route_segments(f)?; },
|
2023-05-13 23:14:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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,
|
2024-03-11 22:03:34 +00:00
|
|
|
error_enum_variant: &Ident,
|
2023-05-13 23:14:30 +00:00
|
|
|
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();
|
2023-07-14 18:40:38 +00:00
|
|
|
let segment = segments.next();
|
2023-09-06 17:56:43 +00:00
|
|
|
let segment = segment.as_deref();
|
2023-07-14 18:40:38 +00:00
|
|
|
let parsed = if let Some(#segment) = segment {
|
2023-05-13 23:14:30 +00:00
|
|
|
Ok(())
|
|
|
|
} else {
|
2024-03-11 22:03:34 +00:00
|
|
|
Err(#error_enum_name::#error_enum_variant(#inner_parse_enum::#error_name(segment.map(|s|s.to_string()).unwrap_or_default())))
|
2023-05-13 23:14:30 +00:00
|
|
|
};
|
|
|
|
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();
|
2023-09-06 17:56:43 +00:00
|
|
|
let segment = segments.next();
|
|
|
|
let parsed = if let Some(segment) = segment.as_deref() {
|
2024-03-11 22:03:34 +00:00
|
|
|
<#ty as dioxus_router::routable::FromRouteSegment>::from_route_segment(segment).map_err(|err| #error_enum_name::#error_enum_variant(#inner_parse_enum::#error_name(err)))
|
2023-05-13 23:14:30 +00:00
|
|
|
} else {
|
2024-03-11 22:03:34 +00:00
|
|
|
Err(#error_enum_name::#error_enum_variant(#inner_parse_enum::#missing_error_name))
|
2023-05-13 23:14:30 +00:00
|
|
|
};
|
|
|
|
match parsed {
|
|
|
|
Ok(#name) => {
|
|
|
|
#parse_children
|
|
|
|
}
|
|
|
|
Err(err) => {
|
|
|
|
errors.push(err);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Self::CatchAll(name, ty) => {
|
|
|
|
quote! {
|
|
|
|
{
|
|
|
|
let parsed = {
|
2023-09-06 17:56:43 +00:00
|
|
|
let remaining_segments: Vec<_> = segments.collect();
|
|
|
|
let mut new_segments: Vec<&str> = Vec::new();
|
|
|
|
for segment in &remaining_segments {
|
|
|
|
new_segments.push(&*segment);
|
|
|
|
}
|
2024-03-11 22:03:34 +00:00
|
|
|
<#ty as dioxus_router::routable::FromRouteSegments>::from_route_segments(&new_segments).map_err(|err| #error_enum_name::#error_enum_variant(#inner_parse_enum::#error_name(err)))
|
2023-05-13 23:14:30 +00:00
|
|
|
};
|
|
|
|
match parsed {
|
|
|
|
Ok(#name) => {
|
|
|
|
#parse_children
|
|
|
|
}
|
|
|
|
Err(err) => {
|
|
|
|
errors.push(err);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn static_segment_idx(idx: usize) -> Ident {
|
|
|
|
format_ident!("StaticSegment{}ParseError", idx)
|
|
|
|
}
|
2023-05-14 22:39:42 +00:00
|
|
|
|
2023-05-22 16:44:24 +00:00
|
|
|
pub fn parse_route_segments<'a>(
|
|
|
|
route_span: Span,
|
2024-04-25 18:30:25 +00:00
|
|
|
mut fields: impl Iterator<Item = (&'a Ident, &'a Type)> + Clone,
|
2023-05-14 22:39:42 +00:00
|
|
|
route: &str,
|
2024-04-25 18:30:25 +00:00
|
|
|
) -> syn::Result<(
|
|
|
|
Vec<RouteSegment>,
|
|
|
|
Option<QuerySegment>,
|
|
|
|
Option<HashFragment>,
|
|
|
|
)> {
|
2023-05-14 22:39:42 +00:00
|
|
|
let mut route_segments = Vec::new();
|
|
|
|
|
2024-04-25 18:30:25 +00:00
|
|
|
let (route_string, hash) = match route.rsplit_once('#') {
|
|
|
|
Some((route, hash)) => (
|
|
|
|
route,
|
|
|
|
Some(HashFragment::parse_from_str(
|
|
|
|
route_span,
|
|
|
|
fields.clone(),
|
|
|
|
hash,
|
|
|
|
)?),
|
|
|
|
),
|
2023-05-14 22:39:42 +00:00
|
|
|
None => (route, None),
|
|
|
|
};
|
2024-04-25 18:30:25 +00:00
|
|
|
|
|
|
|
let (route_string, query) = match route_string.rsplit_once('?') {
|
|
|
|
Some((route, query)) => (
|
|
|
|
route,
|
|
|
|
Some(QuerySegment::parse_from_str(
|
|
|
|
route_span,
|
|
|
|
fields.clone(),
|
|
|
|
query,
|
|
|
|
)?),
|
|
|
|
),
|
|
|
|
None => (route_string, None),
|
|
|
|
};
|
2023-05-14 22:39:42 +00:00
|
|
|
let mut iterator = route_string.split('/');
|
|
|
|
|
|
|
|
// skip the first empty segment
|
|
|
|
let first = iterator.next();
|
|
|
|
if first != Some("") {
|
2023-05-22 16:44:24 +00:00
|
|
|
return Err(syn::Error::new(
|
|
|
|
route_span,
|
2023-05-14 22:39:42 +00:00
|
|
|
format!(
|
|
|
|
"Routes should start with /. Error found in the route '{}'",
|
|
|
|
route
|
|
|
|
),
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
while let Some(segment) = iterator.next() {
|
2023-05-21 00:02:49 +00:00
|
|
|
if let Some(segment) = segment.strip_prefix(':') {
|
2023-07-18 23:34:27 +00:00
|
|
|
let spread = segment.starts_with("..");
|
2023-05-14 22:39:42 +00:00
|
|
|
|
|
|
|
let ident = if spread {
|
2023-07-18 23:34:27 +00:00
|
|
|
segment[2..].to_string()
|
2023-05-14 22:39:42 +00:00
|
|
|
} else {
|
2023-05-21 00:02:49 +00:00
|
|
|
segment.to_string()
|
2023-05-14 22:39:42 +00:00
|
|
|
};
|
|
|
|
|
2024-04-25 17:36:04 +00:00
|
|
|
let field = fields.clone().find(|(name, _)| **name == ident);
|
2023-05-14 22:39:42 +00:00
|
|
|
|
|
|
|
let ty = if let Some(field) = field {
|
2023-05-31 17:11:11 +00:00
|
|
|
field.1.clone()
|
2023-05-14 22:39:42 +00:00
|
|
|
} else {
|
2023-05-22 16:44:24 +00:00
|
|
|
return Err(syn::Error::new(
|
|
|
|
route_span,
|
|
|
|
format!("Could not find a field with the name '{}'", ident,),
|
2023-05-14 22:39:42 +00:00
|
|
|
));
|
|
|
|
};
|
|
|
|
if spread {
|
|
|
|
route_segments.push(RouteSegment::CatchAll(
|
|
|
|
Ident::new(&ident, Span::call_site()),
|
|
|
|
ty,
|
|
|
|
));
|
|
|
|
|
|
|
|
if iterator.next().is_some() {
|
2023-05-22 16:44:24 +00:00
|
|
|
return Err(syn::Error::new(
|
|
|
|
route_span,
|
2023-05-14 22:39:42 +00:00
|
|
|
"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()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-25 18:30:25 +00:00
|
|
|
Ok((route_segments, query, hash))
|
2023-05-14 22:39:42 +00:00
|
|
|
}
|
2023-05-31 17:11:11 +00:00
|
|
|
|
2023-06-28 18:27:11 +00:00
|
|
|
pub(crate) fn create_error_type(
|
|
|
|
error_name: Ident,
|
|
|
|
segments: &[RouteSegment],
|
|
|
|
child_type: Option<&Type>,
|
|
|
|
) -> TokenStream2 {
|
2023-05-31 17:11:11 +00:00
|
|
|
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) => {
|
2023-07-14 18:40:38 +00:00
|
|
|
error_variants.push(quote! { #error_name(String) });
|
2023-07-20 00:20:48 +00:00
|
|
|
display_match.push(quote! { Self::#error_name(found) => write!(f, "Static segment '{}' did not match instead found '{}'", #index, found)? });
|
2023-05-31 17:11:11 +00:00
|
|
|
}
|
|
|
|
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)? });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-28 18:27:11 +00:00
|
|
|
let child_type_variant = child_type
|
|
|
|
.map(|child_type| {
|
|
|
|
quote! { ChildRoute(<#child_type as std::str::FromStr>::Err) }
|
|
|
|
})
|
|
|
|
.into_iter();
|
|
|
|
|
|
|
|
let child_type_error = child_type
|
|
|
|
.map(|_| {
|
|
|
|
quote! {
|
|
|
|
Self::ChildRoute(error) => {
|
|
|
|
write!(f, "{}", error)?
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.into_iter();
|
|
|
|
|
2023-05-31 17:11:11 +00:00
|
|
|
quote! {
|
|
|
|
#[allow(non_camel_case_types)]
|
2024-02-24 13:36:02 +00:00
|
|
|
#[allow(clippy::derive_partial_eq_without_eq)]
|
|
|
|
#[derive(Debug, PartialEq)]
|
2023-05-31 17:11:11 +00:00
|
|
|
pub enum #error_name {
|
|
|
|
ExtraSegments(String),
|
2023-06-28 18:27:11 +00:00
|
|
|
#(#child_type_variant,)*
|
2023-05-31 17:11:11 +00:00
|
|
|
#(#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)?
|
2023-06-28 18:27:11 +00:00
|
|
|
},
|
|
|
|
#(#child_type_error,)*
|
2023-05-31 17:11:11 +00:00
|
|
|
#(#display_match,)*
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|