1use convert_case::{Case, Casing};
2use proc_macro::TokenStream;
3use syn::{parse::Parser, parse_macro_input, ItemFn};
4
5fn is_dependency_type(ty: &syn::Type) -> bool {
6 if let syn::Type::Path(type_path) = ty {
7 if let Some(segment) = type_path.path.segments.last() {
8 return segment.ident == "Dep";
9 }
10 }
11 false
12}
13
14fn get_struct_name_ident(attr: &TokenStream, input_fn: &syn::ItemFn) -> syn::Ident {
15 let fn_name_str = input_fn.sig.ident.to_string();
16
17 let default_struct_name = fn_name_str.to_case(Case::Pascal).to_string();
18
19 if let Ok(args) = syn::punctuated::Punctuated::<syn::Path, syn::Token![,]>::parse_terminated
20 .parse(attr.clone())
21 {
22 if args.is_empty() {
23 return syn::Ident::new(&default_struct_name, proc_macro2::Span::call_site());
24 }
25 if args.len() > 1 {
26 panic!("Expected exactly one for struct name #[structify(StructName)]");
27 }
28 syn::Ident::new(
29 &args[0].get_ident().unwrap().to_string(),
30 proc_macro2::Span::call_site(),
31 )
32 } else {
33 panic!("expected only a punctuated path");
34 }
35}
36
37#[proc_macro_attribute]
38pub fn structify(_attr: TokenStream, item: TokenStream) -> TokenStream {
39 let input_fn = parse_macro_input!(item as ItemFn);
40
41 let struct_name_ident = get_struct_name_ident(&_attr, &input_fn);
42
43 let mut fields = Vec::new();
44 let mut execute_bindings = Vec::new();
45 let mut new_struct_fields = Vec::new();
46 let mut execute_args = vec![quote::quote! {&self}];
47
48 let asyncness = match input_fn.sig.asyncness {
49 Some(_) => quote::quote! {async},
50 None => quote::quote! {},
51 };
52
53 for arg in input_fn.sig.inputs.iter() {
54 match arg {
55 syn::FnArg::Typed(pat_type) => {
56 let pat = pat_type.pat.clone();
57 let ty = pat_type.ty.clone();
58 if is_dependency_type(&pat_type.ty) {
59 execute_args.push(quote::quote! { #pat: impl Into<#ty> });
60 execute_bindings.push(quote::quote! {
61 let #pat = #pat.into();
62 });
63 } else {
64 fields.push(quote::quote! {
65 #pat: #ty
66 });
67 execute_bindings.push(quote::quote! {
68 let #pat = &self.#pat;
69 });
70 new_struct_fields.push(quote::quote! {
71 #pat: #pat
72 });
73 }
74 }
75 syn::FnArg::Receiver(_) => {
76 panic!("Receiver arguments are not supported");
77 }
78 }
79 }
80
81 let block = input_fn.block.clone();
82
83 let ret_ty = match &input_fn.sig.output {
84 syn::ReturnType::Default => quote::quote! { () },
85 syn::ReturnType::Type(_, ty) => quote::quote! { #ty },
86 };
87
88 quote::quote! {
89 #[allow(unused)]
90 #input_fn
91
92 pub struct #struct_name_ident {
93 #(#fields),*
94 }
95
96 impl #struct_name_ident {
97 pub fn new(#(#fields),*) -> Self {
98 Self {
99 #(#new_struct_fields),*
100 }
101 }
102 pub #asyncness fn execute(#(#execute_args),*) -> #ret_ty {
103 #(#execute_bindings)*
104 #block
105 }
106 }
107 }
108 .into()
109}