rust_actions_macros/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{parse_macro_input, DeriveInput, ItemFn, FnArg, Type, LitStr};
4
5#[proc_macro_attribute]
6pub fn step(attr: TokenStream, item: TokenStream) -> TokenStream {
7    let step_name = parse_macro_input!(attr as LitStr);
8    let input = parse_macro_input!(item as ItemFn);
9
10    let fn_name = &input.sig.ident;
11
12    let mut params = input.sig.inputs.iter();
13
14    let world_type = match params.next() {
15        Some(FnArg::Typed(pat_type)) => {
16            extract_world_type(&pat_type.ty)
17        }
18        _ => {
19            return syn::Error::new_spanned(
20                &input.sig,
21                "Step function must have a world parameter as first argument"
22            ).to_compile_error().into();
23        }
24    };
25
26    let has_args = params.next().is_some();
27
28    let step_call = if has_args {
29        quote! {
30            let parsed_args = match ::rust_actions::args::FromArgs::from_args(&args) {
31                Ok(a) => a,
32                Err(e) => return Box::pin(async move { Err(e) }),
33            };
34            Box::pin(async move {
35                let result = #fn_name(world, parsed_args).await?;
36                Ok(::rust_actions::outputs::IntoOutputs::into_outputs(result))
37            })
38        }
39    } else {
40        quote! {
41            Box::pin(async move {
42                let result = #fn_name(world).await?;
43                Ok(::rust_actions::outputs::IntoOutputs::into_outputs(result))
44            })
45        }
46    };
47
48    let step_name_str = step_name.value();
49    let erased_fn_name = syn::Ident::new(
50        &format!("__erased_{}", fn_name),
51        fn_name.span()
52    );
53
54    let expanded = quote! {
55        #input
56
57        #[doc(hidden)]
58        #[allow(non_upper_case_globals)]
59        fn #erased_fn_name<'a>(
60            world_any: &'a mut dyn ::std::any::Any,
61            args: ::rust_actions::args::RawArgs,
62        ) -> ::std::pin::Pin<Box<dyn ::std::future::Future<Output = ::rust_actions::Result<::rust_actions::outputs::StepOutputs>> + Send + 'a>> {
63            let world = match world_any.downcast_mut::<#world_type>() {
64                Some(w) => w,
65                None => {
66                    let msg = format!(
67                        "World type mismatch: expected {}",
68                        ::std::any::type_name::<#world_type>()
69                    );
70                    return Box::pin(async move {
71                        Err(::rust_actions::Error::Custom(msg))
72                    });
73                }
74            };
75
76            #step_call
77        }
78
79        ::rust_actions::inventory::submit! {
80            ::rust_actions::registry::ErasedStepDef::new(
81                #step_name_str,
82                {
83                    use ::std::any::TypeId;
84                    TypeId::of::<#world_type>()
85                },
86                #erased_fn_name,
87            )
88        }
89    };
90
91    TokenStream::from(expanded)
92}
93
94fn extract_world_type(ty: &Type) -> proc_macro2::TokenStream {
95    match ty {
96        Type::Reference(type_ref) => {
97            if let Type::Path(type_path) = &*type_ref.elem {
98                let path = &type_path.path;
99                quote! { #path }
100            } else {
101                quote! { compile_error!("Expected a type path for world parameter") }
102            }
103        }
104        _ => {
105            quote! { compile_error!("World parameter must be a mutable reference") }
106        }
107    }
108}
109
110#[proc_macro_derive(World, attributes(world))]
111pub fn derive_world(input: TokenStream) -> TokenStream {
112    let input = parse_macro_input!(input as DeriveInput);
113    let name = &input.ident;
114
115    let expanded = quote! {
116        impl ::rust_actions::world::World for #name {
117            fn new() -> impl ::std::future::Future<Output = ::rust_actions::Result<Self>> + Send {
118                Self::setup()
119            }
120        }
121    };
122
123    TokenStream::from(expanded)
124}
125
126#[proc_macro_derive(Args, attributes(arg))]
127pub fn derive_args(input: TokenStream) -> TokenStream {
128    let input = parse_macro_input!(input as DeriveInput);
129    let name = &input.ident;
130
131    let expanded = quote! {
132        impl ::rust_actions::args::FromArgs for #name {
133            fn from_args(args: &::rust_actions::args::RawArgs) -> ::rust_actions::Result<Self> {
134                let value = ::rust_actions::serde_json::Value::Object(
135                    args.iter()
136                        .map(|(k, v)| (k.clone(), v.clone()))
137                        .collect()
138                );
139                ::rust_actions::serde_json::from_value(value)
140                    .map_err(|e| ::rust_actions::Error::Args(e.to_string()))
141            }
142        }
143    };
144
145    TokenStream::from(expanded)
146}
147
148#[proc_macro_derive(Outputs)]
149pub fn derive_outputs(input: TokenStream) -> TokenStream {
150    let input = parse_macro_input!(input as DeriveInput);
151    let name = &input.ident;
152
153    let expanded = quote! {
154        impl ::rust_actions::outputs::IntoOutputs for #name {
155            fn into_outputs(self) -> ::rust_actions::outputs::StepOutputs {
156                ::rust_actions::serde_json::to_value(&self)
157                    .map(|v| ::rust_actions::outputs::StepOutputs::from_value(v))
158                    .unwrap_or_default()
159            }
160        }
161    };
162
163    TokenStream::from(expanded)
164}
165
166#[proc_macro_attribute]
167pub fn before_all(_attr: TokenStream, item: TokenStream) -> TokenStream {
168    let input = parse_macro_input!(item as ItemFn);
169    TokenStream::from(quote! { #input })
170}
171
172#[proc_macro_attribute]
173pub fn after_all(_attr: TokenStream, item: TokenStream) -> TokenStream {
174    let input = parse_macro_input!(item as ItemFn);
175    TokenStream::from(quote! { #input })
176}
177
178#[proc_macro_attribute]
179pub fn before_scenario(_attr: TokenStream, item: TokenStream) -> TokenStream {
180    let input = parse_macro_input!(item as ItemFn);
181    TokenStream::from(quote! { #input })
182}
183
184#[proc_macro_attribute]
185pub fn after_scenario(_attr: TokenStream, item: TokenStream) -> TokenStream {
186    let input = parse_macro_input!(item as ItemFn);
187    TokenStream::from(quote! { #input })
188}
189
190#[proc_macro_attribute]
191pub fn before_step(_attr: TokenStream, item: TokenStream) -> TokenStream {
192    let input = parse_macro_input!(item as ItemFn);
193    TokenStream::from(quote! { #input })
194}
195
196#[proc_macro_attribute]
197pub fn after_step(_attr: TokenStream, item: TokenStream) -> TokenStream {
198    let input = parse_macro_input!(item as ItemFn);
199    TokenStream::from(quote! { #input })
200}