battler_wamprat_uri_proc_macro/
lib.rs

1extern crate proc_macro;
2
3use std::collections::{
4    BTreeMap,
5    BTreeSet,
6    HashMap,
7};
8
9use battler_wamp_uri::Uri;
10use itertools::Itertools;
11use proc_macro2::{
12    Span,
13    TokenStream,
14};
15use quote::{
16    format_ident,
17    quote,
18    quote_spanned,
19};
20use regex::Regex;
21use syn::{
22    Error,
23    Expr,
24    Field,
25    Ident,
26    Index,
27    ItemStruct,
28    LitStr,
29    Member,
30    Result,
31    Token,
32    Type,
33    ext::IdentExt,
34    parenthesized,
35    parse::{
36        Parse,
37        ParseStream,
38        Parser,
39    },
40    parse_macro_input,
41};
42
43struct InputFieldAttrs {
44    rest: bool,
45}
46
47fn parse_input_field_attrs(field: &Field) -> Result<InputFieldAttrs> {
48    let mut rest = false;
49    for attr in &field.attrs {
50        if attr.path().is_ident("rest") {
51            attr.meta.require_path_only()?.require_ident()?;
52            rest = true;
53        }
54    }
55    Ok(InputFieldAttrs { rest })
56}
57
58struct InputField {
59    member: Member,
60    ty: Type,
61    attrs: InputFieldAttrs,
62}
63
64impl InputField {
65    fn name(&self) -> String {
66        match &self.member {
67            Member::Unnamed(index) => index.index.to_string(),
68            Member::Named(ident) => ident.to_string(),
69        }
70    }
71}
72
73struct UriAttr {
74    fmt: LitStr,
75    args: TokenStream,
76    match_fields: Vec<usize>,
77}
78
79impl UriAttr {
80    fn new(span: Span, fmt: LitStr, fields: &[InputField]) -> Result<Self> {
81        let mut attr = Self {
82            fmt,
83            args: TokenStream::new(),
84            match_fields: Vec::new(),
85        };
86        attr.extract_fields(fields)?;
87        attr.validate_all_fields_matched(span, fields)?;
88        Ok(attr)
89    }
90
91    fn validate_all_fields_matched(&self, span: Span, fields: &[InputField]) -> Result<()> {
92        let matched_fields = self.match_fields.iter().cloned().collect::<BTreeSet<_>>();
93        let unmatched = (0..fields.len())
94            .collect::<BTreeSet<_>>()
95            .difference(&matched_fields)
96            .cloned()
97            .collect::<Vec<_>>();
98        if !unmatched.is_empty() {
99            return Err(Error::new(
100                span,
101                format!(
102                    "uri format string is missing matches for {}",
103                    unmatched
104                        .iter()
105                        .map(|i| {
106                            // SAFETY: Indices stored in match_fields were generated from positions
107                            // of input.fields.
108                            match &fields.get(*i).unwrap().member {
109                                Member::Unnamed(index) => index.index.to_string(),
110                                Member::Named(ident) => ident.to_string(),
111                            }
112                        })
113                        .join(", ")
114                ),
115            ));
116        }
117        Ok(())
118    }
119
120    fn extract_fields(&mut self, fields: &[InputField]) -> Result<()> {
121        let span = self.fmt.span();
122        let fmt = self.fmt.value();
123        let mut read = fmt.as_str();
124        let mut out = String::new();
125
126        while let Some(brace) = read.find('{') {
127            out += &read[..brace + 1];
128            read = &read[brace + 1..];
129
130            // Escaping.
131            if read.starts_with('{') {
132                out.push('{');
133                read = &read[1..];
134                continue;
135            }
136
137            // Parse out the identifier in the format string.
138            let next = match read.chars().next() {
139                Some(next) => next,
140                None => return Err(Error::new(span, "unexpected end of format string")),
141            };
142            let member = match next {
143                '0'..='9' => {
144                    let index = take_integer_from_string(&mut read);
145                    match index.parse::<u32>() {
146                        Ok(index) => Member::Unnamed(Index { index, span }),
147                        Err(_) => {
148                            return Err(Error::new(
149                                span,
150                                format!(
151                                    "format identifier {index} was expected to parse as an integer"
152                                ),
153                            ));
154                        }
155                    }
156                }
157                'a'..='z' | 'A'..='Z' | '_' => {
158                    let mut ident = take_ident_from_string(&mut read);
159                    ident.set_span(span);
160                    Member::Named(ident)
161                }
162                _ => {
163                    return Err(Error::new(
164                        span,
165                        format!("unexpected start of a formatting identifier: {next}"),
166                    ));
167                }
168            };
169
170            // Find the field the identifier corresponds to.
171            //
172            // Each identifier MUST correspond to a field, since we use this format string for
173            // parsing.
174            let (i, field) = fields
175                .iter()
176                .find_position(|field| field.member == member)
177                .ok_or_else(|| {
178                    Error::new(
179                        span,
180                        format!(
181                            "struct does not have any member \"{}\"",
182                            match member {
183                                Member::Unnamed(index) => index.index.to_string(),
184                                Member::Named(ident) => ident.to_string(),
185                            }
186                        ),
187                    )
188                })?;
189
190            // Remember the order in which fields should be matched.
191            self.match_fields.push(i);
192
193            // Add the local variable to the format arguments.
194            let local = match &field.member {
195                Member::Unnamed(index) => format_ident!("_{}", index),
196                Member::Named(ident) => ident.clone(),
197            };
198            self.args.extend(quote_spanned!(span => ,));
199            if field.attrs.rest {
200                self.args.extend(quote_spanned!(span => #local.join(".")));
201            } else {
202                self.args.extend(quote_spanned!(span => #local));
203            }
204        }
205
206        out += read;
207        self.fmt = LitStr::new(&out, self.fmt.span());
208        Ok(())
209    }
210}
211
212struct GeneratorAttr {
213    name: Ident,
214    required_fields: BTreeSet<usize>,
215    fixed_fields: BTreeMap<usize, Expr>,
216    derive: Option<TokenStream>,
217}
218
219fn take_integer_from_string(read: &mut &str) -> String {
220    let mut int = String::new();
221    for (i, ch) in read.char_indices() {
222        match ch {
223            '0'..='9' => int.push(ch),
224            _ => {
225                *read = &read[i..];
226                break;
227            }
228        }
229    }
230    int
231}
232
233fn take_ident_from_string(read: &mut &str) -> Ident {
234    let mut ident = String::new();
235    let raw = read.starts_with("r#");
236    if raw {
237        ident.push_str("r#");
238        *read = &read[2..];
239    }
240    for (i, ch) in read.char_indices() {
241        match ch {
242            'a'..='z' | 'A'..='Z' | '0'..='9' | '_' => ident.push(ch),
243            _ => {
244                *read = &read[i..];
245                break;
246            }
247        }
248    }
249
250    // SAFETY: We only took characters that are valid for an identifier above.
251    Ident::parse_any.parse_str(&ident).unwrap()
252}
253
254struct InputAttrs {
255    uri: UriAttr,
256    generators: Vec<GeneratorAttr>,
257}
258
259struct Input {
260    ident: Ident,
261    attrs: InputAttrs,
262    fields: Vec<InputField>,
263}
264
265impl Parse for Input {
266    fn parse(input: ParseStream) -> Result<Self> {
267        let call_site = Span::call_site();
268        let input = match ItemStruct::parse(input) {
269            Ok(item) => item,
270            Err(_) => return Err(Error::new(call_site, "input must be a struct")),
271        };
272        let ident = input.ident;
273        let fields = input
274            .fields
275            .into_iter()
276            .enumerate()
277            .map(|(i, field)| {
278                let attrs = parse_input_field_attrs(&field)?;
279                Ok(InputField {
280                    member: field.ident.map(Member::Named).unwrap_or_else(|| {
281                        Member::Unnamed(Index {
282                            index: i as u32,
283                            span: call_site,
284                        })
285                    }),
286                    ty: field.ty,
287                    attrs,
288                })
289            })
290            .collect::<Result<Vec<_>>>()?;
291
292        let mut rest = false;
293        for field in &fields {
294            if rest {
295                return Err(Error::new(
296                    call_site,
297                    "no fields allowed after the rest field",
298                ));
299            }
300            rest = field.attrs.rest;
301        }
302
303        let mut uri = None;
304        let mut generators = Vec::new();
305        for attr in input.attrs {
306            if attr.path().is_ident("uri") {
307                if uri.is_some() {
308                    return Err(Error::new(call_site, "only one \"uri\" attribute allowed"));
309                }
310                attr.parse_args_with(|input: ParseStream| {
311                    let fmt = input.parse::<LitStr>()?;
312                    uri = Some(UriAttr::new(call_site, fmt, &fields)?);
313                    Ok(())
314                })?;
315            } else if attr.path().is_ident("generator") {
316                let mut name = None;
317                let mut required = None;
318                let mut fixed = HashMap::new();
319                let mut derive = None;
320                attr.parse_nested_meta(|meta| {
321                    if meta.path.is_ident("require") {
322                        let content;
323                        parenthesized!(content in meta.input);
324                        required = Some(content.parse_terminated(Ident::parse, Token![,])?);
325                    } else if meta.path.is_ident("fixed") {
326                        meta.parse_nested_meta(|meta| {
327                            let ident = meta.path.require_ident()?;
328                            let value = meta.value()?.parse::<Expr>()?;
329                            fixed.insert(ident.clone(), value);
330                            Ok(())
331                        })?;
332                    } else if meta.path.is_ident("derive") {
333                        let content;
334                        parenthesized!(content in meta.input);
335                        derive = Some(content.parse()?);
336                    } else if name.is_some() {
337                        return Err(Error::new(call_site, "only one name allowed"));
338                    } else {
339                        name = Some(meta.path.require_ident()?.clone());
340                    }
341                    Ok(())
342                })?;
343                let name = name.ok_or_else(|| {
344                    Error::new(call_site, "missing name for \"generator\" attribute")
345                })?;
346
347                let required = required
348                    .map(|fields| fields.into_iter().collect::<BTreeSet<_>>())
349                    .unwrap_or_default();
350
351                let get_field_index = |ident: &Ident| {
352                    fields
353                        .iter()
354                        .enumerate()
355                        .find_map(|(i, field)| match &field.member {
356                            Member::Named(member) => (member == ident).then_some(i),
357                            Member::Unnamed(index) => (index.index
358                                == ident.to_string().strip_prefix('_')?.parse::<u32>().ok()?)
359                            .then_some(i),
360                        })
361                        .ok_or_else(|| {
362                            Error::new(ident.span(), format!("struct has no field \"{ident}\""))
363                        })
364                };
365
366                let required = required
367                    .iter()
368                    .map(get_field_index)
369                    .collect::<Result<BTreeSet<_>>>()?;
370
371                let fixed = fixed
372                    .into_iter()
373                    .map(|(ident, value)| {
374                        let index = get_field_index(&ident)?;
375                        Ok((index, value))
376                    })
377                    .collect::<Result<BTreeMap<_, _>>>()?;
378
379                generators.push(GeneratorAttr {
380                    name,
381                    required_fields: required,
382                    fixed_fields: fixed,
383                    derive,
384                })
385            }
386        }
387
388        let uri = uri.ok_or_else(|| Error::new(call_site, "missing required \"uri\" attribute"))?;
389        let attrs = InputAttrs { uri, generators };
390        Ok(Self {
391            ident,
392            attrs,
393            fields,
394        })
395    }
396}
397
398/// Procedural macro for deriving `battler_wamprat_uri::WampUriMatcher` for a struct.
399#[proc_macro_derive(WampUriMatcher, attributes(uri, rest, generator))]
400pub fn derive_wamp_uri_matcher(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
401    let input = parse_macro_input!(input as Input);
402    let call_site = Span::call_site();
403
404    let ident = input.ident;
405    let uri_pattern = &input.attrs.uri.fmt;
406    let uri_pattern_args = &input.attrs.uri.args;
407
408    let format_pattern = Regex::new(r"\{\}").unwrap();
409
410    // Validate that the base pattern gives us a valid URI.
411    let uri_sample = uri_pattern.value();
412    let uri_sample = format_pattern.replace_all(&uri_sample, "foo");
413    if Uri::try_from(uri_sample.into_owned()).is_err() {
414        return proc_macro::TokenStream::from(
415            Error::new(call_site, "invalid uri").into_compile_error(),
416        );
417    }
418
419    enum Matcher {
420        Static(LitStr),
421        Simple(String),
422        Dynamic(String, usize),
423    }
424
425    let uri_components = uri_pattern
426        .value()
427        .split('.')
428        .map(|str| str.to_owned())
429        .collect::<Vec<_>>();
430
431    // Generate the match style and registration URI.
432    let (match_style, uri_for_router) = match (|| {
433        if input.attrs.uri.match_fields.is_empty() {
434            return Ok(("::core::option::Option::None", input.attrs.uri.fmt.value()));
435        }
436        if input.fields.iter().any(|field| field.attrs.rest) {
437            let uri = uri_pattern.value();
438            let uri = format_pattern.replace_all(&uri, "");
439            let prefix_pattern = Regex::new(r"^((?:[^\.]+\.)*[^\.]+)\.+$").unwrap();
440            if let Some(captures) = prefix_pattern.captures(&uri) {
441                // SAFETY: This pattern has one capture group.
442                return Ok((
443                    "::core::option::Option::Some(::battler_wamp::core::match_style::MatchStyle::Prefix)",
444                    captures.get(1).unwrap().as_str().to_owned(),
445                ));
446            } else {
447                return Err(Error::new(
448                    call_site,
449                    "rest field does not make sense for a non-prefix uri",
450                ));
451            }
452        }
453
454        Ok((
455            "::core::option::Option::Some(::battler_wamp::core::match_style::MatchStyle::Wildcard)",
456            uri_components
457                .iter()
458                .map(|uri_component| {
459                    if format_pattern.is_match(uri_component) {
460                        ""
461                    } else {
462                        uri_component
463                    }
464                })
465                .join("."),
466        ))
467    })() {
468        Ok(result) => result,
469        Err(err) => return proc_macro::TokenStream::from(err.into_compile_error()),
470    };
471
472    let is_prefix_style = match_style.contains("Prefix");
473    let match_style = syn::parse_str::<TokenStream>(&match_style).unwrap();
474    let uri_for_router = LitStr::new(&uri_for_router, call_site);
475
476    // Constructing the type from all fields.
477    let mut members = input.fields.iter().map(|field| &field.member).peekable();
478    let constructor_fields = match members.peek() {
479        Some(Member::Named(_)) => quote!( { #(#members),* }),
480        Some(Member::Unnamed(_)) => {
481            let vars = members.map(|member| match member {
482                Member::Unnamed(index) => format_ident!("_{}", index),
483                _ => unreachable!(),
484            });
485            quote!((#(#vars),*))
486        }
487        None => quote!({}),
488    };
489
490    // Generate matchers for each field.
491    let mut matchers = uri_components
492        .iter()
493        .map(|uri_component| {
494            let matches = format_pattern.find_iter(uri_component).collect::<Vec<_>>();
495
496            // No matches, so we just need to match the static string.
497            if matches.is_empty() {
498                return Matcher::Static(LitStr::new(&uri_component, call_site));
499            }
500            let pattern = format_pattern.replace_all(&uri_component, "([^\\.]+)");
501            if pattern == "([^\\.]+)" && matches.len() == 1 {
502                // If we are only matching exactly one member, we can optimize this to just assign
503                // the string value directly.
504                return Matcher::Simple(pattern.into_owned());
505            }
506
507            // Otherwise, we must match a regular expression and assign to multiple members.
508            Matcher::Dynamic(pattern.into_owned(), matches.len())
509        })
510        .collect::<Vec<_>>();
511
512    let requires_regex = matchers.iter().any(|matcher| match matcher {
513        Matcher::Dynamic { .. } => true,
514        _ => false,
515    });
516
517    // If the last field is marked "rest," its pattern should be adjusted.
518    if let Some(field) = input.fields.last() {
519        if field.attrs.rest {
520            let pattern = "(.+)".to_owned();
521            *matchers.last_mut().unwrap() = if requires_regex {
522                Matcher::Dynamic(pattern, 1)
523            } else {
524                Matcher::Simple(pattern)
525            };
526        }
527    }
528
529    let generator = if input.attrs.uri.match_fields.is_empty() {
530        // No fields to match, so we assume we can construct the type directly.
531        quote! {
532            if uri != #uri_pattern {
533                return ::core::result::Result::Err(
534                    ::battler_wamprat_uri::WampUriMatchError::new("uri does not match the static pattern")
535                );
536            }
537        }
538    } else if !requires_regex {
539        let mut parsed = BTreeSet::new();
540        let mut match_index = 0;
541        let matchers = matchers.iter().enumerate().map(|(i, matcher)| match matcher {
542            Matcher::Static(component) => {
543                let error = LitStr::new(&format!("expected {} for component {i}", component.value()), call_site);
544                quote! {
545                    uri_components.get(#i).and_then(|uri_component| if uri_component == &#component { Some(uri_component) } else { None }).ok_or_else(|| ::battler_wamprat_uri::WampUriMatchError::new(#error))?;
546                }
547            }
548            Matcher::Simple(_) => {
549                let next_match_index = match_index;
550                match_index += 1;
551
552                // SAFETY: If every field needs one match, match_index will still be a valid index into match_fields.
553                let field_index = *input.attrs.uri.match_fields.get(next_match_index).unwrap();
554                // SAFETY: Indices stored in match_fields were generated from positions of input.fields.
555                let field = input.fields.get(field_index).unwrap();
556
557                let ty = &field.ty;
558                let field_name = field.name();
559                let error = LitStr::new(&format!("missing component for {field_name}"), call_site);
560                let parse_error = LitStr::new(&format!("invalid component for {field_name}"), call_site);
561
562                let local = match &field.member {
563                    Member::Unnamed(index) => format_ident!("_{}", index),
564                    Member::Named(ident) => ident.clone(),
565                };
566
567                if field.attrs.rest {
568                    quote! {
569                        let #local = uri_components[#i..].iter().map(|uri_component| uri_component.to_string()).collect();
570                    }
571                } else if parsed.insert(field_index) {
572                    quote! {
573                        let #local = uri_components.get(#i).ok_or_else(|| ::battler_wamprat_uri::WampUriMatchError::new(#error))?;
574                        let #local: #ty = ::core::str::FromStr::from_str(*#local).map_err(|err| ::battler_wamprat_uri::WampUriMatchError::new(#parse_error))?;
575                    }
576                } else {
577                    // Not the first time we are seeing this value. We need to compare it against the original.
578                    let inconsistent_error = LitStr::new(&format!("inconsistent value for {field_name} in component {i}"), call_site);
579                    let local_copy = format_ident!("{local}_copy");
580                    quote! {
581                        let #local_copy = uri_components.get(#i).ok_or_else(|| ::battler_wamprat_uri::WampUriMatchError::new(#error))?;
582                        let #local_copy: #ty = ::core::str::FromStr::from_str(*#local_copy).map_err(|err| ::battler_wamprat_uri::WampUriMatchError::new(#parse_error))?;
583                        if #local != #local_copy {
584                            return ::core::result::Result::Err(::battler_wamprat_uri::WampUriMatchError::new(#inconsistent_error));
585                        }
586                    }
587                }
588            }
589            Matcher::Dynamic { .. } => unreachable!(),
590        }).collect::<Vec<_>>();
591        quote! {
592            let uri_components = uri.split('.').collect::<Vec<_>>();
593            #(#matchers)*
594        }
595    } else {
596        // Compile the URI into a regular expression and match all fields in order.
597        let pattern = matchers
598            .iter()
599            .map(|matcher| match matcher {
600                Matcher::Static(component) => component.value(),
601                Matcher::Simple(pattern) | Matcher::Dynamic(pattern, _) => pattern.clone(),
602            })
603            .join("\\.");
604        let pattern = format!("^{pattern}$");
605        let pattern = match Regex::new(&pattern).map_err(|err| {
606            Error::new(
607                call_site,
608                format!("failed to compile regular expression for matching uri: {err}"),
609            )
610        }) {
611            Ok(pattern) => pattern,
612            Err(err) => return proc_macro::TokenStream::from(err.into_compile_error()),
613        };
614        let pattern_literal = LitStr::new(pattern.as_str(), call_site);
615
616        let mut parsed = BTreeSet::new();
617
618        let matchers = input.attrs.uri.match_fields.iter().enumerate().map(|(i, field_index)| (i + 1, field_index)).map(|(i, field_index)| {
619            // SAFETY: Indices stored in match_fields were generated from positions of input.fields.
620            let field = input.fields.get(*field_index).unwrap();
621            let ty = &field.ty;
622
623            let field_name = field.name();
624            let error = LitStr::new(&format!("missing component for {field_name}"), call_site);
625            let parse_error = LitStr::new(&format!("invalid component for {field_name}"), call_site);
626
627            let local = match &field.member {
628                Member::Unnamed(index) => format_ident!("_{}", index),
629                Member::Named(ident) => ident.clone(),
630            };
631
632            if parsed.insert(field_index) {
633                quote! {
634                    let #local = captures.get(#i).ok_or_else(|| ::battler_wamprat_uri::WampUriMatchError::new(#error))?.as_str();
635                    let #local: #ty = ::core::str::FromStr::from_str(#local).map_err(|err| ::battler_wamprat_uri::WampUriMatchError::new(#parse_error))?;
636                }
637            } else {
638                  // Not the first time we are seeing this value. We need to compare it against the original.
639                  let inconsistent_error = LitStr::new(&format!("inconsistent value for {field_name} in component {i}"), call_site);
640                  let local_copy = format_ident!("{local}_copy");
641                  quote! {
642                    let #local_copy = captures.get(#i).ok_or_else(|| ::battler_wamprat_uri::WampUriMatchError::new(#error))?.as_str();
643                    let #local_copy: #ty = ::core::str::FromStr::from_str(#local_copy).map_err(|err| ::battler_wamprat_uri::WampUriMatchError::new(#parse_error))?;
644                      if #local != #local_copy {
645                          return ::core::result::Result::Err(::battler_wamprat_uri::WampUriMatchError::new(#inconsistent_error));
646                      }
647                  }
648            }
649        }).collect::<Vec<_>>();
650
651        quote! {
652            // SAFETY: Pattern was validated at build time.
653            static RE: ::std::sync::LazyLock<::regex::Regex> = ::std::sync::LazyLock::new(|| ::regex::Regex::new(#pattern_literal).unwrap());
654            let captures = RE.captures(uri).ok_or_else(|| ::battler_wamprat_uri::WampUriMatchError::new("uri does not match the configured pattern"))?;
655            #(#matchers)*
656        }
657    };
658
659    let named = input.fields.iter().any(|field| match &field.member {
660        Member::Named(_) => true,
661        Member::Unnamed(_) => false,
662    });
663
664    // Generators currently only assume wildcard URI patterns in their implementation.
665    if !input.attrs.generators.is_empty() {
666        if is_prefix_style {
667            return proc_macro::TokenStream::from(
668                Error::new(
669                    call_site,
670                    "custom generators are not supported for prefix-style URI patterns",
671                )
672                .into_compile_error(),
673            );
674        }
675    }
676
677    let generators = match input
678        .attrs
679        .generators
680        .iter()
681        .map(|generator| {
682            // Validate that fields requiring dynamic matching are not set as wildcards.
683            let mut match_field_iter = input.attrs.uri.match_fields.iter();
684            for matcher in &matchers {
685                let matches = match matcher {
686                    Matcher::Dynamic(_, matches) => *matches,
687                    Matcher::Simple(_) => 1,
688                    Matcher::Static(_) => 0,
689                };
690                for index in (0..matches).map(|_| {
691                    // SAFETY: Matchers were generated from format patterns in the URI pattern string, and
692                    // each format pattern was pushed into the match_fields list.
693                    match_field_iter.next().unwrap()
694                }) {
695                    if matches <= 1 {
696                        continue;
697                    }
698                    // SAFETY: Indices stored in match_fields were generated from positions of input.fields.
699                    let field = input.fields.get(*index).unwrap();
700                    if !generator.fixed_fields.contains_key(index)
701                        && !generator.required_fields.contains(index) {
702                        return Err(Error::new(generator.name.span(), format!("component for {} requires dynamic matching, so it cannot be a wildcard", field.name())));
703                    }
704                }
705            }
706
707            let generator_ident = &generator.name;
708            let field_declarations = input.fields.iter().enumerate().map(|(i, field)| {
709                let ty = &field.ty;
710                let ty = if generator.fixed_fields.contains_key(&i) {
711                    quote!(::core::marker::PhantomData<#ty>)
712                } else if generator.required_fields.contains(&i) {
713                    quote!(#ty)
714                } else if is_prefix_style {
715                    quote!(::core::marker::PhantomData<#ty>)
716                } else {
717                    quote!(::battler_wamprat_uri::Wildcard<#ty>)
718                };
719                match &field.member {
720                    Member::Named(ident) => quote!(pub #ident: #ty),
721                    Member::Unnamed(_) => quote!(pub #ty),
722                }
723            });
724            let fixed_fields = generator.fixed_fields.iter().map(|(field, value)| {
725                // SAFETY: Indices stored in fixed_fields were generated from positions of input.fields.
726                let field = input.fields.get(*field).unwrap();
727                let ident = match &field.member {
728                    Member::Named(ident) => format_ident!("{ident}"), // Required to avoid unused variable warning.
729                    Member::Unnamed(index) => format_ident!("_{}", index.index),
730                };
731                let ty = &field.ty;
732                Ok(quote! {
733                    let #ident = #value;
734                    let #ident: #ty = #value.into();
735                })
736            }).collect::<Result<Vec<_>>>()?;
737            let field_declarations = if named {
738                quote!({ #(#field_declarations,)* })
739            } else {
740                quote!(( #(#field_declarations,)* );)
741            };
742            let derive = match &generator.derive {
743                Some(derive) => quote!(#[derive(#derive)]),
744                None => quote!(),
745            };
746            Ok(quote! {
747                #[doc = "Custom generator for"]
748                #[doc = concat!("[`", stringify!(#ident),"`]")]
749                #[doc = "."]
750                #[allow(unused, dead_code)]
751                #derive
752                pub struct #generator_ident #field_declarations
753
754                impl ::battler_wamprat_uri::WampWildcardUriGenerator<#ident> for #generator_ident {
755                    fn wamp_generate_wildcard_uri(&self) -> ::core::result::Result<::battler_wamp_uri::WildcardUri, ::battler_wamp_uri::InvalidUri> {
756                        ::battler_wamp_uri::WildcardUri::try_from(self.to_string().as_str())
757                    }
758                }
759
760                impl ::core::fmt::Display for #generator_ident {
761                    #[allow(unused, deprecated)]
762                    fn fmt(&self, __formatter: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
763                        let Self #constructor_fields = self;
764                        #(#fixed_fields)*
765                        ::core::write!(__formatter, #uri_pattern #uri_pattern_args)
766                    }
767                }
768            })
769        })
770        .collect::<Result<Vec<_>>>()
771    {
772        Ok(generators) => generators,
773        Err(err) => return proc_macro::TokenStream::from(err.into_compile_error()),
774    };
775
776    quote! {
777        impl ::battler_wamprat_uri::WampUriMatcher for #ident {
778            fn uri_for_router() -> ::battler_wamp_uri::WildcardUri {
779                ::battler_wamp_uri::WildcardUri::try_from(#uri_for_router).unwrap()
780            }
781
782            fn match_style() -> ::core::option::Option<::battler_wamp::core::match_style::MatchStyle> {
783                #match_style
784            }
785
786            #[allow(unused, dead_code)]
787            fn wamp_match_uri(uri: &str) -> ::core::result::Result<Self, ::battler_wamprat_uri::WampUriMatchError> {
788                #generator
789                ::core::result::Result::Ok(Self #constructor_fields)
790            }
791
792            fn wamp_generate_uri(&self) -> ::core::result::Result<::battler_wamp_uri::Uri, ::battler_wamp_uri::InvalidUri> {
793                ::battler_wamp_uri::Uri::try_from(self.to_string().as_str())
794            }
795        }
796
797        impl ::core::fmt::Display for #ident {
798            #[allow(unused, dead_code, deprecated)]
799            fn fmt(&self, __formatter: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
800                let Self #constructor_fields = self;
801                ::core::write!(__formatter, #uri_pattern #uri_pattern_args)
802            }
803        }
804
805        #(#generators)*
806    }.into()
807}