rust_actions_macros/
lib.rs1use 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}