use proc_macro2::TokenStream as TokenStream2;
use quote::{
format_ident,
quote,
};
use syn::{
parse::{
Parse,
ParseStream,
},
parse_quote,
Field,
Ident,
LitStr,
Path,
Type,
};
use crate::{
hash::HashFragment,
layout::{
Layout,
LayoutId,
},
nest::{
Nest,
NestId,
},
query::QuerySegment,
segment::{
create_error_type,
parse_route_segments,
RouteSegment,
},
};
struct RouteArgs {
route: LitStr,
comp_name: Option<Path>,
}
impl Parse for RouteArgs {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
let route = input.parse::<LitStr>()?;
Ok(RouteArgs {
route,
comp_name: {
let _ = input.parse::<syn::Token![,]>();
input.parse().ok()
},
})
}
}
struct ChildArgs {
route: LitStr,
}
impl Parse for ChildArgs {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
let route = input.parse::<LitStr>()?;
Ok(ChildArgs { route })
}
}
#[derive(Debug)]
pub(crate) struct Route {
pub route_name: Ident,
pub ty: RouteType,
pub route: String,
pub segments: Vec<RouteSegment>,
pub query: Option<QuerySegment>,
pub hash: Option<HashFragment>,
pub nests: Vec<NestId>,
pub layouts: Vec<LayoutId>,
fields: Vec<(Ident, Type)>,
}
impl Route {
pub(crate) fn parse(
nests: Vec<NestId>,
layouts: Vec<LayoutId>,
variant: syn::Variant,
) -> syn::Result<Self> {
let route_attr = variant
.attrs
.iter()
.find(|attr| attr.path().is_ident("route"));
let route;
let ty;
let route_name = variant.ident.clone();
match route_attr {
Some(attr) => {
let args = attr.parse_args::<RouteArgs>()?;
let comp_name = args.comp_name.unwrap_or_else(|| parse_quote!(#route_name));
ty = RouteType::Leaf {
component: comp_name,
};
route = args.route.value();
}
None => {
if let Some(route_attr) = variant
.attrs
.iter()
.find(|attr| attr.path().is_ident("child"))
{
let args = route_attr.parse_args::<ChildArgs>()?;
route = args.route.value();
match &variant.fields {
syn::Fields::Named(fields) => {
let child_field = fields.named.iter().find(|f| {
f.attrs
.iter()
.any(|attr| attr.path().is_ident("child"))
|| *f.ident.as_ref().unwrap() == "child"
});
match child_field{
Some(child) => {
ty = RouteType::Child(child.clone());
}
None => {
return Err(syn::Error::new_spanned(
variant.clone(),
"Routable variants with a #[child(..)] attribute must have a field named \"child\" or a field with a #[child] attribute",
));
}
}
}
_ => {
return Err(syn::Error::new_spanned(
variant.clone(),
"Routable variants with a #[child(..)] attribute must have named fields",
))
}
}
} else {
return Err(syn::Error::new_spanned(
variant.clone(),
"Routable variants must either have a #[route(..)] attribute or a #[child(..)] attribute",
));
}
}
};
let fields = match &variant.fields {
syn::Fields::Named(fields) => fields
.named
.iter()
.filter_map(|f| {
if let RouteType::Child(child) = &ty {
if f.ident == child.ident {
return None;
}
}
Some((f.ident.clone().unwrap(), f.ty.clone()))
})
.collect(),
_ => Vec::new(),
};
let (route_segments, query, hash) = {
parse_route_segments(
variant.ident.span(),
fields.iter().map(|f| (&f.0, &f.1)),
&route,
)?
};
Ok(Self {
ty,
route_name,
segments: route_segments,
route,
query,
hash,
nests,
layouts,
fields,
})
}
pub(crate) fn display_match(&self, nests: &[Nest]) -> TokenStream2 {
let name = &self.route_name;
let dynamic_segments = self.dynamic_segments();
let write_query: Option<TokenStream2> = self.query.as_ref().map(|q| q.write());
let write_hash = self.hash.as_ref().map(|q| q.write());
match &self.ty {
RouteType::Child(field) => {
let write_nests = self.nests.iter().map(|id| nests[id.0].write());
let write_segments = self.segments.iter().map(|s| s.write_segment());
let child = field.ident.as_ref().unwrap();
quote! {
Self::#name { #(#dynamic_segments,)* #child } => {
use std::fmt::Display;
use std::fmt::Write;
let mut route = String::new();
{
let f = &mut route;
#(#write_nests)*
#(#write_segments)*
}
if route.ends_with('/') {
route.pop();
}
f.write_str(&route)?;
#child.fmt(f)?;
}
}
}
RouteType::Leaf { .. } => {
let write_nests = self.nests.iter().map(|id| nests[id.0].write());
let write_segments = self.segments.iter().map(|s| s.write_segment());
quote! {
Self::#name { #(#dynamic_segments,)* } => {
#(#write_nests)*
#(#write_segments)*
#write_query
#write_hash
}
}
}
}
}
pub fn routable_match(&self, layouts: &[Layout], nests: &[Nest]) -> TokenStream2 {
let name = &self.route_name;
let mut tokens = TokenStream2::new();
for (idx, layout_id) in self.layouts.iter().copied().enumerate() {
let render_layout = layouts[layout_id.0].routable_match(nests);
let dynamic_segments = self.dynamic_segments();
let mut field_name = None;
if let RouteType::Child(field) = &self.ty {
field_name = field.ident.as_ref();
}
let field_name = field_name.map(|f| quote!(#f,));
tokens.extend(quote! {
#[allow(unused)]
(#idx, Self::#name { #(#dynamic_segments,)* #field_name .. }) => {
#render_layout
}
});
}
let last_index = self.layouts.len();
tokens.extend(match &self.ty {
RouteType::Child(field) => {
let field_name = field.ident.as_ref().unwrap();
quote! {
#[allow(unused)]
(#last_index.., Self::#name { #field_name, .. }) => {
rsx! {
freya_router::components::child_router::ChildRouter {
route: #field_name,
parse_route_from_root_route: |__route| if let Ok(__route) = __route.parse() {
if let Self::#name { #field_name, .. } = __route {
Some(#field_name)
} else {
None
}
} else {
None
},
format_route_as_root_route: |#field_name| Self::#name { #field_name: #field_name }.to_string(),
}
}
}
}
}
RouteType::Leaf { component } => {
let dynamic_segments = self.dynamic_segments();
let dynamic_segments_from_route = self.dynamic_segments();
quote! {
#[allow(unused)]
(#last_index, Self::#name { #(#dynamic_segments,)* }) => {
rsx! {
#component {
#(#dynamic_segments_from_route: #dynamic_segments_from_route,)*
}
}
}
}
}
});
tokens
}
fn dynamic_segments(&self) -> impl Iterator<Item = TokenStream2> + '_ {
self.fields.iter().map(|(name, _)| {
quote! {#name}
})
}
pub(crate) fn construct(&self, nests: &[Nest], enum_name: Ident) -> TokenStream2 {
let segments = self.fields.iter().map(|(name, _)| {
let mut from_route = false;
for id in &self.nests {
let nest = &nests[id.0];
if nest.dynamic_segments_names().any(|i| &i == name) {
from_route = true
}
}
for segment in &self.segments {
if segment.name().as_ref() == Some(name) {
from_route = true
}
}
if let Some(query) = &self.query {
if query.contains_ident(name) {
from_route = true
}
}
if let Some(hash) = &self.hash {
if hash.contains_ident(name) {
from_route = true
}
}
if from_route {
quote! {#name}
} else {
quote! {#name: Default::default()}
}
});
match &self.ty {
RouteType::Child(field) => {
let name = &self.route_name;
let child_name = field.ident.as_ref().unwrap();
quote! {
#enum_name::#name {
#child_name,
#(#segments,)*
}
}
}
RouteType::Leaf { .. } => {
let name = &self.route_name;
quote! {
#enum_name::#name {
#(#segments,)*
}
}
}
}
}
pub(crate) fn error_ident(&self) -> Ident {
format_ident!("{}ParseError", self.route_name)
}
pub(crate) fn error_type(&self) -> TokenStream2 {
let error_name = self.error_ident();
let child_type = match &self.ty {
RouteType::Child(field) => Some(&field.ty),
RouteType::Leaf { .. } => None,
};
create_error_type(&self.route, error_name, &self.segments, child_type)
}
pub(crate) fn parse_query(&self) -> TokenStream2 {
match &self.query {
Some(query) => query.parse(),
None => quote! {},
}
}
pub(crate) fn parse_hash(&self) -> TokenStream2 {
match &self.hash {
Some(hash) => hash.parse(),
None => quote! {},
}
}
}
#[derive(Debug)]
pub(crate) enum RouteType {
Child(Field),
Leaf { component: Path },
}