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