diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 93fe0cb..841eba8 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -1,130 +1,57 @@ +// macros/src/lib.rs use proc_macro::TokenStream; use quote::quote; -use syn::{ - parse_macro_input, FnArg, ItemFn, LitStr, Pat, PatType, Type, Attribute, spanned::Spanned, -}; +use syn::{parse_macro_input, ItemFn, FnArg, Pat, Type, Ident, Attribute}; -/// Path args parser for `#[endpoint((x:u128,y:String): "/path/{x}/{y}")]` -struct EndpointPath { - args: Vec<(syn::Ident, syn::Type)>, - path: LitStr, -} - -impl syn::parse::Parse for EndpointPath { - fn parse(input: syn::parse::ParseStream) -> syn::Result { - let content; - syn::parenthesized!(content in input); - - let mut args = Vec::new(); - while !content.is_empty() { - let ident: syn::Ident = content.parse()?; - content.parse::()?; - let ty: syn::Type = content.parse()?; - args.push((ident, ty)); - - if content.peek(syn::Token![,]) { - content.parse::()?; - } - } - - input.parse::()?; - let path: LitStr = input.parse()?; - - Ok(EndpointPath { args, path }) - } +/// Simple argument representation +struct Arg { + attrs: Vec, + ident: Ident, + ty: Type, } #[proc_macro_attribute] -pub fn endpoint(attr: TokenStream, item: TokenStream) -> TokenStream { - let path_args = parse_macro_input!(attr as EndpointPath); - let func = parse_macro_input!(item as ItemFn); +pub fn builder(_attr: TokenStream, item: TokenStream) -> TokenStream { + // Parse the input function + let input = parse_macro_input!(item as ItemFn); - let vis = &func.vis; - let name = &func.sig.ident; - let output = &func.sig.output; + let vis = &input.vis; + let name = &input.sig.ident; + let generics = &input.sig.generics; - // Separate query/body args - let mut query_fields = Vec::new(); - let mut body_fields = Vec::new(); + // Collect arguments + let mut body_args: Vec = Vec::new(); - for arg in &func.sig.inputs { - if let FnArg::Typed(PatType { pat, ty, attrs, .. }) = arg { - let pat_ident = match &**pat { - Pat::Ident(pi) => &pi.ident, - _ => continue, - }; - - if attrs.iter().any(|a| a.path().is_ident("query")) { - query_fields.push((pat_ident.clone(), (*ty).clone(), attrs.clone())); - } else if attrs.iter().any(|a| a.path().is_ident("body")) { - body_fields.push((pat_ident.clone(), (*ty).clone(), attrs.clone())); + for arg in &input.sig.inputs { + if let FnArg::Typed(pat_type) = arg { + if let Pat::Ident(pat_ident) = &*pat_type.pat { + body_args.push(Arg { + attrs: pat_type.attrs.clone(), + ident: pat_ident.ident.clone(), + ty: (*pat_type.ty).clone(), + }); + } else { + panic!("Only simple identifier patterns are supported"); } + } else { + panic!("&self / &mut self not supported"); } } - // Path args - let path_idents: Vec<_> = path_args.args.iter().map(|(i, _)| i).collect(); - let path_types: Vec<_> = path_args.args.iter().map(|(_, t)| t).collect(); - let path_fmt = &path_args.path; - - // Build query serialization - let query_pairs = query_fields.iter().map(|(ident, _, _)| { - quote! { - if let Some(v) = &#ident { - query_pairs.push((stringify!(#ident), v.to_string())); - } - } - }); - - // Build body serialization - let body_pairs = body_fields.iter().map(|(ident, _, _)| { - quote! { - body_map.insert(stringify!(#ident).to_string(), serde_json::to_value(&#ident)?); - } - }); - - // Determine method - let method = if body_fields.is_empty() { quote! { GET } } else { quote! { POST } }; - - // Expand query/body fields for function signature - let query_sig = query_fields.iter().map(|(ident, ty, _attrs)| { - if let Type::Path(tp) = &**ty { // <-- dereference the Box - if tp.path.segments.last().unwrap().ident == "Option" { - quote! { #[builder(default)] #ident: #ty } - } else { - quote! { #ident: #ty } - } - } else { - quote! { #ident: #ty } - } - }); - - let body_sig = body_fields.iter().map(|(ident, ty, _attrs)| { - quote! { #ident: #ty } - }); + // Destructure the arguments for quote repetitions + let arg_idents: Vec<_> = body_args.iter().map(|a| &a.ident).collect(); + let arg_tys: Vec<_> = body_args.iter().map(|a| &a.ty).collect(); + let arg_attrs: Vec<_> = body_args.iter().map(|a| &a.attrs).collect(); + // Generate the builder function let expanded = quote! { #[bon::builder] - #vis fn #name( - #[builder(finish_fn)] - client: &restson::RestClient, - #( #[builder(finish_fn)] #path_idents: #path_types, )* - #( #query_sig, )* - #( #body_sig, )* - ) -> impl std::future::Future> + '_ { - let mut path = format!(#path_fmt, #( #path_idents = #path_idents ),*); - let mut query_pairs = Vec::new(); - #( #query_pairs )* - let mut body_map = serde_json::Map::new(); - #( #body_pairs )* - - async move { - if body_map.is_empty() { - client.request_with(#method, &path, &query_pairs, &()).await - } else { - client.request_with(#method, &path, &query_pairs, &body_map).await - } - } + #vis fn #name #generics ( + #( + #( #arg_attrs )* #arg_idents : #arg_tys + ),* + ) -> impl std::future::Future> { + todo!() } };