1use proc_macro::TokenStream;
2use proc_macro2::{Span, TokenStream as TokenStream2};
3use quote::quote;
4use syn::{Ident, ItemFn};
5
6#[proc_macro_attribute]
21pub fn transaction(attr: TokenStream, item: TokenStream) -> TokenStream {
22 transaction_internal(attr, item).into()
23}
24
25fn transaction_internal(_attr: TokenStream, item: TokenStream) -> TokenStream2 {
26 let input = syn::parse::<ItemFn>(item).unwrap();
27
28 let ItemFn {
29 attrs,
30 vis,
31 sig,
32 block,
33 } = input;
34 let stmts = &block.stmts;
35
36 let ident = &sig.ident;
37 quote! {
38 #(#attrs)* #vis #sig {
39 ::balter::transaction::transaction_hook(::balter::core::generate_labels!(#ident), async move {
40 #(#stmts)*
41 }).await
42 }
43 }
44}
45
46#[proc_macro_attribute]
62pub fn scenario(attr: TokenStream, item: TokenStream) -> TokenStream {
63 scenario_internal(attr, item, false).into()
64}
65
66#[proc_macro_attribute]
82pub fn scenario_linkme(attr: TokenStream, item: TokenStream) -> TokenStream {
83 scenario_internal(attr, item, true).into()
84}
85
86fn scenario_internal(_attr: TokenStream, item: TokenStream, linkme: bool) -> TokenStream2 {
87 let input = syn::parse::<ItemFn>(item).expect("Macro only works on fn() items");
88
89 let ItemFn {
90 attrs,
91 vis,
92 sig,
93 block,
94 } = input;
95 let stmts = &block.stmts;
96
97 let new_name = Ident::new(&format!("__balter_{}", sig.ident), Span::call_site());
98 let mut new_sig = sig.clone();
99 new_sig.ident = new_name.clone();
100
101 let mut scen_sig = sig.clone();
102 let scen_name = sig.ident.clone();
103 scen_sig.asyncness = None;
104 scen_sig.output = syn::parse(
105 quote! {
106 -> impl ::balter::scenario::ConfigurableScenario<::balter::prelude::RunStatistics>
107 }
108 .into(),
109 )
110 .expect("Scenario signature is invalid");
111
112 let res = quote! {
113 #(#attrs)* #vis #scen_sig {
114 ::balter::scenario::Scenario::new(stringify!(#scen_name), #new_name)
115 }
116
117 #(#attrs)* #vis #new_sig {
118 #(#stmts)*
119 }
120 };
121
122 if linkme {
123 let mut linkme_sig = sig.clone();
124 let linkme_name = Ident::new(&format!("__balter_distr_{}", sig.ident), Span::call_site());
125 linkme_sig.ident = linkme_name.clone();
126 linkme_sig.asyncness = None;
127 linkme_sig.output = syn::parse(
128 quote! {
129 -> ::core::pin::Pin<Box<dyn ::balter::prelude::DistributedScenario<Output=::balter::prelude::RunStatistics>>>
130 }
131 .into(),
132 )
133 .expect("Scenario signature is invalid");
134
135 let static_name = Ident::new(
136 &format!("__BALTER_{}", sig.ident.to_string().to_ascii_uppercase()),
137 Span::call_site(),
138 );
139
140 let mut linkme = quote! {
141 #[::balter::runtime::distributed_slice(::balter::runtime::BALTER_SCENARIOS)]
142 static #static_name: (&'static str, fn() -> ::core::pin::Pin<Box<dyn ::balter::prelude::DistributedScenario<Output=::balter::prelude::RunStatistics>>>) = (stringify!(#scen_name), #linkme_name);
143
144 #(#attrs)* #vis #linkme_sig {
146 Box::pin(::balter::scenario::Scenario::new(stringify!(#scen_name), #new_name))
147 }
148 };
149
150 linkme.extend(res);
151 linkme
152 } else {
153 res
154 }
155}