freya_router_macro/
route.rs

1use proc_macro2::TokenStream as TokenStream2;
2use quote::{
3    format_ident,
4    quote,
5};
6use syn::{
7    Field,
8    Ident,
9    LitStr,
10    Path,
11    Type,
12    parse::{
13        Parse,
14        ParseStream,
15    },
16    parse_quote,
17};
18
19use crate::{
20    hash::HashFragment,
21    layout::{
22        Layout,
23        LayoutId,
24    },
25    nest::{
26        Nest,
27        NestId,
28    },
29    query::QuerySegment,
30    segment::{
31        RouteSegment,
32        create_error_type,
33        parse_route_segments,
34    },
35};
36
37struct RouteArgs {
38    route: LitStr,
39    comp_name: Option<Path>,
40}
41
42impl Parse for RouteArgs {
43    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
44        let route = input.parse::<LitStr>()?;
45
46        Ok(RouteArgs {
47            route,
48            comp_name: {
49                let _ = input.parse::<syn::Token![,]>();
50                input.parse().ok()
51            },
52        })
53    }
54}
55
56struct ChildArgs {
57    route: LitStr,
58}
59
60impl Parse for ChildArgs {
61    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
62        let route = input.parse::<LitStr>()?;
63
64        Ok(ChildArgs { route })
65    }
66}
67
68#[derive(Debug)]
69pub(crate) struct Route {
70    pub route_name: Ident,
71    pub ty: RouteType,
72    pub route: String,
73    pub segments: Vec<RouteSegment>,
74    pub query: Option<QuerySegment>,
75    pub hash: Option<HashFragment>,
76    pub nests: Vec<NestId>,
77    pub layouts: Vec<LayoutId>,
78    fields: Vec<(Ident, Type)>,
79}
80
81impl Route {
82    pub(crate) fn parse(
83        nests: Vec<NestId>,
84        layouts: Vec<LayoutId>,
85        variant: syn::Variant,
86    ) -> syn::Result<Self> {
87        let route_attr = variant
88            .attrs
89            .iter()
90            .find(|attr| attr.path().is_ident("route"));
91        let route;
92        let ty;
93        let route_name = variant.ident.clone();
94        match route_attr {
95            Some(attr) => {
96                let args = attr.parse_args::<RouteArgs>()?;
97                let comp_name = args.comp_name.unwrap_or_else(|| parse_quote!(#route_name));
98                ty = RouteType::Leaf {
99                    component: comp_name,
100                };
101                route = args.route.value();
102            }
103            None => {
104                if let Some(route_attr) = variant
105                    .attrs
106                    .iter()
107                    .find(|attr| attr.path().is_ident("child"))
108                {
109                    let args = route_attr.parse_args::<ChildArgs>()?;
110                    route = args.route.value();
111                    match &variant.fields {
112                        syn::Fields::Named(fields) => {
113                            // find either a field with #[child] or a field named "child"
114                            let child_field = fields.named.iter().find(|f| {
115                                f.attrs.iter().any(|attr| attr.path().is_ident("child"))
116                                    || *f.ident.as_ref().unwrap() == "child"
117                            });
118                            match child_field {
119                                Some(child) => {
120                                    ty = RouteType::Child(Box::new(child.clone()));
121                                }
122                                None => {
123                                    return Err(syn::Error::new_spanned(
124                                        variant.clone(),
125                                        "Routable variants with a #[child(..)] attribute must have a field named \"child\" or a field with a #[child] attribute",
126                                    ));
127                                }
128                            }
129                        }
130                        _ => {
131                            return Err(syn::Error::new_spanned(
132                                variant.clone(),
133                                "Routable variants with a #[child(..)] attribute must have named fields",
134                            ));
135                        }
136                    }
137                } else {
138                    return Err(syn::Error::new_spanned(
139                        variant.clone(),
140                        "Routable variants must either have a #[route(..)] attribute or a #[child(..)] attribute",
141                    ));
142                }
143            }
144        };
145
146        let fields = match &variant.fields {
147            syn::Fields::Named(fields) => fields
148                .named
149                .iter()
150                .filter_map(|f| {
151                    if let RouteType::Child(child) = &ty
152                        && f.ident == child.ident
153                    {
154                        return None;
155                    }
156                    Some((f.ident.clone().unwrap(), f.ty.clone()))
157                })
158                .collect(),
159            _ => Vec::new(),
160        };
161
162        let (route_segments, query, hash) = {
163            parse_route_segments(
164                variant.ident.span(),
165                fields.iter().map(|f| (&f.0, &f.1)),
166                &route,
167            )?
168        };
169
170        Ok(Self {
171            ty,
172            route_name,
173            segments: route_segments,
174            route,
175            query,
176            hash,
177            nests,
178            layouts,
179            fields,
180        })
181    }
182
183    pub(crate) fn display_match(&self, nests: &[Nest]) -> TokenStream2 {
184        let name = &self.route_name;
185        let dynamic_segments = self.dynamic_segments();
186        let write_query: Option<TokenStream2> = self.query.as_ref().map(|q| q.write());
187        let write_hash = self.hash.as_ref().map(|q| q.write());
188
189        match &self.ty {
190            RouteType::Child(field) => {
191                let write_nests = self.nests.iter().map(|id| nests[id.0].write());
192                let write_segments = self.segments.iter().map(|s| s.write_segment());
193                let child = field.ident.as_ref().unwrap();
194                quote! {
195                    Self::#name { #(#dynamic_segments,)* #child } => {
196                        use std::fmt::Display;
197                        use std::fmt::Write;
198                        let mut route = String::new();
199                        {
200                            let f = &mut route;
201                            #(#write_nests)*
202                            #(#write_segments)*
203                        }
204                        if route.ends_with('/') {
205                            route.pop();
206                        }
207                        f.write_str(&route)?;
208                        #child.fmt(f)?;
209                    }
210                }
211            }
212            RouteType::Leaf { .. } => {
213                let write_nests = self.nests.iter().map(|id| nests[id.0].write());
214                let write_segments = self.segments.iter().map(|s| s.write_segment());
215                quote! {
216                    Self::#name { #(#dynamic_segments,)* } => {
217                        #(#write_nests)*
218                        #(#write_segments)*
219                        #write_query
220                        #write_hash
221                    }
222                }
223            }
224        }
225    }
226
227    pub fn routable_match(&self, layouts: &[Layout], nests: &[Nest]) -> TokenStream2 {
228        let name = &self.route_name;
229
230        let mut tokens = TokenStream2::new();
231
232        // First match all layouts
233        for (idx, layout_id) in self.layouts.iter().copied().enumerate() {
234            let render_layout = layouts[layout_id.0].routable_match(nests);
235            let dynamic_segments = self.dynamic_segments();
236            let mut field_name = None;
237            if let RouteType::Child(field) = &self.ty {
238                field_name = field.ident.as_ref();
239            }
240            let field_name = field_name.map(|f| quote!(#f,));
241            // This is a layout
242            tokens.extend(quote! {
243                #[allow(unused)]
244                (#idx, Self::#name { #(#dynamic_segments,)* #field_name .. }) => {
245                    #render_layout
246                }
247            });
248        }
249
250        // Then match the route
251        let last_index = self.layouts.len();
252        tokens.extend(match &self.ty {
253            RouteType::Child(field) => {
254                let field_name = field.ident.as_ref().unwrap();
255                quote! {
256                    #[allow(unused)]
257                    (#last_index.., Self::#name { #field_name, .. }) => {
258                        freya::prelude::Element::from(freya_router::components::child_router::ChildRouter {
259                            route: #field_name,
260                            // Try to parse the current route as a parent route, and then match it as a child route
261                            parse_route_from_root_route: |__route| if let Ok(__route) = __route.parse() {
262                                if let Self::#name { #field_name, .. } = __route {
263                                    Some(#field_name)
264                                } else {
265                                    None
266                                }
267                            } else {
268                                None
269                            },
270                            // Try to parse the child route and turn it into a parent route
271                            format_route_as_root_route: |#field_name| Self::#name { #field_name: #field_name }.to_string(),
272                        })
273                    }
274                }
275            }
276            RouteType::Leaf { component } => {
277                let dynamic_segments = self.dynamic_segments();
278                let dynamic_segments_from_route = self.dynamic_segments();
279                quote! {
280                    #[allow(unused)]
281                    (#last_index, Self::#name { #(#dynamic_segments,)* }) => {
282                        freya::prelude::Element::from(#component {
283                            #(#dynamic_segments_from_route: #dynamic_segments_from_route,)*
284                        })
285                    }
286                }
287            }
288        });
289
290        tokens
291    }
292
293    fn dynamic_segments(&self) -> impl Iterator<Item = TokenStream2> + '_ {
294        self.fields.iter().map(|(name, _)| {
295            quote! {#name}
296        })
297    }
298
299    pub(crate) fn construct(&self, nests: &[Nest], enum_name: Ident) -> TokenStream2 {
300        let segments = self.fields.iter().map(|(name, _)| {
301            let mut from_route = false;
302
303            for id in &self.nests {
304                let nest = &nests[id.0];
305                if nest.dynamic_segments_names().any(|i| &i == name) {
306                    from_route = true
307                }
308            }
309            for segment in &self.segments {
310                if segment.name().as_ref() == Some(name) {
311                    from_route = true
312                }
313            }
314            if let Some(query) = &self.query
315                && query.contains_ident(name)
316            {
317                from_route = true
318            }
319            if let Some(hash) = &self.hash
320                && hash.contains_ident(name)
321            {
322                from_route = true
323            }
324
325            if from_route {
326                quote! {#name}
327            } else {
328                quote! {#name: Default::default()}
329            }
330        });
331        match &self.ty {
332            RouteType::Child(field) => {
333                let name = &self.route_name;
334                let child_name = field.ident.as_ref().unwrap();
335
336                quote! {
337                    #enum_name::#name {
338                        #child_name,
339                        #(#segments,)*
340                    }
341                }
342            }
343            RouteType::Leaf { .. } => {
344                let name = &self.route_name;
345
346                quote! {
347                    #enum_name::#name {
348                        #(#segments,)*
349                    }
350                }
351            }
352        }
353    }
354
355    pub(crate) fn error_ident(&self) -> Ident {
356        format_ident!("{}ParseError", self.route_name)
357    }
358
359    pub(crate) fn error_type(&self) -> TokenStream2 {
360        let error_name = self.error_ident();
361        let child_type = match &self.ty {
362            RouteType::Child(field) => Some(&field.ty),
363            RouteType::Leaf { .. } => None,
364        };
365
366        create_error_type(&self.route, error_name, &self.segments, child_type)
367    }
368
369    pub(crate) fn parse_query(&self) -> TokenStream2 {
370        match &self.query {
371            Some(query) => query.parse(),
372            None => quote! {},
373        }
374    }
375
376    pub(crate) fn parse_hash(&self) -> TokenStream2 {
377        match &self.hash {
378            Some(hash) => hash.parse(),
379            None => quote! {},
380        }
381    }
382}
383
384#[derive(Debug)]
385pub(crate) enum RouteType {
386    Child(Box<Field>),
387    Leaf { component: Path },
388}