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 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 } 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 } 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 } 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 = µservices;
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, ®istry);
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 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 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 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 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 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 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 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 (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 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#[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 }
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 "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 "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}