update, fixed all warnings, proc macros both work with enums now

This commit is contained in:
2025-07-14 19:51:49 -04:00
parent d82f663c27
commit 259c5562e4

View File

@ -1,4 +1,3 @@
use async_trait::async_trait;
use proc_macro::TokenStream; use proc_macro::TokenStream;
use quote::quote; use quote::quote;
use syn::{parse_macro_input, ItemEnum, Lit, DeriveInput, Fields, Data}; use syn::{parse_macro_input, ItemEnum, Lit, DeriveInput, Fields, Data};
@ -8,22 +7,23 @@ pub fn derive_http_get_request(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput); let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident; let name = &input.ident;
// Extract the base URL from #[http_get(url = "...")] // Extract base_url from #[http_get(url = "...")]
let mut base_url = None; let mut base_url_lit = None;
for attr in &input.attrs { for attr in &input.attrs {
if attr.path().is_ident("http_get") { if attr.path().is_ident("http_get") {
let _ = attr.parse_nested_meta(|meta| { let _ = attr.parse_nested_meta(|meta| {
if meta.path.is_ident("url") { if meta.path.is_ident("url") {
let value: Lit = meta.value()?.parse()?; let value: Lit = meta.value()?.parse()?;
if let Lit::Str(litstr) = value { if let Lit::Str(litstr) = value {
base_url = Some(litstr.value()); base_url_lit = Some(litstr);
} }
} }
Ok(()) Ok(())
}); });
} }
} }
let base_url = base_url.expect("Missing #[http_get(url = \"...\")] attribute");
let base_url_lit = base_url_lit.expect("Missing #[http_get(url = \"...\")] attribute");
let expanded = match &input.data { let expanded = match &input.data {
Data::Struct(data_struct) => { Data::Struct(data_struct) => {
@ -32,23 +32,23 @@ pub fn derive_http_get_request(input: TokenStream) -> TokenStream {
_ => panic!("#[derive(HttpRequest)] only supports structs with named fields"), _ => panic!("#[derive(HttpRequest)] only supports structs with named fields"),
}; };
let mut query_param_code = Vec::new(); let query_param_code: Vec<_> = fields.iter().filter_map(|field| {
for field in fields {
let ident = field.ident.clone().unwrap(); let ident = field.ident.clone().unwrap();
let field_name = ident.to_string(); let field_name = ident.to_string();
if field_name.starts_with("lnk_p_") { if field_name.starts_with("lnk_p_") {
let key = &field_name["lnk_p_".len()..]; let key = &field_name["lnk_p_".len()..];
query_param_code.push(quote! { Some(quote! {
query_params.push((#key.to_string(), self.#ident.to_string())); query_params.push((#key.to_string(), self.#ident.to_string()));
}); })
} } else {
None
} }
}).collect();
quote! { quote! {
impl Queryable for #name { impl Queryable for #name {
fn send( fn send(
&self, &self,
base_url: &str,
headers: Option<Vec<(&str, &str)>>, headers: Option<Vec<(&str, &str)>>,
) -> Result<Response, std::io::Error> { ) -> Result<Response, std::io::Error> {
use urlencoding::encode; use urlencoding::encode;
@ -57,7 +57,7 @@ pub fn derive_http_get_request(input: TokenStream) -> TokenStream {
let mut query_params: Vec<(String, String)> = Vec::new(); let mut query_params: Vec<(String, String)> = Vec::new();
#(#query_param_code)* #(#query_param_code)*
let mut url = base_url.to_string(); let mut url = #base_url_lit.to_string();
if !query_params.is_empty() { if !query_params.is_empty() {
let query_parts: Vec<String> = query_params.iter() let query_parts: Vec<String> = query_params.iter()
.map(|(k, v)| format!("{}={}", k, encode(v))) .map(|(k, v)| format!("{}={}", k, encode(v)))
@ -93,24 +93,22 @@ pub fn derive_http_get_request(input: TokenStream) -> TokenStream {
} }
Data::Enum(data_enum) => { Data::Enum(data_enum) => {
let mut variant_arms = Vec::new(); let variant_arms: Vec<_> = data_enum.variants.iter().map(|variant| {
for variant in &data_enum.variants {
let vname = &variant.ident; let vname = &variant.ident;
match &variant.fields { match &variant.fields {
Fields::Unnamed(fields) if fields.unnamed.len() == 1 => { Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
variant_arms.push(quote! { quote! {
#name::#vname(inner) => inner.send(base_url, headers.clone()), #name::#vname(inner) => inner.send(headers.clone()),
}); }
} }
_ => panic!("#[derive(HttpRequest)] enum variants must have a single unnamed field"), _ => panic!("#[derive(HttpRequest)] enum variants must have a single unnamed field"),
} }
} }).collect();
quote! { quote! {
impl Queryable for #name { impl Queryable for #name {
fn send( fn send(
&self, &self,
base_url: &str,
headers: Option<Vec<(&str, &str)>>, headers: Option<Vec<(&str, &str)>>,
) -> Result<Response, std::io::Error> { ) -> Result<Response, std::io::Error> {
match self { match self {
@ -146,7 +144,37 @@ pub fn derive_http_response(input: TokenStream) -> TokenStream {
} }
} }
_ => panic!("#[derive(HttpResponse)] only supports structs"), Data::Enum(enum_data) => {
let match_arms = enum_data.variants.iter().filter_map(|v| {
let variant_name = &v.ident;
match &v.fields {
syn::Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
let inner_ty = &fields.unnamed.first().unwrap().ty;
Some(quote! {
if let Ok(inner) = <#inner_ty as Responsable>::receive(resp) {
return Ok(#name::#variant_name(inner));
}
})
}
_ => None
}
});
quote! {
impl Responsable for #name {
fn receive(resp: actix_web::ClientResponse) -> Result<Self, std::error::Error> {
#(#match_arms)*
Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!("No matching variant found for {}", stringify!(#name)),
).into())
}
}
}
}
_ => panic!("#[derive(HttpResponse)] only supports structs and enums with tuple variants"),
}; };
TokenStream::from(expanded) TokenStream::from(expanded)