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 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 if read.starts_with('{') {
132 out.push('{');
133 read = &read[1..];
134 continue;
135 }
136
137 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 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 self.match_fields.push(i);
192
193 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 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#[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 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 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 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 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 let mut matchers = uri_components
492 .iter()
493 .map(|uri_component| {
494 let matches = format_pattern.find_iter(uri_component).collect::<Vec<_>>();
495
496 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 return Matcher::Simple(pattern.into_owned());
505 }
506
507 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 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 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 let field_index = *input.attrs.uri.match_fields.get(next_match_index).unwrap();
554 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 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 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 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 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 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 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 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 match_field_iter.next().unwrap()
694 }) {
695 if matches <= 1 {
696 continue;
697 }
698 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 let field = input.fields.get(*field).unwrap();
727 let ident = match &field.member {
728 Member::Named(ident) => format_ident!("{ident}"), 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}