v1
This commit is contained in:
parent
d972547059
commit
a03c1891c8
2 changed files with 121 additions and 0 deletions
12
macros/Cargo.toml
Normal file
12
macros/Cargo.toml
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
[package]
|
||||||
|
name = "spond-macros"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
proc-macro = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
proc-macro2 = "1.0.106"
|
||||||
|
quote = "1.0.44"
|
||||||
|
syn = { version = "2.0.117", features = ["extra-traits", "full"] }
|
||||||
109
macros/src/lib.rs
Normal file
109
macros/src/lib.rs
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
use proc_macro::TokenStream;
|
||||||
|
use quote::{quote, ToTokens};
|
||||||
|
use syn::{
|
||||||
|
parse_macro_input, Attribute, FnArg, ItemFn, Lit, Meta, Pat, PatType, Type, spanned::Spanned,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Endpoint macro
|
||||||
|
#[proc_macro_attribute]
|
||||||
|
pub fn endpoint(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
|
// parse the endpoint attribute
|
||||||
|
let attr = proc_macro2::TokenStream::from(attr);
|
||||||
|
let func = parse_macro_input!(item as ItemFn);
|
||||||
|
|
||||||
|
let vis = &func.vis;
|
||||||
|
let name = &func.sig.ident;
|
||||||
|
let inputs = &func.sig.inputs;
|
||||||
|
let output = &func.sig.output;
|
||||||
|
|
||||||
|
// must be async
|
||||||
|
if func.sig.asyncness.is_none() {
|
||||||
|
return syn::Error::new(name.span(), "endpoint function must be async")
|
||||||
|
.to_compile_error()
|
||||||
|
.into();
|
||||||
|
}
|
||||||
|
|
||||||
|
// defaults
|
||||||
|
let mut method = quote! { GET };
|
||||||
|
let mut path = None;
|
||||||
|
|
||||||
|
// parse #[endpoint(...)]
|
||||||
|
if !attr.is_empty() {
|
||||||
|
let attr_str = attr.to_string();
|
||||||
|
// simple heuristic: if contains "POST", switch
|
||||||
|
if attr_str.contains("POST") {
|
||||||
|
method = quote! { POST };
|
||||||
|
}
|
||||||
|
// simple heuristic: extract path in quotes
|
||||||
|
if let Some(start) = attr_str.find('"') {
|
||||||
|
if let Some(end) = attr_str[start+1..].find('"') {
|
||||||
|
path = Some(attr_str[start+1..start+1+end].to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = match path {
|
||||||
|
Some(p) => p,
|
||||||
|
None => return syn::Error::new(name.span(), "endpoint path must be provided")
|
||||||
|
.to_compile_error()
|
||||||
|
.into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// process arguments
|
||||||
|
let mut client_arg = None;
|
||||||
|
let mut other_args = Vec::new();
|
||||||
|
|
||||||
|
for input in inputs {
|
||||||
|
match input {
|
||||||
|
FnArg::Receiver(_) => continue, // skip self
|
||||||
|
FnArg::Typed(PatType { pat, ty, .. }) => {
|
||||||
|
if let Pat::Ident(ident) = &**pat {
|
||||||
|
if ident.ident == "client" {
|
||||||
|
client_arg = Some((ident.ident.clone(), ty));
|
||||||
|
} else {
|
||||||
|
other_args.push((ident.ident.clone(), ty));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate tokens for function with builder
|
||||||
|
let arg_defs: Vec<proc_macro2::TokenStream> = other_args.iter().map(|(id, ty)| {
|
||||||
|
// wrap Option<T> with #[builder(default)]
|
||||||
|
if let Type::Path(tp) = ty.as_ref() {
|
||||||
|
let is_option = tp.path.segments.last().map(|seg| seg.ident == "Option").unwrap_or(false);
|
||||||
|
if is_option {
|
||||||
|
quote! { #[builder(default)] #id: #ty }
|
||||||
|
} else {
|
||||||
|
quote! { #id: #ty }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote! { #id: #ty }
|
||||||
|
}
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
let client_def = match client_arg {
|
||||||
|
Some((id, ty)) => quote! { #[builder(finish_fn)] #id: #ty },
|
||||||
|
None => quote! { #[builder(finish_fn)] client: &restson::RestClient },
|
||||||
|
};
|
||||||
|
|
||||||
|
let call_args: Vec<proc_macro2::TokenStream> = other_args.iter().map(|(id, _)| {
|
||||||
|
quote! { #id }
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
let expanded = quote! {
|
||||||
|
#[bon::builder]
|
||||||
|
#vis fn #name(
|
||||||
|
#client_def,
|
||||||
|
#(#arg_defs),*
|
||||||
|
) -> impl std::future::Future<Output = Result<_, restson::Error>> + '_ {
|
||||||
|
async move {
|
||||||
|
let result = client.get::<_, serde_json::Value>(#path).await?;
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
expanded.into()
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue