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 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 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 tokens.extend(quote! {
243 #[allow(unused)]
244 (#idx, Self::#name { #(#dynamic_segments,)* #field_name .. }) => {
245 #render_layout
246 }
247 });
248 }
249
250 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 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 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}