Skip to main content

a3s_boot_macros/
lib.rs

1use proc_macro::TokenStream;
2use quote::{format_ident, quote};
3use syn::parse::{Parse, ParseStream};
4use syn::punctuated::Punctuated;
5use syn::{
6    parse_macro_input, Attribute, Expr, Fields, FnArg, GenericArgument, Ident, ImplItem,
7    ImplItemFn, Item, ItemImpl, LitBool, LitInt, LitStr, Pat, PatType, PathArguments, Result,
8    Token, Type,
9};
10
11#[proc_macro_attribute]
12pub fn injectable(attr: TokenStream, item: TokenStream) -> TokenStream {
13    if !attr.is_empty() {
14        return syn::Error::new(
15            proc_macro2::TokenStream::from(attr)
16                .into_iter()
17                .next()
18                .unwrap()
19                .span(),
20            "#[injectable] does not accept arguments",
21        )
22        .to_compile_error()
23        .into();
24    }
25
26    let item = parse_macro_input!(item as Item);
27    match item {
28        Item::Struct(item_struct) => expand_injectable(item_struct)
29            .unwrap_or_else(syn::Error::into_compile_error)
30            .into(),
31        item => syn::Error::new_spanned(item, "#[injectable] can only be used on structs")
32            .to_compile_error()
33            .into(),
34    }
35}
36
37#[proc_macro_attribute]
38pub fn controller(attr: TokenStream, item: TokenStream) -> TokenStream {
39    let prefix = parse_macro_input!(attr as LitStr);
40    let item_impl = parse_macro_input!(item as ItemImpl);
41
42    expand_controller(prefix, item_impl)
43        .unwrap_or_else(syn::Error::into_compile_error)
44        .into()
45}
46
47#[proc_macro_attribute]
48pub fn websocket_gateway(attr: TokenStream, item: TokenStream) -> TokenStream {
49    let path = parse_macro_input!(attr as LitStr);
50    let item_impl = parse_macro_input!(item as ItemImpl);
51
52    expand_websocket_gateway(path, item_impl)
53        .unwrap_or_else(syn::Error::into_compile_error)
54        .into()
55}
56
57#[proc_macro_attribute]
58pub fn message_controller(attr: TokenStream, item: TokenStream) -> TokenStream {
59    if !attr.is_empty() {
60        return syn::Error::new(
61            proc_macro2::TokenStream::from(attr)
62                .into_iter()
63                .next()
64                .unwrap()
65                .span(),
66            "#[message_controller] does not accept arguments",
67        )
68        .to_compile_error()
69        .into();
70    }
71
72    let item_impl = parse_macro_input!(item as ItemImpl);
73    expand_message_controller(item_impl)
74        .unwrap_or_else(syn::Error::into_compile_error)
75        .into()
76}
77
78#[proc_macro_attribute]
79pub fn event_listener(attr: TokenStream, item: TokenStream) -> TokenStream {
80    if !attr.is_empty() {
81        return syn::Error::new(
82            proc_macro2::TokenStream::from(attr)
83                .into_iter()
84                .next()
85                .unwrap()
86                .span(),
87            "#[event_listener] does not accept arguments",
88        )
89        .to_compile_error()
90        .into();
91    }
92
93    let item_impl = parse_macro_input!(item as ItemImpl);
94    expand_event_listener(item_impl)
95        .unwrap_or_else(syn::Error::into_compile_error)
96        .into()
97}
98
99#[proc_macro_attribute]
100pub fn schedule(attr: TokenStream, item: TokenStream) -> TokenStream {
101    if !attr.is_empty() {
102        return syn::Error::new(
103            proc_macro2::TokenStream::from(attr)
104                .into_iter()
105                .next()
106                .unwrap()
107                .span(),
108            "#[schedule] does not accept arguments",
109        )
110        .to_compile_error()
111        .into();
112    }
113
114    let item_impl = parse_macro_input!(item as ItemImpl);
115    expand_schedule(item_impl)
116        .unwrap_or_else(syn::Error::into_compile_error)
117        .into()
118}
119
120#[proc_macro_attribute]
121pub fn subscribe_message(_attr: TokenStream, item: TokenStream) -> TokenStream {
122    websocket_attribute_outside_gateway("subscribe_message", item)
123}
124
125#[proc_macro_attribute]
126pub fn cron(_attr: TokenStream, item: TokenStream) -> TokenStream {
127    schedule_attribute_outside_schedule("cron", item)
128}
129
130#[proc_macro_attribute]
131pub fn interval(_attr: TokenStream, item: TokenStream) -> TokenStream {
132    schedule_attribute_outside_schedule("interval", item)
133}
134
135#[proc_macro_attribute]
136pub fn timeout(_attr: TokenStream, item: TokenStream) -> TokenStream {
137    schedule_attribute_outside_schedule("timeout", item)
138}
139
140#[proc_macro_attribute]
141pub fn message_pattern(_attr: TokenStream, item: TokenStream) -> TokenStream {
142    message_attribute_outside_controller("message_pattern", item)
143}
144
145#[proc_macro_attribute]
146pub fn event_pattern(_attr: TokenStream, item: TokenStream) -> TokenStream {
147    message_attribute_outside_controller("event_pattern", item)
148}
149
150#[proc_macro_attribute]
151pub fn on_event(_attr: TokenStream, item: TokenStream) -> TokenStream {
152    event_attribute_outside_listener("on_event", item)
153}
154
155#[proc_macro_attribute]
156pub fn all(_attr: TokenStream, item: TokenStream) -> TokenStream {
157    route_attribute_outside_controller("all", item)
158}
159
160#[proc_macro_attribute]
161pub fn get(_attr: TokenStream, item: TokenStream) -> TokenStream {
162    route_attribute_outside_controller("get", item)
163}
164
165#[proc_macro_attribute]
166pub fn sse(_attr: TokenStream, item: TokenStream) -> TokenStream {
167    route_attribute_outside_controller("sse", item)
168}
169
170#[proc_macro_attribute]
171pub fn post(_attr: TokenStream, item: TokenStream) -> TokenStream {
172    route_attribute_outside_controller("post", item)
173}
174
175#[proc_macro_attribute]
176pub fn put(_attr: TokenStream, item: TokenStream) -> TokenStream {
177    route_attribute_outside_controller("put", item)
178}
179
180#[proc_macro_attribute]
181pub fn patch(_attr: TokenStream, item: TokenStream) -> TokenStream {
182    route_attribute_outside_controller("patch", item)
183}
184
185#[proc_macro_attribute]
186pub fn delete(_attr: TokenStream, item: TokenStream) -> TokenStream {
187    route_attribute_outside_controller("delete", item)
188}
189
190#[proc_macro_attribute]
191pub fn options(_attr: TokenStream, item: TokenStream) -> TokenStream {
192    route_attribute_outside_controller("options", item)
193}
194
195#[proc_macro_attribute]
196pub fn head(_attr: TokenStream, item: TokenStream) -> TokenStream {
197    route_attribute_outside_controller("head", item)
198}
199
200#[proc_macro_attribute]
201pub fn get_json(_attr: TokenStream, item: TokenStream) -> TokenStream {
202    route_attribute_outside_controller("get_json", item)
203}
204
205#[proc_macro_attribute]
206pub fn post_json(_attr: TokenStream, item: TokenStream) -> TokenStream {
207    route_attribute_outside_controller("post_json", item)
208}
209
210#[proc_macro_attribute]
211pub fn put_json(_attr: TokenStream, item: TokenStream) -> TokenStream {
212    route_attribute_outside_controller("put_json", item)
213}
214
215#[proc_macro_attribute]
216pub fn patch_json(_attr: TokenStream, item: TokenStream) -> TokenStream {
217    route_attribute_outside_controller("patch_json", item)
218}
219
220#[proc_macro_attribute]
221pub fn delete_json(_attr: TokenStream, item: TokenStream) -> TokenStream {
222    route_attribute_outside_controller("delete_json", item)
223}
224
225#[proc_macro_attribute]
226pub fn body(_attr: TokenStream, item: TokenStream) -> TokenStream {
227    extractor_attribute_outside_controller("body", item)
228}
229
230#[proc_macro_attribute]
231pub fn request(_attr: TokenStream, item: TokenStream) -> TokenStream {
232    extractor_attribute_outside_controller("request", item)
233}
234
235#[proc_macro_attribute]
236pub fn param(_attr: TokenStream, item: TokenStream) -> TokenStream {
237    extractor_attribute_outside_controller("param", item)
238}
239
240#[proc_macro_attribute]
241pub fn params(_attr: TokenStream, item: TokenStream) -> TokenStream {
242    extractor_attribute_outside_controller("params", item)
243}
244
245#[proc_macro_attribute]
246pub fn query(_attr: TokenStream, item: TokenStream) -> TokenStream {
247    extractor_attribute_outside_controller("query", item)
248}
249
250#[proc_macro_attribute]
251pub fn header(_attr: TokenStream, item: TokenStream) -> TokenStream {
252    extractor_attribute_outside_controller("header", item)
253}
254
255#[proc_macro_attribute]
256pub fn headers(_attr: TokenStream, item: TokenStream) -> TokenStream {
257    extractor_attribute_outside_controller("headers", item)
258}
259
260#[proc_macro_attribute]
261pub fn host_param(_attr: TokenStream, item: TokenStream) -> TokenStream {
262    extractor_attribute_outside_controller("host_param", item)
263}
264
265#[proc_macro_attribute]
266pub fn ip(_attr: TokenStream, item: TokenStream) -> TokenStream {
267    extractor_attribute_outside_controller("ip", item)
268}
269
270#[proc_macro_attribute]
271pub fn extract(_attr: TokenStream, item: TokenStream) -> TokenStream {
272    extractor_attribute_outside_controller("extract", item)
273}
274
275#[proc_macro_attribute]
276pub fn host(_attr: TokenStream, item: TokenStream) -> TokenStream {
277    host_attribute_outside_controller("host", item)
278}
279
280#[proc_macro_attribute]
281pub fn version(_attr: TokenStream, item: TokenStream) -> TokenStream {
282    version_attribute_outside_controller("version", item)
283}
284
285#[proc_macro_attribute]
286pub fn versions(_attr: TokenStream, item: TokenStream) -> TokenStream {
287    version_attribute_outside_controller("versions", item)
288}
289
290#[proc_macro_attribute]
291pub fn version_neutral(_attr: TokenStream, item: TokenStream) -> TokenStream {
292    version_attribute_outside_controller("version_neutral", item)
293}
294
295#[proc_macro_attribute]
296pub fn serialize(_attr: TokenStream, item: TokenStream) -> TokenStream {
297    serialization_attribute_outside_controller("serialize", item)
298}
299
300#[proc_macro_attribute]
301pub fn tag(_attr: TokenStream, item: TokenStream) -> TokenStream {
302    openapi_attribute_outside_controller("tag", item)
303}
304
305#[proc_macro_attribute]
306pub fn operation(_attr: TokenStream, item: TokenStream) -> TokenStream {
307    openapi_attribute_outside_controller("operation", item)
308}
309
310#[proc_macro_attribute]
311pub fn response(_attr: TokenStream, item: TokenStream) -> TokenStream {
312    openapi_attribute_outside_controller("response", item)
313}
314
315#[proc_macro_attribute]
316pub fn request_body(_attr: TokenStream, item: TokenStream) -> TokenStream {
317    openapi_attribute_outside_controller("request_body", item)
318}
319
320#[proc_macro_attribute]
321pub fn bearer_auth(_attr: TokenStream, item: TokenStream) -> TokenStream {
322    openapi_attribute_outside_controller("bearer_auth", item)
323}
324
325#[proc_macro_attribute]
326pub fn hide_from_openapi(_attr: TokenStream, item: TokenStream) -> TokenStream {
327    openapi_attribute_outside_controller("hide_from_openapi", item)
328}
329
330#[proc_macro_attribute]
331pub fn redirect(_attr: TokenStream, item: TokenStream) -> TokenStream {
332    response_attribute_outside_controller("redirect", item)
333}
334
335#[proc_macro_attribute]
336pub fn http_code(_attr: TokenStream, item: TokenStream) -> TokenStream {
337    http_code_attribute_outside_controller("http_code", item)
338}
339
340#[proc_macro_attribute]
341pub fn metadata(_attr: TokenStream, item: TokenStream) -> TokenStream {
342    metadata_attribute_outside_controller("metadata", item)
343}
344
345#[proc_macro_attribute]
346pub fn validate(_attr: TokenStream, item: TokenStream) -> TokenStream {
347    validation_attribute_outside_controller("validate", item)
348}
349
350#[proc_macro_attribute]
351pub fn skip_validation(_attr: TokenStream, item: TokenStream) -> TokenStream {
352    validation_attribute_outside_controller("skip_validation", item)
353}
354
355#[proc_macro_attribute]
356pub fn use_guard(_attr: TokenStream, item: TokenStream) -> TokenStream {
357    pipeline_attribute_outside_controller("use_guard", item)
358}
359
360#[proc_macro_attribute]
361pub fn use_interceptor(_attr: TokenStream, item: TokenStream) -> TokenStream {
362    pipeline_attribute_outside_controller("use_interceptor", item)
363}
364
365#[proc_macro_attribute]
366pub fn use_filter(_attr: TokenStream, item: TokenStream) -> TokenStream {
367    pipeline_attribute_outside_controller("use_filter", item)
368}
369
370#[proc_macro_attribute]
371pub fn use_pipe(_attr: TokenStream, item: TokenStream) -> TokenStream {
372    pipeline_attribute_outside_controller("use_pipe", item)
373}
374
375fn expand_injectable(mut item_struct: syn::ItemStruct) -> Result<proc_macro2::TokenStream> {
376    let constructor = injectable_constructor(&mut item_struct)?;
377    let ident = &item_struct.ident;
378    let mut from_module_ref_generics = item_struct.generics.clone();
379    from_module_ref_generics
380        .make_where_clause()
381        .predicates
382        .push(syn::parse_quote!(Self: ::std::marker::Send + ::std::marker::Sync + 'static));
383    let (from_impl_generics, _, from_where_clause) = from_module_ref_generics.split_for_impl();
384    let (impl_generics, ty_generics, where_clause) = item_struct.generics.split_for_impl();
385
386    Ok(quote! {
387        #item_struct
388
389        impl #from_impl_generics ::a3s_boot::FromModuleRef for #ident #ty_generics #from_where_clause {
390            fn from_module_ref(module_ref: &::a3s_boot::ModuleRef) -> ::a3s_boot::Result<Self> {
391                #constructor
392            }
393        }
394
395        impl #impl_generics #ident #ty_generics #where_clause {
396            pub fn provider() -> ::a3s_boot::ProviderDefinition
397            where
398                Self: ::a3s_boot::FromModuleRef,
399            {
400                ::a3s_boot::ProviderDefinition::injectable::<Self>()
401            }
402
403            pub fn named_provider(token: impl Into<String>) -> ::a3s_boot::ProviderDefinition
404            where
405                Self: ::a3s_boot::FromModuleRef,
406            {
407                ::a3s_boot::ProviderDefinition::named_injectable::<Self>(token)
408            }
409
410            pub fn request_scoped_provider() -> ::a3s_boot::ProviderDefinition
411            where
412                Self: ::a3s_boot::FromModuleRef,
413            {
414                ::a3s_boot::ProviderDefinition::request_scoped_injectable::<Self>()
415            }
416
417            pub fn named_request_scoped_provider(token: impl Into<String>) -> ::a3s_boot::ProviderDefinition
418            where
419                Self: ::a3s_boot::FromModuleRef,
420            {
421                ::a3s_boot::ProviderDefinition::named_request_scoped_injectable::<Self>(token)
422            }
423
424            pub fn transient_provider() -> ::a3s_boot::ProviderDefinition
425            where
426                Self: ::a3s_boot::FromModuleRef,
427            {
428                ::a3s_boot::ProviderDefinition::transient_injectable::<Self>()
429            }
430
431            pub fn named_transient_provider(token: impl Into<String>) -> ::a3s_boot::ProviderDefinition
432            where
433                Self: ::a3s_boot::FromModuleRef,
434            {
435                ::a3s_boot::ProviderDefinition::named_transient_injectable::<Self>(token)
436            }
437
438            pub fn into_provider(self) -> ::a3s_boot::ProviderDefinition
439            where
440                Self: ::std::marker::Send + ::std::marker::Sync + 'static,
441            {
442                ::a3s_boot::ProviderDefinition::singleton(self)
443            }
444
445            pub fn into_named_provider(self, token: impl Into<String>) -> ::a3s_boot::ProviderDefinition
446            where
447                Self: ::std::marker::Send + ::std::marker::Sync + 'static,
448            {
449                ::a3s_boot::ProviderDefinition::named_singleton(token, self)
450            }
451
452            pub fn from_arc_provider(value: ::std::sync::Arc<Self>) -> ::a3s_boot::ProviderDefinition
453            where
454                Self: ::std::marker::Send + ::std::marker::Sync + 'static,
455            {
456                ::a3s_boot::ProviderDefinition::from_arc(value)
457            }
458
459            pub fn from_named_arc_provider(
460                token: impl Into<String>,
461                value: ::std::sync::Arc<Self>,
462            ) -> ::a3s_boot::ProviderDefinition
463            where
464                Self: ::std::marker::Send + ::std::marker::Sync + 'static,
465            {
466                ::a3s_boot::ProviderDefinition::named_from_arc(token, value)
467            }
468        }
469    })
470}
471
472fn injectable_constructor(item_struct: &mut syn::ItemStruct) -> Result<proc_macro2::TokenStream> {
473    match &mut item_struct.fields {
474        Fields::Unit => Ok(quote! { Ok(Self) }),
475        Fields::Named(fields) => {
476            let mut values = Vec::new();
477            for field in fields.named.iter_mut() {
478                let ident = field.ident.clone().ok_or_else(|| {
479                    syn::Error::new_spanned(&*field, "#[injectable] requires named fields")
480                })?;
481                let token = take_field_inject_attr(&mut field.attrs)?;
482                let value = match injectable_field_dependency(&field.ty) {
483                    Some(InjectableFieldDependency::Required(inner)) => match token {
484                        Some(token) => quote! { module_ref.get_named::<#inner>(#token)? },
485                        None => quote! { module_ref.get::<#inner>()? },
486                    },
487                    Some(InjectableFieldDependency::Optional(inner)) => match token {
488                        Some(token) => quote! { module_ref.get_optional_named::<#inner>(#token)? },
489                        None => quote! { module_ref.get_optional::<#inner>()? },
490                    },
491                    None => {
492                        return Err(syn::Error::new_spanned(
493                            &field.ty,
494                            "#[injectable] fields must be Arc<T> or Option<Arc<T>>",
495                        ));
496                    }
497                };
498                values.push(quote! { #ident: #value });
499            }
500
501            Ok(quote! {
502                Ok(Self {
503                    #(#values,)*
504                })
505            })
506        }
507        Fields::Unnamed(fields) => Err(syn::Error::new_spanned(
508            fields,
509            "#[injectable] auto-wiring supports unit structs and structs with named fields",
510        )),
511    }
512}
513
514enum InjectableFieldDependency<'a> {
515    Required(&'a Type),
516    Optional(&'a Type),
517}
518
519fn injectable_field_dependency(field_type: &Type) -> Option<InjectableFieldDependency<'_>> {
520    if let Some(inner) = single_type_argument(field_type, "Arc") {
521        return Some(InjectableFieldDependency::Required(inner));
522    }
523
524    let inner = single_type_argument(field_type, "Option")?;
525    let inner = single_type_argument(inner, "Arc")?;
526    Some(InjectableFieldDependency::Optional(inner))
527}
528
529fn single_type_argument<'a>(field_type: &'a Type, outer: &str) -> Option<&'a Type> {
530    let Type::Path(type_path) = field_type else {
531        return None;
532    };
533    let segment = type_path.path.segments.last()?;
534    if segment.ident != outer {
535        return None;
536    }
537    let PathArguments::AngleBracketed(arguments) = &segment.arguments else {
538        return None;
539    };
540    if arguments.args.len() != 1 {
541        return None;
542    }
543    let Some(GenericArgument::Type(inner)) = arguments.args.first() else {
544        return None;
545    };
546    Some(inner)
547}
548
549fn take_field_inject_attr(attrs: &mut Vec<Attribute>) -> Result<Option<LitStr>> {
550    let mut kept = Vec::with_capacity(attrs.len());
551    let mut token = None;
552
553    for attr in attrs.drain(..) {
554        if is_field_inject_attr(&attr) {
555            if token.is_some() {
556                return Err(syn::Error::new_spanned(
557                    attr,
558                    "duplicate #[inject(...)] field attribute",
559                ));
560            }
561            token = Some(attr.parse_args::<LitStr>().map_err(|_| {
562                syn::Error::new_spanned(&attr, "#[inject(...)] expects a string token")
563            })?);
564        } else {
565            kept.push(attr);
566        }
567    }
568
569    *attrs = kept;
570    Ok(token)
571}
572
573fn is_field_inject_attr(attr: &Attribute) -> bool {
574    attr.path()
575        .segments
576        .last()
577        .map(|segment| segment.ident == "inject")
578        .unwrap_or(false)
579}
580
581fn expand_controller(prefix: LitStr, mut item_impl: ItemImpl) -> Result<proc_macro2::TokenStream> {
582    if item_impl.trait_.is_some() {
583        return Err(syn::Error::new_spanned(
584            &item_impl,
585            "#[controller] can only be used on inherent impl blocks",
586        ));
587    }
588
589    let self_ty = item_impl.self_ty.clone();
590    let mut routes = Vec::new();
591    let mut errors: Option<syn::Error> = None;
592    let (clean_impl_attrs, controller_validation, controller_validation_errors) =
593        take_controller_validation_attrs(&item_impl.attrs);
594    let (clean_impl_attrs, controller_openapi, controller_openapi_errors) =
595        take_controller_openapi_attrs(&clean_impl_attrs);
596    let (clean_impl_attrs, controller_metadata, controller_metadata_errors) =
597        take_controller_metadata_attrs(&clean_impl_attrs);
598    let (clean_impl_attrs, controller_pipeline, controller_pipeline_errors) =
599        take_controller_pipeline_attrs(&clean_impl_attrs);
600    let (clean_impl_attrs, controller_host, controller_host_errors) =
601        take_controller_host_attrs(&clean_impl_attrs);
602    let (clean_impl_attrs, controller_version, controller_version_errors) =
603        take_controller_version_attrs(&clean_impl_attrs);
604    let (clean_impl_attrs, controller_serialization, controller_serialization_errors) =
605        take_controller_serialization_attrs(&clean_impl_attrs);
606    item_impl.attrs = clean_impl_attrs;
607    for error in controller_validation_errors {
608        push_error(&mut errors, error);
609    }
610    for error in controller_openapi_errors {
611        push_error(&mut errors, error);
612    }
613    for error in controller_metadata_errors {
614        push_error(&mut errors, error);
615    }
616    for error in controller_pipeline_errors {
617        push_error(&mut errors, error);
618    }
619    for error in controller_host_errors {
620        push_error(&mut errors, error);
621    }
622    for error in controller_version_errors {
623        push_error(&mut errors, error);
624    }
625    for error in controller_serialization_errors {
626        push_error(&mut errors, error);
627    }
628    let controller_openapi = controller_openapi.tokens();
629    let controller_metadata = controller_metadata.tokens();
630    let controller_pipeline = controller_pipeline.tokens();
631    let controller_host = controller_host.tokens();
632    let controller_version = controller_version.tokens();
633    let controller_serialization = controller_serialization.tokens();
634
635    for item in &mut item_impl.items {
636        let ImplItem::Fn(method) = item else {
637            continue;
638        };
639
640        let (clean_attrs, method_routes, route_errors) = take_route_attrs(&method.attrs);
641        let (clean_attrs, route_validation, validation_errors) =
642            take_route_validation_attrs(&clean_attrs);
643        let (clean_attrs, openapi_specs, openapi_errors) = take_route_openapi_attrs(&clean_attrs);
644        let (clean_attrs, metadata_specs, metadata_errors) =
645            take_route_metadata_attrs(&clean_attrs);
646        let (clean_attrs, http_code, http_code_errors) = take_route_http_code_attrs(&clean_attrs);
647        let (clean_attrs, response_specs, response_errors) =
648            take_route_response_attrs(&clean_attrs);
649        let (clean_attrs, pipeline_specs, pipeline_errors) =
650            take_route_pipeline_attrs(&clean_attrs);
651        let (clean_attrs, host_specs, host_errors) = take_route_host_attrs(&clean_attrs);
652        let (clean_attrs, version_specs, version_errors) = take_route_version_attrs(&clean_attrs);
653        let (clean_attrs, serialization_specs, serialization_errors) =
654            take_route_serialization_attrs(&clean_attrs);
655        method.attrs = clean_attrs;
656        for error in route_errors {
657            push_error(&mut errors, error);
658        }
659        for error in validation_errors {
660            push_error(&mut errors, error);
661        }
662        for error in openapi_errors {
663            push_error(&mut errors, error);
664        }
665        for error in metadata_errors {
666            push_error(&mut errors, error);
667        }
668        for error in http_code_errors {
669            push_error(&mut errors, error);
670        }
671        for error in response_errors {
672            push_error(&mut errors, error);
673        }
674        for error in pipeline_errors {
675            push_error(&mut errors, error);
676        }
677        for error in host_errors {
678            push_error(&mut errors, error);
679        }
680        for error in version_errors {
681            push_error(&mut errors, error);
682        }
683        for error in serialization_errors {
684            push_error(&mut errors, error);
685        }
686        if method_routes.is_empty() && !openapi_specs.is_empty() {
687            push_error(
688                &mut errors,
689                syn::Error::new_spanned(
690                    &method.sig.ident,
691                    "OpenAPI route attributes must be used on route methods",
692                ),
693            );
694        }
695        if method_routes.is_empty() && !response_specs.is_empty() {
696            push_error(
697                &mut errors,
698                syn::Error::new_spanned(
699                    &method.sig.ident,
700                    "response route attributes must be used on route methods",
701                ),
702            );
703        }
704        if method_routes.is_empty() && !pipeline_specs.is_empty() {
705            push_error(
706                &mut errors,
707                syn::Error::new_spanned(
708                    &method.sig.ident,
709                    "pipeline route attributes must be used on route methods",
710                ),
711            );
712        }
713        if method_routes.is_empty() && host_specs.is_some() {
714            push_error(
715                &mut errors,
716                syn::Error::new_spanned(
717                    &method.sig.ident,
718                    "host route attributes must be used on route methods",
719                ),
720            );
721        }
722        if method_routes.is_empty() && version_specs.is_some() {
723            push_error(
724                &mut errors,
725                syn::Error::new_spanned(
726                    &method.sig.ident,
727                    "version route attributes must be used on route methods",
728                ),
729            );
730        }
731        if method_routes.is_empty() && serialization_specs.is_some() {
732            push_error(
733                &mut errors,
734                syn::Error::new_spanned(
735                    &method.sig.ident,
736                    "serialization route attributes must be used on route methods",
737                ),
738            );
739        }
740        if method_routes.is_empty() && http_code.is_some() {
741            push_error(
742                &mut errors,
743                syn::Error::new_spanned(
744                    &method.sig.ident,
745                    "http_code route attributes must be used on route methods",
746                ),
747            );
748        }
749        if method_routes.is_empty() && !metadata_specs.is_empty() {
750            push_error(
751                &mut errors,
752                syn::Error::new_spanned(
753                    &method.sig.ident,
754                    "metadata route attributes must be used on route methods",
755                ),
756            );
757        }
758        if method_routes.is_empty() && route_validation.is_present() {
759            push_error(
760                &mut errors,
761                syn::Error::new_spanned(
762                    &method.sig.ident,
763                    "validation route attributes must be used on route methods",
764                ),
765            );
766        }
767
768        let input = if method_routes.is_empty() {
769            None
770        } else {
771            match RouteMethodInput::from_method(method) {
772                Ok(input) => Some(input),
773                Err(error) => {
774                    push_error(&mut errors, error);
775                    None
776                }
777            }
778        };
779
780        for route in method_routes {
781            let Some(input) = input.clone() else {
782                continue;
783            };
784            let validation_enabled = route_validation.enabled(controller_validation.enabled);
785            let validation_skipped = route_validation.skip;
786            match route_registration(
787                route,
788                method,
789                input,
790                validation_enabled,
791                validation_skipped,
792                &metadata_specs,
793                http_code.as_ref(),
794                &response_specs,
795                &pipeline_specs,
796                host_specs.as_ref(),
797                version_specs.as_ref(),
798                serialization_specs.as_ref(),
799                &openapi_specs,
800            ) {
801                Ok(registration) => routes.push(registration),
802                Err(error) => push_error(&mut errors, error),
803            }
804        }
805    }
806
807    if let Some(error) = errors {
808        return Err(error);
809    }
810
811    Ok(quote! {
812        #item_impl
813
814        impl #self_ty {
815            pub fn controller(
816                self: ::std::sync::Arc<Self>,
817            ) -> ::a3s_boot::Result<::a3s_boot::ControllerDefinition> {
818                let mut __a3s_boot_controller =
819                    ::a3s_boot::ControllerDefinition::new(#prefix)?;
820                #(
821                    __a3s_boot_controller = __a3s_boot_controller.#controller_openapi;
822                )*
823                #(
824                    __a3s_boot_controller = __a3s_boot_controller.#controller_metadata?;
825                )*
826                #(
827                    __a3s_boot_controller = __a3s_boot_controller.#controller_pipeline;
828                )*
829                #(
830                    __a3s_boot_controller = __a3s_boot_controller.#controller_host?;
831                )*
832                #(
833                    __a3s_boot_controller = __a3s_boot_controller.#controller_version;
834                )*
835                #(
836                    __a3s_boot_controller = __a3s_boot_controller.#controller_serialization;
837                )*
838                #(
839                    __a3s_boot_controller = #routes;
840                )*
841                Ok(__a3s_boot_controller)
842            }
843        }
844    })
845}
846
847fn expand_websocket_gateway(
848    path: LitStr,
849    mut item_impl: ItemImpl,
850) -> Result<proc_macro2::TokenStream> {
851    if item_impl.trait_.is_some() {
852        return Err(syn::Error::new_spanned(
853            &item_impl,
854            "#[websocket_gateway] can only be used on inherent impl blocks",
855        ));
856    }
857
858    let self_ty = item_impl.self_ty.clone();
859    let mut subscriptions = Vec::new();
860    let mut errors: Option<syn::Error> = None;
861
862    for item in &mut item_impl.items {
863        let ImplItem::Fn(method) = item else {
864            continue;
865        };
866
867        let (clean_attrs, events, event_errors) = take_subscribe_message_attrs(&method.attrs);
868        method.attrs = clean_attrs;
869        for error in event_errors {
870            push_error(&mut errors, error);
871        }
872        if events.is_empty() {
873            continue;
874        }
875
876        let input = match RouteMethodInput::from_method(method) {
877            Ok(input) => input,
878            Err(error) => {
879                push_error(&mut errors, error);
880                continue;
881            }
882        };
883
884        if method.sig.asyncness.is_none() {
885            push_error(
886                &mut errors,
887                syn::Error::new_spanned(
888                    &method.sig.fn_token,
889                    "websocket gateway message handlers must be async",
890                ),
891            );
892            continue;
893        }
894
895        for event in events {
896            match websocket_subscription(method, input.clone(), event) {
897                Ok(subscription) => subscriptions.push(subscription),
898                Err(error) => push_error(&mut errors, error),
899            }
900        }
901    }
902
903    if let Some(error) = errors {
904        return Err(error);
905    }
906
907    Ok(quote! {
908        #item_impl
909
910        impl #self_ty {
911            pub fn gateway(
912                self: ::std::sync::Arc<Self>,
913            ) -> ::a3s_boot::Result<::a3s_boot::WebSocketGatewayDefinition> {
914                let mut __a3s_boot_gateway =
915                    ::a3s_boot::WebSocketGatewayDefinition::new(#path)?;
916                #(
917                    __a3s_boot_gateway = #subscriptions;
918                )*
919                Ok(__a3s_boot_gateway)
920            }
921        }
922    })
923}
924
925fn expand_message_controller(mut item_impl: ItemImpl) -> Result<proc_macro2::TokenStream> {
926    if item_impl.trait_.is_some() {
927        return Err(syn::Error::new_spanned(
928            &item_impl,
929            "#[message_controller] can only be used on inherent impl blocks",
930        ));
931    }
932
933    let self_ty = item_impl.self_ty.clone();
934    let mut patterns = Vec::new();
935    let mut errors: Option<syn::Error> = None;
936    let (clean_impl_attrs, controller_validation, controller_validation_errors) =
937        take_controller_validation_attrs(&item_impl.attrs);
938    item_impl.attrs = clean_impl_attrs;
939    for error in controller_validation_errors {
940        push_error(&mut errors, error);
941    }
942
943    for item in &mut item_impl.items {
944        let ImplItem::Fn(method) = item else {
945            continue;
946        };
947
948        let (clean_attrs, specs, pattern_errors) = take_message_pattern_attrs(&method.attrs);
949        let (clean_attrs, route_validation, validation_errors) =
950            take_route_validation_attrs(&clean_attrs);
951        method.attrs = clean_attrs;
952        for error in pattern_errors {
953            push_error(&mut errors, error);
954        }
955        for error in validation_errors {
956            push_error(&mut errors, error);
957        }
958        if specs.is_empty() && route_validation.is_present() {
959            push_error(
960                &mut errors,
961                syn::Error::new_spanned(
962                    &method.sig.ident,
963                    "validation attributes must be used on message pattern methods",
964                ),
965            );
966        }
967        if specs.is_empty() {
968            continue;
969        }
970
971        let input = match RouteMethodInput::from_method(method) {
972            Ok(input) => input,
973            Err(error) => {
974                push_error(&mut errors, error);
975                continue;
976            }
977        };
978
979        if method.sig.asyncness.is_none() {
980            push_error(
981                &mut errors,
982                syn::Error::new_spanned(
983                    &method.sig.fn_token,
984                    "message pattern handlers must be async",
985                ),
986            );
987            continue;
988        }
989
990        let validation_enabled = route_validation.enabled(controller_validation.enabled);
991        for spec in specs {
992            match message_pattern_registration(method, input.clone(), spec, validation_enabled) {
993                Ok(pattern) => patterns.push(pattern),
994                Err(error) => push_error(&mut errors, error),
995            }
996        }
997    }
998
999    if let Some(error) = errors {
1000        return Err(error);
1001    }
1002
1003    Ok(quote! {
1004        #item_impl
1005
1006        impl #self_ty {
1007            pub fn message_patterns(
1008                self: ::std::sync::Arc<Self>,
1009            ) -> ::a3s_boot::Result<::std::vec::Vec<::a3s_boot::MessagePatternDefinition>> {
1010                let mut __a3s_boot_patterns = ::std::vec::Vec::new();
1011                #(
1012                    __a3s_boot_patterns.push(#patterns);
1013                )*
1014                Ok(__a3s_boot_patterns)
1015            }
1016        }
1017    })
1018}
1019
1020fn expand_event_listener(mut item_impl: ItemImpl) -> Result<proc_macro2::TokenStream> {
1021    if item_impl.trait_.is_some() {
1022        return Err(syn::Error::new_spanned(
1023            &item_impl,
1024            "#[event_listener] can only be used on inherent impl blocks",
1025        ));
1026    }
1027
1028    let self_ty = item_impl.self_ty.clone();
1029    let mut listeners = Vec::new();
1030    let mut errors: Option<syn::Error> = None;
1031
1032    for item in &mut item_impl.items {
1033        let ImplItem::Fn(method) = item else {
1034            continue;
1035        };
1036
1037        let (clean_attrs, patterns, event_errors) = take_on_event_attrs(&method.attrs);
1038        method.attrs = clean_attrs;
1039        for error in event_errors {
1040            push_error(&mut errors, error);
1041        }
1042        if patterns.is_empty() {
1043            continue;
1044        }
1045
1046        let input = match EventMethodInput::from_method(method) {
1047            Ok(input) => input,
1048            Err(error) => {
1049                push_error(&mut errors, error);
1050                continue;
1051            }
1052        };
1053
1054        if method.sig.asyncness.is_none() {
1055            push_error(
1056                &mut errors,
1057                syn::Error::new_spanned(
1058                    &method.sig.fn_token,
1059                    "event listener methods must be async",
1060                ),
1061            );
1062            continue;
1063        }
1064
1065        for pattern in patterns {
1066            match event_listener_registration(method, &input, pattern) {
1067                Ok(listener) => listeners.push(listener),
1068                Err(error) => push_error(&mut errors, error),
1069            }
1070        }
1071    }
1072
1073    if let Some(error) = errors {
1074        return Err(error);
1075    }
1076
1077    Ok(quote! {
1078        #item_impl
1079
1080        impl #self_ty {
1081            pub fn event_listeners(
1082                self: ::std::sync::Arc<Self>,
1083            ) -> ::std::vec::Vec<::a3s_boot::EventListenerDefinition> {
1084                let mut __a3s_boot_listeners = ::std::vec::Vec::new();
1085                #(
1086                    __a3s_boot_listeners.push(#listeners);
1087                )*
1088                __a3s_boot_listeners
1089            }
1090        }
1091    })
1092}
1093
1094fn take_on_event_attrs(attrs: &[Attribute]) -> (Vec<Attribute>, Vec<LitStr>, Vec<syn::Error>) {
1095    let mut clean_attrs = Vec::new();
1096    let mut patterns = Vec::new();
1097    let mut errors = Vec::new();
1098
1099    for attr in attrs {
1100        let Some(ident) = attr.path().segments.last().map(|segment| &segment.ident) else {
1101            clean_attrs.push(attr.clone());
1102            continue;
1103        };
1104
1105        if ident != "on_event" {
1106            clean_attrs.push(attr.clone());
1107            continue;
1108        }
1109
1110        match attr.parse_args::<LitStr>() {
1111            Ok(pattern) => patterns.push(pattern),
1112            Err(error) => errors.push(error),
1113        }
1114    }
1115
1116    (clean_attrs, patterns, errors)
1117}
1118
1119fn event_listener_registration(
1120    method: &ImplItemFn,
1121    input: &EventMethodInput,
1122    pattern: LitStr,
1123) -> Result<proc_macro2::TokenStream> {
1124    let method_ident = &method.sig.ident;
1125    let listener_name = format_ident!("__a3s_boot_event_listener_{}", method_ident);
1126    let (extractors, args) = input.argument_tokens();
1127
1128    Ok(quote! {
1129        ::a3s_boot::EventListenerDefinition::new(#pattern, {
1130            let #listener_name = ::std::sync::Arc::clone(&self);
1131            move |__a3s_boot_event: ::a3s_boot::EventEnvelope,
1132                  __a3s_boot_context: ::a3s_boot::EventContext| {
1133                let #listener_name = ::std::sync::Arc::clone(&#listener_name);
1134                async move {
1135                    #(#extractors)*
1136                    #listener_name.#method_ident(#(#args),*).await
1137                }
1138            }
1139        })
1140    })
1141}
1142
1143fn expand_schedule(mut item_impl: ItemImpl) -> Result<proc_macro2::TokenStream> {
1144    if item_impl.trait_.is_some() {
1145        return Err(syn::Error::new_spanned(
1146            &item_impl,
1147            "#[schedule] can only be used on inherent impl blocks",
1148        ));
1149    }
1150
1151    let self_ty = item_impl.self_ty.clone();
1152    let mut jobs = Vec::new();
1153    let mut errors: Option<syn::Error> = None;
1154
1155    for item in &mut item_impl.items {
1156        let ImplItem::Fn(method) = item else {
1157            continue;
1158        };
1159
1160        let (clean_attrs, specs, schedule_errors) = take_schedule_job_attrs(&method.attrs);
1161        method.attrs = clean_attrs;
1162        for error in schedule_errors {
1163            push_error(&mut errors, error);
1164        }
1165        if specs.is_empty() {
1166            continue;
1167        }
1168
1169        let input = match ScheduleMethodInput::from_method(method) {
1170            Ok(input) => input,
1171            Err(error) => {
1172                push_error(&mut errors, error);
1173                continue;
1174            }
1175        };
1176
1177        if method.sig.asyncness.is_none() {
1178            push_error(
1179                &mut errors,
1180                syn::Error::new_spanned(
1181                    &method.sig.fn_token,
1182                    "scheduled job methods must be async",
1183                ),
1184            );
1185            continue;
1186        }
1187
1188        for spec in specs {
1189            match schedule_job_registration(method, &input, spec) {
1190                Ok(job) => jobs.push(job),
1191                Err(error) => push_error(&mut errors, error),
1192            }
1193        }
1194    }
1195
1196    if let Some(error) = errors {
1197        return Err(error);
1198    }
1199
1200    Ok(quote! {
1201        #item_impl
1202
1203        impl #self_ty {
1204            pub fn scheduled_jobs(
1205                self: ::std::sync::Arc<Self>,
1206            ) -> ::std::vec::Vec<::a3s_boot::ScheduledJob> {
1207                let mut __a3s_boot_jobs = ::std::vec::Vec::new();
1208                #(
1209                    __a3s_boot_jobs.push(#jobs);
1210                )*
1211                __a3s_boot_jobs
1212            }
1213        }
1214    })
1215}
1216
1217fn take_schedule_job_attrs(
1218    attrs: &[Attribute],
1219) -> (Vec<Attribute>, Vec<ScheduleJobSpec>, Vec<syn::Error>) {
1220    let mut clean_attrs = Vec::new();
1221    let mut specs = Vec::new();
1222    let mut errors = Vec::new();
1223
1224    for attr in attrs {
1225        let Some(kind) = ScheduleJobKind::from_attribute(attr) else {
1226            clean_attrs.push(attr.clone());
1227            continue;
1228        };
1229
1230        match kind.parse_args(attr) {
1231            Ok(args) => specs.push(ScheduleJobSpec { kind, args }),
1232            Err(error) => errors.push(error),
1233        }
1234    }
1235
1236    (clean_attrs, specs, errors)
1237}
1238
1239fn schedule_job_registration(
1240    method: &ImplItemFn,
1241    input: &ScheduleMethodInput,
1242    spec: ScheduleJobSpec,
1243) -> Result<proc_macro2::TokenStream> {
1244    let method_ident = &method.sig.ident;
1245    let name = spec.args.name_token(method_ident);
1246    let handler = scheduled_task_handler(method_ident, input);
1247
1248    Ok(match spec.kind {
1249        ScheduleJobKind::Cron => {
1250            let ScheduleJobArgs::Cron(args) = spec.args else {
1251                unreachable!("cron schedule spec must use cron args")
1252            };
1253            let expression = args.expression;
1254            quote! {
1255                ::a3s_boot::ScheduledJob::cron(#name, #expression, #handler)
1256            }
1257        }
1258        ScheduleJobKind::Interval => {
1259            let ScheduleJobArgs::Interval(args) = spec.args else {
1260                unreachable!("interval schedule spec must use interval args")
1261            };
1262            let millis = args.duration_millis()?;
1263            quote! {
1264                ::a3s_boot::ScheduledJob::interval(
1265                    #name,
1266                    ::std::time::Duration::from_millis(#millis),
1267                    #handler
1268                )
1269            }
1270        }
1271        ScheduleJobKind::Timeout => {
1272            let ScheduleJobArgs::Timeout(args) = spec.args else {
1273                unreachable!("timeout schedule spec must use timeout args")
1274            };
1275            let millis = args.duration_millis()?;
1276            quote! {
1277                ::a3s_boot::ScheduledJob::timeout(
1278                    #name,
1279                    ::std::time::Duration::from_millis(#millis),
1280                    #handler
1281                )
1282            }
1283        }
1284    })
1285}
1286
1287fn scheduled_task_handler(
1288    method_ident: &Ident,
1289    input: &ScheduleMethodInput,
1290) -> proc_macro2::TokenStream {
1291    let scheduled_name = format_ident!("__a3s_boot_scheduled_{}", method_ident);
1292    let (closure_arg, method_args) = if input.accepts_context {
1293        (quote!(__a3s_boot_context), quote!(__a3s_boot_context))
1294    } else {
1295        (quote!(_context), quote!())
1296    };
1297
1298    quote! {
1299        {
1300            let #scheduled_name = ::std::sync::Arc::clone(&self);
1301            move |#closure_arg: ::a3s_boot::ScheduleContext| {
1302                let #scheduled_name = ::std::sync::Arc::clone(&#scheduled_name);
1303                async move { #scheduled_name.#method_ident(#method_args).await }
1304            }
1305        }
1306    }
1307}
1308
1309fn take_subscribe_message_attrs(
1310    attrs: &[Attribute],
1311) -> (Vec<Attribute>, Vec<LitStr>, Vec<syn::Error>) {
1312    let mut clean_attrs = Vec::new();
1313    let mut events = Vec::new();
1314    let mut errors = Vec::new();
1315
1316    for attr in attrs {
1317        let Some(ident) = attr.path().segments.last().map(|segment| &segment.ident) else {
1318            clean_attrs.push(attr.clone());
1319            continue;
1320        };
1321
1322        if ident != "subscribe_message" {
1323            clean_attrs.push(attr.clone());
1324            continue;
1325        }
1326
1327        match attr.parse_args::<LitStr>() {
1328            Ok(event) => events.push(event),
1329            Err(error) => errors.push(error),
1330        }
1331    }
1332
1333    (clean_attrs, events, errors)
1334}
1335
1336fn websocket_subscription(
1337    method: &ImplItemFn,
1338    input: RouteMethodInput,
1339    event: LitStr,
1340) -> Result<proc_macro2::TokenStream> {
1341    let method_ident = &method.sig.ident;
1342    let controller_name = format_ident!("__a3s_boot_ws_{}", method_ident);
1343    let handler = match input.into_legacy_arg()? {
1344        Some(MethodArg { ident, ty, .. }) => quote! {
1345            {
1346                let #controller_name = ::std::sync::Arc::clone(&self);
1347                move |__a3s_boot_message: ::a3s_boot::WebSocketMessage| {
1348                    let #controller_name = ::std::sync::Arc::clone(&#controller_name);
1349                    async move {
1350                        let #ident: #ty = __a3s_boot_message;
1351                        #controller_name.#method_ident(#ident).await
1352                    }
1353                }
1354            }
1355        },
1356        None => quote! {
1357            {
1358                let #controller_name = ::std::sync::Arc::clone(&self);
1359                move |_message: ::a3s_boot::WebSocketMessage| {
1360                    let #controller_name = ::std::sync::Arc::clone(&#controller_name);
1361                    async move { #controller_name.#method_ident().await }
1362                }
1363            }
1364        },
1365    };
1366
1367    Ok(quote! {
1368        __a3s_boot_gateway.subscribe(#event, #handler)?
1369    })
1370}
1371
1372fn take_message_pattern_attrs(
1373    attrs: &[Attribute],
1374) -> (Vec<Attribute>, Vec<MessagePatternSpec>, Vec<syn::Error>) {
1375    let mut clean_attrs = Vec::new();
1376    let mut patterns = Vec::new();
1377    let mut errors = Vec::new();
1378
1379    for attr in attrs {
1380        let Some(kind) = MessagePatternAttrKind::from_attribute(attr) else {
1381            clean_attrs.push(attr.clone());
1382            continue;
1383        };
1384
1385        match attr.parse_args::<MessagePatternArgs>() {
1386            Ok(args) => {
1387                if matches!(kind, MessagePatternAttrKind::Event) && args.raw.is_some() {
1388                    errors.push(syn::Error::new_spanned(
1389                        args.raw.unwrap(),
1390                        "raw is not supported on event pattern attributes",
1391                    ));
1392                } else {
1393                    patterns.push(MessagePatternSpec { kind, args });
1394                }
1395            }
1396            Err(error) => errors.push(error),
1397        }
1398    }
1399
1400    (clean_attrs, patterns, errors)
1401}
1402
1403fn message_pattern_registration(
1404    method: &ImplItemFn,
1405    input: RouteMethodInput,
1406    spec: MessagePatternSpec,
1407    validation_enabled: bool,
1408) -> Result<proc_macro2::TokenStream> {
1409    let method_ident = &method.sig.ident;
1410    let pattern = spec.args.pattern;
1411    let raw = spec.args.raw.is_some();
1412    let definition = match spec.kind {
1413        MessagePatternAttrKind::Message => {
1414            let handler = message_request_handler(method_ident, input.clone(), raw)?;
1415            quote! {
1416                ::a3s_boot::MessagePatternDefinition::request(#pattern, #handler)?
1417            }
1418        }
1419        MessagePatternAttrKind::Event => {
1420            let handler = message_event_handler(method_ident, input.clone())?;
1421            quote! {
1422                ::a3s_boot::MessagePatternDefinition::event(#pattern, #handler)?
1423            }
1424        }
1425    };
1426
1427    let definition = message_validation_definition(definition, input, validation_enabled)?;
1428    Ok(definition)
1429}
1430
1431fn message_request_handler(
1432    method_ident: &Ident,
1433    input: RouteMethodInput,
1434    raw: bool,
1435) -> Result<proc_macro2::TokenStream> {
1436    let controller_name = format_ident!("__a3s_boot_message_{}", method_ident);
1437    Ok(match input.into_legacy_arg()? {
1438        Some(arg) if is_type_ident(&arg.ty, "TransportMessage") => {
1439            let MethodArg { ident, ty, .. } = arg;
1440            let call = message_request_call(method_ident, &controller_name, raw, quote!(#ident));
1441            quote! {
1442                {
1443                    let #controller_name = ::std::sync::Arc::clone(&self);
1444                    move |__a3s_boot_message: ::a3s_boot::TransportMessage| {
1445                        let #controller_name = ::std::sync::Arc::clone(&#controller_name);
1446                        async move {
1447                            let #ident: #ty = __a3s_boot_message;
1448                            #call
1449                        }
1450                    }
1451                }
1452            }
1453        }
1454        Some(MethodArg { ident, ty, .. }) => {
1455            let call = message_request_call(method_ident, &controller_name, raw, quote!(#ident));
1456            quote! {
1457                {
1458                    let #controller_name = ::std::sync::Arc::clone(&self);
1459                    move |__a3s_boot_message: ::a3s_boot::TransportMessage| {
1460                        let #controller_name = ::std::sync::Arc::clone(&#controller_name);
1461                        async move {
1462                            let #ident: #ty = __a3s_boot_message.data_as::<#ty>()?;
1463                            #call
1464                        }
1465                    }
1466                }
1467            }
1468        }
1469        None => {
1470            let call = message_request_call(method_ident, &controller_name, raw, quote!());
1471            quote! {
1472                {
1473                    let #controller_name = ::std::sync::Arc::clone(&self);
1474                    move |_message: ::a3s_boot::TransportMessage| {
1475                        let #controller_name = ::std::sync::Arc::clone(&#controller_name);
1476                        async move { #call }
1477                    }
1478                }
1479            }
1480        }
1481    })
1482}
1483
1484fn message_request_call(
1485    method_ident: &Ident,
1486    controller_name: &Ident,
1487    raw: bool,
1488    args: proc_macro2::TokenStream,
1489) -> proc_macro2::TokenStream {
1490    if raw {
1491        quote! {
1492            #controller_name.#method_ident(#args).await
1493        }
1494    } else {
1495        quote! {
1496            {
1497                let __a3s_boot_reply = #controller_name.#method_ident(#args).await?;
1498                ::a3s_boot::TransportReply::json(&__a3s_boot_reply)
1499            }
1500        }
1501    }
1502}
1503
1504fn message_event_handler(
1505    method_ident: &Ident,
1506    input: RouteMethodInput,
1507) -> Result<proc_macro2::TokenStream> {
1508    let controller_name = format_ident!("__a3s_boot_event_{}", method_ident);
1509    Ok(match input.into_legacy_arg()? {
1510        Some(arg) if is_type_ident(&arg.ty, "TransportMessage") => {
1511            let MethodArg { ident, ty, .. } = arg;
1512            quote! {
1513                {
1514                    let #controller_name = ::std::sync::Arc::clone(&self);
1515                    move |__a3s_boot_message: ::a3s_boot::TransportMessage| {
1516                        let #controller_name = ::std::sync::Arc::clone(&#controller_name);
1517                        async move {
1518                            let #ident: #ty = __a3s_boot_message;
1519                            let _ = #controller_name.#method_ident(#ident).await?;
1520                            Ok(())
1521                        }
1522                    }
1523                }
1524            }
1525        }
1526        Some(MethodArg { ident, ty, .. }) => quote! {
1527            {
1528                let #controller_name = ::std::sync::Arc::clone(&self);
1529                move |__a3s_boot_message: ::a3s_boot::TransportMessage| {
1530                    let #controller_name = ::std::sync::Arc::clone(&#controller_name);
1531                    async move {
1532                        let #ident: #ty = __a3s_boot_message.data_as::<#ty>()?;
1533                        let _ = #controller_name.#method_ident(#ident).await?;
1534                        Ok(())
1535                    }
1536                }
1537            }
1538        },
1539        None => quote! {
1540            {
1541                let #controller_name = ::std::sync::Arc::clone(&self);
1542                move |_message: ::a3s_boot::TransportMessage| {
1543                    let #controller_name = ::std::sync::Arc::clone(&#controller_name);
1544                    async move {
1545                        let _ = #controller_name.#method_ident().await?;
1546                        Ok(())
1547                    }
1548                }
1549            }
1550        },
1551    })
1552}
1553
1554fn message_validation_definition(
1555    definition: proc_macro2::TokenStream,
1556    input: RouteMethodInput,
1557    validation_enabled: bool,
1558) -> Result<proc_macro2::TokenStream> {
1559    if !validation_enabled {
1560        return Ok(definition);
1561    }
1562
1563    let Some(arg) = input.into_legacy_arg()? else {
1564        return Err(syn::Error::new(
1565            proc_macro2::Span::call_site(),
1566            "message validation requires one typed payload argument",
1567        ));
1568    };
1569
1570    if is_type_ident(&arg.ty, "TransportMessage") {
1571        return Err(syn::Error::new_spanned(
1572            arg.ident,
1573            "message validation requires a DTO payload argument, not TransportMessage",
1574        ));
1575    }
1576
1577    let ty = arg.ty;
1578    Ok(quote! {
1579        (#definition).with_payload_validation::<#ty>()
1580    })
1581}
1582
1583fn take_route_attrs(attrs: &[Attribute]) -> (Vec<Attribute>, Vec<RouteSpec>, Vec<syn::Error>) {
1584    let mut clean_attrs = Vec::new();
1585    let mut routes = Vec::new();
1586    let mut errors = Vec::new();
1587
1588    for attr in attrs {
1589        let Some(kind) = RouteKind::from_attribute(attr) else {
1590            clean_attrs.push(attr.clone());
1591            continue;
1592        };
1593
1594        match attr.parse_args::<RouteArgs>() {
1595            Ok(args) => routes.push(RouteSpec { kind, args }),
1596            Err(error) => errors.push(error),
1597        }
1598    }
1599
1600    (clean_attrs, routes, errors)
1601}
1602
1603fn route_registration(
1604    route: RouteSpec,
1605    method: &ImplItemFn,
1606    input: RouteMethodInput,
1607    validation_enabled: bool,
1608    validation_skipped: bool,
1609    metadata_specs: &[MetadataSpec],
1610    http_code: Option<&LitInt>,
1611    response_specs: &[RouteResponseSpec],
1612    pipeline_specs: &[PipelineSpec],
1613    host_spec: Option<&HostSpec>,
1614    version_spec: Option<&VersionSpec>,
1615    serialization_spec: Option<&SerializationSpec>,
1616    openapi_specs: &[RouteOpenApiSpec],
1617) -> Result<proc_macro2::TokenStream> {
1618    if method.sig.asyncness.is_none() {
1619        return Err(syn::Error::new_spanned(
1620            &method.sig.fn_token,
1621            "controller route methods must be async",
1622        ));
1623    }
1624
1625    let method_ident = &method.sig.ident;
1626    let explicit_status = route.args.explicit_status(http_code)?;
1627    let status = status_value(explicit_status)?;
1628    let path = route.args.path.clone();
1629    let metadata_input = input.clone();
1630
1631    let raw = route.args.raw.is_some();
1632    if raw && route.kind.is_explicit_json() {
1633        return Err(syn::Error::new_spanned(
1634            route.args.raw.unwrap(),
1635            "raw is not supported on *_json route attributes",
1636        ));
1637    }
1638
1639    let flavor = route.kind.flavor(raw);
1640    let mut json_success_status = None;
1641    let route_definition = match flavor {
1642        RouteFlavor::Sse => {
1643            if let Some(status) = explicit_status {
1644                return Err(syn::Error::new_spanned(
1645                    status,
1646                    "status is not supported on SSE route attributes",
1647                ));
1648            }
1649            if route.args.raw.is_some() {
1650                return Err(syn::Error::new_spanned(
1651                    route.args.raw.unwrap(),
1652                    "raw is not supported on SSE route attributes",
1653                ));
1654            }
1655            let handler = if input.has_extractors() {
1656                extracted_sse_handler(method_ident, input)?
1657            } else {
1658                raw_or_json_request_handler(method_ident, input)?
1659            };
1660            quote! {
1661                ::a3s_boot::RouteDefinition::sse(#path, #handler)?
1662            }
1663        }
1664        RouteFlavor::Raw => {
1665            if let Some(status) = explicit_status {
1666                return Err(syn::Error::new_spanned(
1667                    status,
1668                    "status is only supported on JSON route attributes",
1669                ));
1670            }
1671            let builder = route.kind.raw_builder_ident();
1672            let handler = if input.has_extractors() {
1673                extracted_raw_handler(method_ident, input)?
1674            } else {
1675                raw_or_json_request_handler(method_ident, input)?
1676            };
1677            quote! {
1678                ::a3s_boot::RouteDefinition::#builder(#path, #handler)?
1679            }
1680        }
1681        RouteFlavor::JsonRequest => {
1682            if input.has_extractors() {
1683                let builder = route.kind.raw_builder_ident();
1684                let handler = extracted_json_response_handler(method_ident, input, status.clone())?;
1685                json_success_status = Some(status.clone());
1686                quote! {
1687                    ::a3s_boot::RouteDefinition::#builder(#path, #handler)?
1688                }
1689            } else {
1690                let builder = route.kind.json_builder_ident().ok_or_else(|| {
1691                    syn::Error::new_spanned(
1692                        &method.sig.ident,
1693                        "this HTTP method does not support JSON route inference",
1694                    )
1695                })?;
1696                let handler = raw_or_json_request_handler(method_ident, input)?;
1697                quote! {
1698                    ::a3s_boot::RouteDefinition::#builder(#path, #status, #handler)?
1699                }
1700            }
1701        }
1702        RouteFlavor::JsonBody => {
1703            if input.has_extractors() {
1704                let builder = route.kind.raw_builder_ident();
1705                let handler = extracted_json_response_handler(method_ident, input, status.clone())?;
1706                json_success_status = Some(status.clone());
1707                quote! {
1708                    ::a3s_boot::RouteDefinition::#builder(#path, #handler)?
1709                }
1710            } else {
1711                let Some(input) = input.into_legacy_arg()? else {
1712                    return Err(syn::Error::new_spanned(
1713                        &method.sig.ident,
1714                        "JSON body routes must accept one DTO argument after &self",
1715                    ));
1716                };
1717                let builder = route.kind.json_builder_ident().ok_or_else(|| {
1718                    syn::Error::new_spanned(
1719                        &method.sig.ident,
1720                        "this HTTP method does not support JSON route inference",
1721                    )
1722                })?;
1723                let handler = json_body_handler(method_ident, input);
1724                quote! {
1725                    ::a3s_boot::RouteDefinition::#builder(#path, #status, #handler)?
1726                }
1727            }
1728        }
1729    };
1730
1731    let route_definition = validation_route_definition(
1732        route_definition,
1733        &metadata_input,
1734        flavor,
1735        validation_enabled,
1736        validation_skipped,
1737    )?;
1738
1739    let route_definition = metadata_route_definition(route_definition, metadata_specs);
1740
1741    let route_definition = pipeline_route_definition(route_definition, pipeline_specs);
1742
1743    let route_definition = host_route_definition(route_definition, host_spec);
1744
1745    let route_definition = version_route_definition(route_definition, version_spec);
1746
1747    let route_definition = serialization_route_definition(route_definition, serialization_spec);
1748
1749    let route_definition = response_route_definition(route_definition, response_specs)?;
1750
1751    let route_definition = openapi_route_definition(
1752        route_definition,
1753        &metadata_input,
1754        flavor,
1755        json_success_status,
1756        openapi_specs,
1757    )?;
1758
1759    Ok(quote! {
1760        __a3s_boot_controller.route(#route_definition)?
1761    })
1762}
1763
1764fn metadata_route_definition(
1765    mut route_definition: proc_macro2::TokenStream,
1766    metadata_specs: &[MetadataSpec],
1767) -> proc_macro2::TokenStream {
1768    for spec in metadata_specs {
1769        let key = &spec.key;
1770        let value = &spec.value;
1771        route_definition = quote! {
1772            (#route_definition).with_metadata(#key, #value)?
1773        };
1774    }
1775    route_definition
1776}
1777
1778fn response_route_definition(
1779    mut route_definition: proc_macro2::TokenStream,
1780    response_specs: &[RouteResponseSpec],
1781) -> Result<proc_macro2::TokenStream> {
1782    for spec in response_specs {
1783        let token = spec.token()?;
1784        route_definition = quote! {
1785            (#route_definition).#token
1786        };
1787    }
1788    Ok(route_definition)
1789}
1790
1791fn pipeline_route_definition(
1792    mut route_definition: proc_macro2::TokenStream,
1793    pipeline_specs: &[PipelineSpec],
1794) -> proc_macro2::TokenStream {
1795    for spec in pipeline_specs {
1796        let token = spec.token();
1797        route_definition = quote! {
1798            (#route_definition).#token
1799        };
1800    }
1801    route_definition
1802}
1803
1804fn host_route_definition(
1805    route_definition: proc_macro2::TokenStream,
1806    host_spec: Option<&HostSpec>,
1807) -> proc_macro2::TokenStream {
1808    let Some(spec) = host_spec else {
1809        return route_definition;
1810    };
1811    let token = spec.token();
1812    quote! {
1813        (#route_definition).#token?
1814    }
1815}
1816
1817fn version_route_definition(
1818    route_definition: proc_macro2::TokenStream,
1819    version_spec: Option<&VersionSpec>,
1820) -> proc_macro2::TokenStream {
1821    let Some(spec) = version_spec else {
1822        return route_definition;
1823    };
1824    let token = spec.token();
1825    quote! {
1826        (#route_definition).#token
1827    }
1828}
1829
1830fn serialization_route_definition(
1831    route_definition: proc_macro2::TokenStream,
1832    serialization_spec: Option<&SerializationSpec>,
1833) -> proc_macro2::TokenStream {
1834    let Some(spec) = serialization_spec else {
1835        return route_definition;
1836    };
1837    let token = spec.token();
1838    quote! {
1839        (#route_definition).#token
1840    }
1841}
1842
1843fn validation_route_definition(
1844    mut route_definition: proc_macro2::TokenStream,
1845    input: &RouteMethodInput,
1846    flavor: RouteFlavor,
1847    validation_enabled: bool,
1848    validation_skipped: bool,
1849) -> Result<proc_macro2::TokenStream> {
1850    if validation_skipped {
1851        return Ok(quote! {
1852            (#route_definition).without_validation()
1853        });
1854    }
1855
1856    if !validation_enabled {
1857        return Ok(route_definition);
1858    }
1859
1860    for token in extractor_validation_tokens(input, flavor) {
1861        route_definition = quote! {
1862            (#route_definition).#token
1863        };
1864    }
1865
1866    Ok(quote! {
1867        (#route_definition).with_validation()
1868    })
1869}
1870
1871fn extractor_validation_tokens(
1872    input: &RouteMethodInput,
1873    flavor: RouteFlavor,
1874) -> Vec<proc_macro2::TokenStream> {
1875    let mut tokens = Vec::new();
1876
1877    if matches!(flavor, RouteFlavor::JsonBody) && !input.has_extractors() {
1878        if let Some(arg) = input.args.first() {
1879            let ty = &arg.ty;
1880            tokens.push(quote! {
1881                with_body_validation::<#ty>()
1882            });
1883        }
1884    }
1885
1886    for arg in &input.args {
1887        let Some(extractor) = &arg.extractor else {
1888            continue;
1889        };
1890        let ty = &arg.ty;
1891
1892        match extractor {
1893            Extractor::Body => tokens.push(quote! {
1894                with_body_validation::<#ty>()
1895            }),
1896            Extractor::Params => tokens.push(quote! {
1897                with_params_validation::<#ty>()
1898            }),
1899            Extractor::Query(query) => {
1900                if query.name.is_none() {
1901                    tokens.push(quote! {
1902                        with_query_validation::<#ty>()
1903                    });
1904                }
1905            }
1906            Extractor::Request
1907            | Extractor::Param(_)
1908            | Extractor::Header(_)
1909            | Extractor::Headers
1910            | Extractor::HostParam(_)
1911            | Extractor::Ip(_)
1912            | Extractor::Custom(_) => {}
1913        }
1914    }
1915
1916    tokens
1917}
1918
1919fn raw_or_json_request_handler(
1920    method_ident: &Ident,
1921    input: RouteMethodInput,
1922) -> Result<proc_macro2::TokenStream> {
1923    let controller_name = format_ident!("__a3s_boot_{}", method_ident);
1924    Ok(match input.into_legacy_arg()? {
1925        Some(MethodArg { ident, ty, .. }) => quote! {
1926            {
1927                let #controller_name = ::std::sync::Arc::clone(&self);
1928                move |#ident: #ty| {
1929                    let #controller_name = ::std::sync::Arc::clone(&#controller_name);
1930                    async move { #controller_name.#method_ident(#ident).await }
1931                }
1932            }
1933        },
1934        None => quote! {
1935            {
1936                let #controller_name = ::std::sync::Arc::clone(&self);
1937                move |_request: ::a3s_boot::BootRequest| {
1938                    let #controller_name = ::std::sync::Arc::clone(&#controller_name);
1939                    async move { #controller_name.#method_ident().await }
1940                }
1941            }
1942        },
1943    })
1944}
1945
1946fn json_body_handler(method_ident: &Ident, input: MethodArg) -> proc_macro2::TokenStream {
1947    let controller_name = format_ident!("__a3s_boot_{}", method_ident);
1948    let MethodArg { ident, ty, .. } = input;
1949    quote! {
1950        {
1951            let #controller_name = ::std::sync::Arc::clone(&self);
1952            move |#ident: #ty| {
1953                let #controller_name = ::std::sync::Arc::clone(&#controller_name);
1954                async move { #controller_name.#method_ident(#ident).await }
1955            }
1956        }
1957    }
1958}
1959
1960fn extracted_raw_handler(
1961    method_ident: &Ident,
1962    input: RouteMethodInput,
1963) -> Result<proc_macro2::TokenStream> {
1964    let controller_name = format_ident!("__a3s_boot_{}", method_ident);
1965    let (extractors, args) = extracted_arguments(input)?;
1966
1967    Ok(quote! {
1968        {
1969            let #controller_name = ::std::sync::Arc::clone(&self);
1970            move |__a3s_boot_request: ::a3s_boot::BootRequest| {
1971                let #controller_name = ::std::sync::Arc::clone(&#controller_name);
1972                async move {
1973                    #(#extractors)*
1974                    #controller_name.#method_ident(#(#args),*).await
1975                }
1976            }
1977        }
1978    })
1979}
1980
1981fn extracted_json_response_handler(
1982    method_ident: &Ident,
1983    input: RouteMethodInput,
1984    status: proc_macro2::TokenStream,
1985) -> Result<proc_macro2::TokenStream> {
1986    let controller_name = format_ident!("__a3s_boot_{}", method_ident);
1987    let (extractors, args) = extracted_arguments(input)?;
1988
1989    Ok(quote! {
1990        {
1991            let #controller_name = ::std::sync::Arc::clone(&self);
1992            move |__a3s_boot_request: ::a3s_boot::BootRequest| {
1993                let #controller_name = ::std::sync::Arc::clone(&#controller_name);
1994                async move {
1995                    __a3s_boot_request.require_accepts_json()?;
1996                    #(#extractors)*
1997                    let __a3s_boot_body = #controller_name.#method_ident(#(#args),*).await?;
1998                    ::a3s_boot::BootResponse::json_with_status(#status, &__a3s_boot_body)
1999                }
2000            }
2001        }
2002    })
2003}
2004
2005fn extracted_sse_handler(
2006    method_ident: &Ident,
2007    input: RouteMethodInput,
2008) -> Result<proc_macro2::TokenStream> {
2009    let controller_name = format_ident!("__a3s_boot_{}", method_ident);
2010    let (extractors, args) = extracted_arguments(input)?;
2011
2012    Ok(quote! {
2013        {
2014            let #controller_name = ::std::sync::Arc::clone(&self);
2015            move |__a3s_boot_request: ::a3s_boot::BootRequest| {
2016                let #controller_name = ::std::sync::Arc::clone(&#controller_name);
2017                async move {
2018                    #(#extractors)*
2019                    #controller_name.#method_ident(#(#args),*).await
2020                }
2021            }
2022        }
2023    })
2024}
2025
2026fn extracted_arguments(
2027    input: RouteMethodInput,
2028) -> Result<(Vec<proc_macro2::TokenStream>, Vec<Ident>)> {
2029    let mut body_arg: Option<Ident> = None;
2030    let mut extractors = Vec::new();
2031    let mut args = Vec::new();
2032
2033    for arg in input.args {
2034        let extractor = arg.extractor.clone().ok_or_else(|| {
2035            syn::Error::new_spanned(
2036                &arg.ident,
2037                "all route arguments must use extractor attributes when any extractor is used",
2038            )
2039        })?;
2040
2041        if matches!(extractor, Extractor::Body) {
2042            if let Some(existing) = body_arg {
2043                return Err(syn::Error::new_spanned(
2044                    existing,
2045                    "route methods can accept at most one #[body] argument",
2046                ));
2047            }
2048            body_arg = Some(arg.ident.clone());
2049        }
2050
2051        args.push(arg.ident.clone());
2052        extractors.push(extractor_tokens(arg, extractor));
2053    }
2054
2055    Ok((extractors, args))
2056}
2057
2058fn extractor_tokens(arg: MethodArg, extractor: Extractor) -> proc_macro2::TokenStream {
2059    let MethodArg { ident, ty, .. } = arg;
2060    match extractor {
2061        Extractor::Body => quote! {
2062            __a3s_boot_request.require_json_content_type()?;
2063            let #ident: #ty = __a3s_boot_request.json::<#ty>()?;
2064        },
2065        Extractor::Request => quote! {
2066            let #ident: #ty = __a3s_boot_request.clone();
2067        },
2068        Extractor::Params => quote! {
2069            let #ident: #ty = __a3s_boot_request.params::<#ty>()?;
2070        },
2071        Extractor::Param(spec) => {
2072            let SingleValueExtractor { name, pipe } = spec;
2073            single_value_extractor_tokens(
2074                ident,
2075                ty,
2076                pipe,
2077                |value_ty| quote!(__a3s_boot_request.param_as::<#value_ty>(#name)),
2078                |value_ty| quote!(__a3s_boot_request.optional_param_as::<#value_ty>(#name)),
2079            )
2080        }
2081        Extractor::Query(spec) => {
2082            if let Some(name) = spec.name {
2083                single_value_extractor_tokens(
2084                    ident,
2085                    ty,
2086                    spec.pipe,
2087                    |value_ty| quote!(__a3s_boot_request.query_value_as::<#value_ty>(#name)),
2088                    |value_ty| quote!(__a3s_boot_request.optional_query_value_as::<#value_ty>(#name)),
2089                )
2090            } else {
2091                quote! {
2092                    let #ident: #ty = __a3s_boot_request.query::<#ty>()?;
2093                }
2094            }
2095        }
2096        Extractor::Header(spec) => {
2097            let SingleValueExtractor { name, pipe } = spec;
2098            single_value_extractor_tokens(
2099                ident,
2100                ty,
2101                pipe,
2102                |value_ty| quote!(__a3s_boot_request.header_as::<#value_ty>(#name)),
2103                |value_ty| quote!(__a3s_boot_request.optional_header_as::<#value_ty>(#name)),
2104            )
2105        }
2106        Extractor::Headers => quote! {
2107            let #ident: #ty = __a3s_boot_request.headers.clone();
2108        },
2109        Extractor::HostParam(spec) => {
2110            let SingleValueExtractor { name, pipe } = spec;
2111            single_value_extractor_tokens(
2112                ident,
2113                ty,
2114                pipe,
2115                |value_ty| quote!(__a3s_boot_request.host_param_as::<#value_ty>(#name)),
2116                |value_ty| quote!(__a3s_boot_request.optional_host_param_as::<#value_ty>(#name)),
2117            )
2118        }
2119        Extractor::Ip(pipe) => single_value_extractor_tokens(
2120            ident,
2121            ty,
2122            pipe,
2123            |value_ty| quote!(__a3s_boot_request.ip_as::<#value_ty>()),
2124            |value_ty| quote!(__a3s_boot_request.optional_ip_as::<#value_ty>()),
2125        ),
2126        Extractor::Custom(extractor) => quote! {
2127            let #ident: #ty = ::a3s_boot::extract_request_value::<#ty, _>(&__a3s_boot_request, #extractor)?;
2128        },
2129    }
2130}
2131
2132fn single_value_extractor_tokens<Required, Optional>(
2133    ident: Ident,
2134    ty: Box<Type>,
2135    pipe: Option<Expr>,
2136    required: Required,
2137    optional: Optional,
2138) -> proc_macro2::TokenStream
2139where
2140    Required: FnOnce(&Type) -> proc_macro2::TokenStream,
2141    Optional: FnOnce(&Type) -> proc_macro2::TokenStream,
2142{
2143    if let Some(pipe) = pipe {
2144        if let Some(inner) = option_inner_type(&ty) {
2145            let value = optional(&parse_string_type());
2146            quote! {
2147                let #ident: #ty = match #value? {
2148                    Some(__a3s_boot_value) => {
2149                        Some(::a3s_boot::transform_request_value::<String, #inner, _>(
2150                            __a3s_boot_value,
2151                            #pipe,
2152                        )?)
2153                    }
2154                    None => None,
2155                };
2156            }
2157        } else {
2158            let value = required(&parse_string_type());
2159            quote! {
2160                let #ident: #ty = ::a3s_boot::transform_request_value::<String, #ty, _>(
2161                    #value?,
2162                    #pipe,
2163                )?;
2164            }
2165        }
2166    } else if let Some(inner) = option_inner_type(&ty) {
2167        let value = optional(&inner);
2168        quote! {
2169            let #ident: #ty = #value?;
2170        }
2171    } else {
2172        let value = required(&ty);
2173        quote! {
2174            let #ident: #ty = #value?;
2175        }
2176    }
2177}
2178
2179fn parse_string_type() -> Type {
2180    syn::parse_quote!(String)
2181}
2182
2183fn single_value_extractor_schema_type(extractor_ty: &Type, pipe: Option<&Expr>) -> Type {
2184    if pipe.is_some() {
2185        return syn::parse_quote!(String);
2186    }
2187
2188    extractor_ty.clone()
2189}
2190
2191fn single_value_extractor_required(ty: &Type) -> bool {
2192    option_inner_type(ty).is_none()
2193}
2194
2195fn single_value_extractor_schema(ty: &Type, pipe: Option<&Expr>) -> proc_macro2::TokenStream {
2196    let schema_ty = single_value_extractor_schema_type(ty, pipe);
2197    openapi_schema_tokens(&schema_ty)
2198}
2199
2200fn single_value_extractor_required_schema(
2201    ty: &Type,
2202    pipe: Option<&Expr>,
2203) -> (bool, proc_macro2::TokenStream) {
2204    (
2205        single_value_extractor_required(ty),
2206        single_value_extractor_schema(ty, pipe),
2207    )
2208}
2209
2210fn single_value_extractor_openapi_tokens(
2211    name: &LitStr,
2212    ty: &Type,
2213    pipe: Option<&Expr>,
2214    kind: SingleValueOpenApiKind,
2215) -> proc_macro2::TokenStream {
2216    match kind {
2217        SingleValueOpenApiKind::Path => {
2218            let schema = single_value_extractor_schema(ty, pipe);
2219            quote! {
2220                with_path_parameter(#name, #schema)
2221            }
2222        }
2223        SingleValueOpenApiKind::Query => {
2224            let (required, schema) = single_value_extractor_required_schema(ty, pipe);
2225            quote! {
2226                with_query_parameter(#name, #required, #schema)
2227            }
2228        }
2229        SingleValueOpenApiKind::Header => {
2230            let (required, schema) = single_value_extractor_required_schema(ty, pipe);
2231            quote! {
2232                with_header_parameter(#name, #required, #schema)
2233            }
2234        }
2235    }
2236}
2237
2238enum SingleValueOpenApiKind {
2239    Path,
2240    Query,
2241    Header,
2242}
2243
2244fn openapi_route_definition(
2245    mut route_definition: proc_macro2::TokenStream,
2246    input: &RouteMethodInput,
2247    flavor: RouteFlavor,
2248    json_success_status: Option<proc_macro2::TokenStream>,
2249    specs: &[RouteOpenApiSpec],
2250) -> Result<proc_macro2::TokenStream> {
2251    if let Some(status) = json_success_status {
2252        route_definition = quote! {
2253            (#route_definition).with_response(
2254                #status,
2255                ::a3s_boot::OpenApiResponse::description("Success")
2256            )
2257        };
2258    }
2259
2260    for token in extractor_openapi_tokens(input, flavor) {
2261        route_definition = quote! {
2262            (#route_definition).#token
2263        };
2264    }
2265
2266    for spec in specs {
2267        for token in spec.tokens()? {
2268            route_definition = quote! {
2269                (#route_definition).#token
2270            };
2271        }
2272    }
2273
2274    Ok(route_definition)
2275}
2276
2277fn extractor_openapi_tokens(
2278    input: &RouteMethodInput,
2279    flavor: RouteFlavor,
2280) -> Vec<proc_macro2::TokenStream> {
2281    let mut tokens = Vec::new();
2282
2283    if matches!(flavor, RouteFlavor::JsonBody) && !input.has_extractors() {
2284        if let Some(arg) = input.args.first() {
2285            let schema = openapi_schema_tokens(&arg.ty);
2286            tokens.push(quote! {
2287                with_json_request_body(#schema)
2288            });
2289        }
2290    }
2291
2292    for arg in &input.args {
2293        let Some(extractor) = &arg.extractor else {
2294            continue;
2295        };
2296
2297        match extractor {
2298            Extractor::Body => {
2299                let schema = openapi_schema_tokens(&arg.ty);
2300                tokens.push(quote! {
2301                    with_json_request_body(#schema)
2302                });
2303            }
2304            Extractor::Param(spec) => tokens.push(single_value_extractor_openapi_tokens(
2305                &spec.name,
2306                &arg.ty,
2307                spec.pipe.as_ref(),
2308                SingleValueOpenApiKind::Path,
2309            )),
2310            Extractor::Query(spec) => {
2311                if let Some(name) = &spec.name {
2312                    tokens.push(single_value_extractor_openapi_tokens(
2313                        name,
2314                        &arg.ty,
2315                        spec.pipe.as_ref(),
2316                        SingleValueOpenApiKind::Query,
2317                    ));
2318                }
2319            }
2320            Extractor::Header(spec) => tokens.push(single_value_extractor_openapi_tokens(
2321                &spec.name,
2322                &arg.ty,
2323                spec.pipe.as_ref(),
2324                SingleValueOpenApiKind::Header,
2325            )),
2326            Extractor::Request
2327            | Extractor::Params
2328            | Extractor::Headers
2329            | Extractor::HostParam(_)
2330            | Extractor::Ip(_)
2331            | Extractor::Custom(_) => {}
2332        }
2333    }
2334
2335    tokens
2336}
2337
2338fn openapi_schema_tokens(ty: &Type) -> proc_macro2::TokenStream {
2339    if let Some(inner) = option_inner_type(ty) {
2340        return openapi_schema_tokens(&inner);
2341    }
2342
2343    let Type::Path(type_path) = ty else {
2344        return quote!(::a3s_boot::OpenApiSchema::object());
2345    };
2346    let Some(segment) = type_path.path.segments.last() else {
2347        return quote!(::a3s_boot::OpenApiSchema::object());
2348    };
2349    let ident = &segment.ident;
2350    let ident_string = ident.to_string();
2351
2352    if ident == "Vec" {
2353        if let PathArguments::AngleBracketed(arguments) = &segment.arguments {
2354            if let Some(GenericArgument::Type(inner)) = arguments.args.first() {
2355                let inner_schema = openapi_schema_tokens(inner);
2356                return quote!(::a3s_boot::OpenApiSchema::array(#inner_schema));
2357            }
2358        }
2359    }
2360
2361    match ident_string.as_str() {
2362        "String" | "str" => quote!(::a3s_boot::OpenApiSchema::string()),
2363        "bool" => quote!(::a3s_boot::OpenApiSchema::boolean()),
2364        "i8" | "i16" | "i32" | "i64" | "i128" | "isize" | "u8" | "u16" | "u32" | "u64" | "u128"
2365        | "usize" => quote!(::a3s_boot::OpenApiSchema::integer()),
2366        "f32" | "f64" => quote!(::a3s_boot::OpenApiSchema::number()),
2367        _ => quote!(::a3s_boot::OpenApiSchema::reference(#ident_string)),
2368    }
2369}
2370
2371fn take_controller_validation_attrs(
2372    attrs: &[Attribute],
2373) -> (Vec<Attribute>, ControllerValidationAttrs, Vec<syn::Error>) {
2374    let mut clean_attrs = Vec::new();
2375    let mut validation = ControllerValidationAttrs::default();
2376    let mut errors = Vec::new();
2377
2378    for attr in attrs {
2379        let Some(kind) = ValidationAttrKind::from_attribute(attr) else {
2380            clean_attrs.push(attr.clone());
2381            continue;
2382        };
2383
2384        match kind {
2385            ValidationAttrKind::Validate => {
2386                if let Err(error) = expect_no_extractor_args(attr, "validate") {
2387                    errors.push(error);
2388                } else if validation.enabled {
2389                    errors.push(syn::Error::new_spanned(
2390                        attr,
2391                        "duplicate #[validate] attribute",
2392                    ));
2393                } else {
2394                    validation.enabled = true;
2395                }
2396            }
2397            ValidationAttrKind::SkipValidation => errors.push(syn::Error::new_spanned(
2398                attr,
2399                "#[skip_validation] is only supported on route methods",
2400            )),
2401        }
2402    }
2403
2404    (clean_attrs, validation, errors)
2405}
2406
2407fn take_route_validation_attrs(
2408    attrs: &[Attribute],
2409) -> (Vec<Attribute>, RouteValidationAttrs, Vec<syn::Error>) {
2410    let mut clean_attrs = Vec::new();
2411    let mut validation = RouteValidationAttrs::default();
2412    let mut errors = Vec::new();
2413
2414    for attr in attrs {
2415        let Some(kind) = ValidationAttrKind::from_attribute(attr) else {
2416            clean_attrs.push(attr.clone());
2417            continue;
2418        };
2419
2420        if let Err(error) = expect_no_extractor_args(attr, kind.name()) {
2421            errors.push(error);
2422            continue;
2423        }
2424
2425        match kind {
2426            ValidationAttrKind::Validate => validation.validate = true,
2427            ValidationAttrKind::SkipValidation => validation.skip = true,
2428        }
2429    }
2430
2431    if validation.validate && validation.skip {
2432        errors.push(syn::Error::new(
2433            proc_macro2::Span::call_site(),
2434            "route methods cannot use both #[validate] and #[skip_validation]",
2435        ));
2436    }
2437
2438    (clean_attrs, validation, errors)
2439}
2440
2441fn take_controller_openapi_attrs(
2442    attrs: &[Attribute],
2443) -> (Vec<Attribute>, ControllerOpenApiAttrs, Vec<syn::Error>) {
2444    let mut clean_attrs = Vec::new();
2445    let mut openapi = ControllerOpenApiAttrs::default();
2446    let mut errors = Vec::new();
2447
2448    for attr in attrs {
2449        let Some(kind) = OpenApiAttrKind::from_attribute(attr) else {
2450            clean_attrs.push(attr.clone());
2451            continue;
2452        };
2453
2454        match kind {
2455            OpenApiAttrKind::Tag => match attr.parse_args::<LitStr>() {
2456                Ok(tag) => openapi.tags.push(tag),
2457                Err(error) => errors.push(error),
2458            },
2459            _ => errors.push(syn::Error::new_spanned(
2460                attr,
2461                "only #[tag(\"name\")] is supported on #[controller] impl blocks",
2462            )),
2463        }
2464    }
2465
2466    (clean_attrs, openapi, errors)
2467}
2468
2469fn take_route_openapi_attrs(
2470    attrs: &[Attribute],
2471) -> (Vec<Attribute>, Vec<RouteOpenApiSpec>, Vec<syn::Error>) {
2472    let mut clean_attrs = Vec::new();
2473    let mut specs = Vec::new();
2474    let mut errors = Vec::new();
2475
2476    for attr in attrs {
2477        let Some(kind) = OpenApiAttrKind::from_attribute(attr) else {
2478            clean_attrs.push(attr.clone());
2479            continue;
2480        };
2481
2482        match kind.parse_route_spec(attr) {
2483            Ok(spec) => specs.push(spec),
2484            Err(error) => errors.push(error),
2485        }
2486    }
2487
2488    (clean_attrs, specs, errors)
2489}
2490
2491fn take_controller_metadata_attrs(
2492    attrs: &[Attribute],
2493) -> (Vec<Attribute>, ControllerMetadataAttrs, Vec<syn::Error>) {
2494    let mut clean_attrs = Vec::new();
2495    let mut metadata = ControllerMetadataAttrs::default();
2496    let mut errors = Vec::new();
2497
2498    for attr in attrs {
2499        if !is_metadata_attribute(attr) {
2500            clean_attrs.push(attr.clone());
2501            continue;
2502        }
2503
2504        match attr.parse_args::<MetadataSpec>() {
2505            Ok(spec) => metadata.specs.push(spec),
2506            Err(error) => errors.push(error),
2507        }
2508    }
2509
2510    (clean_attrs, metadata, errors)
2511}
2512
2513fn take_route_metadata_attrs(
2514    attrs: &[Attribute],
2515) -> (Vec<Attribute>, Vec<MetadataSpec>, Vec<syn::Error>) {
2516    let mut clean_attrs = Vec::new();
2517    let mut specs = Vec::new();
2518    let mut errors = Vec::new();
2519
2520    for attr in attrs {
2521        if !is_metadata_attribute(attr) {
2522            clean_attrs.push(attr.clone());
2523            continue;
2524        }
2525
2526        match attr.parse_args::<MetadataSpec>() {
2527            Ok(spec) => specs.push(spec),
2528            Err(error) => errors.push(error),
2529        }
2530    }
2531
2532    (clean_attrs, specs, errors)
2533}
2534
2535fn take_route_http_code_attrs(
2536    attrs: &[Attribute],
2537) -> (Vec<Attribute>, Option<LitInt>, Vec<syn::Error>) {
2538    let mut clean_attrs = Vec::new();
2539    let mut status = None;
2540    let mut errors = Vec::new();
2541
2542    for attr in attrs {
2543        if !is_http_code_attribute(attr) {
2544            clean_attrs.push(attr.clone());
2545            continue;
2546        }
2547
2548        match attr.parse_args::<LitInt>() {
2549            Ok(value) if status.is_none() => status = Some(value),
2550            Ok(value) => errors.push(syn::Error::new_spanned(
2551                value,
2552                "route methods can use at most one #[http_code(...)] attribute",
2553            )),
2554            Err(error) => errors.push(error),
2555        }
2556    }
2557
2558    (clean_attrs, status, errors)
2559}
2560
2561fn take_route_response_attrs(
2562    attrs: &[Attribute],
2563) -> (Vec<Attribute>, Vec<RouteResponseSpec>, Vec<syn::Error>) {
2564    let mut clean_attrs = Vec::new();
2565    let mut specs = Vec::new();
2566    let mut errors = Vec::new();
2567    let mut redirect_seen = false;
2568
2569    for attr in attrs {
2570        let Some(kind) = ResponseAttrKind::from_attribute(attr) else {
2571            clean_attrs.push(attr.clone());
2572            continue;
2573        };
2574
2575        match kind {
2576            ResponseAttrKind::Header => match attr.parse_args::<ResponseHeaderArgs>() {
2577                Ok(args) => specs.push(RouteResponseSpec::Header(args)),
2578                Err(error) => errors.push(error),
2579            },
2580            ResponseAttrKind::Redirect => match attr.parse_args::<RedirectArgs>() {
2581                Ok(args) if !redirect_seen => {
2582                    redirect_seen = true;
2583                    specs.push(RouteResponseSpec::Redirect(args));
2584                }
2585                Ok(args) => errors.push(syn::Error::new_spanned(
2586                    args.location,
2587                    "route methods can use at most one #[redirect(...)] attribute",
2588                )),
2589                Err(error) => errors.push(error),
2590            },
2591        }
2592    }
2593
2594    (clean_attrs, specs, errors)
2595}
2596
2597fn take_controller_pipeline_attrs(
2598    attrs: &[Attribute],
2599) -> (Vec<Attribute>, ControllerPipelineAttrs, Vec<syn::Error>) {
2600    let mut clean_attrs = Vec::new();
2601    let mut pipeline = ControllerPipelineAttrs::default();
2602    let mut errors = Vec::new();
2603
2604    for attr in attrs {
2605        let Some(kind) = PipelineAttrKind::from_attribute(attr) else {
2606            clean_attrs.push(attr.clone());
2607            continue;
2608        };
2609
2610        match attr.parse_args::<Expr>() {
2611            Ok(expr) => pipeline.specs.push(PipelineSpec { kind, expr }),
2612            Err(error) => errors.push(error),
2613        }
2614    }
2615
2616    (clean_attrs, pipeline, errors)
2617}
2618
2619fn take_route_pipeline_attrs(
2620    attrs: &[Attribute],
2621) -> (Vec<Attribute>, Vec<PipelineSpec>, Vec<syn::Error>) {
2622    let mut clean_attrs = Vec::new();
2623    let mut specs = Vec::new();
2624    let mut errors = Vec::new();
2625
2626    for attr in attrs {
2627        let Some(kind) = PipelineAttrKind::from_attribute(attr) else {
2628            clean_attrs.push(attr.clone());
2629            continue;
2630        };
2631
2632        match attr.parse_args::<Expr>() {
2633            Ok(expr) => specs.push(PipelineSpec { kind, expr }),
2634            Err(error) => errors.push(error),
2635        }
2636    }
2637
2638    (clean_attrs, specs, errors)
2639}
2640
2641fn take_controller_host_attrs(
2642    attrs: &[Attribute],
2643) -> (Vec<Attribute>, ControllerHostAttrs, Vec<syn::Error>) {
2644    let mut clean_attrs = Vec::new();
2645    let mut host = ControllerHostAttrs::default();
2646    let mut errors = Vec::new();
2647
2648    for attr in attrs {
2649        if !is_host_attribute(attr) {
2650            clean_attrs.push(attr.clone());
2651            continue;
2652        }
2653
2654        match attr.parse_args::<LitStr>() {
2655            Ok(pattern) => {
2656                if host.pattern.is_some() {
2657                    errors.push(syn::Error::new_spanned(
2658                        attr,
2659                        "controller impl blocks can use at most one #[host] attribute",
2660                    ));
2661                } else {
2662                    host.pattern = Some(pattern);
2663                }
2664            }
2665            Err(_) => errors.push(syn::Error::new_spanned(
2666                attr,
2667                "#[host] requires one string literal argument",
2668            )),
2669        }
2670    }
2671
2672    (clean_attrs, host, errors)
2673}
2674
2675fn take_route_host_attrs(
2676    attrs: &[Attribute],
2677) -> (Vec<Attribute>, Option<HostSpec>, Vec<syn::Error>) {
2678    let mut clean_attrs = Vec::new();
2679    let mut spec = None;
2680    let mut errors = Vec::new();
2681
2682    for attr in attrs {
2683        if !is_host_attribute(attr) {
2684            clean_attrs.push(attr.clone());
2685            continue;
2686        }
2687
2688        match attr.parse_args::<LitStr>() {
2689            Ok(pattern) => {
2690                if spec.is_some() {
2691                    errors.push(syn::Error::new_spanned(
2692                        attr,
2693                        "route methods can use at most one #[host] attribute",
2694                    ));
2695                } else {
2696                    spec = Some(HostSpec { pattern });
2697                }
2698            }
2699            Err(_) => errors.push(syn::Error::new_spanned(
2700                attr,
2701                "#[host] requires one string literal argument",
2702            )),
2703        }
2704    }
2705
2706    (clean_attrs, spec, errors)
2707}
2708
2709fn is_host_attribute(attr: &Attribute) -> bool {
2710    attr.path()
2711        .segments
2712        .last()
2713        .is_some_and(|segment| segment.ident == "host")
2714}
2715
2716fn take_controller_version_attrs(
2717    attrs: &[Attribute],
2718) -> (Vec<Attribute>, ControllerVersionAttrs, Vec<syn::Error>) {
2719    let mut clean_attrs = Vec::new();
2720    let mut version = ControllerVersionAttrs::default();
2721    let mut errors = Vec::new();
2722
2723    for attr in attrs {
2724        let Some(kind) = VersionAttrKind::from_attribute(attr) else {
2725            clean_attrs.push(attr.clone());
2726            continue;
2727        };
2728
2729        match VersionSpec::from_attribute(kind, attr) {
2730            Ok(spec) => {
2731                if version.spec.is_some() {
2732                    errors.push(syn::Error::new_spanned(
2733                        attr,
2734                        "controller impl blocks can use at most one version attribute",
2735                    ));
2736                } else {
2737                    version.spec = Some(spec);
2738                }
2739            }
2740            Err(error) => errors.push(error),
2741        }
2742    }
2743
2744    (clean_attrs, version, errors)
2745}
2746
2747fn take_route_version_attrs(
2748    attrs: &[Attribute],
2749) -> (Vec<Attribute>, Option<VersionSpec>, Vec<syn::Error>) {
2750    let mut clean_attrs = Vec::new();
2751    let mut spec = None;
2752    let mut errors = Vec::new();
2753
2754    for attr in attrs {
2755        let Some(kind) = VersionAttrKind::from_attribute(attr) else {
2756            clean_attrs.push(attr.clone());
2757            continue;
2758        };
2759
2760        match VersionSpec::from_attribute(kind, attr) {
2761            Ok(parsed) => {
2762                if spec.is_some() {
2763                    errors.push(syn::Error::new_spanned(
2764                        attr,
2765                        "route methods can use at most one version attribute",
2766                    ));
2767                } else {
2768                    spec = Some(parsed);
2769                }
2770            }
2771            Err(error) => errors.push(error),
2772        }
2773    }
2774
2775    (clean_attrs, spec, errors)
2776}
2777
2778fn take_controller_serialization_attrs(
2779    attrs: &[Attribute],
2780) -> (
2781    Vec<Attribute>,
2782    ControllerSerializationAttrs,
2783    Vec<syn::Error>,
2784) {
2785    let mut clean_attrs = Vec::new();
2786    let mut serialization = ControllerSerializationAttrs::default();
2787    let mut errors = Vec::new();
2788
2789    for attr in attrs {
2790        if !is_serialization_attribute(attr) {
2791            clean_attrs.push(attr.clone());
2792            continue;
2793        }
2794
2795        match attr.parse_args::<SerializationSpec>() {
2796            Ok(spec) => {
2797                if serialization.spec.is_some() {
2798                    errors.push(syn::Error::new_spanned(
2799                        attr,
2800                        "controller impl blocks can use at most one #[serialize] attribute",
2801                    ));
2802                } else {
2803                    serialization.spec = Some(spec);
2804                }
2805            }
2806            Err(error) => errors.push(error),
2807        }
2808    }
2809
2810    (clean_attrs, serialization, errors)
2811}
2812
2813fn take_route_serialization_attrs(
2814    attrs: &[Attribute],
2815) -> (Vec<Attribute>, Option<SerializationSpec>, Vec<syn::Error>) {
2816    let mut clean_attrs = Vec::new();
2817    let mut spec = None;
2818    let mut errors = Vec::new();
2819
2820    for attr in attrs {
2821        if !is_serialization_attribute(attr) {
2822            clean_attrs.push(attr.clone());
2823            continue;
2824        }
2825
2826        match attr.parse_args::<SerializationSpec>() {
2827            Ok(parsed) => {
2828                if spec.is_some() {
2829                    errors.push(syn::Error::new_spanned(
2830                        attr,
2831                        "route methods can use at most one #[serialize] attribute",
2832                    ));
2833                } else {
2834                    spec = Some(parsed);
2835                }
2836            }
2837            Err(error) => errors.push(error),
2838        }
2839    }
2840
2841    (clean_attrs, spec, errors)
2842}
2843
2844fn is_serialization_attribute(attr: &Attribute) -> bool {
2845    attr.path()
2846        .segments
2847        .last()
2848        .is_some_and(|segment| segment.ident == "serialize")
2849}
2850
2851fn is_metadata_attribute(attr: &Attribute) -> bool {
2852    attr.path()
2853        .segments
2854        .last()
2855        .is_some_and(|segment| segment.ident == "metadata")
2856}
2857
2858fn is_http_code_attribute(attr: &Attribute) -> bool {
2859    attr.path()
2860        .segments
2861        .last()
2862        .is_some_and(|segment| segment.ident == "http_code")
2863}
2864
2865#[derive(Default)]
2866struct ControllerOpenApiAttrs {
2867    tags: Vec<LitStr>,
2868}
2869
2870impl ControllerOpenApiAttrs {
2871    fn tokens(&self) -> Vec<proc_macro2::TokenStream> {
2872        self.tags.iter().map(|tag| quote!(with_tag(#tag))).collect()
2873    }
2874}
2875
2876#[derive(Default)]
2877struct ControllerMetadataAttrs {
2878    specs: Vec<MetadataSpec>,
2879}
2880
2881impl ControllerMetadataAttrs {
2882    fn tokens(&self) -> Vec<proc_macro2::TokenStream> {
2883        self.specs
2884            .iter()
2885            .map(|spec| {
2886                let key = &spec.key;
2887                let value = &spec.value;
2888                quote!(with_metadata(#key, #value))
2889            })
2890            .collect()
2891    }
2892}
2893
2894#[derive(Clone)]
2895struct MetadataSpec {
2896    key: LitStr,
2897    value: Expr,
2898}
2899
2900impl Parse for MetadataSpec {
2901    fn parse(input: ParseStream<'_>) -> Result<Self> {
2902        let key = input.parse::<LitStr>()?;
2903        input.parse::<Token![,]>()?;
2904        let value = input.parse::<Expr>()?;
2905        parse_optional_comma(input)?;
2906        Ok(Self { key, value })
2907    }
2908}
2909
2910#[derive(Default)]
2911struct ControllerPipelineAttrs {
2912    specs: Vec<PipelineSpec>,
2913}
2914
2915impl ControllerPipelineAttrs {
2916    fn tokens(&self) -> Vec<proc_macro2::TokenStream> {
2917        self.specs.iter().map(PipelineSpec::token).collect()
2918    }
2919}
2920
2921#[derive(Clone)]
2922struct PipelineSpec {
2923    kind: PipelineAttrKind,
2924    expr: Expr,
2925}
2926
2927impl PipelineSpec {
2928    fn token(&self) -> proc_macro2::TokenStream {
2929        let expr = &self.expr;
2930        match self.kind {
2931            PipelineAttrKind::Guard => quote!(with_guard(#expr)),
2932            PipelineAttrKind::Interceptor => quote!(with_interceptor(#expr)),
2933            PipelineAttrKind::Filter => quote!(with_filter(#expr)),
2934            PipelineAttrKind::Pipe => quote!(with_pipe(#expr)),
2935        }
2936    }
2937}
2938
2939#[derive(Clone, Copy)]
2940enum PipelineAttrKind {
2941    Guard,
2942    Interceptor,
2943    Filter,
2944    Pipe,
2945}
2946
2947impl PipelineAttrKind {
2948    fn from_attribute(attr: &Attribute) -> Option<Self> {
2949        let ident = attr.path().segments.last()?.ident.to_string();
2950        match ident.as_str() {
2951            "use_guard" => Some(Self::Guard),
2952            "use_interceptor" => Some(Self::Interceptor),
2953            "use_filter" => Some(Self::Filter),
2954            "use_pipe" => Some(Self::Pipe),
2955            _ => None,
2956        }
2957    }
2958}
2959
2960#[derive(Default)]
2961struct ControllerHostAttrs {
2962    pattern: Option<LitStr>,
2963}
2964
2965impl ControllerHostAttrs {
2966    fn tokens(&self) -> Vec<proc_macro2::TokenStream> {
2967        self.pattern
2968            .iter()
2969            .map(|pattern| quote!(with_host(#pattern)))
2970            .collect()
2971    }
2972}
2973
2974struct HostSpec {
2975    pattern: LitStr,
2976}
2977
2978impl HostSpec {
2979    fn token(&self) -> proc_macro2::TokenStream {
2980        let pattern = &self.pattern;
2981        quote!(with_host(#pattern))
2982    }
2983}
2984
2985#[derive(Default)]
2986struct ControllerVersionAttrs {
2987    spec: Option<VersionSpec>,
2988}
2989
2990impl ControllerVersionAttrs {
2991    fn tokens(&self) -> Vec<proc_macro2::TokenStream> {
2992        self.spec.iter().map(VersionSpec::token).collect()
2993    }
2994}
2995
2996#[derive(Clone)]
2997enum VersionSpec {
2998    Version(LitStr),
2999    Versions(Vec<LitStr>),
3000    Neutral,
3001}
3002
3003impl VersionSpec {
3004    fn from_attribute(kind: VersionAttrKind, attr: &Attribute) -> Result<Self> {
3005        match kind {
3006            VersionAttrKind::Version => {
3007                attr.parse_args::<LitStr>().map(Self::Version).map_err(|_| {
3008                    syn::Error::new_spanned(attr, "#[version] requires one string literal argument")
3009                })
3010            }
3011            VersionAttrKind::Versions => {
3012                let values = attr.parse_args::<VersionList>().map_err(|_| {
3013                    syn::Error::new_spanned(
3014                        attr,
3015                        "#[versions] requires one or more string literal arguments",
3016                    )
3017                })?;
3018                if values.0.is_empty() {
3019                    Err(syn::Error::new_spanned(
3020                        attr,
3021                        "#[versions] requires one or more string literal arguments",
3022                    ))
3023                } else {
3024                    Ok(Self::Versions(values.0))
3025                }
3026            }
3027            VersionAttrKind::Neutral => {
3028                expect_no_extractor_args(attr, "version_neutral")?;
3029                Ok(Self::Neutral)
3030            }
3031        }
3032    }
3033
3034    fn token(&self) -> proc_macro2::TokenStream {
3035        match self {
3036            Self::Version(version) => quote!(with_version(#version)),
3037            Self::Versions(versions) => quote!(with_versions([#(#versions),*])),
3038            Self::Neutral => quote!(version_neutral()),
3039        }
3040    }
3041}
3042
3043#[derive(Clone, Copy)]
3044enum VersionAttrKind {
3045    Version,
3046    Versions,
3047    Neutral,
3048}
3049
3050impl VersionAttrKind {
3051    fn from_attribute(attr: &Attribute) -> Option<Self> {
3052        let ident = attr.path().segments.last()?.ident.to_string();
3053        match ident.as_str() {
3054            "version" => Some(Self::Version),
3055            "versions" => Some(Self::Versions),
3056            "version_neutral" => Some(Self::Neutral),
3057            _ => None,
3058        }
3059    }
3060}
3061
3062struct VersionList(Vec<LitStr>);
3063
3064impl Parse for VersionList {
3065    fn parse(input: ParseStream<'_>) -> Result<Self> {
3066        let values = Punctuated::<LitStr, Token![,]>::parse_terminated(input)?
3067            .into_iter()
3068            .collect();
3069        Ok(Self(values))
3070    }
3071}
3072
3073#[derive(Default)]
3074struct ControllerSerializationAttrs {
3075    spec: Option<SerializationSpec>,
3076}
3077
3078impl ControllerSerializationAttrs {
3079    fn tokens(&self) -> Vec<proc_macro2::TokenStream> {
3080        self.spec.iter().map(SerializationSpec::token).collect()
3081    }
3082}
3083
3084#[derive(Clone, Default)]
3085struct SerializationSpec {
3086    include_fields: Vec<LitStr>,
3087    exclude_fields: Vec<LitStr>,
3088    skip_null_fields: bool,
3089}
3090
3091impl SerializationSpec {
3092    fn token(&self) -> proc_macro2::TokenStream {
3093        let mut options = quote!(::a3s_boot::SerializationOptions::new());
3094
3095        if !self.include_fields.is_empty() {
3096            let fields = &self.include_fields;
3097            options = quote! {
3098                (#options).include_fields([#(#fields),*])
3099            };
3100        }
3101
3102        if !self.exclude_fields.is_empty() {
3103            let fields = &self.exclude_fields;
3104            options = quote! {
3105                (#options).exclude_fields([#(#fields),*])
3106            };
3107        }
3108
3109        if self.skip_null_fields {
3110            options = quote! {
3111                (#options).skip_null_fields()
3112            };
3113        }
3114
3115        quote!(with_serialization(#options))
3116    }
3117}
3118
3119impl Parse for SerializationSpec {
3120    fn parse(input: ParseStream<'_>) -> Result<Self> {
3121        let mut spec = Self::default();
3122
3123        while !input.is_empty() {
3124            let name = input.parse::<Ident>()?;
3125            let key = name.to_string();
3126            match key.as_str() {
3127                "include" => {
3128                    input.parse::<Token![=]>()?;
3129                    spec.include_fields.extend(parse_lit_str_array(input)?);
3130                }
3131                "exclude" => {
3132                    input.parse::<Token![=]>()?;
3133                    spec.exclude_fields.extend(parse_lit_str_array(input)?);
3134                }
3135                "skip_null" => {
3136                    if input.peek(Token![=]) {
3137                        input.parse::<Token![=]>()?;
3138                        spec.skip_null_fields = input.parse::<LitBool>()?.value;
3139                    } else {
3140                        spec.skip_null_fields = true;
3141                    }
3142                }
3143                _ => {
3144                    return Err(syn::Error::new_spanned(
3145                        name,
3146                        "expected `include`, `exclude`, or `skip_null`",
3147                    ));
3148                }
3149            }
3150
3151            if input.is_empty() {
3152                break;
3153            }
3154            input.parse::<Token![,]>()?;
3155        }
3156
3157        Ok(spec)
3158    }
3159}
3160
3161fn parse_lit_str_array(input: ParseStream<'_>) -> Result<Vec<LitStr>> {
3162    let content;
3163    syn::bracketed!(content in input);
3164    Ok(Punctuated::<LitStr, Token![,]>::parse_terminated(&content)?
3165        .into_iter()
3166        .collect())
3167}
3168
3169#[derive(Clone, Copy)]
3170enum ResponseAttrKind {
3171    Header,
3172    Redirect,
3173}
3174
3175impl ResponseAttrKind {
3176    fn from_attribute(attr: &Attribute) -> Option<Self> {
3177        let ident = attr.path().segments.last()?.ident.to_string();
3178        match ident.as_str() {
3179            "header" => Some(Self::Header),
3180            "redirect" => Some(Self::Redirect),
3181            _ => None,
3182        }
3183    }
3184}
3185
3186#[derive(Clone)]
3187enum RouteResponseSpec {
3188    Header(ResponseHeaderArgs),
3189    Redirect(RedirectArgs),
3190}
3191
3192impl RouteResponseSpec {
3193    fn token(&self) -> Result<proc_macro2::TokenStream> {
3194        match self {
3195            Self::Header(args) => {
3196                let name = &args.name;
3197                let value = &args.value;
3198                Ok(quote!(with_response_header(#name, #value)))
3199            }
3200            Self::Redirect(args) => {
3201                let location = &args.location;
3202                let status = status_value(args.status.as_ref())?;
3203                Ok(quote!(with_redirect_status(#status, #location)))
3204            }
3205        }
3206    }
3207}
3208
3209#[derive(Clone)]
3210struct ResponseHeaderArgs {
3211    name: LitStr,
3212    value: LitStr,
3213}
3214
3215impl Parse for ResponseHeaderArgs {
3216    fn parse(input: ParseStream<'_>) -> Result<Self> {
3217        let name = input.parse::<LitStr>()?;
3218        input.parse::<Token![,]>()?;
3219        let value = input.parse::<LitStr>()?;
3220        parse_optional_comma(input)?;
3221        Ok(Self { name, value })
3222    }
3223}
3224
3225#[derive(Clone)]
3226struct RedirectArgs {
3227    location: LitStr,
3228    status: Option<LitInt>,
3229}
3230
3231impl Parse for RedirectArgs {
3232    fn parse(input: ParseStream<'_>) -> Result<Self> {
3233        let location = input.parse::<LitStr>()?;
3234        let mut status = None;
3235
3236        if !input.is_empty() {
3237            input.parse::<Token![,]>()?;
3238            if input.peek(LitInt) {
3239                status = Some(input.parse::<LitInt>()?);
3240            } else {
3241                let name = input.parse::<Ident>()?;
3242                if name != "status" {
3243                    return Err(syn::Error::new_spanned(name, "expected `status`"));
3244                }
3245                input.parse::<Token![=]>()?;
3246                status = Some(input.parse::<LitInt>()?);
3247            }
3248            parse_optional_comma(input)?;
3249        }
3250
3251        Ok(Self { location, status })
3252    }
3253}
3254
3255#[derive(Clone, Copy)]
3256enum ValidationAttrKind {
3257    Validate,
3258    SkipValidation,
3259}
3260
3261impl ValidationAttrKind {
3262    fn from_attribute(attr: &Attribute) -> Option<Self> {
3263        let ident = attr.path().segments.last()?.ident.to_string();
3264        match ident.as_str() {
3265            "validate" => Some(Self::Validate),
3266            "skip_validation" => Some(Self::SkipValidation),
3267            _ => None,
3268        }
3269    }
3270
3271    fn name(self) -> &'static str {
3272        match self {
3273            Self::Validate => "validate",
3274            Self::SkipValidation => "skip_validation",
3275        }
3276    }
3277}
3278
3279#[derive(Clone, Copy, Default)]
3280struct ControllerValidationAttrs {
3281    enabled: bool,
3282}
3283
3284#[derive(Clone, Copy, Default)]
3285struct RouteValidationAttrs {
3286    validate: bool,
3287    skip: bool,
3288}
3289
3290impl RouteValidationAttrs {
3291    fn is_present(self) -> bool {
3292        self.validate || self.skip
3293    }
3294
3295    fn enabled(self, controller_enabled: bool) -> bool {
3296        !self.skip && (controller_enabled || self.validate)
3297    }
3298}
3299
3300#[derive(Clone, Copy)]
3301enum MessagePatternAttrKind {
3302    Message,
3303    Event,
3304}
3305
3306impl MessagePatternAttrKind {
3307    fn from_attribute(attr: &Attribute) -> Option<Self> {
3308        let ident = attr.path().segments.last()?.ident.to_string();
3309        match ident.as_str() {
3310            "message_pattern" => Some(Self::Message),
3311            "event_pattern" => Some(Self::Event),
3312            _ => None,
3313        }
3314    }
3315}
3316
3317struct MessagePatternSpec {
3318    kind: MessagePatternAttrKind,
3319    args: MessagePatternArgs,
3320}
3321
3322struct EventMethodInput {
3323    args: Vec<EventMethodArg>,
3324}
3325
3326impl EventMethodInput {
3327    fn from_method(method: &ImplItemFn) -> Result<Self> {
3328        let mut inputs = method.sig.inputs.iter();
3329        let Some(FnArg::Receiver(receiver)) = inputs.next() else {
3330            return Err(syn::Error::new_spanned(
3331                &method.sig.ident,
3332                "event listener methods must take &self as their first argument",
3333            ));
3334        };
3335
3336        if receiver.reference.is_none() || receiver.mutability.is_some() {
3337            return Err(syn::Error::new_spanned(
3338                receiver,
3339                "event listener methods must use an immutable &self receiver",
3340            ));
3341        }
3342
3343        let mut args = Vec::new();
3344        let mut event_arg_seen = false;
3345        let mut context_seen = false;
3346
3347        for (index, input) in inputs.enumerate() {
3348            let FnArg::Typed(input) = input else {
3349                return Err(syn::Error::new_spanned(
3350                    input,
3351                    "unexpected receiver argument",
3352                ));
3353            };
3354
3355            if index > 1 {
3356                return Err(syn::Error::new_spanned(
3357                    input,
3358                    "event listener methods can accept at most one event argument and one EventContext argument after &self",
3359                ));
3360            }
3361
3362            let Pat::Ident(ident) = input.pat.as_ref() else {
3363                return Err(syn::Error::new_spanned(
3364                    &input.pat,
3365                    "event listener arguments must be simple identifiers",
3366                ));
3367            };
3368
3369            let ty = input.ty.clone();
3370            let kind = if is_type_ident(&ty, "EventEnvelope") {
3371                EventMethodArgKind::Envelope
3372            } else if is_type_ident(&ty, "EventContext") {
3373                EventMethodArgKind::Context
3374            } else {
3375                EventMethodArgKind::Payload
3376            };
3377
3378            match kind {
3379                EventMethodArgKind::Envelope | EventMethodArgKind::Payload => {
3380                    if event_arg_seen {
3381                        return Err(syn::Error::new_spanned(
3382                            input,
3383                            "event listener methods can accept at most one event payload or EventEnvelope argument",
3384                        ));
3385                    }
3386                    event_arg_seen = true;
3387                }
3388                EventMethodArgKind::Context => {
3389                    if context_seen {
3390                        return Err(syn::Error::new_spanned(
3391                            input,
3392                            "event listener methods can accept at most one EventContext argument",
3393                        ));
3394                    }
3395                    context_seen = true;
3396                }
3397            }
3398
3399            args.push(EventMethodArg {
3400                ident: ident.ident.clone(),
3401                ty,
3402                kind,
3403            });
3404        }
3405
3406        Ok(Self { args })
3407    }
3408
3409    fn argument_tokens(&self) -> (Vec<proc_macro2::TokenStream>, Vec<Ident>) {
3410        let mut extractors = Vec::new();
3411        let mut args = Vec::new();
3412
3413        for arg in &self.args {
3414            let ident = &arg.ident;
3415            let ty = &arg.ty;
3416            args.push(ident.clone());
3417            extractors.push(match arg.kind {
3418                EventMethodArgKind::Envelope => quote! {
3419                    let #ident: #ty = __a3s_boot_event.clone();
3420                },
3421                EventMethodArgKind::Context => quote! {
3422                    let #ident: #ty = __a3s_boot_context.clone();
3423                },
3424                EventMethodArgKind::Payload => quote! {
3425                    let #ident: #ty = __a3s_boot_event.data_as::<#ty>()?;
3426                },
3427            });
3428        }
3429
3430        (extractors, args)
3431    }
3432}
3433
3434struct EventMethodArg {
3435    ident: Ident,
3436    ty: Box<Type>,
3437    kind: EventMethodArgKind,
3438}
3439
3440#[derive(Clone, Copy)]
3441enum EventMethodArgKind {
3442    Envelope,
3443    Context,
3444    Payload,
3445}
3446
3447#[derive(Clone, Copy)]
3448enum ScheduleJobKind {
3449    Cron,
3450    Interval,
3451    Timeout,
3452}
3453
3454impl ScheduleJobKind {
3455    fn from_attribute(attr: &Attribute) -> Option<Self> {
3456        let ident = attr.path().segments.last()?.ident.to_string();
3457        match ident.as_str() {
3458            "cron" => Some(Self::Cron),
3459            "interval" => Some(Self::Interval),
3460            "timeout" => Some(Self::Timeout),
3461            _ => None,
3462        }
3463    }
3464
3465    fn parse_args(self, attr: &Attribute) -> Result<ScheduleJobArgs> {
3466        match self {
3467            Self::Cron => attr
3468                .parse_args::<CronScheduleArgs>()
3469                .map(ScheduleJobArgs::Cron),
3470            Self::Interval => attr
3471                .parse_args::<DurationScheduleArgs>()
3472                .map(ScheduleJobArgs::Interval),
3473            Self::Timeout => attr
3474                .parse_args::<DurationScheduleArgs>()
3475                .map(ScheduleJobArgs::Timeout),
3476        }
3477    }
3478}
3479
3480struct ScheduleJobSpec {
3481    kind: ScheduleJobKind,
3482    args: ScheduleJobArgs,
3483}
3484
3485enum ScheduleJobArgs {
3486    Cron(CronScheduleArgs),
3487    Interval(DurationScheduleArgs),
3488    Timeout(DurationScheduleArgs),
3489}
3490
3491impl ScheduleJobArgs {
3492    fn name_token(&self, method_ident: &Ident) -> proc_macro2::TokenStream {
3493        let explicit_name = match self {
3494            Self::Cron(args) => args.name.as_ref(),
3495            Self::Interval(args) | Self::Timeout(args) => args.name.as_ref(),
3496        };
3497
3498        match explicit_name {
3499            Some(name) => quote!(#name),
3500            None => {
3501                let default_name = LitStr::new(&method_ident.to_string(), method_ident.span());
3502                quote!(#default_name)
3503            }
3504        }
3505    }
3506}
3507
3508struct CronScheduleArgs {
3509    name: Option<LitStr>,
3510    expression: LitStr,
3511}
3512
3513impl Parse for CronScheduleArgs {
3514    fn parse(input: ParseStream<'_>) -> Result<Self> {
3515        let first = input.parse::<LitStr>()?;
3516        if input.is_empty() {
3517            return Ok(Self {
3518                name: None,
3519                expression: first,
3520            });
3521        }
3522
3523        input.parse::<Token![,]>()?;
3524        let expression = input.parse::<LitStr>()?;
3525        parse_optional_comma(input)?;
3526        Ok(Self {
3527            name: Some(first),
3528            expression,
3529        })
3530    }
3531}
3532
3533struct DurationScheduleArgs {
3534    name: Option<LitStr>,
3535    millis: LitInt,
3536}
3537
3538impl DurationScheduleArgs {
3539    fn duration_millis(&self) -> Result<u64> {
3540        self.millis.base10_parse::<u64>()
3541    }
3542}
3543
3544impl Parse for DurationScheduleArgs {
3545    fn parse(input: ParseStream<'_>) -> Result<Self> {
3546        let (name, millis) = if input.peek(LitInt) {
3547            (None, input.parse::<LitInt>()?)
3548        } else if input.peek(LitStr) {
3549            let name = input.parse::<LitStr>()?;
3550            input.parse::<Token![,]>()?;
3551            (Some(name), input.parse::<LitInt>()?)
3552        } else {
3553            return Err(input.error("expected milliseconds or a job name followed by milliseconds"));
3554        };
3555
3556        parse_optional_comma(input)?;
3557        Ok(Self { name, millis })
3558    }
3559}
3560
3561#[derive(Clone, Copy)]
3562struct ScheduleMethodInput {
3563    accepts_context: bool,
3564}
3565
3566impl ScheduleMethodInput {
3567    fn from_method(method: &ImplItemFn) -> Result<Self> {
3568        let mut inputs = method.sig.inputs.iter();
3569        let Some(FnArg::Receiver(receiver)) = inputs.next() else {
3570            return Err(syn::Error::new_spanned(
3571                &method.sig.ident,
3572                "scheduled job methods must take &self as their first argument",
3573            ));
3574        };
3575
3576        if receiver.reference.is_none() || receiver.mutability.is_some() {
3577            return Err(syn::Error::new_spanned(
3578                receiver,
3579                "scheduled job methods must use an immutable &self receiver",
3580            ));
3581        }
3582
3583        let mut accepts_context = false;
3584        for (index, input) in inputs.enumerate() {
3585            let FnArg::Typed(input) = input else {
3586                return Err(syn::Error::new_spanned(
3587                    input,
3588                    "unexpected receiver argument",
3589                ));
3590            };
3591
3592            if index > 0 {
3593                return Err(syn::Error::new_spanned(
3594                    input,
3595                    "scheduled job methods can accept at most one ScheduleContext argument after &self",
3596                ));
3597            }
3598
3599            let Pat::Ident(_) = input.pat.as_ref() else {
3600                return Err(syn::Error::new_spanned(
3601                    &input.pat,
3602                    "scheduled job arguments must be simple identifiers",
3603                ));
3604            };
3605            accepts_context = true;
3606        }
3607
3608        Ok(Self { accepts_context })
3609    }
3610}
3611
3612#[derive(Clone)]
3613struct MessagePatternArgs {
3614    pattern: LitStr,
3615    raw: Option<Ident>,
3616}
3617
3618impl Parse for MessagePatternArgs {
3619    fn parse(input: ParseStream<'_>) -> Result<Self> {
3620        let pattern = input.parse::<LitStr>()?;
3621        let mut raw = None;
3622
3623        while !input.is_empty() {
3624            input.parse::<Token![,]>()?;
3625            let name = input.parse::<Ident>()?;
3626
3627            if name == "raw" {
3628                if raw.is_some() {
3629                    return Err(syn::Error::new_spanned(name, "duplicate `raw` option"));
3630                }
3631                raw = Some(name);
3632            } else {
3633                return Err(syn::Error::new_spanned(name, "expected `raw`"));
3634            }
3635        }
3636
3637        Ok(Self { pattern, raw })
3638    }
3639}
3640
3641#[derive(Clone, Copy)]
3642enum OpenApiAttrKind {
3643    Tag,
3644    Operation,
3645    Response,
3646    RequestBody,
3647    BearerAuth,
3648    HideFromOpenApi,
3649}
3650
3651impl OpenApiAttrKind {
3652    fn from_attribute(attr: &Attribute) -> Option<Self> {
3653        let ident = attr.path().segments.last()?.ident.to_string();
3654        match ident.as_str() {
3655            "tag" => Some(Self::Tag),
3656            "operation" => Some(Self::Operation),
3657            "response" => Some(Self::Response),
3658            "request_body" => Some(Self::RequestBody),
3659            "bearer_auth" => Some(Self::BearerAuth),
3660            "hide_from_openapi" => Some(Self::HideFromOpenApi),
3661            _ => None,
3662        }
3663    }
3664
3665    fn parse_route_spec(self, attr: &Attribute) -> Result<RouteOpenApiSpec> {
3666        match self {
3667            Self::Tag => attr.parse_args::<LitStr>().map(RouteOpenApiSpec::Tag),
3668            Self::Operation => attr
3669                .parse_args::<OperationArgs>()
3670                .map(RouteOpenApiSpec::Operation),
3671            Self::Response => attr
3672                .parse_args::<ResponseArgs>()
3673                .map(RouteOpenApiSpec::Response),
3674            Self::RequestBody => attr
3675                .parse_args::<RequestBodyArgs>()
3676                .map(RouteOpenApiSpec::RequestBody),
3677            Self::BearerAuth => {
3678                expect_no_extractor_args(attr, "bearer_auth")?;
3679                Ok(RouteOpenApiSpec::BearerAuth)
3680            }
3681            Self::HideFromOpenApi => {
3682                expect_no_extractor_args(attr, "hide_from_openapi")?;
3683                Ok(RouteOpenApiSpec::HideFromOpenApi)
3684            }
3685        }
3686    }
3687}
3688
3689#[derive(Clone)]
3690enum RouteOpenApiSpec {
3691    Tag(LitStr),
3692    Operation(OperationArgs),
3693    Response(ResponseArgs),
3694    RequestBody(RequestBodyArgs),
3695    BearerAuth,
3696    HideFromOpenApi,
3697}
3698
3699impl RouteOpenApiSpec {
3700    fn tokens(&self) -> Result<Vec<proc_macro2::TokenStream>> {
3701        match self {
3702            Self::Tag(tag) => Ok(vec![quote!(with_tag(#tag))]),
3703            Self::Operation(args) => Ok(args.tokens()),
3704            Self::Response(args) => args.tokens().map(|token| vec![token]),
3705            Self::RequestBody(args) => Ok(vec![args.tokens()]),
3706            Self::BearerAuth => Ok(vec![quote!(with_bearer_auth())]),
3707            Self::HideFromOpenApi => Ok(vec![quote!(hide_from_openapi())]),
3708        }
3709    }
3710}
3711
3712#[derive(Clone, Default)]
3713struct OperationArgs {
3714    summary: Option<LitStr>,
3715    description: Option<LitStr>,
3716    operation_id: Option<LitStr>,
3717    deprecated: bool,
3718}
3719
3720impl OperationArgs {
3721    fn tokens(&self) -> Vec<proc_macro2::TokenStream> {
3722        let mut tokens = Vec::new();
3723        if let Some(summary) = &self.summary {
3724            tokens.push(quote!(with_summary(#summary)));
3725        }
3726        if let Some(description) = &self.description {
3727            tokens.push(quote!(with_description(#description)));
3728        }
3729        if let Some(operation_id) = &self.operation_id {
3730            tokens.push(quote!(with_operation_id(#operation_id)));
3731        }
3732        if self.deprecated {
3733            tokens.push(quote!(with_deprecated()));
3734        }
3735        tokens
3736    }
3737}
3738
3739impl Parse for OperationArgs {
3740    fn parse(input: ParseStream<'_>) -> Result<Self> {
3741        let mut args = Self::default();
3742
3743        while !input.is_empty() {
3744            let name = input.parse::<Ident>()?;
3745            if name == "deprecated" {
3746                if args.deprecated {
3747                    return Err(syn::Error::new_spanned(
3748                        name,
3749                        "duplicate `deprecated` option",
3750                    ));
3751                }
3752                args.deprecated = true;
3753            } else {
3754                input.parse::<Token![=]>()?;
3755                let value = input.parse::<LitStr>()?;
3756                if name == "summary" {
3757                    set_once(&mut args.summary, value, name)?;
3758                } else if name == "description" {
3759                    set_once(&mut args.description, value, name)?;
3760                } else if name == "operation_id" || name == "id" {
3761                    set_once(&mut args.operation_id, value, name)?;
3762                } else {
3763                    return Err(syn::Error::new_spanned(
3764                        name,
3765                        "expected `summary`, `description`, `operation_id`, or `deprecated`",
3766                    ));
3767                }
3768            }
3769            parse_optional_comma(input)?;
3770        }
3771
3772        Ok(args)
3773    }
3774}
3775
3776#[derive(Clone)]
3777struct ResponseArgs {
3778    status: LitInt,
3779    description: Option<LitStr>,
3780    schema: Option<Type>,
3781}
3782
3783impl ResponseArgs {
3784    fn tokens(&self) -> Result<proc_macro2::TokenStream> {
3785        let status = self.status.base10_parse::<u16>()?;
3786        let description = match &self.description {
3787            Some(description) => quote!(#description),
3788            None => quote!("Success"),
3789        };
3790
3791        Ok(match &self.schema {
3792            Some(schema) => {
3793                let schema = openapi_schema_tokens(schema);
3794                quote!(with_json_response(#status, #description, #schema))
3795            }
3796            None => quote! {
3797                with_response(
3798                    #status,
3799                    ::a3s_boot::OpenApiResponse::description(#description)
3800                )
3801            },
3802        })
3803    }
3804}
3805
3806impl Parse for ResponseArgs {
3807    fn parse(input: ParseStream<'_>) -> Result<Self> {
3808        let mut status = None;
3809        let mut description = None;
3810        let mut schema = None;
3811
3812        while !input.is_empty() {
3813            let name = input.parse::<Ident>()?;
3814            input.parse::<Token![=]>()?;
3815            if name == "status" {
3816                set_once(&mut status, input.parse::<LitInt>()?, name)?;
3817            } else if name == "description" {
3818                set_once(&mut description, input.parse::<LitStr>()?, name)?;
3819            } else if name == "schema" || name == "ty" || name == "body" {
3820                set_once(&mut schema, input.parse::<Type>()?, name)?;
3821            } else {
3822                return Err(syn::Error::new_spanned(
3823                    name,
3824                    "expected `status`, `description`, or `schema`",
3825                ));
3826            }
3827            parse_optional_comma(input)?;
3828        }
3829
3830        let Some(status) = status else {
3831            return Err(input.error("missing required `status` option"));
3832        };
3833
3834        Ok(Self {
3835            status,
3836            description,
3837            schema,
3838        })
3839    }
3840}
3841
3842#[derive(Clone, Default)]
3843struct RequestBodyArgs {
3844    schema: Option<Type>,
3845    description: Option<LitStr>,
3846    required: Option<LitBool>,
3847}
3848
3849impl RequestBodyArgs {
3850    fn tokens(&self) -> proc_macro2::TokenStream {
3851        let schema = self
3852            .schema
3853            .as_ref()
3854            .map(openapi_schema_tokens)
3855            .unwrap_or_else(|| quote!(::a3s_boot::OpenApiSchema::object()));
3856        let mut request_body = quote!(::a3s_boot::OpenApiRequestBody::json(#schema));
3857
3858        if let Some(description) = &self.description {
3859            request_body = quote!((#request_body).with_description(#description));
3860        }
3861
3862        if self
3863            .required
3864            .as_ref()
3865            .is_some_and(|required| !required.value)
3866        {
3867            request_body = quote!((#request_body).optional());
3868        }
3869
3870        quote!(with_request_body(#request_body))
3871    }
3872}
3873
3874impl Parse for RequestBodyArgs {
3875    fn parse(input: ParseStream<'_>) -> Result<Self> {
3876        let mut args = Self::default();
3877
3878        while !input.is_empty() {
3879            let name = input.parse::<Ident>()?;
3880            input.parse::<Token![=]>()?;
3881            if name == "schema" || name == "ty" || name == "body" {
3882                set_once(&mut args.schema, input.parse::<Type>()?, name)?;
3883            } else if name == "description" {
3884                set_once(&mut args.description, input.parse::<LitStr>()?, name)?;
3885            } else if name == "required" {
3886                set_once(&mut args.required, input.parse::<LitBool>()?, name)?;
3887            } else {
3888                return Err(syn::Error::new_spanned(
3889                    name,
3890                    "expected `schema`, `description`, or `required`",
3891                ));
3892            }
3893            parse_optional_comma(input)?;
3894        }
3895
3896        Ok(args)
3897    }
3898}
3899
3900fn set_once<T>(slot: &mut Option<T>, value: T, name: Ident) -> Result<()> {
3901    if slot.is_some() {
3902        let message = format!("duplicate `{name}` option");
3903        return Err(syn::Error::new_spanned(&name, message));
3904    }
3905    *slot = Some(value);
3906    Ok(())
3907}
3908
3909fn parse_optional_comma(input: ParseStream<'_>) -> Result<()> {
3910    if input.is_empty() {
3911        return Ok(());
3912    }
3913    input.parse::<Token![,]>()?;
3914    Ok(())
3915}
3916
3917fn route_attribute_outside_controller(name: &str, item: TokenStream) -> TokenStream {
3918    let item = proc_macro2::TokenStream::from(item);
3919    let message =
3920        format!("#[{name}] must be used inside an impl block annotated with #[controller]");
3921    quote! {
3922        compile_error!(#message);
3923        #item
3924    }
3925    .into()
3926}
3927
3928fn extractor_attribute_outside_controller(name: &str, item: TokenStream) -> TokenStream {
3929    let item = proc_macro2::TokenStream::from(item);
3930    let message = format!("#[{name}] must be used on a route method argument inside #[controller]");
3931    quote! {
3932        compile_error!(#message);
3933        #item
3934    }
3935    .into()
3936}
3937
3938fn openapi_attribute_outside_controller(name: &str, item: TokenStream) -> TokenStream {
3939    let item = proc_macro2::TokenStream::from(item);
3940    let message =
3941        format!("#[{name}] must be used inside an impl block annotated with #[controller]");
3942    quote! {
3943        compile_error!(#message);
3944        #item
3945    }
3946    .into()
3947}
3948
3949fn response_attribute_outside_controller(name: &str, item: TokenStream) -> TokenStream {
3950    let item = proc_macro2::TokenStream::from(item);
3951    let message =
3952        format!("#[{name}] must be used inside an impl block annotated with #[controller]");
3953    quote! {
3954        compile_error!(#message);
3955        #item
3956    }
3957    .into()
3958}
3959
3960fn http_code_attribute_outside_controller(name: &str, item: TokenStream) -> TokenStream {
3961    let item = proc_macro2::TokenStream::from(item);
3962    let message =
3963        format!("#[{name}] must be used inside an impl block annotated with #[controller]");
3964    quote! {
3965        compile_error!(#message);
3966        #item
3967    }
3968    .into()
3969}
3970
3971fn metadata_attribute_outside_controller(name: &str, item: TokenStream) -> TokenStream {
3972    let item = proc_macro2::TokenStream::from(item);
3973    let message =
3974        format!("#[{name}] must be used inside an impl block annotated with #[controller]");
3975    quote! {
3976        compile_error!(#message);
3977        #item
3978    }
3979    .into()
3980}
3981
3982fn message_attribute_outside_controller(name: &str, item: TokenStream) -> TokenStream {
3983    let item = proc_macro2::TokenStream::from(item);
3984    let message =
3985        format!("#[{name}] must be used inside an impl block annotated with #[message_controller]");
3986    quote! {
3987        compile_error!(#message);
3988        #item
3989    }
3990    .into()
3991}
3992
3993fn event_attribute_outside_listener(name: &str, item: TokenStream) -> TokenStream {
3994    let item = proc_macro2::TokenStream::from(item);
3995    let message =
3996        format!("#[{name}] must be used inside an impl block annotated with #[event_listener]");
3997    quote! {
3998        compile_error!(#message);
3999        #item
4000    }
4001    .into()
4002}
4003
4004fn websocket_attribute_outside_gateway(name: &str, item: TokenStream) -> TokenStream {
4005    let item = proc_macro2::TokenStream::from(item);
4006    let message =
4007        format!("#[{name}] must be used inside an impl block annotated with #[websocket_gateway]");
4008    quote! {
4009        compile_error!(#message);
4010        #item
4011    }
4012    .into()
4013}
4014
4015fn schedule_attribute_outside_schedule(name: &str, item: TokenStream) -> TokenStream {
4016    let item = proc_macro2::TokenStream::from(item);
4017    let message = format!("#[{name}] must be used inside an impl block annotated with #[schedule]");
4018    quote! {
4019        compile_error!(#message);
4020        #item
4021    }
4022    .into()
4023}
4024
4025fn validation_attribute_outside_controller(name: &str, item: TokenStream) -> TokenStream {
4026    let item = proc_macro2::TokenStream::from(item);
4027    let message =
4028        format!("#[{name}] must be used inside an impl block annotated with #[controller]");
4029    quote! {
4030        compile_error!(#message);
4031        #item
4032    }
4033    .into()
4034}
4035
4036fn pipeline_attribute_outside_controller(name: &str, item: TokenStream) -> TokenStream {
4037    let item = proc_macro2::TokenStream::from(item);
4038    let message =
4039        format!("#[{name}] must be used inside an impl block annotated with #[controller]");
4040    quote! {
4041        compile_error!(#message);
4042        #item
4043    }
4044    .into()
4045}
4046
4047fn host_attribute_outside_controller(name: &str, item: TokenStream) -> TokenStream {
4048    let item = proc_macro2::TokenStream::from(item);
4049    let message =
4050        format!("#[{name}] must be used inside an impl block annotated with #[controller]");
4051    quote! {
4052        compile_error!(#message);
4053        #item
4054    }
4055    .into()
4056}
4057
4058fn version_attribute_outside_controller(name: &str, item: TokenStream) -> TokenStream {
4059    let item = proc_macro2::TokenStream::from(item);
4060    let message =
4061        format!("#[{name}] must be used inside an impl block annotated with #[controller]");
4062    quote! {
4063        compile_error!(#message);
4064        #item
4065    }
4066    .into()
4067}
4068
4069fn serialization_attribute_outside_controller(name: &str, item: TokenStream) -> TokenStream {
4070    let item = proc_macro2::TokenStream::from(item);
4071    let message =
4072        format!("#[{name}] must be used inside an impl block annotated with #[controller]");
4073    quote! {
4074        compile_error!(#message);
4075        #item
4076    }
4077    .into()
4078}
4079
4080fn push_error(slot: &mut Option<syn::Error>, error: syn::Error) {
4081    if let Some(existing) = slot {
4082        existing.combine(error);
4083    } else {
4084        *slot = Some(error);
4085    }
4086}
4087
4088fn is_type_ident(ty: &Type, ident: &str) -> bool {
4089    let Type::Path(type_path) = ty else {
4090        return false;
4091    };
4092    type_path
4093        .path
4094        .segments
4095        .last()
4096        .is_some_and(|segment| segment.ident == ident)
4097}
4098
4099struct RouteArgs {
4100    path: LitStr,
4101    status: Option<LitInt>,
4102    raw: Option<Ident>,
4103}
4104
4105impl RouteArgs {
4106    fn explicit_status<'a>(&'a self, http_code: Option<&'a LitInt>) -> Result<Option<&'a LitInt>> {
4107        match (&self.status, http_code) {
4108            (Some(_), Some(http_code)) => Err(syn::Error::new_spanned(
4109                http_code,
4110                "route status cannot be set with both `status = ...` and #[http_code(...)]",
4111            )),
4112            (Some(status), None) => Ok(Some(status)),
4113            (None, Some(http_code)) => Ok(Some(http_code)),
4114            (None, None) => Ok(None),
4115        }
4116    }
4117}
4118
4119fn status_value(status: Option<&LitInt>) -> Result<proc_macro2::TokenStream> {
4120    let Some(status) = status else {
4121        return Ok(quote!(200));
4122    };
4123    let value = status.base10_parse::<u16>()?;
4124    Ok(quote!(#value))
4125}
4126
4127impl Parse for RouteArgs {
4128    fn parse(input: ParseStream<'_>) -> Result<Self> {
4129        let path = input.parse::<LitStr>()?;
4130        let mut status = None;
4131        let mut raw = None;
4132
4133        if !input.is_empty() {
4134            while !input.is_empty() {
4135                input.parse::<Token![,]>()?;
4136                let name = input.parse::<Ident>()?;
4137
4138                if name == "status" {
4139                    if status.is_some() {
4140                        return Err(syn::Error::new_spanned(name, "duplicate `status` option"));
4141                    }
4142                    input.parse::<Token![=]>()?;
4143                    status = Some(input.parse::<LitInt>()?);
4144                } else if name == "raw" {
4145                    if raw.is_some() {
4146                        return Err(syn::Error::new_spanned(name, "duplicate `raw` option"));
4147                    }
4148                    raw = Some(name);
4149                } else {
4150                    return Err(syn::Error::new_spanned(
4151                        name,
4152                        "expected `status = <u16>` or `raw`",
4153                    ));
4154                }
4155            }
4156        }
4157
4158        if !input.is_empty() {
4159            return Err(input.error("unexpected route attribute arguments"));
4160        }
4161
4162        Ok(Self { path, status, raw })
4163    }
4164}
4165
4166struct RouteSpec {
4167    kind: RouteKind,
4168    args: RouteArgs,
4169}
4170
4171#[derive(Clone, Copy)]
4172enum RouteKind {
4173    All,
4174    Get,
4175    Sse,
4176    Post,
4177    Put,
4178    Patch,
4179    Delete,
4180    Options,
4181    Head,
4182    GetJson,
4183    PostJson,
4184    PutJson,
4185    PatchJson,
4186    DeleteJson,
4187}
4188
4189impl RouteKind {
4190    fn from_attribute(attr: &Attribute) -> Option<Self> {
4191        let ident = attr.path().segments.last()?.ident.to_string();
4192        match ident.as_str() {
4193            "all" => Some(Self::All),
4194            "get" => Some(Self::Get),
4195            "sse" => Some(Self::Sse),
4196            "post" => Some(Self::Post),
4197            "put" => Some(Self::Put),
4198            "patch" => Some(Self::Patch),
4199            "delete" => Some(Self::Delete),
4200            "options" => Some(Self::Options),
4201            "head" => Some(Self::Head),
4202            "get_json" => Some(Self::GetJson),
4203            "post_json" => Some(Self::PostJson),
4204            "put_json" => Some(Self::PutJson),
4205            "patch_json" => Some(Self::PatchJson),
4206            "delete_json" => Some(Self::DeleteJson),
4207            _ => None,
4208        }
4209    }
4210
4211    fn raw_builder_ident(self) -> Ident {
4212        match self {
4213            Self::All => format_ident!("all"),
4214            Self::Get => format_ident!("get"),
4215            Self::Sse => format_ident!("get"),
4216            Self::Post => format_ident!("post"),
4217            Self::Put => format_ident!("put"),
4218            Self::Patch => format_ident!("patch"),
4219            Self::Delete => format_ident!("delete"),
4220            Self::Options => format_ident!("options"),
4221            Self::Head => format_ident!("head"),
4222            Self::GetJson => format_ident!("get"),
4223            Self::PostJson => format_ident!("post"),
4224            Self::PutJson => format_ident!("put"),
4225            Self::PatchJson => format_ident!("patch"),
4226            Self::DeleteJson => format_ident!("delete"),
4227        }
4228    }
4229
4230    fn json_builder_ident(self) -> Option<Ident> {
4231        match self {
4232            Self::All => Some(format_ident!("all_json_with_status")),
4233            Self::Get | Self::GetJson => Some(format_ident!("get_json_with_status")),
4234            Self::Post | Self::PostJson => Some(format_ident!("post_json_with_status")),
4235            Self::Put | Self::PutJson => Some(format_ident!("put_json_with_status")),
4236            Self::Patch | Self::PatchJson => Some(format_ident!("patch_json_with_status")),
4237            Self::Delete | Self::DeleteJson => Some(format_ident!("delete_json_with_status")),
4238            Self::Sse | Self::Options | Self::Head => None,
4239        }
4240    }
4241
4242    fn is_explicit_json(self) -> bool {
4243        matches!(
4244            self,
4245            Self::GetJson | Self::PostJson | Self::PutJson | Self::PatchJson | Self::DeleteJson
4246        )
4247    }
4248
4249    fn flavor(self, raw: bool) -> RouteFlavor {
4250        if matches!(self, Self::Sse) {
4251            return RouteFlavor::Sse;
4252        }
4253
4254        if raw {
4255            return RouteFlavor::Raw;
4256        }
4257
4258        match self {
4259            Self::Sse => RouteFlavor::Sse,
4260            Self::All | Self::Get | Self::GetJson | Self::Delete | Self::DeleteJson => {
4261                RouteFlavor::JsonRequest
4262            }
4263            Self::Post
4264            | Self::PostJson
4265            | Self::Put
4266            | Self::PutJson
4267            | Self::Patch
4268            | Self::PatchJson => RouteFlavor::JsonBody,
4269            Self::Options | Self::Head => RouteFlavor::Raw,
4270        }
4271    }
4272}
4273
4274#[derive(Clone, Copy)]
4275enum RouteFlavor {
4276    Sse,
4277    Raw,
4278    JsonRequest,
4279    JsonBody,
4280}
4281
4282#[derive(Clone)]
4283struct RouteMethodInput {
4284    args: Vec<MethodArg>,
4285}
4286
4287impl RouteMethodInput {
4288    fn from_method(method: &mut ImplItemFn) -> Result<Self> {
4289        let mut inputs = method.sig.inputs.iter_mut();
4290        let Some(FnArg::Receiver(receiver)) = inputs.next() else {
4291            return Err(syn::Error::new_spanned(
4292                &method.sig.ident,
4293                "controller route methods must take &self as their first argument",
4294            ));
4295        };
4296
4297        if receiver.reference.is_none() || receiver.mutability.is_some() {
4298            return Err(syn::Error::new_spanned(
4299                receiver,
4300                "controller route methods must use an immutable &self receiver",
4301            ));
4302        }
4303
4304        let args = inputs
4305            .map(|input| match input {
4306                FnArg::Typed(input) => MethodArg::from_pat_type(input),
4307                FnArg::Receiver(receiver) => Err(syn::Error::new_spanned(
4308                    receiver,
4309                    "unexpected receiver argument",
4310                )),
4311            })
4312            .collect::<Result<Vec<_>>>()?;
4313
4314        Ok(Self { args })
4315    }
4316
4317    fn has_extractors(&self) -> bool {
4318        self.args.iter().any(|arg| arg.extractor.is_some())
4319    }
4320
4321    fn into_legacy_arg(self) -> Result<Option<MethodArg>> {
4322        if self.has_extractors() {
4323            return Err(syn::Error::new_spanned(
4324                self.args
4325                    .iter()
4326                    .find(|arg| arg.extractor.is_some())
4327                    .map(|arg| arg.ident.clone())
4328                    .unwrap_or_else(|| format_ident!("argument")),
4329                "route methods with extractor attributes must use extractor attributes on every argument",
4330            ));
4331        }
4332
4333        if self.args.len() > 1 {
4334            return Err(syn::Error::new_spanned(
4335                self.args[1].ident.clone(),
4336                "controller route methods without extractor attributes can accept at most one argument after &self",
4337            ));
4338        }
4339
4340        Ok(self.args.into_iter().next())
4341    }
4342}
4343
4344#[derive(Clone)]
4345struct MethodArg {
4346    ident: Ident,
4347    ty: Box<Type>,
4348    extractor: Option<Extractor>,
4349}
4350
4351impl MethodArg {
4352    fn from_pat_type(input: &mut PatType) -> Result<Self> {
4353        let ident = match input.pat.as_ref() {
4354            Pat::Ident(ident) => ident.ident.clone(),
4355            _ => {
4356                return Err(syn::Error::new_spanned(
4357                    &input.pat,
4358                    "controller route arguments must be simple identifiers",
4359                ));
4360            }
4361        };
4362        let extractor = take_extractor_attrs(input)?;
4363
4364        Ok(Self {
4365            ident,
4366            ty: input.ty.clone(),
4367            extractor,
4368        })
4369    }
4370}
4371
4372#[derive(Clone)]
4373enum Extractor {
4374    Body,
4375    Request,
4376    Params,
4377    Param(SingleValueExtractor),
4378    Query(QueryExtractor),
4379    Header(SingleValueExtractor),
4380    Headers,
4381    HostParam(SingleValueExtractor),
4382    Ip(Option<Expr>),
4383    Custom(Expr),
4384}
4385
4386#[derive(Clone)]
4387struct SingleValueExtractor {
4388    name: LitStr,
4389    pipe: Option<Expr>,
4390}
4391
4392#[derive(Clone)]
4393struct QueryExtractor {
4394    name: Option<LitStr>,
4395    pipe: Option<Expr>,
4396}
4397
4398impl Extractor {
4399    fn from_attribute(attr: &Attribute) -> Result<Option<Self>> {
4400        let Some(ident) = attr.path().segments.last().map(|segment| &segment.ident) else {
4401            return Ok(None);
4402        };
4403
4404        let extractor = if ident == "body" {
4405            expect_no_extractor_args(attr, "body")?;
4406            Self::Body
4407        } else if ident == "request" {
4408            expect_no_extractor_args(attr, "request")?;
4409            Self::Request
4410        } else if ident == "params" {
4411            expect_no_extractor_args(attr, "params")?;
4412            Self::Params
4413        } else if ident == "param" {
4414            Self::Param(parse_single_value_extractor(attr, "param")?)
4415        } else if ident == "query" {
4416            Self::Query(parse_query_extractor(attr)?)
4417        } else if ident == "header" {
4418            Self::Header(parse_single_value_extractor(attr, "header")?)
4419        } else if ident == "headers" {
4420            expect_no_extractor_args(attr, "headers")?;
4421            Self::Headers
4422        } else if ident == "host_param" {
4423            Self::HostParam(parse_single_value_extractor(attr, "host_param")?)
4424        } else if ident == "ip" {
4425            Self::Ip(parse_optional_pipe_only_extractor(attr, "ip")?)
4426        } else if ident == "extract" {
4427            Self::Custom(parse_extractor_expr(attr)?)
4428        } else {
4429            return Ok(None);
4430        };
4431
4432        Ok(Some(extractor))
4433    }
4434}
4435
4436fn take_extractor_attrs(input: &mut PatType) -> Result<Option<Extractor>> {
4437    let mut clean_attrs = Vec::new();
4438    let mut extractor = None;
4439
4440    for attr in std::mem::take(&mut input.attrs) {
4441        let Some(parsed) = Extractor::from_attribute(&attr)? else {
4442            clean_attrs.push(attr);
4443            continue;
4444        };
4445
4446        if extractor.is_some() {
4447            return Err(syn::Error::new_spanned(
4448                attr,
4449                "route arguments can use at most one extractor attribute",
4450            ));
4451        }
4452        extractor = Some(parsed);
4453    }
4454
4455    input.attrs = clean_attrs;
4456    Ok(extractor)
4457}
4458
4459fn expect_no_extractor_args(attr: &Attribute, name: &str) -> Result<()> {
4460    match &attr.meta {
4461        syn::Meta::Path(_) => Ok(()),
4462        _ => Err(syn::Error::new_spanned(
4463            attr,
4464            format!("#[{name}] does not accept arguments"),
4465        )),
4466    }
4467}
4468
4469fn parse_extractor_expr(attr: &Attribute) -> Result<Expr> {
4470    attr.parse_args::<Expr>().map_err(|_| {
4471        syn::Error::new_spanned(attr, "#[extract] requires one request extractor expression")
4472    })
4473}
4474
4475fn parse_single_value_extractor(attr: &Attribute, name: &str) -> Result<SingleValueExtractor> {
4476    attr.parse_args::<SingleValueExtractorArgs>()
4477        .map(|args| SingleValueExtractor {
4478            name: args.name,
4479            pipe: args.pipe,
4480        })
4481        .map_err(|_| {
4482            syn::Error::new_spanned(
4483                attr,
4484                format!("#[{name}] requires a string literal and optional `pipe = <expr>`"),
4485            )
4486        })
4487}
4488
4489fn parse_query_extractor(attr: &Attribute) -> Result<QueryExtractor> {
4490    match &attr.meta {
4491        syn::Meta::Path(_) => Ok(QueryExtractor {
4492            name: None,
4493            pipe: None,
4494        }),
4495        _ => attr
4496            .parse_args::<SingleValueExtractorArgs>()
4497            .map(|args| QueryExtractor {
4498                name: Some(args.name),
4499                pipe: args.pipe,
4500            })
4501            .map_err(|_| {
4502                syn::Error::new_spanned(
4503                    attr,
4504                    "#[query] accepts no arguments for a DTO or a string literal and optional `pipe = <expr>` for one value",
4505                )
4506            }),
4507    }
4508}
4509
4510fn parse_optional_pipe_only_extractor(attr: &Attribute, name: &str) -> Result<Option<Expr>> {
4511    match &attr.meta {
4512        syn::Meta::Path(_) => Ok(None),
4513        _ => attr
4514            .parse_args::<PipeOnlyExtractorArgs>()
4515            .map(|args| Some(args.pipe))
4516            .map_err(|_| {
4517                syn::Error::new_spanned(
4518                    attr,
4519                    format!("#[{name}] accepts no arguments or `pipe = <expr>`"),
4520                )
4521            }),
4522    }
4523}
4524
4525struct SingleValueExtractorArgs {
4526    name: LitStr,
4527    pipe: Option<Expr>,
4528}
4529
4530impl Parse for SingleValueExtractorArgs {
4531    fn parse(input: ParseStream<'_>) -> Result<Self> {
4532        let name = input.parse::<LitStr>()?;
4533        let pipe = parse_optional_pipe_arg(input)?;
4534        Ok(Self { name, pipe })
4535    }
4536}
4537
4538struct PipeOnlyExtractorArgs {
4539    pipe: Expr,
4540}
4541
4542impl Parse for PipeOnlyExtractorArgs {
4543    fn parse(input: ParseStream<'_>) -> Result<Self> {
4544        let pipe = parse_required_pipe_arg(input)?;
4545        Ok(Self { pipe })
4546    }
4547}
4548
4549fn parse_optional_pipe_arg(input: ParseStream<'_>) -> Result<Option<Expr>> {
4550    let mut pipe = None;
4551
4552    while !input.is_empty() {
4553        input.parse::<Token![,]>()?;
4554        if input.is_empty() {
4555            break;
4556        }
4557        let ident = input.parse::<Ident>()?;
4558        if ident != "pipe" {
4559            return Err(syn::Error::new_spanned(ident, "expected `pipe`"));
4560        }
4561        if pipe.is_some() {
4562            return Err(syn::Error::new_spanned(ident, "duplicate `pipe` option"));
4563        }
4564        input.parse::<Token![=]>()?;
4565        pipe = Some(input.parse::<Expr>()?);
4566    }
4567
4568    Ok(pipe)
4569}
4570
4571fn parse_required_pipe_arg(input: ParseStream<'_>) -> Result<Expr> {
4572    let ident = input.parse::<Ident>()?;
4573    if ident != "pipe" {
4574        return Err(syn::Error::new_spanned(ident, "expected `pipe`"));
4575    }
4576    input.parse::<Token![=]>()?;
4577    let pipe = input.parse::<Expr>()?;
4578    parse_optional_comma(input)?;
4579    Ok(pipe)
4580}
4581
4582fn option_inner_type(ty: &Type) -> Option<Type> {
4583    let Type::Path(type_path) = ty else {
4584        return None;
4585    };
4586    if type_path.qself.is_some() {
4587        return None;
4588    }
4589
4590    let segment = type_path.path.segments.last()?;
4591    if segment.ident != "Option" {
4592        return None;
4593    }
4594
4595    let PathArguments::AngleBracketed(arguments) = &segment.arguments else {
4596        return None;
4597    };
4598    if arguments.args.len() != 1 {
4599        return None;
4600    }
4601
4602    match arguments.args.first()? {
4603        GenericArgument::Type(inner) => Some(inner.clone()),
4604        _ => None,
4605    }
4606}