closure like syntax v3
This commit is contained in:
parent
9a5a5fb9d2
commit
aaf9781fe8
1 changed files with 41 additions and 100 deletions
|
|
@ -1,120 +1,61 @@
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::{
|
use syn::{parse_macro_input, ItemFn, FnArg, Pat, PatType, PatIdent, Type, LitStr};
|
||||||
parse_macro_input, Attribute, FnArg, Ident, ItemFn, Pat, PatIdent, PatType, Type,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Represents a function argument we care about
|
use syn::{punctuated::Punctuated, token::Comma};
|
||||||
#[derive(Clone)]
|
|
||||||
struct Arg {
|
|
||||||
ident: Ident,
|
|
||||||
ty: Type,
|
|
||||||
attrs: Vec<Attribute>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Extract arguments marked as `#[path]` or the rest
|
fn extract_path_args(inputs: &Punctuated<syn::FnArg, Comma>) -> Vec<(syn::Ident, syn::Type)> {
|
||||||
fn extract_args<'a, I>(inputs: I, path_only: bool) -> Vec<Arg>
|
|
||||||
where
|
|
||||||
I: IntoIterator<Item = &'a FnArg>,
|
|
||||||
{
|
|
||||||
let mut args = Vec::new();
|
let mut args = Vec::new();
|
||||||
for fnarg in inputs {
|
for arg in inputs {
|
||||||
if let FnArg::Typed(PatType { pat, ty, attrs, .. }) = fnarg {
|
if let syn::FnArg::Typed(pat_type) = arg {
|
||||||
let ident = if let Pat::Ident(PatIdent { ident, .. }) = &**pat {
|
if pat_type.attrs.iter().any(|a| a.path().is_ident("path")) {
|
||||||
ident.clone()
|
if let syn::Pat::Ident(pat_ident) = &*pat_type.pat {
|
||||||
} else {
|
args.push((pat_ident.ident.clone(), (*pat_type.ty).clone()));
|
||||||
panic!("Only simple identifiers are supported in endpoint arguments");
|
}
|
||||||
};
|
|
||||||
|
|
||||||
if path_only && !attrs.iter().any(|a| a.path().is_ident("path")) {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
args.push(Arg {
|
|
||||||
ident,
|
|
||||||
ty: *ty.clone(),
|
|
||||||
attrs: attrs.clone(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
args
|
args
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Implements #[get("…")] and #[post("…")]
|
fn generate_endpoint(attr: TokenStream, item: TokenStream, method: &str) -> TokenStream {
|
||||||
macro_rules! endpoint_macro {
|
let item_fn = parse_macro_input!(item as ItemFn);
|
||||||
($method:ident) => {
|
let path_lit = parse_macro_input!(attr as LitStr);
|
||||||
#[proc_macro_attribute]
|
|
||||||
pub fn $method(attr: TokenStream, item: TokenStream) -> TokenStream {
|
|
||||||
let path_lit = parse_macro_input!(attr as syn::LitStr);
|
|
||||||
let input_fn = parse_macro_input!(item as ItemFn);
|
|
||||||
|
|
||||||
let vis = &input_fn.vis;
|
let fn_name = &item_fn.sig.ident;
|
||||||
let fn_name = &input_fn.sig.ident;
|
let vis = &item_fn.vis;
|
||||||
let generics = &input_fn.sig.generics;
|
let generics = &item_fn.sig.generics;
|
||||||
let inputs = &input_fn.sig.inputs;
|
|
||||||
let output = &input_fn.sig.output;
|
|
||||||
|
|
||||||
let path_args = extract_args(inputs, true);
|
let path_args = extract_path_args(&item_fn.sig.inputs);
|
||||||
let other_args: Vec<_> = extract_args(inputs, false)
|
let path_idents: Vec<_> = path_args.iter().map(|(id, _)| id).collect();
|
||||||
.into_iter()
|
let path_types: Vec<_> = path_args.iter().map(|(_, ty)| ty).collect();
|
||||||
.filter(|a| !path_args.iter().any(|p| p.ident == a.ident))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// for builder: path args first
|
let ret_type = match &item_fn.sig.output {
|
||||||
let path_idents: Vec<_> = path_args.iter().map(|a| &a.ident).collect();
|
syn::ReturnType::Default => quote! { () },
|
||||||
let path_types: Vec<_> = path_args.iter().map(|a| &a.ty).collect();
|
syn::ReturnType::Type(_, ty) => quote! { #ty },
|
||||||
|
};
|
||||||
|
|
||||||
let body_idents: Vec<_> = other_args.iter().map(|a| &a.ident).collect();
|
let expanded = quote! {
|
||||||
let body_types: Vec<_> = other_args.iter().map(|a| &a.ty).collect();
|
#[bon::builder]
|
||||||
|
#vis async fn #fn_name #generics(
|
||||||
|
#[builder(finish_fn)] client: restson::RestClient,
|
||||||
|
#( #[builder(finish_fn)] #path_idents: #path_types, )*
|
||||||
|
) -> Result<#ret_type, restson::Error> {
|
||||||
|
let path = format!(#path_lit, #( #path_idents = #path_idents, )* );
|
||||||
|
|
||||||
let method_upper = stringify!($method).to_uppercase();
|
todo!("Replace this with client.{} call", #method)
|
||||||
|
|
||||||
let expanded = quote! {
|
|
||||||
#[bon::builder]
|
|
||||||
#vis async fn #fn_name #generics (
|
|
||||||
#[ builder(finish_fn) ] client: restson::RestClient,
|
|
||||||
#( #[builder(finish_fn)] #path_idents: #path_types, )*
|
|
||||||
#( #body_idents: #body_types, )*
|
|
||||||
) -> Result<#output, restson::Error> {
|
|
||||||
// build path
|
|
||||||
let path = format!(#path_lit, #( #path_idents = #path_idents ),*);
|
|
||||||
|
|
||||||
#[derive(serde::Serialize)]
|
|
||||||
struct Body {
|
|
||||||
#( #body_idents: #body_idents, )*
|
|
||||||
}
|
|
||||||
|
|
||||||
let body = Body {
|
|
||||||
#( #body_idents, )*
|
|
||||||
};
|
|
||||||
|
|
||||||
// for query arguments, if any
|
|
||||||
#[derive(serde::Serialize)]
|
|
||||||
struct Query {
|
|
||||||
#( #body_idents: #body_idents, )*
|
|
||||||
}
|
|
||||||
|
|
||||||
let query = Query {
|
|
||||||
#( #body_idents, )*
|
|
||||||
};
|
|
||||||
// placeholder: convert query to vec of pairs
|
|
||||||
let query_pairs: Vec<(&str, &str)> = Vec::new();
|
|
||||||
|
|
||||||
let response = match #method_upper {
|
|
||||||
"GET" => client.get_with(path, query_pairs).await?,
|
|
||||||
"POST" => client.post_capture_with(body, query_pairs).await?,
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
todo!(response)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
expanded.into()
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
TokenStream::from(expanded)
|
||||||
}
|
}
|
||||||
|
|
||||||
endpoint_macro!(get);
|
#[proc_macro_attribute]
|
||||||
endpoint_macro!(post);
|
pub fn get(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
|
generate_endpoint(attr, item, "get_with")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[proc_macro_attribute]
|
||||||
|
pub fn post(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
|
generate_endpoint(attr, item, "post_capture_with")
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue