Skip to main content

nestrs_macros/
lib.rs

1use proc_macro::TokenStream;
2use quote::{format_ident, quote};
3use std::collections::hash_map::DefaultHasher;
4use std::collections::HashSet;
5use std::hash::{Hash, Hasher};
6use syn::parse::{Parse, ParseStream, Parser};
7use syn::punctuated::Punctuated;
8use syn::spanned::Spanned;
9use syn::{
10    parse_macro_input, DeriveInput, Expr, Field, Fields, Ident, ImplItemFn, Item, ItemStruct,
11    LitInt, LitStr, Meta, Result, Token, Type,
12};
13
14struct ModuleArgs {
15    imports: Vec<Expr>,
16    controllers: Vec<Type>,
17    providers: Vec<Type>,
18    microservices: Option<Vec<Type>>,
19    exports: Vec<Type>,
20    re_exports: Vec<Type>,
21}
22
23impl Parse for ModuleArgs {
24    fn parse(input: ParseStream<'_>) -> Result<Self> {
25        let mut imports = Vec::new();
26        let mut controllers = Vec::new();
27        let mut providers = Vec::new();
28        let mut microservices = None::<Vec<Type>>;
29        let mut exports = Vec::new();
30        let mut re_exports = Vec::new();
31
32        while !input.is_empty() {
33            let key: Ident = input.parse()?;
34            input.parse::<Token![=]>()?;
35
36            let content;
37            syn::bracketed!(content in input);
38
39            match key.to_string().as_str() {
40                "imports" => {
41                    let values: Punctuated<Expr, Token![,]> =
42                        content.parse_terminated(Expr::parse, Token![,])?;
43                    imports = values.into_iter().collect::<Vec<_>>();
44                }
45                "controllers" => {
46                    let values: Punctuated<Type, Token![,]> =
47                        content.parse_terminated(Type::parse, Token![,])?;
48                    controllers = values.into_iter().collect::<Vec<_>>();
49                }
50                "providers" => {
51                    let values: Punctuated<Type, Token![,]> =
52                        content.parse_terminated(Type::parse, Token![,])?;
53                    providers = values.into_iter().collect::<Vec<_>>();
54                }
55                "microservices" => {
56                    let values: Punctuated<Type, Token![,]> =
57                        content.parse_terminated(Type::parse, Token![,])?;
58                    microservices = Some(values.into_iter().collect::<Vec<_>>());
59                }
60                "exports" => {
61                    let values: Punctuated<Type, Token![,]> =
62                        content.parse_terminated(Type::parse, Token![,])?;
63                    exports = values.into_iter().collect::<Vec<_>>();
64                }
65                "re_exports" => {
66                    let values: Punctuated<Type, Token![,]> =
67                        content.parse_terminated(Type::parse, Token![,])?;
68                    re_exports = values.into_iter().collect::<Vec<_>>();
69                }
70                _ => return Err(syn::Error::new(key.span(), "unknown module key")),
71            }
72
73            if input.peek(Token![,]) {
74                input.parse::<Token![,]>()?;
75            }
76        }
77
78        Ok(Self {
79            imports,
80            controllers,
81            providers,
82            microservices,
83            exports,
84            re_exports,
85        })
86    }
87}
88
89#[proc_macro_attribute]
90pub fn module(attr: TokenStream, item: TokenStream) -> TokenStream {
91    let args = parse_macro_input!(attr as ModuleArgs);
92    let module_struct = parse_macro_input!(item as ItemStruct);
93    let name = &module_struct.ident;
94
95    enum ImportKind {
96        Normal,
97        ForwardRef,
98        Lazy,
99    }
100
101    struct ImportItem {
102        ty: Type,
103        kind: ImportKind,
104    }
105
106    fn lazy_static_ident(ty: &Type) -> Ident {
107        let mut hasher = DefaultHasher::new();
108        quote!(#ty).to_string().hash(&mut hasher);
109        let h = hasher.finish();
110        format_ident!("__NESTRS_LAZY_{:016x}", h)
111    }
112
113    fn forward_ref_expr_to_type(expr: &Expr) -> Option<Type> {
114        fn is_forward_ref_ident(ident: &Ident) -> bool {
115            ident == "forward_ref" || ident == "forwardRef"
116        }
117
118        let (path, args) = match expr {
119            Expr::Call(call) => {
120                let Expr::Path(p) = call.func.as_ref() else {
121                    return None;
122                };
123                if !call.args.is_empty() {
124                    return None;
125                }
126                let seg = p.path.segments.last()?;
127                if !is_forward_ref_ident(&seg.ident) {
128                    return None;
129                }
130                (p.path.clone(), &seg.arguments)
131            }
132            Expr::Path(p) => {
133                let seg = p.path.segments.last()?;
134                if !is_forward_ref_ident(&seg.ident) {
135                    return None;
136                }
137                (p.path.clone(), &seg.arguments)
138            }
139            _ => return None,
140        };
141
142        let syn::PathArguments::AngleBracketed(ab) = args else {
143            return None;
144        };
145
146        let ty = ab.args.iter().find_map(|arg| match arg {
147            syn::GenericArgument::Type(t) => Some(t.clone()),
148            _ => None,
149        })?;
150
151        // Preserve the qualified path for nicer error messages; only the type matters.
152        let _ = path;
153        Some(ty)
154    }
155
156    fn lazy_module_expr_to_type(expr: &Expr) -> Option<Type> {
157        fn is_lazy_ident(ident: &Ident) -> bool {
158            ident == "lazy_module" || ident == "lazy"
159        }
160
161        let args = match expr {
162            Expr::Call(call) => {
163                let Expr::Path(p) = call.func.as_ref() else {
164                    return None;
165                };
166                if !call.args.is_empty() {
167                    return None;
168                }
169                let seg = p.path.segments.last()?;
170                if !is_lazy_ident(&seg.ident) {
171                    return None;
172                }
173                &seg.arguments
174            }
175            Expr::Path(p) => {
176                let seg = p.path.segments.last()?;
177                if !is_lazy_ident(&seg.ident) {
178                    return None;
179                }
180                &seg.arguments
181            }
182            _ => return None,
183        };
184
185        let syn::PathArguments::AngleBracketed(ab) = args else {
186            return None;
187        };
188
189        ab.args.iter().find_map(|arg| match arg {
190            syn::GenericArgument::Type(t) => Some(t.clone()),
191            _ => None,
192        })
193    }
194
195    let imports_exprs = args.imports;
196    let mut imports_static = Vec::<ImportItem>::new();
197    let mut imports_dynamic = Vec::<Expr>::new();
198    for expr in imports_exprs {
199        if let Some(ty) = forward_ref_expr_to_type(&expr) {
200            imports_static.push(ImportItem {
201                ty,
202                kind: ImportKind::ForwardRef,
203            });
204            continue;
205        }
206        if let Some(ty) = lazy_module_expr_to_type(&expr) {
207            imports_static.push(ImportItem {
208                ty,
209                kind: ImportKind::Lazy,
210            });
211            continue;
212        }
213
214        match expr {
215            Expr::Path(p) => imports_static.push(ImportItem {
216                ty: Type::Path(syn::TypePath {
217                    qself: None,
218                    path: p.path,
219                }),
220                kind: ImportKind::Normal,
221            }),
222            other => imports_dynamic.push(other),
223        };
224    }
225
226    let mut lazy_seen: HashSet<String> = HashSet::new();
227    let mut lazy_unique: Vec<Type> = Vec::new();
228    for imp in &imports_static {
229        if matches!(imp.kind, ImportKind::Lazy) {
230            let ty = &imp.ty;
231            let key = quote!(#ty).to_string();
232            if lazy_seen.insert(key) {
233                lazy_unique.push(imp.ty.clone());
234            }
235        }
236    }
237    let lazy_static_items = lazy_unique.iter().map(|ty| {
238        let id = lazy_static_ident(ty);
239        quote! {
240            static #id: std::sync::OnceLock<nestrs::core::DynamicModule> =
241                std::sync::OnceLock::new();
242        }
243    });
244
245    let import_builds = imports_static
246        .iter()
247        .map(|imp| {
248            let ty = &imp.ty;
249            match imp.kind {
250                ImportKind::ForwardRef => quote! {
251                    {
252                        let __type_id = std::any::TypeId::of::<#ty>();
253                        if nestrs::core::__nestrs_module_stack_contains(__type_id) {
254                            // `forward_ref` back-edge: referenced module is already being built.
255                            // Skip to avoid infinite recursion.
256                        } else {
257                            let (child_registry, child_router) = <#ty as nestrs::core::Module>::build();
258                            let child_exports = <#ty as nestrs::core::Module>::exports();
259                            registry.absorb_exported(child_registry, &child_exports);
260                            router = router.merge(child_router);
261                        }
262                    }
263                },
264                ImportKind::Lazy => {
265                    let lazy_ident = lazy_static_ident(ty);
266                    quote! {
267                        {
268                            let __dm_lazy = #lazy_ident
269                                .get_or_init(|| nestrs::core::DynamicModule::from_module::<#ty>());
270                            registry.absorb_exported(__dm_lazy.registry.clone(), &__dm_lazy.exports);
271                            router = router.merge(__dm_lazy.router.clone());
272                        }
273                    }
274                }
275                ImportKind::Normal => quote! {
276                    {
277                        let __type_id = std::any::TypeId::of::<#ty>();
278                        if nestrs::core::__nestrs_module_stack_contains(__type_id) {
279                            nestrs::core::__nestrs_panic_circular_module_dependency(std::any::type_name::<#ty>());
280                        }
281                        let (child_registry, child_router) = <#ty as nestrs::core::Module>::build();
282                        let child_exports = <#ty as nestrs::core::Module>::exports();
283                        registry.absorb_exported(child_registry, &child_exports);
284                        router = router.merge(child_router);
285                    }
286                },
287            }
288        })
289        .collect::<Vec<_>>();
290
291    let import_graph_providers = imports_static
292        .iter()
293        .map(|imp| {
294            let ty = &imp.ty;
295            match imp.kind {
296                ImportKind::ForwardRef => quote! {
297                    {
298                        let __type_id = std::any::TypeId::of::<#ty>();
299                        if nestrs::core::__nestrs_module_stack_contains(__type_id) {
300                            // `forward_ref` back-edge: referenced module is already being traversed.
301                            // Skip to avoid infinite recursion.
302                        } else {
303                            <#ty as nestrs::core::ModuleGraph>::register_providers(registry);
304                        }
305                    }
306                },
307                ImportKind::Lazy => {
308                    let lazy_ident = lazy_static_ident(ty);
309                    quote! {
310                        {
311                            let __dm_lazy = #lazy_ident
312                                .get_or_init(|| nestrs::core::DynamicModule::from_module::<#ty>());
313                            registry.absorb_exported(__dm_lazy.registry.clone(), &__dm_lazy.exports);
314                        }
315                    }
316                }
317                ImportKind::Normal => quote! {
318                    {
319                        let __type_id = std::any::TypeId::of::<#ty>();
320                        if nestrs::core::__nestrs_module_stack_contains(__type_id) {
321                            nestrs::core::__nestrs_panic_circular_module_dependency(std::any::type_name::<#ty>());
322                        }
323                        <#ty as nestrs::core::ModuleGraph>::register_providers(registry);
324                    }
325                },
326            }
327        })
328        .collect::<Vec<_>>();
329
330    let import_graph_controllers = imports_static
331        .iter()
332        .map(|imp| {
333            let ty = &imp.ty;
334            match imp.kind {
335                ImportKind::ForwardRef => quote! {
336                    {
337                        let __type_id = std::any::TypeId::of::<#ty>();
338                        if nestrs::core::__nestrs_module_stack_contains(__type_id) {
339                            // `forward_ref` back-edge: referenced module is already being traversed.
340                            // Skip to avoid infinite recursion.
341                        } else {
342                            router = <#ty as nestrs::core::ModuleGraph>::register_controllers(router, registry);
343                        }
344                    }
345                },
346                ImportKind::Lazy => {
347                    let lazy_ident = lazy_static_ident(ty);
348                    quote! {
349                        {
350                            let __dm_lazy = #lazy_ident
351                                .get_or_init(|| nestrs::core::DynamicModule::from_module::<#ty>());
352                            router = router.merge(__dm_lazy.router.clone());
353                        }
354                    }
355                }
356                ImportKind::Normal => quote! {
357                    {
358                        let __type_id = std::any::TypeId::of::<#ty>();
359                        if nestrs::core::__nestrs_module_stack_contains(__type_id) {
360                            nestrs::core::__nestrs_panic_circular_module_dependency(std::any::type_name::<#ty>());
361                        }
362                        router = <#ty as nestrs::core::ModuleGraph>::register_controllers(router, registry);
363                    }
364                },
365            }
366        })
367        .collect::<Vec<_>>();
368
369    let controllers = args.controllers;
370    let providers = args.providers;
371    let microservices = args.microservices.unwrap_or_default();
372    let microservices_ref = &microservices;
373    let exports = args.exports;
374    let re_exports = args.re_exports;
375
376    let microservices_impl = if microservices.is_empty() {
377        quote! {}
378    } else {
379        quote! {
380            impl nestrs::microservices::MicroserviceModule for #name {
381                fn microservice_handlers() -> Vec<nestrs::microservices::MicroserviceHandlerFactory> {
382                    vec![
383                        #(
384                            nestrs::microservices::handler_factory::<#microservices_ref>
385                                as nestrs::microservices::MicroserviceHandlerFactory
386                        ),*
387                    ]
388                }
389            }
390        }
391    };
392
393    let expanded = quote! {
394        #module_struct
395
396        #(#lazy_static_items)*
397
398        impl nestrs::core::Module for #name {
399            fn build() -> (nestrs::core::ProviderRegistry, axum::Router) {
400                let _module_guard = nestrs::core::__NestrsModuleBuildGuard::push(
401                    std::any::TypeId::of::<#name>(),
402                    std::any::type_name::<#name>(),
403                );
404                let mut registry = nestrs::core::ProviderRegistry::new();
405                let mut router = axum::Router::new();
406
407                #(#import_builds)*
408
409                #(
410                    {
411                        let __dm: nestrs::core::DynamicModule = (#imports_dynamic);
412                        registry.absorb_exported(__dm.registry, &__dm.exports);
413                        router = router.merge(__dm.router);
414                    }
415                )*
416
417                #(
418                    registry.register::<#providers>();
419                )*
420
421                #(
422                    registry.register::<#microservices_ref>();
423                )*
424
425                #(
426                    router = <#controllers as nestrs::core::Controller>::register(router, &registry);
427                )*
428
429                (registry, router)
430            }
431
432            fn exports() -> Vec<std::any::TypeId> {
433                let mut out = vec![
434                    #(std::any::TypeId::of::<#exports>()),*
435                ];
436                #(
437                    out.extend(<#re_exports as nestrs::core::Module>::exports());
438                )*
439                out
440            }
441        }
442
443        impl nestrs::core::ModuleGraph for #name {
444            fn register_providers(registry: &mut nestrs::core::ProviderRegistry) {
445                let _module_guard = nestrs::core::__NestrsModuleBuildGuard::push(
446                    std::any::TypeId::of::<#name>(),
447                    std::any::type_name::<#name>(),
448                );
449                #(#import_graph_providers)*
450                #(
451                    {
452                        let __dm: nestrs::core::DynamicModule = (#imports_dynamic);
453                        registry.absorb_exported(__dm.registry, &__dm.exports);
454                    }
455                )*
456                #(
457                    registry.register::<#providers>();
458                )*
459                #(
460                    registry.register::<#microservices_ref>();
461                )*
462            }
463
464            fn register_controllers(
465                mut router: axum::Router,
466                registry: &nestrs::core::ProviderRegistry,
467            ) -> axum::Router {
468                let _module_guard = nestrs::core::__NestrsModuleBuildGuard::push(
469                    std::any::TypeId::of::<#name>(),
470                    std::any::type_name::<#name>(),
471                );
472                #(#import_graph_controllers)*
473                #(
474                    router = <#controllers as nestrs::core::Controller>::register(router, registry);
475                )*
476                router
477            }
478        }
479
480        #microservices_impl
481    };
482
483    expanded.into()
484}
485
486#[proc_macro_attribute]
487pub fn injectable(attr: TokenStream, item: TokenStream) -> TokenStream {
488    #[derive(Default)]
489    struct InjectableArgs {
490        scope: Option<String>,
491    }
492
493    impl Parse for InjectableArgs {
494        fn parse(input: ParseStream<'_>) -> Result<Self> {
495            let mut args = InjectableArgs::default();
496
497            while !input.is_empty() {
498                let key: Ident = input.parse()?;
499                input.parse::<Token![=]>()?;
500                match key.to_string().as_str() {
501                    "scope" => {
502                        let v: LitStr = input.parse()?;
503                        args.scope = Some(v.value());
504                    }
505                    _ => return Err(syn::Error::new(key.span(), "unknown injectable key")),
506                }
507
508                if input.peek(Token![,]) {
509                    input.parse::<Token![,]>()?;
510                }
511            }
512
513            Ok(args)
514        }
515    }
516
517    let args = parse_macro_input!(attr as InjectableArgs);
518    let item_struct = parse_macro_input!(item as ItemStruct);
519    let name = &item_struct.ident;
520    let scope_impl = match args.scope.as_deref() {
521        None | Some("singleton") | Some("default") => quote! {},
522        Some("transient") => quote! {
523            fn scope() -> nestrs::core::ProviderScope {
524                nestrs::core::ProviderScope::Transient
525            }
526        },
527        Some("request") => quote! {
528            fn scope() -> nestrs::core::ProviderScope {
529                nestrs::core::ProviderScope::Request
530            }
531        },
532        Some(other) => {
533            return syn::Error::new_spanned(
534                item_struct,
535                format!(
536                    "unsupported injectable scope `{other}` (expected singleton|transient|request)"
537                ),
538            )
539            .to_compile_error()
540            .into();
541        }
542    };
543
544    let construct_body = match &item_struct.fields {
545        Fields::Unit => {
546            quote! {
547                std::sync::Arc::new(#name)
548            }
549        }
550        Fields::Named(named) => {
551            let assignments = named.named.iter().map(|field| {
552                let field_ident = field.ident.as_ref().expect("named field should have ident");
553                let ty = &field.ty;
554
555                // Inject `Arc<T>` fields from the registry.
556                let Type::Path(tp) = ty else {
557                    return syn::Error::new_spanned(
558                        ty,
559                        "injectable currently supports fields typed `Arc<T>` only",
560                    )
561                    .to_compile_error();
562                };
563                let seg = tp
564                    .path
565                    .segments
566                    .last()
567                    .cloned()
568                    .expect("path has at least one segment");
569                if seg.ident != "Arc" {
570                    return syn::Error::new_spanned(
571                        ty,
572                        "injectable currently supports fields typed `Arc<T>` only",
573                    )
574                    .to_compile_error();
575                }
576                let syn::PathArguments::AngleBracketed(args) = seg.arguments else {
577                    return syn::Error::new_spanned(ty, "Arc field must be `Arc<T>`")
578                        .to_compile_error();
579                };
580                let inner = args
581                    .args
582                    .iter()
583                    .filter_map(|a| match a {
584                        syn::GenericArgument::Type(t) => Some(t),
585                        _ => None,
586                    })
587                    .next();
588                let Some(inner) = inner else {
589                    return syn::Error::new_spanned(ty, "Arc field must be `Arc<T>`")
590                        .to_compile_error();
591                };
592
593                quote! {
594                    #field_ident: registry.get::<#inner>()
595                }
596            });
597
598            quote! {
599                std::sync::Arc::new(Self {
600                    #(#assignments,)*
601                })
602            }
603        }
604        Fields::Unnamed(_) => {
605            return syn::Error::new_spanned(
606                item_struct,
607                "injectable currently supports unit structs and named-field structs only",
608            )
609            .to_compile_error()
610            .into();
611        }
612    };
613    let expanded = quote! {
614        #item_struct
615
616        impl nestrs::core::Injectable for #name {
617            fn construct(_registry: &nestrs::core::ProviderRegistry) -> std::sync::Arc<Self> {
618                let registry = _registry;
619                #construct_body
620            }
621
622            #scope_impl
623        }
624    };
625    expanded.into()
626}
627
628#[proc_macro_attribute]
629pub fn controller(attr: TokenStream, item: TokenStream) -> TokenStream {
630    let mut item_struct = parse_macro_input!(item as ItemStruct);
631    let attr_tokens = proc_macro2::TokenStream::from(attr);
632    let mut version_from_attr = None::<String>;
633    item_struct.attrs.retain(|a| {
634        if a.path().is_ident("version") || a.path().is_ident("__nestrs_version_marker") {
635            if let Meta::List(list) = &a.meta {
636                if let Ok(v) = syn::parse2::<LitStr>(list.tokens.clone()) {
637                    version_from_attr = Some(v.value());
638                }
639            }
640            false
641        } else {
642            true
643        }
644    });
645
646    let (prefix, mut version, host) = if attr_tokens.is_empty() {
647        ("/".to_string(), "".to_string(), None::<String>)
648    } else if let Ok(v) = syn::parse2::<LitStr>(attr_tokens.clone()) {
649        (v.value(), "".to_string(), None::<String>)
650    } else {
651        let mut prefix = "/".to_string();
652        let mut version = "".to_string();
653        let mut host: Option<String> = None;
654        let parser = syn::meta::parser(|meta| {
655            if meta.path.is_ident("prefix") {
656                let value: LitStr = meta.value()?.parse()?;
657                prefix = value.value();
658                Ok(())
659            } else if meta.path.is_ident("version") {
660                let value: LitStr = meta.value()?.parse()?;
661                version = value.value();
662                Ok(())
663            } else if meta.path.is_ident("host") {
664                let value: LitStr = meta.value()?.parse()?;
665                host = Some(value.value());
666                Ok(())
667            } else {
668                Err(meta.error("unknown controller key; expected `prefix`, `version`, or `host`"))
669            }
670        });
671
672        if parser.parse2(attr_tokens.clone()).is_err() {
673            return syn::Error::new_spanned(
674                item_struct,
675                "controller expects `#[controller]`, `#[controller(\"/x\")]`, or `#[controller(prefix = \"/x\", version = \"v1\")]`",
676            )
677            .to_compile_error()
678            .into();
679        }
680        (prefix, version, host)
681    };
682    if version.is_empty() {
683        if let Some(v) = version_from_attr {
684            version = v;
685        }
686    }
687
688    let name = &item_struct.ident;
689    let host_fn = match &host {
690        Some(h) => quote! {
691            pub fn __nestrs_host() -> Option<&'static str> {
692                Some(#h)
693            }
694        },
695        None => quote! {
696            pub fn __nestrs_host() -> Option<&'static str> {
697                None
698            }
699        },
700    };
701    let expanded = quote! {
702        #item_struct
703
704        impl #name {
705            pub fn __nestrs_prefix() -> &'static str {
706                #prefix
707            }
708
709            pub fn __nestrs_version() -> &'static str {
710                #version
711            }
712
713            #host_fn
714        }
715    };
716
717    expanded.into()
718}
719
720#[proc_macro_attribute]
721pub fn version(attr: TokenStream, item: TokenStream) -> TokenStream {
722    let version = parse_macro_input!(attr as LitStr);
723    let mut parsed_item = parse_macro_input!(item as Item);
724    let marker: syn::Attribute = syn::parse_quote!(#[__nestrs_version_marker(#version)]);
725
726    match &mut parsed_item {
727        Item::Struct(item_struct) => {
728            item_struct.attrs.push(marker);
729            quote!(#item_struct).into()
730        }
731        _ => syn::Error::new_spanned(
732            parsed_item,
733            "version currently supports structs only (for use with #[controller])",
734        )
735        .to_compile_error()
736        .into(),
737    }
738}
739
740fn passthrough(_attr: TokenStream, item: TokenStream) -> TokenStream {
741    item
742}
743
744#[proc_macro_attribute]
745pub fn get(attr: TokenStream, item: TokenStream) -> TokenStream {
746    passthrough(attr, item)
747}
748#[proc_macro_attribute]
749pub fn post(attr: TokenStream, item: TokenStream) -> TokenStream {
750    passthrough(attr, item)
751}
752#[proc_macro_attribute]
753pub fn put(attr: TokenStream, item: TokenStream) -> TokenStream {
754    passthrough(attr, item)
755}
756#[proc_macro_attribute]
757pub fn patch(attr: TokenStream, item: TokenStream) -> TokenStream {
758    passthrough(attr, item)
759}
760#[proc_macro_attribute]
761pub fn delete(attr: TokenStream, item: TokenStream) -> TokenStream {
762    passthrough(attr, item)
763}
764#[proc_macro_attribute]
765pub fn options(attr: TokenStream, item: TokenStream) -> TokenStream {
766    passthrough(attr, item)
767}
768#[proc_macro_attribute]
769pub fn head(attr: TokenStream, item: TokenStream) -> TokenStream {
770    passthrough(attr, item)
771}
772#[proc_macro_attribute]
773pub fn all(attr: TokenStream, item: TokenStream) -> TokenStream {
774    passthrough(attr, item)
775}
776
777#[proc_macro_attribute]
778pub fn openapi(attr: TokenStream, item: TokenStream) -> TokenStream {
779    passthrough(attr, item)
780}
781
782#[proc_macro_attribute]
783pub fn sse(attr: TokenStream, item: TokenStream) -> TokenStream {
784    passthrough(attr, item)
785}
786
787#[proc_macro_attribute]
788pub fn raw_body(attr: TokenStream, item: TokenStream) -> TokenStream {
789    passthrough(attr, item)
790}
791
792struct RoutesArgs {
793    state: Type,
794    controller_guards: Option<Type>,
795}
796
797impl Parse for RoutesArgs {
798    fn parse(input: ParseStream<'_>) -> Result<Self> {
799        let mut state = None::<Type>;
800        let mut controller_guards = None::<Type>;
801
802        while !input.is_empty() {
803            let key: Ident = input.parse()?;
804            input.parse::<Token![=]>()?;
805            match key.to_string().as_str() {
806                "state" => state = Some(input.parse()?),
807                "controller_guards" => controller_guards = Some(input.parse()?),
808                _ => return Err(syn::Error::new(key.span(), "unknown routes key")),
809            }
810
811            if input.peek(Token![,]) {
812                input.parse::<Token![,]>()?;
813            }
814        }
815
816        let Some(state) = state else {
817            return Err(syn::Error::new(
818                proc_macro2::Span::call_site(),
819                "routes requires `state = SomeProviderType`",
820            ));
821        };
822
823        Ok(Self {
824            state,
825            controller_guards,
826        })
827    }
828}
829
830struct WsGatewayArgs {
831    path: LitStr,
832}
833
834impl Parse for WsGatewayArgs {
835    fn parse(input: ParseStream<'_>) -> Result<Self> {
836        if input.is_empty() {
837            return Err(syn::Error::new(
838                proc_macro2::Span::call_site(),
839                "ws_gateway requires `path = \"/ws\"`",
840            ));
841        }
842
843        // Allow `#[ws_gateway(\"/ws\")]` or `#[ws_gateway(path = \"/ws\")]`.
844        if input.peek(LitStr) {
845            let path: LitStr = input.parse()?;
846            return Ok(Self { path });
847        }
848
849        let key: Ident = input.parse()?;
850        input.parse::<Token![=]>()?;
851        if key != "path" {
852            return Err(syn::Error::new(
853                key.span(),
854                "ws_gateway expects `path = \"/ws\"`",
855            ));
856        }
857        let path: LitStr = input.parse()?;
858        Ok(Self { path })
859    }
860}
861
862#[derive(Clone, Copy)]
863enum HttpMethod {
864    Get,
865    Post,
866    Put,
867    Patch,
868    Delete,
869    Options,
870    Head,
871    All,
872}
873
874impl HttpMethod {
875    fn to_ident(self) -> Ident {
876        Ident::new(
877            match self {
878                HttpMethod::Get => "GET",
879                HttpMethod::Post => "POST",
880                HttpMethod::Put => "PUT",
881                HttpMethod::Patch => "PATCH",
882                HttpMethod::Delete => "DELETE",
883                HttpMethod::Options => "OPTIONS",
884                HttpMethod::Head => "HEAD",
885                HttpMethod::All => "ALL",
886            },
887            proc_macro2::Span::call_site(),
888        )
889    }
890}
891
892struct RouteDef {
893    method: HttpMethod,
894    path: LitStr,
895    handler: Ident,
896    version: Option<LitStr>,
897    guards: Vec<Type>,
898    #[allow(dead_code)]
899    pipes: Vec<Type>,
900    interceptors: Vec<Type>,
901    filters: Vec<Type>,
902    metadata: Vec<(LitStr, LitStr)>,
903    /// Empty, or `openapi <expr>` for `impl_routes!`.
904    openapi_line: proc_macro2::TokenStream,
905}
906
907#[derive(Clone, Copy, Debug, PartialEq, Eq)]
908enum ParamDecorator {
909    Body,
910    Query,
911    Param,
912    Req,
913    Headers,
914    Ip,
915}
916
917fn path_ends_with_2(path: &syn::Path, a: &str, b: &str) -> bool {
918    let segs = &path.segments;
919    if segs.len() < 2 {
920        return false;
921    }
922    let last = segs.last().unwrap().ident.to_string();
923    let prev = segs.iter().nth(segs.len() - 2).unwrap().ident.to_string();
924    prev == a && last == b
925}
926
927fn param_decorator_from_attr(attr: &syn::Attribute) -> Option<ParamDecorator> {
928    let p = attr.path();
929    if path_ends_with_2(p, "param", "body") {
930        Some(ParamDecorator::Body)
931    } else if path_ends_with_2(p, "param", "query") {
932        Some(ParamDecorator::Query)
933    } else if path_ends_with_2(p, "param", "param") {
934        Some(ParamDecorator::Param)
935    } else if path_ends_with_2(p, "param", "req") {
936        Some(ParamDecorator::Req)
937    } else if path_ends_with_2(p, "param", "headers") {
938        Some(ParamDecorator::Headers)
939    } else if path_ends_with_2(p, "param", "ip") {
940        Some(ParamDecorator::Ip)
941    } else {
942        None
943    }
944}
945
946fn is_validation_pipe(ty: &Type) -> bool {
947    let Type::Path(tp) = ty else {
948        return false;
949    };
950    let Some(seg) = tp.path.segments.last() else {
951        return false;
952    };
953    seg.ident == "ValidationPipe"
954}
955
956fn parse_subscribe_message(attrs: &[syn::Attribute]) -> Result<Option<LitStr>> {
957    let mut out = None::<LitStr>;
958    for attr in attrs {
959        if !attr.path().is_ident("subscribe_message") {
960            continue;
961        }
962        if out.is_some() {
963            return Err(syn::Error::new_spanned(
964                attr,
965                "subscribe_message can only be specified once per handler",
966            ));
967        }
968        let Meta::List(list) = &attr.meta else {
969            return Err(syn::Error::new_spanned(
970                attr,
971                "subscribe_message expects a string literal, e.g. #[subscribe_message(\"ping\")]",
972            ));
973        };
974        if list.tokens.is_empty() {
975            return Err(syn::Error::new_spanned(
976                list,
977                "subscribe_message expects a string literal, e.g. #[subscribe_message(\"ping\")]",
978            ));
979        }
980        let lit = syn::parse2::<LitStr>(list.tokens.clone()).map_err(|_| {
981            syn::Error::new_spanned(
982                list,
983                "subscribe_message expects a string literal, e.g. #[subscribe_message(\"ping\")]",
984            )
985        })?;
986        out = Some(lit);
987    }
988    Ok(out)
989}
990
991fn parse_message_pattern(attrs: &[syn::Attribute]) -> Result<Option<LitStr>> {
992    let mut out = None::<LitStr>;
993    for attr in attrs {
994        if !attr.path().is_ident("message_pattern") {
995            continue;
996        }
997        if out.is_some() {
998            return Err(syn::Error::new_spanned(
999                attr,
1000                "message_pattern can only be specified once per handler",
1001            ));
1002        }
1003        let Meta::List(list) = &attr.meta else {
1004            return Err(syn::Error::new_spanned(
1005                attr,
1006                "message_pattern expects a string literal, e.g. #[message_pattern(\"user.get\")]",
1007            ));
1008        };
1009        if list.tokens.is_empty() {
1010            return Err(syn::Error::new_spanned(
1011                list,
1012                "message_pattern expects a string literal, e.g. #[message_pattern(\"user.get\")]",
1013            ));
1014        }
1015        let lit = syn::parse2::<LitStr>(list.tokens.clone()).map_err(|_| {
1016            syn::Error::new_spanned(
1017                list,
1018                "message_pattern expects a string literal, e.g. #[message_pattern(\"user.get\")]",
1019            )
1020        })?;
1021        out = Some(lit);
1022    }
1023    Ok(out)
1024}
1025
1026fn parse_event_pattern(attrs: &[syn::Attribute]) -> Result<Option<LitStr>> {
1027    let mut out = None::<LitStr>;
1028    for attr in attrs {
1029        if !attr.path().is_ident("event_pattern") {
1030            continue;
1031        }
1032        if out.is_some() {
1033            return Err(syn::Error::new_spanned(
1034                attr,
1035                "event_pattern can only be specified once per handler",
1036            ));
1037        }
1038        let Meta::List(list) = &attr.meta else {
1039            return Err(syn::Error::new_spanned(
1040                attr,
1041                "event_pattern expects a string literal, e.g. #[event_pattern(\"user.created\")]",
1042            ));
1043        };
1044        if list.tokens.is_empty() {
1045            return Err(syn::Error::new_spanned(
1046                list,
1047                "event_pattern expects a string literal, e.g. #[event_pattern(\"user.created\")]",
1048            ));
1049        }
1050        let lit = syn::parse2::<LitStr>(list.tokens.clone()).map_err(|_| {
1051            syn::Error::new_spanned(
1052                list,
1053                "event_pattern expects a string literal, e.g. #[event_pattern(\"user.created\")]",
1054            )
1055        })?;
1056        out = Some(lit);
1057    }
1058    Ok(out)
1059}
1060
1061fn parse_on_event(attrs: &[syn::Attribute]) -> Result<Option<LitStr>> {
1062    let mut out = None::<LitStr>;
1063    for attr in attrs {
1064        if !attr.path().is_ident("on_event") {
1065            continue;
1066        }
1067        if out.is_some() {
1068            return Err(syn::Error::new_spanned(
1069                attr,
1070                "on_event can only be specified once per handler",
1071            ));
1072        }
1073        let Meta::List(list) = &attr.meta else {
1074            return Err(syn::Error::new_spanned(
1075                attr,
1076                "on_event expects a string literal, e.g. #[on_event(\"order.created\")]",
1077            ));
1078        };
1079        if list.tokens.is_empty() {
1080            return Err(syn::Error::new_spanned(
1081                list,
1082                "on_event expects a string literal, e.g. #[on_event(\"order.created\")]",
1083            ));
1084        }
1085        let lit = syn::parse2::<LitStr>(list.tokens.clone()).map_err(|_| {
1086            syn::Error::new_spanned(
1087                list,
1088                "on_event expects a string literal, e.g. #[on_event(\"order.created\")]",
1089            )
1090        })?;
1091        out = Some(lit);
1092    }
1093    Ok(out)
1094}
1095
1096fn parse_cron(attrs: &[syn::Attribute]) -> Result<Option<LitStr>> {
1097    let mut out = None::<LitStr>;
1098    for attr in attrs {
1099        if !attr.path().is_ident("cron") {
1100            continue;
1101        }
1102        if out.is_some() {
1103            return Err(syn::Error::new_spanned(
1104                attr,
1105                "cron can only be specified once per handler",
1106            ));
1107        }
1108        let Meta::List(list) = &attr.meta else {
1109            return Err(syn::Error::new_spanned(
1110                attr,
1111                "cron expects a string literal, e.g. #[cron(\"0 * * * * *\")]",
1112            ));
1113        };
1114        if list.tokens.is_empty() {
1115            return Err(syn::Error::new_spanned(
1116                list,
1117                "cron expects a string literal, e.g. #[cron(\"0 * * * * *\")]",
1118            ));
1119        }
1120        let lit = syn::parse2::<LitStr>(list.tokens.clone()).map_err(|_| {
1121            syn::Error::new_spanned(
1122                list,
1123                "cron expects a string literal, e.g. #[cron(\"0 * * * * *\")]",
1124            )
1125        })?;
1126        out = Some(lit);
1127    }
1128    Ok(out)
1129}
1130
1131fn parse_interval(attrs: &[syn::Attribute]) -> Result<Option<LitInt>> {
1132    let mut out = None::<LitInt>;
1133    for attr in attrs {
1134        if !attr.path().is_ident("interval") {
1135            continue;
1136        }
1137        if out.is_some() {
1138            return Err(syn::Error::new_spanned(
1139                attr,
1140                "interval can only be specified once per handler",
1141            ));
1142        }
1143        let Meta::List(list) = &attr.meta else {
1144            return Err(syn::Error::new_spanned(
1145                attr,
1146                "interval expects an integer literal in milliseconds, e.g. #[interval(30_000)]",
1147            ));
1148        };
1149        if list.tokens.is_empty() {
1150            return Err(syn::Error::new_spanned(
1151                list,
1152                "interval expects an integer literal in milliseconds, e.g. #[interval(30_000)]",
1153            ));
1154        }
1155        let lit = syn::parse2::<LitInt>(list.tokens.clone()).map_err(|_| {
1156            syn::Error::new_spanned(
1157                list,
1158                "interval expects an integer literal in milliseconds, e.g. #[interval(30_000)]",
1159            )
1160        })?;
1161        out = Some(lit);
1162    }
1163    Ok(out)
1164}
1165
1166fn is_ws_client_type(ty: &Type) -> bool {
1167    let Type::Path(tp) = ty else {
1168        return false;
1169    };
1170    let Some(seg) = tp.path.segments.last() else {
1171        return false;
1172    };
1173    seg.ident == "WsClient"
1174}
1175
1176fn is_serde_json_value_type(ty: &Type) -> bool {
1177    let Type::Path(tp) = ty else {
1178        return false;
1179    };
1180    let segs = &tp.path.segments;
1181    let Some(last) = segs.last() else {
1182        return false;
1183    };
1184    if last.ident != "Value" {
1185        return false;
1186    }
1187    if segs.len() >= 2 {
1188        let prev = segs.iter().nth(segs.len() - 2).unwrap();
1189        return prev.ident == "serde_json";
1190    }
1191    false
1192}
1193
1194fn is_transport_error_type(ty: &Type) -> bool {
1195    let Type::Path(tp) = ty else {
1196        return false;
1197    };
1198    let Some(seg) = tp.path.segments.last() else {
1199        return false;
1200    };
1201    seg.ident == "TransportError"
1202}
1203
1204fn is_http_exception_type(ty: &Type) -> bool {
1205    let Type::Path(tp) = ty else {
1206        return false;
1207    };
1208    let Some(seg) = tp.path.segments.last() else {
1209        return false;
1210    };
1211    seg.ident == "HttpException"
1212}
1213
1214fn split_result_type(ty: &Type) -> Option<(Type, Type)> {
1215    let Type::Path(tp) = ty else {
1216        return None;
1217    };
1218    let seg = tp.path.segments.last()?;
1219    if seg.ident != "Result" {
1220        return None;
1221    }
1222    let syn::PathArguments::AngleBracketed(args) = &seg.arguments else {
1223        return None;
1224    };
1225    let mut it = args.args.iter().filter_map(|a| match a {
1226        syn::GenericArgument::Type(t) => Some(t.clone()),
1227        _ => None,
1228    });
1229    let ok = it.next()?;
1230    let err = it.next()?;
1231    Some((ok, err))
1232}
1233
1234fn parse_route_method(attrs: &[syn::Attribute]) -> Result<Option<(HttpMethod, LitStr)>> {
1235    for attr in attrs {
1236        let Some(ident) = attr.path().get_ident().cloned() else {
1237            continue;
1238        };
1239        let method = match ident.to_string().as_str() {
1240            "get" => HttpMethod::Get,
1241            "post" => HttpMethod::Post,
1242            "put" => HttpMethod::Put,
1243            "patch" => HttpMethod::Patch,
1244            "delete" => HttpMethod::Delete,
1245            "options" => HttpMethod::Options,
1246            "head" => HttpMethod::Head,
1247            "all" => HttpMethod::All,
1248            _ => continue,
1249        };
1250
1251        let path = match &attr.meta {
1252            Meta::Path(_) => LitStr::new("/", attr.span()),
1253            Meta::List(list) => {
1254                if list.tokens.is_empty() {
1255                    LitStr::new("/", attr.span())
1256                } else {
1257                    syn::parse2::<LitStr>(list.tokens.clone()).map_err(|_| {
1258                        syn::Error::new_spanned(
1259                            list,
1260                            "route attribute expects a string literal path, e.g. #[get(\"/\")]",
1261                        )
1262                    })?
1263                }
1264            }
1265            Meta::NameValue(_) => {
1266                return Err(syn::Error::new_spanned(
1267                    attr,
1268                    "route attribute expects #[get(\"/\")] syntax",
1269                ));
1270            }
1271        };
1272        return Ok(Some((method, path)));
1273    }
1274    Ok(None)
1275}
1276
1277fn parse_route_version(attrs: &[syn::Attribute]) -> Result<Option<LitStr>> {
1278    for attr in attrs {
1279        if !attr.path().is_ident("ver") {
1280            continue;
1281        }
1282        match &attr.meta {
1283            Meta::Path(_) => {
1284                return Err(syn::Error::new_spanned(
1285                    attr,
1286                    "ver expects a version string, e.g. #[ver(\"v2\")]",
1287                ));
1288            }
1289            Meta::List(list) => {
1290                if list.tokens.is_empty() {
1291                    return Err(syn::Error::new_spanned(
1292                        list,
1293                        "ver expects a version string, e.g. #[ver(\"v2\")]",
1294                    ));
1295                }
1296                let lit = syn::parse2::<LitStr>(list.tokens.clone()).map_err(|_| {
1297                    syn::Error::new_spanned(
1298                        list,
1299                        "ver expects a version string, e.g. #[ver(\"v2\")]",
1300                    )
1301                })?;
1302                return Ok(Some(lit));
1303            }
1304            Meta::NameValue(_) => {
1305                return Err(syn::Error::new_spanned(
1306                    attr,
1307                    "ver expects #[ver(\"v2\")] syntax",
1308                ));
1309            }
1310        }
1311    }
1312    Ok(None)
1313}
1314
1315fn parse_use_guards(attrs: &[syn::Attribute]) -> Result<Vec<Type>> {
1316    for attr in attrs {
1317        if !attr.path().is_ident("use_guards") {
1318            continue;
1319        }
1320        let Meta::List(list) = &attr.meta else {
1321            return Err(syn::Error::new_spanned(
1322                attr,
1323                "use_guards expects types, e.g. #[use_guards(AuthGuard, RolesGuard)]",
1324            ));
1325        };
1326        if list.tokens.is_empty() {
1327            return Ok(Vec::new());
1328        }
1329        let guards: Punctuated<Type, Token![,]> = Punctuated::<Type, Token![,]>::parse_terminated
1330            .parse2(list.tokens.clone())
1331            .map_err(|_| {
1332                syn::Error::new_spanned(
1333                    list,
1334                    "use_guards expects types, e.g. #[use_guards(AuthGuard, RolesGuard)]",
1335                )
1336            })?;
1337        return Ok(guards.into_iter().collect());
1338    }
1339    Ok(Vec::new())
1340}
1341
1342fn parse_use_pipes(attrs: &[syn::Attribute]) -> Result<Vec<Type>> {
1343    for attr in attrs {
1344        if !attr.path().is_ident("use_pipes") {
1345            continue;
1346        }
1347        let Meta::List(list) = &attr.meta else {
1348            return Err(syn::Error::new_spanned(
1349                attr,
1350                "use_pipes expects types, e.g. #[use_pipes(ValidationPipe)]",
1351            ));
1352        };
1353        if list.tokens.is_empty() {
1354            return Ok(Vec::new());
1355        }
1356        let values: Punctuated<Type, Token![,]> = Punctuated::<Type, Token![,]>::parse_terminated
1357            .parse2(list.tokens.clone())
1358            .map_err(|_| {
1359                syn::Error::new_spanned(
1360                    list,
1361                    "use_pipes expects types, e.g. #[use_pipes(ValidationPipe)]",
1362                )
1363            })?;
1364        return Ok(values.into_iter().collect());
1365    }
1366    Ok(Vec::new())
1367}
1368
1369fn parse_use_ws_guards(attrs: &[syn::Attribute]) -> Result<Vec<Type>> {
1370    for attr in attrs {
1371        if !attr.path().is_ident("use_ws_guards") {
1372            continue;
1373        }
1374        let Meta::List(list) = &attr.meta else {
1375            return Err(syn::Error::new_spanned(
1376                attr,
1377                "use_ws_guards expects types, e.g. #[use_ws_guards(MyWsGuard)]",
1378            ));
1379        };
1380        if list.tokens.is_empty() {
1381            return Ok(Vec::new());
1382        }
1383        let guards: Punctuated<Type, Token![,]> = Punctuated::<Type, Token![,]>::parse_terminated
1384            .parse2(list.tokens.clone())
1385            .map_err(|_| {
1386                syn::Error::new_spanned(
1387                    list,
1388                    "use_ws_guards expects types, e.g. #[use_ws_guards(MyWsGuard)]",
1389                )
1390            })?;
1391        return Ok(guards.into_iter().collect());
1392    }
1393    Ok(Vec::new())
1394}
1395
1396fn parse_use_ws_pipes(attrs: &[syn::Attribute]) -> Result<Vec<Type>> {
1397    for attr in attrs {
1398        if !attr.path().is_ident("use_ws_pipes") {
1399            continue;
1400        }
1401        let Meta::List(list) = &attr.meta else {
1402            return Err(syn::Error::new_spanned(
1403                attr,
1404                "use_ws_pipes expects types, e.g. #[use_ws_pipes(MyWsPipe)]",
1405            ));
1406        };
1407        if list.tokens.is_empty() {
1408            return Ok(Vec::new());
1409        }
1410        let values: Punctuated<Type, Token![,]> = Punctuated::<Type, Token![,]>::parse_terminated
1411            .parse2(list.tokens.clone())
1412            .map_err(|_| {
1413                syn::Error::new_spanned(
1414                    list,
1415                    "use_ws_pipes expects types, e.g. #[use_ws_pipes(MyWsPipe)]",
1416                )
1417            })?;
1418        return Ok(values.into_iter().collect());
1419    }
1420    Ok(Vec::new())
1421}
1422
1423fn parse_use_ws_interceptors(attrs: &[syn::Attribute]) -> Result<Vec<Type>> {
1424    for attr in attrs {
1425        if !attr.path().is_ident("use_ws_interceptors") {
1426            continue;
1427        }
1428        let Meta::List(list) = &attr.meta else {
1429            return Err(syn::Error::new_spanned(
1430                attr,
1431                "use_ws_interceptors expects types, e.g. #[use_ws_interceptors(LogWs)]",
1432            ));
1433        };
1434        if list.tokens.is_empty() {
1435            return Ok(Vec::new());
1436        }
1437        let values: Punctuated<Type, Token![,]> = Punctuated::<Type, Token![,]>::parse_terminated
1438            .parse2(list.tokens.clone())
1439            .map_err(|_| {
1440                syn::Error::new_spanned(
1441                    list,
1442                    "use_ws_interceptors expects types, e.g. #[use_ws_interceptors(LogWs)]",
1443                )
1444            })?;
1445        return Ok(values.into_iter().collect());
1446    }
1447    Ok(Vec::new())
1448}
1449
1450fn parse_use_micro_guards(attrs: &[syn::Attribute]) -> Result<Vec<Type>> {
1451    for attr in attrs {
1452        if !attr.path().is_ident("use_micro_guards") {
1453            continue;
1454        }
1455        let Meta::List(list) = &attr.meta else {
1456            return Err(syn::Error::new_spanned(
1457                attr,
1458                "use_micro_guards expects types, e.g. #[use_micro_guards(MyMicroGuard)]",
1459            ));
1460        };
1461        if list.tokens.is_empty() {
1462            return Ok(Vec::new());
1463        }
1464        let guards: Punctuated<Type, Token![,]> = Punctuated::<Type, Token![,]>::parse_terminated
1465            .parse2(list.tokens.clone())
1466            .map_err(|_| {
1467                syn::Error::new_spanned(
1468                    list,
1469                    "use_micro_guards expects types, e.g. #[use_micro_guards(MyMicroGuard)]",
1470                )
1471            })?;
1472        return Ok(guards.into_iter().collect());
1473    }
1474    Ok(Vec::new())
1475}
1476
1477fn parse_use_micro_pipes(attrs: &[syn::Attribute]) -> Result<Vec<Type>> {
1478    for attr in attrs {
1479        if !attr.path().is_ident("use_micro_pipes") {
1480            continue;
1481        }
1482        let Meta::List(list) = &attr.meta else {
1483            return Err(syn::Error::new_spanned(
1484                attr,
1485                "use_micro_pipes expects types, e.g. #[use_micro_pipes(MyMicroPipe)]",
1486            ));
1487        };
1488        if list.tokens.is_empty() {
1489            return Ok(Vec::new());
1490        }
1491        let values: Punctuated<Type, Token![,]> = Punctuated::<Type, Token![,]>::parse_terminated
1492            .parse2(list.tokens.clone())
1493            .map_err(|_| {
1494                syn::Error::new_spanned(
1495                    list,
1496                    "use_micro_pipes expects types, e.g. #[use_micro_pipes(MyMicroPipe)]",
1497                )
1498            })?;
1499        return Ok(values.into_iter().collect());
1500    }
1501    Ok(Vec::new())
1502}
1503
1504fn parse_use_micro_interceptors(attrs: &[syn::Attribute]) -> Result<Vec<Type>> {
1505    for attr in attrs {
1506        if !attr.path().is_ident("use_micro_interceptors") {
1507            continue;
1508        }
1509        let Meta::List(list) = &attr.meta else {
1510            return Err(syn::Error::new_spanned(
1511                attr,
1512                "use_micro_interceptors expects types, e.g. #[use_micro_interceptors(LogMicro)]",
1513            ));
1514        };
1515        if list.tokens.is_empty() {
1516            return Ok(Vec::new());
1517        }
1518        let values: Punctuated<Type, Token![,]> = Punctuated::<Type, Token![,]>::parse_terminated
1519            .parse2(list.tokens.clone())
1520            .map_err(|_| {
1521                syn::Error::new_spanned(
1522                    list,
1523                    "use_micro_interceptors expects types, e.g. #[use_micro_interceptors(LogMicro)]",
1524                )
1525            })?;
1526        return Ok(values.into_iter().collect());
1527    }
1528    Ok(Vec::new())
1529}
1530
1531fn parse_use_interceptors(attrs: &[syn::Attribute]) -> Result<Vec<Type>> {
1532    for attr in attrs {
1533        if !attr.path().is_ident("use_interceptors") {
1534            continue;
1535        }
1536        let Meta::List(list) = &attr.meta else {
1537            return Err(syn::Error::new_spanned(
1538                attr,
1539                "use_interceptors expects types, e.g. #[use_interceptors(LoggingInterceptor)]",
1540            ));
1541        };
1542        if list.tokens.is_empty() {
1543            return Ok(Vec::new());
1544        }
1545        let values: Punctuated<Type, Token![,]> = Punctuated::<Type, Token![,]>::parse_terminated
1546            .parse2(list.tokens.clone())
1547            .map_err(|_| {
1548                syn::Error::new_spanned(
1549                    list,
1550                    "use_interceptors expects types, e.g. #[use_interceptors(LoggingInterceptor)]",
1551                )
1552            })?;
1553        return Ok(values.into_iter().collect());
1554    }
1555    Ok(Vec::new())
1556}
1557
1558fn parse_use_filters(attrs: &[syn::Attribute]) -> Result<Vec<Type>> {
1559    for attr in attrs {
1560        if !attr.path().is_ident("use_filters") {
1561            continue;
1562        }
1563        let Meta::List(list) = &attr.meta else {
1564            return Err(syn::Error::new_spanned(
1565                attr,
1566                "use_filters expects types, e.g. #[use_filters(HttpExceptionFilter)]",
1567            ));
1568        };
1569        if list.tokens.is_empty() {
1570            return Ok(Vec::new());
1571        }
1572        let values: Punctuated<Type, Token![,]> = Punctuated::<Type, Token![,]>::parse_terminated
1573            .parse2(list.tokens.clone())
1574            .map_err(|_| {
1575                syn::Error::new_spanned(
1576                    list,
1577                    "use_filters expects types, e.g. #[use_filters(HttpExceptionFilter)]",
1578                )
1579            })?;
1580        return Ok(values.into_iter().collect());
1581    }
1582    Ok(Vec::new())
1583}
1584
1585fn parse_set_metadata(attrs: &[syn::Attribute]) -> Result<Vec<(LitStr, LitStr)>> {
1586    let mut out = Vec::new();
1587    for attr in attrs {
1588        if !attr.path().is_ident("set_metadata") {
1589            continue;
1590        }
1591        let Meta::List(list) = &attr.meta else {
1592            return Err(syn::Error::new_spanned(
1593                attr,
1594                "set_metadata expects two string literals, e.g. #[set_metadata(\"roles\", \"admin\")]",
1595            ));
1596        };
1597        let args: Punctuated<LitStr, Token![,]> =
1598            Punctuated::<LitStr, Token![,]>::parse_terminated
1599                .parse2(list.tokens.clone())
1600                .map_err(|_| {
1601                    syn::Error::new_spanned(
1602                        list,
1603                        "set_metadata expects two string literals, e.g. #[set_metadata(\"roles\", \"admin\")]",
1604                    )
1605                })?;
1606        let mut it = args.into_iter();
1607        let key = it.next().ok_or_else(|| {
1608            syn::Error::new_spanned(
1609                list,
1610                "set_metadata expects two string literals, e.g. #[set_metadata(\"roles\", \"admin\")]",
1611            )
1612        })?;
1613        let value = it.next().ok_or_else(|| {
1614            syn::Error::new_spanned(
1615                list,
1616                "set_metadata expects two string literals, e.g. #[set_metadata(\"roles\", \"admin\")]",
1617            )
1618        })?;
1619        out.push((key, value));
1620    }
1621    Ok(out)
1622}
1623
1624fn parse_roles(attrs: &[syn::Attribute]) -> Result<Vec<(LitStr, LitStr)>> {
1625    let mut out = Vec::new();
1626    for attr in attrs {
1627        if !attr.path().is_ident("roles") {
1628            continue;
1629        }
1630        let Meta::List(list) = &attr.meta else {
1631            return Err(syn::Error::new_spanned(
1632                attr,
1633                "roles expects one or more string literals, e.g. #[roles(\"admin\")]",
1634            ));
1635        };
1636        let args: Punctuated<LitStr, Token![,]> = Punctuated::<LitStr, Token![,]>::parse_terminated
1637            .parse2(list.tokens.clone())
1638            .map_err(|_| {
1639                syn::Error::new_spanned(
1640                    list,
1641                    "roles expects one or more string literals, e.g. #[roles(\"admin\")]",
1642                )
1643            })?;
1644        let joined = args.iter().map(|s| s.value()).collect::<Vec<_>>().join(",");
1645        out.push((
1646            LitStr::new("roles", attr.span()),
1647            LitStr::new(&joined, attr.span()),
1648        ));
1649    }
1650    Ok(out)
1651}
1652
1653struct OpenApiResponsePair(LitInt, LitStr);
1654
1655impl Parse for OpenApiResponsePair {
1656    fn parse(input: ParseStream<'_>) -> Result<Self> {
1657        let content;
1658        syn::parenthesized!(content in input);
1659        let status: LitInt = content.parse()?;
1660        content.parse::<Token![,]>()?;
1661        let desc: LitStr = content.parse()?;
1662        Ok(Self(status, desc))
1663    }
1664}
1665
1666struct OpenApiAttrBody {
1667    summary: Option<LitStr>,
1668    tag: Option<LitStr>,
1669    responses: Vec<(LitInt, LitStr)>,
1670}
1671
1672impl Parse for OpenApiAttrBody {
1673    fn parse(input: ParseStream<'_>) -> Result<Self> {
1674        let mut summary = None::<LitStr>;
1675        let mut tag = None::<LitStr>;
1676        let mut responses = Vec::<(LitInt, LitStr)>::new();
1677
1678        while !input.is_empty() {
1679            let key: Ident = input.parse()?;
1680            input.parse::<Token![=]>()?;
1681            match key.to_string().as_str() {
1682                "summary" => {
1683                    if summary.is_some() {
1684                        return Err(syn::Error::new_spanned(
1685                            key,
1686                            "duplicate `summary` in #[openapi(...)]",
1687                        ));
1688                    }
1689                    summary = Some(input.parse::<LitStr>()?);
1690                }
1691                "tag" => {
1692                    if tag.is_some() {
1693                        return Err(syn::Error::new_spanned(
1694                            key,
1695                            "duplicate `tag` in #[openapi(...)]",
1696                        ));
1697                    }
1698                    tag = Some(input.parse::<LitStr>()?);
1699                }
1700                "responses" => {
1701                    if !responses.is_empty() {
1702                        return Err(syn::Error::new_spanned(
1703                            key,
1704                            "duplicate `responses` in #[openapi(...)]",
1705                        ));
1706                    }
1707                    let content;
1708                    syn::parenthesized!(content in input);
1709                    let pairs: Punctuated<OpenApiResponsePair, Token![,]> =
1710                        content.parse_terminated(OpenApiResponsePair::parse, Token![,])?;
1711                    for pair in pairs {
1712                        let OpenApiResponsePair(st, ds) = pair;
1713                        let v: u128 = st.base10_parse().map_err(|_| {
1714                            syn::Error::new_spanned(&st, "response status must be a valid integer")
1715                        })?;
1716                        if v > u16::MAX as u128 {
1717                            return Err(syn::Error::new_spanned(
1718                                st,
1719                                "HTTP status code must fit in u16",
1720                            ));
1721                        }
1722                        responses.push((st, ds));
1723                    }
1724                }
1725                _ => {
1726                    return Err(syn::Error::new_spanned(
1727                        key,
1728                        "unknown #[openapi(...)] field (expected summary, tag, responses)",
1729                    ));
1730                }
1731            }
1732            if !input.is_empty() {
1733                input.parse::<Token![,]>()?;
1734            }
1735        }
1736
1737        Ok(OpenApiAttrBody {
1738            summary,
1739            tag,
1740            responses,
1741        })
1742    }
1743}
1744
1745fn parse_openapi(attrs: &[syn::Attribute]) -> Result<Option<OpenApiAttrBody>> {
1746    let mut found = None::<OpenApiAttrBody>;
1747    for attr in attrs {
1748        if !attr.path().is_ident("openapi") {
1749            continue;
1750        }
1751        if found.is_some() {
1752            return Err(syn::Error::new_spanned(
1753                attr,
1754                "#[openapi(...)] can only appear once per handler",
1755            ));
1756        }
1757        let Meta::List(list) = &attr.meta else {
1758            return Err(syn::Error::new_spanned(
1759                attr,
1760                "#[openapi] expects parentheses, e.g. #[openapi(summary = \"...\")]",
1761            ));
1762        };
1763        let parsed: OpenApiAttrBody = syn::parse2(list.tokens.clone())?;
1764        found = Some(parsed);
1765    }
1766    Ok(found)
1767}
1768
1769fn openapi_impl_line(body: &OpenApiAttrBody) -> proc_macro2::TokenStream {
1770    if body.summary.is_none() && body.tag.is_none() && body.responses.is_empty() {
1771        return quote! {};
1772    }
1773    let summary = match &body.summary {
1774        Some(s) => quote! { ::core::option::Option::Some(#s) },
1775        None => quote! { ::core::option::Option::None },
1776    };
1777    let tag = match &body.tag {
1778        Some(s) => quote! { ::core::option::Option::Some(#s) },
1779        None => quote! { ::core::option::Option::None },
1780    };
1781    let pairs = body
1782        .responses
1783        .iter()
1784        .map(|(st, ds)| quote! { (#st as u16, #ds) });
1785    quote! {
1786        openapi ( nestrs::__nestrs_openapi_spec_leaked(#summary, #tag, &[ #(#pairs),* ]) )
1787    }
1788}
1789
1790#[proc_macro_attribute]
1791pub fn routes(attr: TokenStream, item: TokenStream) -> TokenStream {
1792    let args = parse_macro_input!(attr as RoutesArgs);
1793    let mut item_impl = parse_macro_input!(item as syn::ItemImpl);
1794
1795    if item_impl.trait_.is_some() {
1796        return syn::Error::new_spanned(item_impl, "routes supports inherent impl blocks only")
1797            .to_compile_error()
1798            .into();
1799    }
1800
1801    let self_ty = item_impl.self_ty.clone();
1802
1803    let controller_ident = match &*self_ty {
1804        Type::Path(tp) => tp
1805            .path
1806            .segments
1807            .last()
1808            .map(|s| s.ident.clone())
1809            .unwrap_or_else(|| Ident::new("Controller", proc_macro2::Span::call_site())),
1810        _ => Ident::new("Controller", proc_macro2::Span::call_site()),
1811    };
1812
1813    let mut routes = Vec::<RouteDef>::new();
1814    for it in &mut item_impl.items {
1815        let syn::ImplItem::Fn(func) = it else {
1816            continue;
1817        };
1818
1819        let (method, path) = match parse_route_method(&func.attrs) {
1820            Ok(Some(v)) => v,
1821            Ok(None) => continue,
1822            Err(e) => return e.to_compile_error().into(),
1823        };
1824
1825        let version = match parse_route_version(&func.attrs) {
1826            Ok(v) => v,
1827            Err(e) => return e.to_compile_error().into(),
1828        };
1829
1830        let guards = match parse_use_guards(&func.attrs) {
1831            Ok(v) => v,
1832            Err(e) => return e.to_compile_error().into(),
1833        };
1834
1835        let pipes = match parse_use_pipes(&func.attrs) {
1836            Ok(v) => v,
1837            Err(e) => return e.to_compile_error().into(),
1838        };
1839
1840        let has_validation = pipes.iter().any(is_validation_pipe);
1841
1842        // Expand Nest-like parameter decorators into Axum extractors.
1843        for input in func.sig.inputs.iter_mut() {
1844            let syn::FnArg::Typed(pat_ty) = input else {
1845                continue;
1846            };
1847
1848            let decorators = pat_ty
1849                .attrs
1850                .iter()
1851                .filter_map(param_decorator_from_attr)
1852                .collect::<Vec<_>>();
1853            if decorators.len() > 1 {
1854                return syn::Error::new_spanned(
1855                    &pat_ty.attrs[0],
1856                    "only one #[param::...] decorator is allowed per parameter",
1857                )
1858                .to_compile_error()
1859                .into();
1860            }
1861            let decorator = decorators.first().copied();
1862            pat_ty
1863                .attrs
1864                .retain(|a| param_decorator_from_attr(a).is_none());
1865
1866            let Some(decorator) = decorator else {
1867                continue;
1868            };
1869
1870            let inner_pat = (*pat_ty.pat).clone();
1871            let inner_ty = (*pat_ty.ty).clone();
1872
1873            match decorator {
1874                ParamDecorator::Body => {
1875                    if has_validation {
1876                        pat_ty.pat = syn::parse_quote!(nestrs::ValidatedBody(#inner_pat));
1877                        pat_ty.ty = syn::parse_quote!(nestrs::ValidatedBody<#inner_ty>);
1878                    } else {
1879                        pat_ty.pat = syn::parse_quote!(nestrs::axum::Json(#inner_pat));
1880                        pat_ty.ty = syn::parse_quote!(nestrs::axum::Json<#inner_ty>);
1881                    }
1882                }
1883                ParamDecorator::Query => {
1884                    if has_validation {
1885                        pat_ty.pat = syn::parse_quote!(nestrs::ValidatedQuery(#inner_pat));
1886                        pat_ty.ty = syn::parse_quote!(nestrs::ValidatedQuery<#inner_ty>);
1887                    } else {
1888                        pat_ty.pat = syn::parse_quote!(nestrs::axum::extract::Query(#inner_pat));
1889                        pat_ty.ty = syn::parse_quote!(nestrs::axum::extract::Query<#inner_ty>);
1890                    }
1891                }
1892                ParamDecorator::Param => {
1893                    if has_validation {
1894                        pat_ty.pat = syn::parse_quote!(nestrs::ValidatedPath(#inner_pat));
1895                        pat_ty.ty = syn::parse_quote!(nestrs::ValidatedPath<#inner_ty>);
1896                    } else {
1897                        pat_ty.pat = syn::parse_quote!(nestrs::axum::extract::Path(#inner_pat));
1898                        pat_ty.ty = syn::parse_quote!(nestrs::axum::extract::Path<#inner_ty>);
1899                    }
1900                }
1901                ParamDecorator::Req => {
1902                    pat_ty.ty = syn::parse_quote!(nestrs::axum::extract::Request);
1903                }
1904                ParamDecorator::Headers => {
1905                    pat_ty.ty = syn::parse_quote!(nestrs::axum::http::HeaderMap);
1906                }
1907                ParamDecorator::Ip => {
1908                    pat_ty.pat = syn::parse_quote!(nestrs::ClientIp(#inner_pat));
1909                    pat_ty.ty = syn::parse_quote!(nestrs::ClientIp);
1910                }
1911            }
1912        }
1913
1914        let interceptors = match parse_use_interceptors(&func.attrs) {
1915            Ok(v) => v,
1916            Err(e) => return e.to_compile_error().into(),
1917        };
1918
1919        let filters = match parse_use_filters(&func.attrs) {
1920            Ok(v) => v,
1921            Err(e) => return e.to_compile_error().into(),
1922        };
1923
1924        let mut metadata = match parse_set_metadata(&func.attrs) {
1925            Ok(v) => v,
1926            Err(e) => return e.to_compile_error().into(),
1927        };
1928        match parse_roles(&func.attrs) {
1929            Ok(v) => metadata.extend(v),
1930            Err(e) => return e.to_compile_error().into(),
1931        }
1932
1933        let openapi_line = match parse_openapi(&func.attrs) {
1934            Ok(Some(body)) => openapi_impl_line(&body),
1935            Ok(None) => quote! {},
1936            Err(e) => return e.to_compile_error().into(),
1937        };
1938        func.attrs.retain(|a| !a.path().is_ident("openapi"));
1939
1940        routes.push(RouteDef {
1941            method,
1942            path,
1943            handler: func.sig.ident.clone(),
1944            version,
1945            guards,
1946            pipes,
1947            interceptors,
1948            filters,
1949            metadata,
1950            openapi_line,
1951        });
1952    }
1953
1954    if routes.is_empty() {
1955        let msg = format!(
1956            "routes found no #[get]/#[post]/... handlers in impl {controller_ident} {{ ... }}",
1957        );
1958        return syn::Error::new_spanned(item_impl, msg)
1959            .to_compile_error()
1960            .into();
1961    }
1962
1963    let state_ty = args.state;
1964    let controller_guards = args.controller_guards;
1965
1966    let route_entries = routes
1967        .into_iter()
1968        .map(|r| {
1969            let method = r.method.to_ident();
1970            let path = r.path;
1971            let handler_name = r.handler;
1972            let handler = quote!(#self_ty::#handler_name);
1973            let guards = r.guards;
1974            let interceptors = r.interceptors;
1975            let filters = r.filters;
1976            let metadata = r.metadata;
1977            let openapi_line = r.openapi_line;
1978            let maybe_ver = r.version.map(|v| quote!(@ver(#v)));
1979            let interceptors_tokens = if interceptors.is_empty() {
1980                quote! {}
1981            } else {
1982                quote! { interceptors ( #(#interceptors),* ) }
1983            };
1984            let filters_tokens = if filters.is_empty() {
1985                quote! {}
1986            } else {
1987                quote! { filters ( #(#filters),* ) }
1988            };
1989            let metadata_tokens = if metadata.is_empty() {
1990                quote! {}
1991            } else {
1992                let keys = metadata.iter().map(|(k, _)| k);
1993                let values = metadata.iter().map(|(_, v)| v);
1994                quote! { metadata ( #( ( #keys, #values ) ),* ) }
1995            };
1996
1997            quote! {
1998                #maybe_ver
1999                    #method #path
2000                    #openapi_line
2001                    with ( #(#guards),* )
2002                    #interceptors_tokens
2003                    #filters_tokens
2004                    #metadata_tokens
2005                    => #handler,
2006            }
2007        })
2008        .collect::<Vec<_>>();
2009
2010    let register = if let Some(ctrl_guard) = controller_guards {
2011        quote! {
2012            nestrs::impl_routes!(#self_ty, state #state_ty, controller_guards ( #ctrl_guard ) => [
2013                #(#route_entries)*
2014            ]);
2015        }
2016    } else {
2017        quote! {
2018            nestrs::impl_routes!(#self_ty, state #state_ty => [
2019                #(#route_entries)*
2020            ]);
2021        }
2022    };
2023
2024    let expanded = quote! {
2025        #item_impl
2026        #register
2027    };
2028
2029    expanded.into()
2030}
2031
2032#[proc_macro_attribute]
2033pub fn subscribe_message(attr: TokenStream, item: TokenStream) -> TokenStream {
2034    passthrough(attr, item)
2035}
2036
2037#[proc_macro_attribute]
2038pub fn ws_routes(_attr: TokenStream, item: TokenStream) -> TokenStream {
2039    let item_impl = parse_macro_input!(item as syn::ItemImpl);
2040
2041    if item_impl.trait_.is_some() {
2042        return syn::Error::new_spanned(item_impl, "ws_routes supports inherent impl blocks only")
2043            .to_compile_error()
2044            .into();
2045    }
2046
2047    let self_ty = item_impl.self_ty.clone();
2048
2049    struct WsHandlerDef {
2050        event: LitStr,
2051        name: Ident,
2052        expects_client: bool,
2053        payload_ty: Option<Type>,
2054        ws_interceptors: Vec<Type>,
2055        ws_guards: Vec<Type>,
2056        ws_pipes: Vec<Type>,
2057    }
2058
2059    let mut handlers = Vec::<WsHandlerDef>::new();
2060    for it in &item_impl.items {
2061        let syn::ImplItem::Fn(func) = it else {
2062            continue;
2063        };
2064        let event = match parse_subscribe_message(&func.attrs) {
2065            Ok(Some(v)) => v,
2066            Ok(None) => continue,
2067            Err(e) => return e.to_compile_error().into(),
2068        };
2069
2070        // Require `&self` receiver so we can call methods from `WsGateway::on_message(&self, ...)`.
2071        let mut inputs = func.sig.inputs.iter();
2072        let Some(first) = inputs.next() else {
2073            return syn::Error::new_spanned(
2074                func,
2075                "subscribe_message handlers must be methods with `&self` receiver",
2076            )
2077            .to_compile_error()
2078            .into();
2079        };
2080        let syn::FnArg::Receiver(recv) = first else {
2081            return syn::Error::new_spanned(
2082                first,
2083                "subscribe_message handlers must be methods with `&self` receiver",
2084            )
2085            .to_compile_error()
2086            .into();
2087        };
2088        if recv.reference.is_none() {
2089            return syn::Error::new_spanned(
2090                recv,
2091                "subscribe_message handlers must use `&self` receiver",
2092            )
2093            .to_compile_error()
2094            .into();
2095        }
2096        if recv.mutability.is_some() {
2097            return syn::Error::new_spanned(
2098                recv,
2099                "subscribe_message handlers must use `&self` receiver (not `&mut self`)",
2100            )
2101            .to_compile_error()
2102            .into();
2103        }
2104
2105        let typed_args = inputs
2106            .filter_map(|arg| match arg {
2107                syn::FnArg::Typed(pat_ty) => Some((*pat_ty.ty).clone()),
2108                syn::FnArg::Receiver(_) => None,
2109            })
2110            .collect::<Vec<_>>();
2111
2112        if typed_args.len() > 2 {
2113            return syn::Error::new_spanned(
2114                func,
2115                "subscribe_message handlers support at most: (&self, WsClient?, Payload?)",
2116            )
2117            .to_compile_error()
2118            .into();
2119        }
2120
2121        let (expects_client, payload_ty) = match typed_args.as_slice() {
2122            [] => (false, None),
2123            [a] if is_ws_client_type(a) => (true, None),
2124            [payload_ty] => (false, Some(payload_ty.clone())),
2125            [a, payload_ty] if is_ws_client_type(a) => (true, Some(payload_ty.clone())),
2126            _ => {
2127                return syn::Error::new_spanned(
2128                    func.sig.clone(),
2129                    "subscribe_message handlers must be one of: (&self), (&self, WsClient), (&self, Payload), (&self, WsClient, Payload)",
2130                )
2131                .to_compile_error()
2132                .into();
2133            }
2134        };
2135
2136        let ws_interceptors = match parse_use_ws_interceptors(&func.attrs) {
2137            Ok(v) => v,
2138            Err(e) => return e.to_compile_error().into(),
2139        };
2140        let ws_guards = match parse_use_ws_guards(&func.attrs) {
2141            Ok(v) => v,
2142            Err(e) => return e.to_compile_error().into(),
2143        };
2144        let ws_pipes = match parse_use_ws_pipes(&func.attrs) {
2145            Ok(v) => v,
2146            Err(e) => return e.to_compile_error().into(),
2147        };
2148
2149        handlers.push(WsHandlerDef {
2150            event,
2151            name: func.sig.ident.clone(),
2152            expects_client,
2153            payload_ty,
2154            ws_interceptors,
2155            ws_guards,
2156            ws_pipes,
2157        });
2158    }
2159
2160    if handlers.is_empty() {
2161        return syn::Error::new_spanned(
2162            item_impl,
2163            "ws_routes found no #[subscribe_message(\"...\")] handlers in this impl block",
2164        )
2165        .to_compile_error()
2166        .into();
2167    }
2168
2169    let mut arms = Vec::new();
2170    for h in handlers {
2171        let event = h.event;
2172        let name = h.name;
2173        let expects_client = h.expects_client;
2174        let payload_ty = h.payload_ty;
2175        let ws_interceptors = h.ws_interceptors;
2176        let ws_guards = h.ws_guards;
2177        let ws_pipes = h.ws_pipes;
2178
2179        let call = match (expects_client, payload_ty) {
2180            (false, None) => quote! {
2181                let _ = self.#name().await;
2182            },
2183            (true, None) => quote! {
2184                let _ = self.#name(client.clone()).await;
2185            },
2186            (false, Some(payload_ty)) => {
2187                if is_serde_json_value_type(&payload_ty) {
2188                    quote! {
2189                        let __pl = __ws_payload.clone();
2190                        let _ = self.#name(__pl).await;
2191                    }
2192                } else {
2193                    quote! {
2194                        let __pl = __ws_payload.clone();
2195                        let __value: #payload_ty = match nestrs::serde_json::from_value(__pl) {
2196                            Ok(v) => v,
2197                            Err(e) => {
2198                                let _ = client.emit(
2199                                    nestrs::ws::WS_ERROR_EVENT,
2200                                    nestrs::serde_json::json!({
2201                                        "event": #event,
2202                                        "message": "invalid payload",
2203                                        "details": e.to_string()
2204                                    }),
2205                                );
2206                                return;
2207                            }
2208                        };
2209                        let _ = self.#name(__value).await;
2210                    }
2211                }
2212            }
2213            (true, Some(payload_ty)) => {
2214                if is_serde_json_value_type(&payload_ty) {
2215                    quote! {
2216                        let __pl = __ws_payload.clone();
2217                        let _ = self.#name(client.clone(), __pl).await;
2218                    }
2219                } else {
2220                    quote! {
2221                        let __pl = __ws_payload.clone();
2222                        let __value: #payload_ty = match nestrs::serde_json::from_value(__pl) {
2223                            Ok(v) => v,
2224                            Err(e) => {
2225                                let _ = client.emit(
2226                                    nestrs::ws::WS_ERROR_EVENT,
2227                                    nestrs::serde_json::json!({
2228                                        "event": #event,
2229                                        "message": "invalid payload",
2230                                        "details": e.to_string()
2231                                    }),
2232                                );
2233                                return;
2234                            }
2235                        };
2236                        let _ = self.#name(client.clone(), __value).await;
2237                    }
2238                }
2239            }
2240        };
2241
2242        let inter_ts: Vec<_> = ws_interceptors
2243            .iter()
2244            .map(|t| {
2245                quote! {
2246                    <#t as ::core::default::Default>::default()
2247                        .before_handle(client.handshake(), #event, &__ws_payload)
2248                        .await;
2249                }
2250            })
2251            .collect();
2252
2253        let guard_ts: Vec<_> = ws_guards
2254            .iter()
2255            .map(|g| {
2256                quote! {
2257                    if let Err(__e) = <#g as ::core::default::Default>::default()
2258                        .can_activate_ws(client.handshake(), #event, &__ws_payload)
2259                        .await
2260                    {
2261                        let _ = client.emit(nestrs::ws::WS_ERROR_EVENT, __e.to_json());
2262                        return;
2263                    }
2264                }
2265            })
2266            .collect();
2267
2268        let pipe_ts: Vec<_> = ws_pipes
2269            .iter()
2270            .map(|p| {
2271                quote! {
2272                    __ws_payload = match <#p as ::core::default::Default>::default()
2273                        .transform(#event, __ws_payload)
2274                        .await
2275                    {
2276                        Ok(__v) => __v,
2277                        Err(__e) => {
2278                            let _ = client.emit(nestrs::ws::WS_ERROR_EVENT, __e.to_json());
2279                            return;
2280                        }
2281                    };
2282                }
2283            })
2284            .collect();
2285
2286        arms.push(quote! {
2287            #event => {
2288                let mut __ws_payload = payload.clone();
2289                #(#inter_ts)*
2290                #(#guard_ts)*
2291                #(#pipe_ts)*
2292                #call
2293            }
2294        });
2295    }
2296
2297    let expanded = quote! {
2298        #item_impl
2299
2300        #[nestrs::async_trait]
2301        impl nestrs::ws::WsGateway for #self_ty {
2302            async fn on_message(
2303                &self,
2304                client: nestrs::ws::WsClient,
2305                event: &str,
2306                payload: nestrs::serde_json::Value,
2307            ) {
2308                match event {
2309                    #(#arms,)*
2310                    _ => {
2311                        let _ = client.emit(
2312                            nestrs::ws::WS_ERROR_EVENT,
2313                            nestrs::serde_json::json!({
2314                                "event": event,
2315                                "message": "unknown event"
2316                            }),
2317                        );
2318                    }
2319                }
2320            }
2321        }
2322    };
2323
2324    expanded.into()
2325}
2326
2327#[proc_macro_attribute]
2328pub fn ws_gateway(attr: TokenStream, item: TokenStream) -> TokenStream {
2329    let args = parse_macro_input!(attr as WsGatewayArgs);
2330    let item_struct = parse_macro_input!(item as ItemStruct);
2331    let name = &item_struct.ident;
2332    let path = args.path;
2333
2334    let expanded = quote! {
2335        #item_struct
2336
2337        impl nestrs::core::Controller for #name {
2338            fn register(
2339                router: nestrs::axum::Router,
2340                registry: &nestrs::core::ProviderRegistry
2341            ) -> nestrs::axum::Router {
2342                let gateway = registry.get::<#name>();
2343                router.route(#path, nestrs::ws::ws_route(gateway))
2344            }
2345        }
2346    };
2347
2348    expanded.into()
2349}
2350
2351#[proc_macro_attribute]
2352pub fn event_routes(_attr: TokenStream, item: TokenStream) -> TokenStream {
2353    let item_impl = parse_macro_input!(item as syn::ItemImpl);
2354
2355    if item_impl.trait_.is_some() {
2356        return syn::Error::new_spanned(
2357            item_impl,
2358            "event_routes supports inherent impl blocks only",
2359        )
2360        .to_compile_error()
2361        .into();
2362    }
2363
2364    let self_ty = item_impl.self_ty.clone();
2365
2366    struct EventHandlerDef {
2367        pattern: LitStr,
2368        name: Ident,
2369        payload_ty: Option<Type>,
2370    }
2371
2372    let mut handlers = Vec::<EventHandlerDef>::new();
2373
2374    for it in &item_impl.items {
2375        let syn::ImplItem::Fn(func) = it else {
2376            continue;
2377        };
2378
2379        let pattern = match parse_on_event(&func.attrs) {
2380            Ok(Some(v)) => v,
2381            Ok(None) => continue,
2382            Err(e) => return e.to_compile_error().into(),
2383        };
2384
2385        if func.sig.asyncness.is_none() {
2386            return syn::Error::new_spanned(func.sig.clone(), "on_event handlers must be async")
2387                .to_compile_error()
2388                .into();
2389        }
2390
2391        // Require `&self` receiver so we can call methods from the subscription closure.
2392        let mut inputs = func.sig.inputs.iter();
2393        let Some(first) = inputs.next() else {
2394            return syn::Error::new_spanned(
2395                func,
2396                "on_event handlers must be methods with `&self` receiver",
2397            )
2398            .to_compile_error()
2399            .into();
2400        };
2401        let syn::FnArg::Receiver(recv) = first else {
2402            return syn::Error::new_spanned(
2403                first,
2404                "on_event handlers must be methods with `&self` receiver",
2405            )
2406            .to_compile_error()
2407            .into();
2408        };
2409        if recv.reference.is_none() {
2410            return syn::Error::new_spanned(recv, "on_event handlers must use `&self` receiver")
2411                .to_compile_error()
2412                .into();
2413        }
2414        if recv.mutability.is_some() {
2415            return syn::Error::new_spanned(
2416                recv,
2417                "on_event handlers must use `&self` receiver (not `&mut self`)",
2418            )
2419            .to_compile_error()
2420            .into();
2421        }
2422
2423        let typed_args = inputs
2424            .filter_map(|arg| match arg {
2425                syn::FnArg::Typed(pat_ty) => Some((*pat_ty.ty).clone()),
2426                syn::FnArg::Receiver(_) => None,
2427            })
2428            .collect::<Vec<_>>();
2429
2430        if typed_args.len() > 1 {
2431            return syn::Error::new_spanned(
2432                func,
2433                "on_event handlers support at most: (&self, Payload?)",
2434            )
2435            .to_compile_error()
2436            .into();
2437        }
2438
2439        let payload_ty = typed_args.into_iter().next();
2440
2441        handlers.push(EventHandlerDef {
2442            pattern,
2443            name: func.sig.ident.clone(),
2444            payload_ty,
2445        });
2446    }
2447
2448    if handlers.is_empty() {
2449        return syn::Error::new_spanned(
2450            item_impl,
2451            "event_routes found no #[on_event(\"...\")] handlers in this impl block",
2452        )
2453        .to_compile_error()
2454        .into();
2455    }
2456
2457    let subscribe_stmts = handlers
2458        .into_iter()
2459        .map(|h| {
2460            let pattern = h.pattern;
2461            let name = h.name;
2462            let payload_ty = h.payload_ty;
2463
2464            let call = match payload_ty {
2465                None => quote! {
2466                    let _ = service.#name().await;
2467                },
2468                Some(payload_ty) if is_serde_json_value_type(&payload_ty) => quote! {
2469                    let payload = payload.clone();
2470                    let _ = service.#name(payload).await;
2471                },
2472                Some(payload_ty) => quote! {
2473                    let decoded: #payload_ty = match nestrs::serde_json::from_value(payload.clone()) {
2474                        Ok(v) => v,
2475                        Err(_) => return,
2476                    };
2477                    let _ = service.#name(decoded).await;
2478                },
2479            };
2480
2481            quote! {
2482                bus.subscribe(#pattern, {
2483                    let service = service.clone();
2484                    move |payload: nestrs::serde_json::Value| {
2485                        let service = service.clone();
2486                        async move {
2487                            #call
2488                        }
2489                    }
2490                });
2491            }
2492        })
2493        .collect::<Vec<_>>();
2494
2495    let expanded = quote! {
2496        #item_impl
2497
2498        const _: () = {
2499            fn __nestrs_register(registry: &nestrs::core::ProviderRegistry) {
2500                let bus = registry.get::<nestrs::EventBus>();
2501                let service = registry.get::<#self_ty>();
2502                #(#subscribe_stmts)*
2503            }
2504
2505            #[nestrs::microservices::linkme::distributed_slice(nestrs::microservices::ON_EVENT_REGISTRATIONS)]
2506            static __NES_ON_EVENT: nestrs::microservices::OnEventRegistration =
2507                nestrs::microservices::OnEventRegistration { register: __nestrs_register };
2508        };
2509    };
2510
2511    expanded.into()
2512}
2513
2514#[proc_macro_attribute]
2515pub fn schedule_routes(_attr: TokenStream, item: TokenStream) -> TokenStream {
2516    let item_impl = parse_macro_input!(item as syn::ItemImpl);
2517
2518    if item_impl.trait_.is_some() {
2519        return syn::Error::new_spanned(
2520            item_impl,
2521            "schedule_routes supports inherent impl blocks only",
2522        )
2523        .to_compile_error()
2524        .into();
2525    }
2526
2527    let self_ty = item_impl.self_ty.clone();
2528
2529    enum TaskKind {
2530        Cron(LitStr),
2531        Interval(LitInt),
2532    }
2533
2534    struct TaskDef {
2535        kind: TaskKind,
2536        name: Ident,
2537    }
2538
2539    let mut tasks = Vec::<TaskDef>::new();
2540
2541    for it in &item_impl.items {
2542        let syn::ImplItem::Fn(func) = it else {
2543            continue;
2544        };
2545
2546        let cron = match parse_cron(&func.attrs) {
2547            Ok(v) => v,
2548            Err(e) => return e.to_compile_error().into(),
2549        };
2550        let interval = match parse_interval(&func.attrs) {
2551            Ok(v) => v,
2552            Err(e) => return e.to_compile_error().into(),
2553        };
2554
2555        let kind = match (cron, interval) {
2556            (None, None) => continue,
2557            (Some(c), None) => TaskKind::Cron(c),
2558            (None, Some(i)) => TaskKind::Interval(i),
2559            (Some(_), Some(_)) => {
2560                return syn::Error::new_spanned(
2561                    func,
2562                    "scheduled task cannot have both #[cron] and #[interval]",
2563                )
2564                .to_compile_error()
2565                .into();
2566            }
2567        };
2568
2569        if func.sig.asyncness.is_none() {
2570            return syn::Error::new_spanned(func.sig.clone(), "scheduled tasks must be async")
2571                .to_compile_error()
2572                .into();
2573        }
2574
2575        let mut inputs = func.sig.inputs.iter();
2576        let Some(first) = inputs.next() else {
2577            return syn::Error::new_spanned(
2578                func,
2579                "scheduled tasks must be methods with `&self` receiver",
2580            )
2581            .to_compile_error()
2582            .into();
2583        };
2584        let syn::FnArg::Receiver(recv) = first else {
2585            return syn::Error::new_spanned(
2586                first,
2587                "scheduled tasks must be methods with `&self` receiver",
2588            )
2589            .to_compile_error()
2590            .into();
2591        };
2592        if recv.reference.is_none() {
2593            return syn::Error::new_spanned(recv, "scheduled tasks must use `&self` receiver")
2594                .to_compile_error()
2595                .into();
2596        }
2597        if recv.mutability.is_some() {
2598            return syn::Error::new_spanned(
2599                recv,
2600                "scheduled tasks must use `&self` receiver (not `&mut self`)",
2601            )
2602            .to_compile_error()
2603            .into();
2604        }
2605
2606        let typed_args = inputs
2607            .filter_map(|arg| match arg {
2608                syn::FnArg::Typed(pat_ty) => Some((*pat_ty.ty).clone()),
2609                syn::FnArg::Receiver(_) => None,
2610            })
2611            .collect::<Vec<_>>();
2612
2613        if !typed_args.is_empty() {
2614            return syn::Error::new_spanned(func, "scheduled tasks must be one of: (&self)")
2615                .to_compile_error()
2616                .into();
2617        }
2618
2619        tasks.push(TaskDef {
2620            kind,
2621            name: func.sig.ident.clone(),
2622        });
2623    }
2624
2625    if tasks.is_empty() {
2626        return syn::Error::new_spanned(
2627            item_impl,
2628            "schedule_routes found no #[cron(\"...\")] or #[interval(...)] tasks in this impl block",
2629        )
2630        .to_compile_error()
2631        .into();
2632    }
2633
2634    let job_stmts = tasks
2635        .into_iter()
2636        .map(|t| {
2637            let name = t.name;
2638            match t.kind {
2639                TaskKind::Cron(expr) => {
2640                    quote! {
2641                        let job = nestrs::schedule::Job::new_async(#expr, {
2642                            let service = service.clone();
2643                            move |_uuid, _lock| {
2644                                let service = service.clone();
2645                                ::std::boxed::Box::pin(async move {
2646                                    let _ = service.#name().await;
2647                                })
2648                            }
2649                        })
2650                        .unwrap_or_else(|e| panic!("failed to register cron job: {e:?}"));
2651                        jobs.push(job);
2652                    }
2653                }
2654                TaskKind::Interval(ms) => {
2655                    quote! {
2656                        let job = nestrs::schedule::Job::new_repeated_async(
2657                            ::std::time::Duration::from_millis(#ms as u64),
2658                            {
2659                                let service = service.clone();
2660                                move |_uuid, _lock| {
2661                                    let service = service.clone();
2662                                    ::std::boxed::Box::pin(async move {
2663                                        let _ = service.#name().await;
2664                                    })
2665                                }
2666                            },
2667                        )
2668                        .unwrap_or_else(|e| panic!("failed to register interval job: {e:?}"));
2669                        jobs.push(job);
2670                    }
2671                }
2672            }
2673        })
2674        .collect::<Vec<_>>();
2675
2676    let expanded = quote! {
2677        #item_impl
2678
2679        const _: () = {
2680            fn __nestrs_build(registry: &nestrs::core::ProviderRegistry) -> ::std::vec::Vec<nestrs::schedule::Job> {
2681                let service = registry.get::<#self_ty>();
2682                let mut jobs = ::std::vec::Vec::<nestrs::schedule::Job>::new();
2683                #(#job_stmts)*
2684                jobs
2685            }
2686
2687            #[nestrs::schedule::linkme::distributed_slice(nestrs::schedule::SCHEDULE_REGISTRATIONS)]
2688            static __NES_SCHEDULE: nestrs::schedule::ScheduleRegistration =
2689                nestrs::schedule::ScheduleRegistration { build: __nestrs_build };
2690        };
2691    };
2692
2693    expanded.into()
2694}
2695
2696#[proc_macro_attribute]
2697pub fn micro_routes(_attr: TokenStream, item: TokenStream) -> TokenStream {
2698    let item_impl = parse_macro_input!(item as syn::ItemImpl);
2699
2700    if item_impl.trait_.is_some() {
2701        return syn::Error::new_spanned(
2702            item_impl,
2703            "micro_routes supports inherent impl blocks only",
2704        )
2705        .to_compile_error()
2706        .into();
2707    }
2708
2709    let self_ty = item_impl.self_ty.clone();
2710
2711    struct MsHandlerDef {
2712        pattern: LitStr,
2713        name: Ident,
2714        is_message: bool,
2715        payload_ty: Option<Type>,
2716        ok_ty: Option<Type>,
2717        err_ty: Option<Type>,
2718        micro_interceptors: Vec<Type>,
2719        micro_guards: Vec<Type>,
2720        micro_pipes: Vec<Type>,
2721    }
2722
2723    let mut handlers = Vec::<MsHandlerDef>::new();
2724
2725    for it in &item_impl.items {
2726        let syn::ImplItem::Fn(func) = it else {
2727            continue;
2728        };
2729
2730        let msg = match parse_message_pattern(&func.attrs) {
2731            Ok(v) => v,
2732            Err(e) => return e.to_compile_error().into(),
2733        };
2734        let evt = match parse_event_pattern(&func.attrs) {
2735            Ok(v) => v,
2736            Err(e) => return e.to_compile_error().into(),
2737        };
2738
2739        let (pattern, is_message) = match (msg, evt) {
2740            (Some(m), None) => (m, true),
2741            (None, Some(e)) => (e, false),
2742            (None, None) => continue,
2743            (Some(_), Some(_)) => {
2744                return syn::Error::new_spanned(
2745                    func,
2746                    "handler cannot have both #[message_pattern] and #[event_pattern]",
2747                )
2748                .to_compile_error()
2749                .into();
2750            }
2751        };
2752
2753        if func.sig.asyncness.is_none() {
2754            return syn::Error::new_spanned(
2755                func.sig.clone(),
2756                "microservice handlers must be async",
2757            )
2758            .to_compile_error()
2759            .into();
2760        }
2761
2762        // Require `&self`.
2763        let mut inputs = func.sig.inputs.iter();
2764        let Some(first) = inputs.next() else {
2765            return syn::Error::new_spanned(
2766                func,
2767                "microservice handlers must be methods with `&self` receiver",
2768            )
2769            .to_compile_error()
2770            .into();
2771        };
2772        let syn::FnArg::Receiver(recv) = first else {
2773            return syn::Error::new_spanned(
2774                first,
2775                "microservice handlers must be methods with `&self` receiver",
2776            )
2777            .to_compile_error()
2778            .into();
2779        };
2780        if recv.reference.is_none() || recv.mutability.is_some() {
2781            return syn::Error::new_spanned(
2782                recv,
2783                "microservice handlers must use `&self` receiver",
2784            )
2785            .to_compile_error()
2786            .into();
2787        }
2788
2789        let typed_args = inputs
2790            .filter_map(|arg| match arg {
2791                syn::FnArg::Typed(pat_ty) => Some((*pat_ty.ty).clone()),
2792                syn::FnArg::Receiver(_) => None,
2793            })
2794            .collect::<Vec<_>>();
2795
2796        if typed_args.len() > 1 {
2797            return syn::Error::new_spanned(
2798                func.sig.clone(),
2799                "microservice handlers support at most one payload parameter",
2800            )
2801            .to_compile_error()
2802            .into();
2803        }
2804
2805        let payload_ty = typed_args.first().cloned();
2806
2807        let (ok_ty, err_ty) = if is_message {
2808            match &func.sig.output {
2809                syn::ReturnType::Default => (Some(syn::parse_quote!(())), None),
2810                syn::ReturnType::Type(_, ty) => {
2811                    if let Some((ok, err)) = split_result_type(ty) {
2812                        (Some(ok), Some(err))
2813                    } else {
2814                        (Some((**ty).clone()), None)
2815                    }
2816                }
2817            }
2818        } else {
2819            // Event handlers don't produce responses.
2820            (None, None)
2821        };
2822
2823        if let Some(ref err) = err_ty {
2824            if !(is_transport_error_type(err) || is_http_exception_type(err)) {
2825                return syn::Error::new_spanned(
2826                    err,
2827                    "message_pattern handlers returning Result must use HttpException or TransportError as the error type",
2828                )
2829                .to_compile_error()
2830                .into();
2831            }
2832        }
2833
2834        let micro_interceptors = match parse_use_micro_interceptors(&func.attrs) {
2835            Ok(v) => v,
2836            Err(e) => return e.to_compile_error().into(),
2837        };
2838        let micro_guards = match parse_use_micro_guards(&func.attrs) {
2839            Ok(v) => v,
2840            Err(e) => return e.to_compile_error().into(),
2841        };
2842        let micro_pipes = match parse_use_micro_pipes(&func.attrs) {
2843            Ok(v) => v,
2844            Err(e) => return e.to_compile_error().into(),
2845        };
2846
2847        handlers.push(MsHandlerDef {
2848            pattern,
2849            name: func.sig.ident.clone(),
2850            is_message,
2851            payload_ty,
2852            ok_ty,
2853            err_ty,
2854            micro_interceptors,
2855            micro_guards,
2856            micro_pipes,
2857        });
2858    }
2859
2860    let mut message_arms = Vec::new();
2861    let mut event_arms = Vec::new();
2862
2863    for h in handlers {
2864        if h.is_message {
2865            let pattern = h.pattern;
2866            let name = h.name;
2867            let payload_ty = h.payload_ty.clone();
2868            let ok_ty = h.ok_ty.clone();
2869            let err_ty = h.err_ty.clone();
2870            let micro_interceptors = h.micro_interceptors.clone();
2871            let micro_guards = h.micro_guards.clone();
2872            let micro_pipes = h.micro_pipes.clone();
2873
2874            let micro_inter_ts: Vec<_> = micro_interceptors
2875                .iter()
2876                .map(|t| {
2877                    quote! {
2878                        <#t as ::core::default::Default>::default()
2879                            .before_handle_micro(#pattern, &__ms_payload)
2880                            .await;
2881                    }
2882                })
2883                .collect();
2884
2885            let micro_guard_ts: Vec<_> = micro_guards
2886                .iter()
2887                .map(|g| {
2888                    quote! {
2889                        if let Err(__e) = <#g as ::core::default::Default>::default()
2890                            .can_activate_micro(#pattern, &__ms_payload)
2891                            .await
2892                        {
2893                            return Some(Err(__e));
2894                        }
2895                    }
2896                })
2897                .collect();
2898
2899            let micro_pipe_ts: Vec<_> = micro_pipes
2900                .iter()
2901                .map(|p| {
2902                    quote! {
2903                        __ms_payload = match <#p as ::core::default::Default>::default()
2904                            .transform_micro(#pattern, __ms_payload)
2905                            .await
2906                        {
2907                            Ok(__v) => __v,
2908                            Err(__e) => return Some(Err(__e)),
2909                        };
2910                    }
2911                })
2912                .collect();
2913
2914            let decode = if let Some(payload_ty) = payload_ty.clone() {
2915                if is_serde_json_value_type(&payload_ty) {
2916                    quote! { let __payload = __ms_payload.clone(); }
2917                } else {
2918                    quote! {
2919                        let __payload: #payload_ty = match nestrs::serde_json::from_value(__ms_payload.clone()) {
2920                            Ok(v) => v,
2921                            Err(e) => {
2922                                return Some(Err(nestrs::microservices::TransportError::new(format!(
2923                                    "invalid payload for `{}`: {}",
2924                                    #pattern,
2925                                    e
2926                                ))));
2927                            }
2928                        };
2929                    }
2930                }
2931            } else {
2932                quote! {}
2933            };
2934
2935            let call = match (payload_ty.as_ref(), ok_ty.as_ref(), err_ty.as_ref()) {
2936                (None, Some(ok_ty), None) => quote! {
2937                    let __out: #ok_ty = self.#name().await;
2938                    nestrs::serde_json::to_value(__out)
2939                        .map_err(|e| nestrs::microservices::TransportError::new(format!("serialize response failed: {e}")))
2940                },
2941                (Some(_payload_ty), Some(ok_ty), None) => {
2942                    quote! {
2943                        let __out: #ok_ty = self.#name(__payload).await;
2944                        nestrs::serde_json::to_value(__out)
2945                            .map_err(|e| nestrs::microservices::TransportError::new(format!("serialize response failed: {e}")))
2946                    }
2947                }
2948                (None, Some(_ok_ty), Some(err_ty)) => {
2949                    if is_transport_error_type(err_ty) {
2950                        quote! {
2951                            match self.#name().await {
2952                                Ok(v) => nestrs::serde_json::to_value(v)
2953                                    .map_err(|e| nestrs::microservices::TransportError::new(format!("serialize response failed: {e}"))),
2954                                Err(e) => Err(e),
2955                            }
2956                        }
2957                    } else {
2958                        // HttpException
2959                        quote! {
2960                            match self.#name().await {
2961                                Ok(v) => nestrs::serde_json::to_value(v)
2962                                    .map_err(|e| nestrs::microservices::TransportError::new(format!("serialize response failed: {e}"))),
2963                                Err(ex) => {
2964                                    let details = nestrs::serde_json::json!({
2965                                        "type": "HttpException",
2966                                        "statusCode": ex.status.as_u16(),
2967                                        "message": ex.message,
2968                                        "error": ex.error,
2969                                        "errors": ex.details,
2970                                    });
2971                                    Err(nestrs::microservices::TransportError::new("microservice handler threw HttpException").with_details(details))
2972                                }
2973                            }
2974                        }
2975                    }
2976                }
2977                (Some(_payload_ty), Some(_ok_ty), Some(err_ty)) => {
2978                    let pass_payload = quote! { __payload };
2979
2980                    if is_transport_error_type(err_ty) {
2981                        quote! {
2982                            match self.#name(#pass_payload).await {
2983                                Ok(v) => nestrs::serde_json::to_value(v)
2984                                    .map_err(|e| nestrs::microservices::TransportError::new(format!("serialize response failed: {e}"))),
2985                                Err(e) => Err(e),
2986                            }
2987                        }
2988                    } else {
2989                        quote! {
2990                            match self.#name(#pass_payload).await {
2991                                Ok(v) => nestrs::serde_json::to_value(v)
2992                                    .map_err(|e| nestrs::microservices::TransportError::new(format!("serialize response failed: {e}"))),
2993                                Err(ex) => {
2994                                    let details = nestrs::serde_json::json!({
2995                                        "type": "HttpException",
2996                                        "statusCode": ex.status.as_u16(),
2997                                        "message": ex.message,
2998                                        "error": ex.error,
2999                                        "errors": ex.details,
3000                                    });
3001                                    Err(nestrs::microservices::TransportError::new("microservice handler threw HttpException").with_details(details))
3002                                }
3003                            }
3004                        }
3005                    }
3006                }
3007                _ => {
3008                    return syn::Error::new_spanned(
3009                        &name,
3010                        "unsupported message_pattern handler signature",
3011                    )
3012                    .to_compile_error()
3013                    .into();
3014                }
3015            };
3016
3017            message_arms.push(quote! {
3018                #pattern => {
3019                    let mut __ms_payload = payload.clone();
3020                    #(#micro_inter_ts)*
3021                    #(#micro_guard_ts)*
3022                    #(#micro_pipe_ts)*
3023                    #decode
3024                    Some({
3025                        #call
3026                    })
3027                }
3028            });
3029        } else {
3030            let pattern = h.pattern;
3031            let name = h.name;
3032            let payload_ty = h.payload_ty.clone();
3033            let micro_interceptors = h.micro_interceptors.clone();
3034            let micro_guards = h.micro_guards.clone();
3035            let micro_pipes = h.micro_pipes.clone();
3036
3037            let micro_inter_ts: Vec<_> = micro_interceptors
3038                .iter()
3039                .map(|t| {
3040                    quote! {
3041                        <#t as ::core::default::Default>::default()
3042                            .before_handle_micro(#pattern, &__ms_payload)
3043                            .await;
3044                    }
3045                })
3046                .collect();
3047
3048            let micro_guard_ts: Vec<_> = micro_guards
3049                .iter()
3050                .map(|g| {
3051                    quote! {
3052                        if <#g as ::core::default::Default>::default()
3053                            .can_activate_micro(#pattern, &__ms_payload)
3054                            .await
3055                            .is_err()
3056                        {
3057                            return true;
3058                        }
3059                    }
3060                })
3061                .collect();
3062
3063            let micro_pipe_ts: Vec<_> = micro_pipes
3064                .iter()
3065                .map(|p| {
3066                    quote! {
3067                        __ms_payload = match <#p as ::core::default::Default>::default()
3068                            .transform_micro(#pattern, __ms_payload)
3069                            .await
3070                        {
3071                            Ok(__v) => __v,
3072                            Err(_) => return true,
3073                        };
3074                    }
3075                })
3076                .collect();
3077
3078            let decode = if let Some(payload_ty) = payload_ty.clone() {
3079                if is_serde_json_value_type(&payload_ty) {
3080                    quote! { let __payload = __ms_payload.clone(); }
3081                } else {
3082                    quote! {
3083                        let __payload: #payload_ty = match nestrs::serde_json::from_value(__ms_payload.clone()) {
3084                            Ok(v) => v,
3085                            Err(_) => {
3086                                return true;
3087                            }
3088                        };
3089                    }
3090                }
3091            } else {
3092                quote! {}
3093            };
3094
3095            let call = match payload_ty.as_ref() {
3096                None => quote! { let _ = self.#name().await; },
3097                Some(_payload_ty) => quote! { let _ = self.#name(__payload).await; },
3098            };
3099
3100            event_arms.push(quote! {
3101                #pattern => {
3102                    let mut __ms_payload = payload.clone();
3103                    #(#micro_inter_ts)*
3104                    #(#micro_guard_ts)*
3105                    #(#micro_pipe_ts)*
3106                    #decode
3107                    #call
3108                    true
3109                }
3110            });
3111        }
3112    }
3113
3114    if message_arms.is_empty() && event_arms.is_empty() {
3115        return syn::Error::new_spanned(
3116            item_impl,
3117            "micro_routes found no #[message_pattern] or #[event_pattern] handlers in this impl block",
3118        )
3119        .to_compile_error()
3120        .into();
3121    }
3122
3123    let expanded = quote! {
3124        #item_impl
3125
3126        #[nestrs::async_trait]
3127        impl nestrs::microservices::MicroserviceHandler for #self_ty {
3128            async fn handle_message(
3129                &self,
3130                pattern: &str,
3131                payload: nestrs::serde_json::Value,
3132            ) -> Option<Result<nestrs::serde_json::Value, nestrs::microservices::TransportError>> {
3133                match pattern {
3134                    #(#message_arms,)*
3135                    _ => None,
3136                }
3137            }
3138
3139            async fn handle_event(&self, pattern: &str, payload: nestrs::serde_json::Value) -> bool {
3140                match pattern {
3141                    #(#event_arms,)*
3142                    _ => false,
3143                }
3144            }
3145        }
3146    };
3147
3148    expanded.into()
3149}
3150
3151#[proc_macro_attribute]
3152pub fn ver(attr: TokenStream, item: TokenStream) -> TokenStream {
3153    passthrough(attr, item)
3154}
3155
3156#[proc_macro_attribute]
3157pub fn use_guards(attr: TokenStream, item: TokenStream) -> TokenStream {
3158    passthrough(attr, item)
3159}
3160
3161#[proc_macro_attribute]
3162pub fn use_pipes(attr: TokenStream, item: TokenStream) -> TokenStream {
3163    passthrough(attr, item)
3164}
3165
3166#[proc_macro_attribute]
3167pub fn use_interceptors(attr: TokenStream, item: TokenStream) -> TokenStream {
3168    passthrough(attr, item)
3169}
3170
3171#[proc_macro_attribute]
3172pub fn use_ws_guards(attr: TokenStream, item: TokenStream) -> TokenStream {
3173    passthrough(attr, item)
3174}
3175
3176#[proc_macro_attribute]
3177pub fn use_ws_pipes(attr: TokenStream, item: TokenStream) -> TokenStream {
3178    passthrough(attr, item)
3179}
3180
3181#[proc_macro_attribute]
3182pub fn use_ws_interceptors(attr: TokenStream, item: TokenStream) -> TokenStream {
3183    passthrough(attr, item)
3184}
3185
3186#[proc_macro_attribute]
3187pub fn use_micro_guards(attr: TokenStream, item: TokenStream) -> TokenStream {
3188    passthrough(attr, item)
3189}
3190
3191#[proc_macro_attribute]
3192pub fn use_micro_pipes(attr: TokenStream, item: TokenStream) -> TokenStream {
3193    passthrough(attr, item)
3194}
3195
3196#[proc_macro_attribute]
3197pub fn use_micro_interceptors(attr: TokenStream, item: TokenStream) -> TokenStream {
3198    passthrough(attr, item)
3199}
3200
3201#[proc_macro_attribute]
3202pub fn use_filters(attr: TokenStream, item: TokenStream) -> TokenStream {
3203    passthrough(attr, item)
3204}
3205
3206#[proc_macro_attribute]
3207pub fn set_metadata(attr: TokenStream, item: TokenStream) -> TokenStream {
3208    passthrough(attr, item)
3209}
3210
3211#[proc_macro_attribute]
3212pub fn roles(attr: TokenStream, item: TokenStream) -> TokenStream {
3213    passthrough(attr, item)
3214}
3215
3216#[proc_macro_attribute]
3217pub fn message_pattern(attr: TokenStream, item: TokenStream) -> TokenStream {
3218    passthrough(attr, item)
3219}
3220
3221#[proc_macro_attribute]
3222pub fn event_pattern(attr: TokenStream, item: TokenStream) -> TokenStream {
3223    passthrough(attr, item)
3224}
3225
3226#[proc_macro_attribute]
3227pub fn on_event(attr: TokenStream, item: TokenStream) -> TokenStream {
3228    passthrough(attr, item)
3229}
3230
3231#[proc_macro_attribute]
3232pub fn cron(attr: TokenStream, item: TokenStream) -> TokenStream {
3233    passthrough(attr, item)
3234}
3235
3236#[proc_macro_attribute]
3237pub fn interval(attr: TokenStream, item: TokenStream) -> TokenStream {
3238    passthrough(attr, item)
3239}
3240
3241#[proc_macro_attribute]
3242pub fn queue_processor(attr: TokenStream, item: TokenStream) -> TokenStream {
3243    let queue = parse_macro_input!(attr as LitStr);
3244    let item_struct = parse_macro_input!(item as ItemStruct);
3245    let name = &item_struct.ident;
3246
3247    let expanded = quote! {
3248        #item_struct
3249
3250        const _: () = {
3251            #[nestrs::queues::linkme::distributed_slice(nestrs::queues::QUEUE_PROCESSORS)]
3252            static __NES_QUEUE_PROCESSOR: nestrs::queues::QueueProcessorRegistration =
3253                nestrs::queues::QueueProcessorRegistration {
3254                    queue: #queue,
3255                    create: nestrs::queues::handler_factory::<#name>,
3256                };
3257        };
3258    };
3259
3260    expanded.into()
3261}
3262
3263/// Wraps handler return values in `axum::Json(...)` (NestJS "return object => JSON" ergonomics).
3264///
3265/// Supports:
3266/// - `async fn handler() -> T` where `T: serde::Serialize`
3267/// - `async fn handler() -> Result<T, E>` where `T: serde::Serialize` and `E: IntoResponse`
3268/// - `#[serialize(strip_null)]` — drop JSON `null` values after serialization (Nest `@Exclude` / transformer-style cleanup).
3269#[proc_macro_attribute]
3270pub fn serialize(attr: TokenStream, item: TokenStream) -> TokenStream {
3271    let strip_null = if attr.is_empty() {
3272        false
3273    } else {
3274        let id = parse_macro_input!(attr as Ident);
3275        if id != "strip_null" {
3276            return syn::Error::new_spanned(
3277                id,
3278                "unknown serialize option (expected `strip_null` or empty)",
3279            )
3280            .to_compile_error()
3281            .into();
3282        }
3283        true
3284    };
3285
3286    let mut method = parse_macro_input!(item as ImplItemFn);
3287    let output = method.sig.output.clone();
3288    let block = method.block;
3289
3290    method.sig.output = syn::parse_quote!(-> axum::response::Response);
3291
3292    let ok_json = if strip_null {
3293        quote! {
3294            match serde_json::to_value(&__ok) {
3295                Ok(__v) => {
3296                    let __v = nestrs::strip_null_json_value(__v);
3297                    axum::response::IntoResponse::into_response(axum::Json(__v))
3298                }
3299                Err(__e) => axum::response::IntoResponse::into_response(
3300                    nestrs::InternalServerErrorException::new(__e.to_string()),
3301                ),
3302            }
3303        }
3304    } else {
3305        quote! {
3306            axum::response::IntoResponse::into_response(axum::Json(__ok))
3307        }
3308    };
3309
3310    let body = match output {
3311        syn::ReturnType::Default => {
3312            quote! {
3313                let __value: () = (async move #block).await;
3314                axum::response::IntoResponse::into_response(axum::Json(__value))
3315            }
3316        }
3317        syn::ReturnType::Type(_, ty) => {
3318            if let Some((_ok, _err)) = split_result_type(&ty) {
3319                quote! {
3320                    let __value: #ty = (async move #block).await;
3321                    match __value {
3322                        Ok(__ok) => { #ok_json }
3323                        Err(__err) => axum::response::IntoResponse::into_response(__err),
3324                    }
3325                }
3326            } else if strip_null {
3327                quote! {
3328                    let __value: #ty = (async move #block).await;
3329                    match serde_json::to_value(&__value) {
3330                        Ok(__v) => {
3331                            let __v = nestrs::strip_null_json_value(__v);
3332                            axum::response::IntoResponse::into_response(axum::Json(__v))
3333                        }
3334                        Err(__e) => axum::response::IntoResponse::into_response(
3335                            nestrs::InternalServerErrorException::new(__e.to_string()),
3336                        ),
3337                    }
3338                }
3339            } else {
3340                quote! {
3341                    let __value: #ty = (async move #block).await;
3342                    axum::response::IntoResponse::into_response(axum::Json(__value))
3343                }
3344            }
3345        }
3346    };
3347
3348    method.block = syn::parse_quote!({ #body });
3349
3350    quote!(#method).into()
3351}
3352
3353#[proc_macro_attribute]
3354pub fn http_code(attr: TokenStream, item: TokenStream) -> TokenStream {
3355    let code = parse_macro_input!(attr as LitInt);
3356    let mut method = parse_macro_input!(item as ImplItemFn);
3357    let block = method.block;
3358
3359    method.sig.output = syn::parse_quote!(-> axum::response::Response);
3360    method.block = syn::parse_quote!({
3361        let __value = (async move #block).await;
3362        let mut __response = axum::response::IntoResponse::into_response(__value);
3363        if let Ok(__status) = axum::http::StatusCode::from_u16(#code) {
3364            __response.status_mut().clone_from(&__status);
3365        }
3366        __response
3367    });
3368
3369    quote!(#method).into()
3370}
3371
3372struct HeaderArgs {
3373    name: LitStr,
3374    value: LitStr,
3375}
3376
3377impl Parse for HeaderArgs {
3378    fn parse(input: ParseStream<'_>) -> Result<Self> {
3379        let name: LitStr = input.parse()?;
3380        input.parse::<Token![,]>()?;
3381        let value: LitStr = input.parse()?;
3382        Ok(Self { name, value })
3383    }
3384}
3385
3386#[proc_macro_attribute]
3387pub fn response_header(attr: TokenStream, item: TokenStream) -> TokenStream {
3388    let args = parse_macro_input!(attr as HeaderArgs);
3389    let mut method = parse_macro_input!(item as ImplItemFn);
3390    let block = method.block;
3391    let name = args.name;
3392    let value = args.value;
3393
3394    method.sig.output = syn::parse_quote!(-> axum::response::Response);
3395    method.block = syn::parse_quote!({
3396        let __value = (async move #block).await;
3397        let mut __response = axum::response::IntoResponse::into_response(__value);
3398        if let (Ok(__name), Ok(__value)) = (
3399            axum::http::header::HeaderName::from_bytes(#name.as_bytes()),
3400            axum::http::HeaderValue::from_str(#value),
3401        ) {
3402            __response.headers_mut().insert(__name, __value);
3403        }
3404        __response
3405    });
3406
3407    quote!(#method).into()
3408}
3409
3410struct RedirectArgs {
3411    url: LitStr,
3412    code: Option<LitInt>,
3413}
3414
3415impl Parse for RedirectArgs {
3416    fn parse(input: ParseStream<'_>) -> Result<Self> {
3417        let url: LitStr = input.parse()?;
3418        let mut code = None;
3419        if input.peek(Token![,]) {
3420            input.parse::<Token![,]>()?;
3421            code = Some(input.parse()?);
3422        }
3423        Ok(Self { url, code })
3424    }
3425}
3426
3427#[proc_macro_attribute]
3428pub fn redirect(attr: TokenStream, item: TokenStream) -> TokenStream {
3429    let args = parse_macro_input!(attr as RedirectArgs);
3430    let mut method = parse_macro_input!(item as ImplItemFn);
3431    let url = args.url;
3432    let maybe_code = args.code;
3433
3434    method.sig.output = syn::parse_quote!(-> axum::response::Response);
3435    method.block = if let Some(code) = maybe_code {
3436        syn::parse_quote!({
3437            let mut __response = axum::response::Redirect::to(#url).into_response();
3438            if let Ok(__status) = axum::http::StatusCode::from_u16(#code) {
3439                __response.status_mut().clone_from(&__status);
3440            }
3441            __response
3442        })
3443    } else {
3444        syn::parse_quote!({
3445            axum::response::Redirect::to(#url).into_response()
3446        })
3447    };
3448
3449    quote!(#method).into()
3450}
3451
3452fn field_has_marker(field: &Field, marker: &str) -> bool {
3453    field.attrs.iter().any(|a| a.path().is_ident(marker))
3454}
3455
3456fn convert_dto_field_attrs(field: &Field, expose_only: bool) -> Vec<syn::Attribute> {
3457    let mut out = Vec::new();
3458
3459    for attr in &field.attrs {
3460        let Some(name) = attr.path().get_ident().map(|v| v.to_string()) else {
3461            out.push(attr.clone());
3462            continue;
3463        };
3464
3465        match name.as_str() {
3466            "Exclude" => {
3467                out.push(syn::parse_quote!(#[serde(skip_serializing)]));
3468            }
3469            "Expose" => {}
3470            "IsEmail" => out.push(syn::parse_quote!(#[validate(email)])),
3471            "IsNotEmpty" => out.push(syn::parse_quote!(#[validate(length(min = 1))])),
3472            "IsString" => {
3473                // Type-level no-op in Rust, retained for Nest-like readability.
3474            }
3475            "IsUUID" => out.push(syn::parse_quote!(#[validate(uuid)])),
3476            "IsBoolean" => {}
3477            "IsPositive" => out.push(syn::parse_quote!(#[validate(range(min = 1))])),
3478            "IsNegative" => out.push(syn::parse_quote!(#[validate(range(max = -1))])),
3479            "MinLength" => {
3480                if let Meta::List(list) = &attr.meta {
3481                    let tokens = list.tokens.clone();
3482                    out.push(syn::parse_quote!(#[validate(length(min = #tokens))]));
3483                }
3484            }
3485            "MaxLength" => {
3486                if let Meta::List(list) = &attr.meta {
3487                    let tokens = list.tokens.clone();
3488                    out.push(syn::parse_quote!(#[validate(length(max = #tokens))]));
3489                }
3490            }
3491            "Length" => {
3492                if let Meta::List(list) = &attr.meta {
3493                    let tokens = list.tokens.clone();
3494                    out.push(syn::parse_quote!(#[validate(length(#tokens))]));
3495                }
3496            }
3497            "Min" => {
3498                if let Meta::List(list) = &attr.meta {
3499                    let tokens = list.tokens.clone();
3500                    out.push(syn::parse_quote!(#[validate(range(min = #tokens))]));
3501                }
3502            }
3503            "Max" => {
3504                if let Meta::List(list) = &attr.meta {
3505                    let tokens = list.tokens.clone();
3506                    out.push(syn::parse_quote!(#[validate(range(max = #tokens))]));
3507                }
3508            }
3509            // Integer / number checks are expressed by Rust types (`i32`, `f64`, …) and `range` where needed.
3510            "IsInt" | "IsNumber" => {}
3511            "IsUrl" => out.push(syn::parse_quote!(#[validate(url)])),
3512            "Matches" => {
3513                if let Meta::List(list) = &attr.meta {
3514                    let tokens = list.tokens.clone();
3515                    out.push(syn::parse_quote!(#[validate(regex = #tokens)]));
3516                }
3517            }
3518            "Contains" => {
3519                if let Meta::List(list) = &attr.meta {
3520                    let tokens = list.tokens.clone();
3521                    out.push(syn::parse_quote!(#[validate(contains(#tokens))]));
3522                }
3523            }
3524            // Nest `@IsOptional()` maps to `Option<T>` in Rust; strip the marker attribute.
3525            "IsOptional" => {}
3526            "ValidateNested" => out.push(syn::parse_quote!(#[validate(nested)])),
3527            _ => out.push(attr.clone()),
3528        }
3529    }
3530
3531    if expose_only && !field_has_marker(field, "Expose") {
3532        out.push(syn::parse_quote!(#[serde(skip_serializing)]));
3533    }
3534
3535    out
3536}
3537
3538#[derive(Default)]
3539struct DtoAttr {
3540    expose_only: bool,
3541    allow_unknown_fields: bool,
3542}
3543
3544impl Parse for DtoAttr {
3545    fn parse(input: ParseStream<'_>) -> Result<Self> {
3546        let mut out = DtoAttr::default();
3547        if input.is_empty() {
3548            return Ok(out);
3549        }
3550
3551        let vars = Punctuated::<Ident, Token![,]>::parse_terminated(input)?;
3552        for id in vars {
3553            if id == "expose_only" {
3554                out.expose_only = true;
3555            } else if id == "allow_unknown_fields" {
3556                out.allow_unknown_fields = true;
3557            } else {
3558                return Err(syn::Error::new_spanned(
3559                    id,
3560                    "unknown dto option (expected `expose_only` or `allow_unknown_fields`)",
3561                ));
3562            }
3563        }
3564        Ok(out)
3565    }
3566}
3567
3568#[proc_macro_attribute]
3569pub fn dto(attr: TokenStream, item: TokenStream) -> TokenStream {
3570    let opts = parse_macro_input!(attr as DtoAttr);
3571    let expose_only = opts.expose_only;
3572    let deny_unknown_fields = !opts.allow_unknown_fields;
3573    let item_struct = parse_macro_input!(item as ItemStruct);
3574    let vis = item_struct.vis;
3575    let ident = item_struct.ident;
3576
3577    let fields = match item_struct.fields {
3578        Fields::Named(named) => named,
3579        _ => {
3580            return syn::Error::new_spanned(
3581                ident,
3582                "dto currently supports named-field structs only",
3583            )
3584            .to_compile_error()
3585            .into()
3586        }
3587    };
3588
3589    let field_defs: Vec<_> = fields
3590        .named
3591        .iter()
3592        .map(|field| {
3593            let attrs = convert_dto_field_attrs(field, expose_only);
3594            let field_ident = field.ident.clone();
3595            let ty = field.ty.clone();
3596            quote! {
3597                #(#attrs)*
3598                pub #field_ident: #ty
3599            }
3600        })
3601        .collect();
3602
3603    if deny_unknown_fields {
3604        quote! {
3605            #[derive(Debug, Clone, serde::Deserialize, serde::Serialize, validator::Validate, nestrs::NestDto)]
3606            #[serde(deny_unknown_fields)]
3607            #vis struct #ident {
3608                #(#field_defs,)*
3609            }
3610        }
3611        .into()
3612    } else {
3613        quote! {
3614            #[derive(Debug, Clone, serde::Deserialize, serde::Serialize, validator::Validate, nestrs::NestDto)]
3615            #vis struct #ident {
3616                #(#field_defs,)*
3617            }
3618        }
3619        .into()
3620    }
3621}
3622
3623#[proc_macro_derive(
3624    NestDto,
3625    attributes(
3626        IsString,
3627        IsEmail,
3628        IsNotEmpty,
3629        MinLength,
3630        MaxLength,
3631        Length,
3632        Min,
3633        Max,
3634        IsInt,
3635        IsNumber,
3636        IsUrl,
3637        IsOptional,
3638        ValidateNested,
3639        Exclude,
3640        Expose,
3641        IsUUID,
3642        IsBoolean,
3643        IsPositive,
3644        IsNegative,
3645        Matches,
3646        Contains
3647    )
3648)]
3649pub fn derive_nest_dto(item: TokenStream) -> TokenStream {
3650    let input = parse_macro_input!(item as DeriveInput);
3651    let ident = input.ident;
3652
3653    let expanded = quote! {
3654        impl nestrs::NestDto for #ident {}
3655    };
3656
3657    expanded.into()
3658}
3659
3660#[proc_macro_derive(NestConfig)]
3661pub fn derive_nest_config(item: TokenStream) -> TokenStream {
3662    let input = parse_macro_input!(item as DeriveInput);
3663    let ident = input.ident;
3664
3665    let expanded = quote! {
3666        impl nestrs::NestConfig for #ident {}
3667
3668        impl nestrs::core::Injectable for #ident {
3669            fn construct(_registry: &nestrs::core::ProviderRegistry) -> std::sync::Arc<Self> {
3670                let cfg = nestrs::load_config::<Self>().unwrap_or_else(|e| {
3671                    panic!("config load failed for `{}`: {e}", std::any::type_name::<Self>())
3672                });
3673                std::sync::Arc::new(cfg)
3674            }
3675        }
3676    };
3677
3678    expanded.into()
3679}