empty
This commit is contained in:
parent
a0ddaf89a9
commit
10bbee30db
1 changed files with 40 additions and 113 deletions
|
|
@ -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<Self> {
|
||||
let content;
|
||||
syn::parenthesized!(content in input);
|
||||
|
||||
let mut args = Vec::new();
|
||||
while !content.is_empty() {
|
||||
let ident: syn::Ident = content.parse()?;
|
||||
content.parse::<syn::Token![:]>()?;
|
||||
let ty: syn::Type = content.parse()?;
|
||||
args.push((ident, ty));
|
||||
|
||||
if content.peek(syn::Token![,]) {
|
||||
content.parse::<syn::Token![,]>()?;
|
||||
}
|
||||
}
|
||||
|
||||
input.parse::<syn::Token![:]>()?;
|
||||
let path: LitStr = input.parse()?;
|
||||
|
||||
Ok(EndpointPath { args, path })
|
||||
}
|
||||
/// Simple argument representation
|
||||
struct Arg {
|
||||
attrs: Vec<Attribute>,
|
||||
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<Arg> = 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<Type>
|
||||
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<Output = Result<serde_json::Value, restson::Error>> + '_ {
|
||||
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<Output = Result<(), restson::Error>> {
|
||||
todo!()
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue