yew_nested_router_macros/
lib.rs

1extern crate core;
2
3use convert_case::{Case, Casing};
4use darling::{
5    util::{Flag, Override},
6    FromField, FromVariant,
7};
8use proc_macro2::{Ident, TokenStream};
9use quote::{format_ident, quote, quote_spanned};
10use syn::{
11    parse_macro_input, punctuated::Punctuated, spanned::Spanned, Data, DataEnum, DeriveInput,
12    Field, Fields, Path, Token, Variant,
13};
14
15/// Get the value of the path segment
16fn to_discriminator(variant: &Variant, opts: &Opts) -> String {
17    if opts.index.is_present() {
18        return "".to_string();
19    }
20
21    opts.rename
22        .clone()
23        .unwrap_or_else(|| variant.ident.to_string().to_lowercase())
24}
25
26#[derive(FromVariant, Default)]
27#[darling(default, attributes(target))]
28struct Opts {
29    index: Flag,
30    rename: Option<String>,
31}
32
33#[derive(FromField, Default)]
34#[darling(default, attributes(target))]
35struct FieldOpts {
36    nested: Flag,
37    value: Flag,
38    default: Option<Override<String>>,
39}
40
41impl FieldOpts {
42    fn validate(self) -> Self {
43        if self.nested.is_present() && self.value.is_present() {
44            panic!("Cannot configure a field as both 'nested' and 'value'");
45        }
46        self
47    }
48}
49
50/// Find the fields which points to the nested target.
51fn nested_field<P>(
52    expect_target: bool,
53    fields: &Punctuated<Field, P>,
54) -> (Vec<&Field>, Option<&Field>) {
55    let mut values = vec![];
56
57    for (i, field) in fields.iter().enumerate() {
58        let opts = FieldOpts::from_field(field)
59            .expect("Unable to parse field options")
60            .validate();
61
62        let last = i == fields.len() - 1;
63
64        if last {
65            if (expect_target || opts.nested.is_present()) && !opts.value.is_present() {
66                // this is the last field, and it is flagged as nested, we can return now
67                return (values, Some(field));
68            }
69        } else {
70            #[allow(clippy::collapsible_else_if)]
71            if opts.nested.is_present() {
72                panic!(
73                    "Only the last field can be a nested target: {}",
74                    field
75                        .ident
76                        .as_ref()
77                        .map(ToString::to_string)
78                        .unwrap_or_else(|| format!("{}", i))
79                );
80            }
81        }
82
83        values.push(field);
84    }
85
86    (values, None)
87}
88
89/// render the full path, this needs to dive into nested entries.
90fn render_path(data: &DataEnum) -> impl Iterator<Item = TokenStream> + '_ {
91    data.variants.iter().map(|v| {
92        let name = &v.ident;
93
94        match &v.fields {
95            Fields::Unit => {
96                quote_spanned! { v.span() =>
97                    Self::#name => {
98                        self.render_self_into(__internal_path);
99                    }
100                }
101            }
102            Fields::Unnamed(fields) => {
103                let (values, nested) = nested_field(true, &fields.unnamed);
104
105                // expand all values to captures of _, expect if the last one is "nested".
106                let values = values
107                    .iter()
108                    .map(|_| quote!(_))
109                    .chain(nested.map(|_| quote!(nested)));
110
111                let nested = match nested.is_some() {
112                    true => {
113                        quote! { nested.render_path_into(__internal_path); }
114                    }
115                    false => {
116                        quote! {}
117                    }
118                };
119
120                quote_spanned! { v.span() =>
121                    Self::#name(#(#values),*) => {
122                        self.render_self_into(__internal_path);
123                        #nested
124                    }
125                }
126            }
127            Fields::Named(fields) => {
128                // we capture the nested field as "nested" and then call it
129                let (capture, nested) = match nested_field(false, &fields.named) {
130                    (_, Some(nested)) => {
131                        let nested = nested.ident.as_ref().expect("Field must have a name");
132                        (
133                            quote! { #nested: nested, .. },
134                            quote! { nested.render_path_into(__internal_path); },
135                        )
136                    }
137                    (_, None) => (quote! {..}, quote! {}),
138                };
139
140                quote_spanned! { v.span() =>
141                    Self::#name{ #capture } => {
142                        self.render_self_into(__internal_path);
143                        #nested
144                    }
145                }
146            }
147        }
148    })
149}
150
151/// generate iterators for capturing and rendering values
152fn capture_values<P, F>(
153    expect_target: bool,
154    fields: &Punctuated<Field, P>,
155    skip_nested: TokenStream,
156    push: F,
157) -> (
158    impl Iterator<Item = TokenStream> + '_,
159    impl Iterator<Item = TokenStream> + '_,
160)
161where
162    F: Fn(&Ident) -> TokenStream + 'static,
163{
164    let (values, nested) = nested_field(expect_target, fields);
165
166    let captures = values
167        .clone()
168        .into_iter()
169        .enumerate()
170        .map(|(i, f)| {
171            let anon = format_ident!("arg_{}", i);
172            let name = f.ident.as_ref().unwrap_or(&anon);
173            quote! { #name }
174        })
175        .chain(nested.map(|_| skip_nested));
176
177    let values = values.into_iter().enumerate().map(move |(i, f)| {
178        let anon = format_ident!("arg_{}", i);
179        let name = f.ident.as_ref().unwrap_or(&anon);
180        push(name)
181    });
182
183    (captures, values)
184}
185
186/// rendering (local) target to its path.
187fn render_self(data: &DataEnum) -> impl Iterator<Item = TokenStream> + '_ {
188    data.variants.iter().map(|v| {
189        let name = &v.ident;
190
191        let opts = Opts::from_variant(v).expect("Unable to parse options");
192        let disc = to_discriminator(v, &opts);
193
194        match &v.fields {
195            // plain route
196            Fields::Unit => {
197                quote_spanned! { v.span() =>
198                    Self::#name => { __internal_path.push(#disc.into()); }
199                }
200            }
201            // nested route
202            Fields::Unnamed(fields) => {
203                let (captures, values) =
204                    capture_values(true, &fields.unnamed, quote! { _ }, |name| {
205                        quote! { __internal_path.push(#name.to_string()); }
206                    });
207
208                quote_spanned! { v.span() =>
209                    Self::#name(#(#captures),*) => {
210                        __internal_path.push(#disc.into());
211                        #(#values)*
212                    }
213                }
214            }
215            // variables
216            Fields::Named(fields) => {
217                let (captures, values) =
218                    capture_values(false, &fields.named, quote! { .. }, |name| {
219                        quote! { __internal_path.push(#name.to_string()); }
220                    });
221
222                quote_spanned! { v.span() =>
223                    Self::#name { #(#captures),* } => {
224                        __internal_path.push(#disc.into());
225                        #(#values)*
226                    }
227                }
228            }
229        }
230    })
231}
232
233/// parsing the path, into a target
234fn parse_path(data: &DataEnum) -> impl Iterator<Item = TokenStream> + '_ {
235    data.variants.iter().map(|v| {
236        let name = &v.ident;
237
238        let opts = Opts::from_variant(v).expect("Unable to parse options");
239        let value = to_discriminator(v, &opts);
240
241        match &v.fields {
242            Fields::Unit => {
243                quote_spanned! { v.span() =>
244                    [#value] => Some(Self::#name)
245                }
246            }
247            Fields::Unnamed(fields) => parse_rules(
248                v,
249                true,
250                &fields.unnamed,
251                |_, cap| from_str(cap),
252                |_, target| quote!(#target),
253                |name, values, target| quote!(Some(Self::#name(#(#values, )* #target))),
254            ),
255            Fields::Named(fields) => parse_rules(
256                v,
257                false,
258                &fields.named,
259                |name, cap| {
260                    let name = name.expect("Must have a name");
261                    let from = from_str(cap);
262                    quote!(#name: #from)
263                },
264                |name, target| {
265                    let name = name.expect("Must have a name");
266                    quote!(#name: #target)
267                },
268                |name, values, target| quote!(Some(Self::#name { #(#values, )* #target})),
269            ),
270        }
271    })
272}
273
274fn parse_rules<P, F1, F2, F3>(
275    v: &Variant,
276    expect_target: bool,
277    fields: &Punctuated<Field, P>,
278    converter: F1,
279    target_converter: F2,
280    ctor: F3,
281) -> TokenStream
282where
283    F1: Fn(Option<&Ident>, &Ident) -> TokenStream,
284    F2: Fn(Option<&Ident>, TokenStream) -> TokenStream,
285    F3: Fn(&Ident, &Vec<TokenStream>, TokenStream) -> TokenStream,
286{
287    let name = &v.ident;
288
289    let (values, nested) = nested_field(expect_target, fields);
290
291    let opts = Opts::from_variant(v).expect("Unable to parse options");
292    let disc = to_discriminator(v, &opts);
293
294    let (captures, values): (Vec<_>, Vec<_>) = values
295        .iter()
296        .enumerate()
297        .map(|(i, f)| {
298            let name = f.ident.as_ref();
299            let cap = f
300                .ident
301                .as_ref()
302                .map(|f| format_ident!("value_{f}"))
303                .unwrap_or_else(|| format_ident!("arg_{i}"));
304            (quote!(#cap), converter(name, &cap))
305        })
306        .unzip();
307
308    match nested {
309        Some(nested) => {
310            let t = &nested.ty;
311
312            let field_opts = FieldOpts::from_field(nested).expect("Unable to parse field options");
313
314            let default = match &field_opts.default {
315                Some(Override::Inherit) => {
316                    let init = ctor(
317                        name,
318                        &values,
319                        target_converter(
320                            nested.ident.as_ref(),
321                            quote!(<#t as core::default::Default>::default()),
322                        ),
323                    );
324                    quote! {
325                        [#disc, #(#captures, )*] => #init,
326                    }
327                }
328                Some(Override::Explicit(default)) => {
329                    let default = syn::parse_str::<Path>(default).expect("Path to function");
330                    let init = ctor(
331                        name,
332                        &values,
333                        target_converter(nested.ident.as_ref(), quote!(#default ())),
334                    );
335                    quote! {
336                        [#disc, #(#captures, )*] => #init,
337                    }
338                }
339                None => {
340                    quote! {}
341                }
342            };
343
344            let init = ctor(
345                name,
346                &values,
347                target_converter(nested.ident.as_ref(), quote!(target)),
348            );
349            quote_spanned! { v.span() =>
350                #default
351                [#disc, #(#captures, )* rest@..] => match #t::parse_path(rest) {
352                    Some(target) => #init,
353                    None => None,
354                }
355            }
356        }
357        None => {
358            let init = ctor(name, &values, quote!());
359            quote_spanned! { v.span() =>
360                [#disc, #(#captures),*] => #init
361            }
362        }
363    }
364}
365
366fn from_str(cap: &Ident) -> TokenStream {
367    quote!({
368        match std::str::FromStr::from_str(#cap) {
369            Ok(v) => v,
370            Err(err) => return None,
371        }
372    })
373}
374
375/// Mapping of variants to its values.
376fn mappers(data: &DataEnum) -> impl Iterator<Item = TokenStream> + '_ {
377    data.variants.iter().map(|v| {
378        let name = &v.ident;
379
380        let fn_base_name = name.to_string().to_case(Case::Snake);
381
382        let map_name = format_ident!("map_{}", fn_base_name);
383        let mapper_name = format_ident!("mapper_{}", fn_base_name);
384        let with_name = format_ident!("with_{}", fn_base_name);
385
386        let none = Punctuated::<Field, Token![,]>::new();
387        let fields = match &v.fields {
388            Fields::Unnamed(fields) => fields.unnamed.iter(),
389            Fields::Named(fields) => fields.named.iter(),
390            Fields::Unit => none.iter(),
391        };
392
393        let (captures, types): (Vec<_>, Vec<_>) = fields
394            .enumerate()
395            .map(|(i, f)| {
396                let cap = f.ident.clone().unwrap_or_else(|| format_ident!("arg_{i}"));
397                let ty = &f.ty;
398                (quote!(#cap), quote!(#ty))
399            })
400            .unzip();
401
402        let (types, init) = match types.len() {
403            1 => {
404                let t = types.first().unwrap();
405                let c = captures.first().unwrap();
406                (quote!(#t), quote!(#c))
407            }
408            _ => (quote!((#(#types),*)), quote!((#(#captures),*))),
409        };
410
411        match &v.fields {
412            Fields::Unit => quote_spanned! { v.span() => },
413            Fields::Unnamed(fields) => {
414                let (_, nested ) = nested_field(true, &fields.unnamed);
415
416                let mapper = match nested {
417                    Some(_) => {
418                        quote!{
419                            #[allow(unused)]
420                            pub fn #mapper_name(_:()) -> yew_nested_router::prelude::Mapper<Self, #types> {
421                                (Self::#map_name, Self::#name).into()
422                            }
423                        }
424                    },
425                    None => quote!(),
426                };
427
428                quote_spanned! { v.span() =>
429                    #[allow(unused)]
430                    pub fn #map_name(self) -> Option<#types> {
431                        match self {
432                            Self::#name(#(#captures),*) => Some(#init),
433                            _ => None,
434                        }
435                    }
436
437                    #mapper
438
439                    #[allow(unused)]
440                    pub fn #with_name<F, R>(f: F) -> impl Fn(Self) -> R
441                    where
442                        F: Fn(#types) -> R,
443                        R: std::default::Default
444                    {
445                        move |s| s.#map_name().map(&f).unwrap_or_default()
446                    }
447                }
448            },
449            Fields::Named(_) => quote_spanned! { v.span() =>
450                #[allow(unused)]
451                pub fn #map_name(self) -> Option<#types> {
452                    match self {
453                        Self::#name{#(#captures),*} => Some(#init),
454                        _ => None,
455                    }
456                }
457
458                #[allow(unused)]
459                pub fn #with_name<F, R>(f: F) -> impl Fn(Self) -> R
460                where
461                    F: Fn(#types) -> R,
462                    R: std::default::Default
463                {
464                    move |s| s.#map_name().map(&f).unwrap_or_default()
465                }
466            },
467        }
468    })
469}
470
471/// create `is_<variant>` functions, which check if the instance is matches the variant, ignoring
472/// additional values.
473fn predicates(data: &DataEnum) -> impl Iterator<Item = TokenStream> + '_ {
474    data.variants.iter().map(|v| {
475        let name = &v.ident;
476
477        let fn_name = name.to_string().to_case(Case::Snake);
478        let fn_name = format_ident!("is_{}", fn_name);
479
480        match &v.fields {
481            Fields::Unit => quote_spanned! { v.span() =>
482                #[allow(unused)]
483                #[allow(clippy::wrong_self_convention)]
484                pub fn #fn_name(self) -> bool {
485                    matches!(self, Self::#name)
486                }
487            },
488            Fields::Unnamed(_) => {
489                quote_spanned! { v.span() =>
490                    #[allow(unused)]
491                    #[allow(clippy::wrong_self_convention)]
492                    pub fn #fn_name(self) -> bool {
493                        matches!(self, Self::#name( .. ))
494                    }
495                }
496            }
497            Fields::Named(_) => {
498                quote_spanned! { v.span() =>
499                    #[allow(unused)]
500                    #[allow(clippy::wrong_self_convention)]
501                    pub fn #fn_name(self) -> bool {
502                        matches!(self, Self::#name{..})
503                    }
504                }
505            }
506        }
507    })
508}
509
510/// Helps implementing the `Target` trait in an enum.
511#[proc_macro_derive(Target, attributes(target))]
512pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
513    let DeriveInput { ident, data, .. } = parse_macro_input!(input);
514
515    let data = match data {
516        Data::Enum(e) => e,
517        _ => panic!("Derive must be used on enum only"),
518    };
519
520    let render_path = render_path(&data);
521    let render_self = render_self(&data);
522    let parse_path = parse_path(&data);
523    let mappers = mappers(&data);
524    let predicates = predicates(&data);
525
526    let output = quote! {
527        impl yew_nested_router::target::Target for #ident {
528
529                fn render_self_into(&self, __internal_path: &mut Vec<String>) {
530                    match self {
531                        #(#render_self ,)*
532                    }
533                }
534
535                fn render_path_into(&self, __internal_path: &mut Vec<String>) {
536                    match self {
537                        #(#render_path ,)*
538                    }
539                }
540
541                fn parse_path(__internal_path: &[&str]) -> Option<Self> {
542                    match __internal_path {
543                        #(#parse_path ,)*
544                        _ => None,
545                    }
546                }
547
548        }
549
550        impl #ident {
551            #(#mappers)*
552            #(#predicates)*
553
554            #[inline]
555            pub fn any(self) -> bool { true }
556        }
557    };
558
559    output.into()
560}