fixed macro and made take response type attr optionally

This commit is contained in:
2025-07-15 10:57:59 -04:00
parent 1524769694
commit 3d97489712

View File

@ -3,17 +3,37 @@ use quote::quote;
use syn::{parse_macro_input, Lit, ItemEnum, DeriveInput, Fields, Data};
use quote::format_ident;
#[proc_macro_derive(HttpRequest, attributes(http_get))]
#[proc_macro_derive(HttpRequest, attributes(http_get, http_response))]
pub fn derive_http_get_request(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let query_name = &input.ident;
let query_name_str = query_name.to_string();
// Derive response enum name by replacing "Q" suffix with "R"
let response_name_str = if query_name_str.ends_with("Q") {
query_name_str.trim_end_matches("Q").to_string() + "R"
// Parse optional #[http_response = "..."] attribute via parse_nested_meta
let mut response_name_opt: Option<String> = None;
for attr in &input.attrs {
if attr.path().is_ident("http_response") {
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("http_response") {
let lit: Lit = meta.value()?.parse()?;
if let Lit::Str(litstr) = lit {
response_name_opt = Some(litstr.value());
}
}
Ok(())
}).unwrap_or_else(|e| panic!("Error parsing http_response attribute: {}", e));
}
}
// Determine response type name
let response_name_str = if let Some(custom_resp) = response_name_opt {
custom_resp
} else if query_name_str == "Q" {
"R".to_string()
} else if query_name_str.ends_with('Q') {
format!("{}R", &query_name_str[..query_name_str.len() - 1])
} else {
panic!("HttpRequest derive macro expects the struct name to end with 'Q'");
panic!("HttpRequest derive macro expects the type name to be 'Q' or end with 'Q', or specify #[http_response = \"...\"] to override");
};
let response_name = format_ident!("{}", response_name_str);
@ -23,19 +43,19 @@ pub fn derive_http_get_request(input: TokenStream) -> TokenStream {
if attr.path().is_ident("http_get") {
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("url") {
let value: Lit = meta.value()?.parse()?;
if let Lit::Str(litstr) = value {
let lit: Lit = meta.value()?.parse()?;
if let Lit::Str(litstr) = lit {
base_url = Some(litstr.value());
}
}
Ok(())
}).unwrap();
}).unwrap_or_else(|e| panic!("Error parsing http_get attribute: {}", e));
}
}
let base_url = base_url.expect("Missing #[http_get(url = \"...\")] attribute");
let base_url_lit = syn::LitStr::new(&base_url, proc_macro2::Span::call_site());
// Collect query parameters from fields named "lnk_p_*"
// Collect query parameters from fields named "lnk_p_*" (only for structs)
let query_param_code = if let Data::Struct(data_struct) = &input.data {
if let Fields::Named(fields_named) = &data_struct.fields {
fields_named.named.iter().filter_map(|field| {
@ -57,7 +77,6 @@ pub fn derive_http_get_request(input: TokenStream) -> TokenStream {
Vec::new()
};
// Generate the impl
let expanded = quote! {
#[async_trait::async_trait]
impl Queryable for #query_name {
@ -75,10 +94,15 @@ pub fn derive_http_get_request(input: TokenStream) -> TokenStream {
let mut url = #base_url_lit.to_string();
if !query_params.is_empty() {
let query_string: String = query_params.iter()
.map(|(k, v)| format!("{}={}", k, encode(v)))
.collect::<Vec<_>>()
.join("&");
let mut query_string = String::new();
let mut first = true;
for (k, v) in &query_params {
if !first {
query_string.push('&');
}
first = false;
query_string.push_str(&format!("{}={}", k, encode(v)));
}
url.push('?');
url.push_str(&query_string);
}
@ -95,7 +119,6 @@ pub fn derive_http_get_request(input: TokenStream) -> TokenStream {
let response = request.send().await?;
let bytes = response.body().await?;
// Deserialize into associated R type
let parsed: Self::R = serde_json::from_slice(&bytes)?;
Ok(parsed)
}