Files
derive_http/src/lib.rs

194 lines
6.8 KiB
Rust
Raw Normal View History

2025-07-03 15:03:18 -04:00
use proc_macro::TokenStream;
use quote::quote;
2025-07-10 16:40:53 -04:00
use syn::{parse_macro_input, DeriveInput, Data, Fields, Lit};
2025-07-03 15:03:18 -04:00
#[proc_macro_derive(HttpRequest, attributes(http_get))]
2025-07-03 15:03:18 -04:00
pub fn derive_http_get_request(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
2025-07-10 16:40:53 -04:00
let name = &input.ident;
2025-07-03 15:03:18 -04:00
2025-07-10 16:40:53 -04:00
// Parse #[http_get(url = "...")] attribute
2025-07-10 16:34:43 -04:00
let mut base_url = None;
for attr in &input.attrs {
if attr.path().is_ident("http_get") {
2025-07-10 16:40:53 -04:00
let _ = attr.parse_nested_meta(|meta| {
if meta.path.is_ident("url") {
let value: Lit = meta.value()?.parse()?;
if let Lit::Str(litstr) = value {
base_url = Some(litstr.value());
2025-07-10 16:34:43 -04:00
}
}
2025-07-10 16:40:53 -04:00
Ok(())
});
2025-07-10 16:34:43 -04:00
}
}
let base_url = base_url.expect("Missing #[http_get(url = \"...\")] attribute");
2025-07-03 15:03:18 -04:00
let expanded = match &input.data {
Data::Struct(data_struct) => {
let fields = match &data_struct.fields {
Fields::Named(named) => &named.named,
_ => panic!("#[derive(HttpRequest)] only supports structs with named fields"),
2025-07-03 15:03:18 -04:00
};
let mut query_param_code = Vec::new();
for field in fields {
let ident = field.ident.clone().unwrap();
let field_name = ident.to_string();
if field_name.starts_with("lnk_p_") {
let key = &field_name["lnk_p_".len()..];
query_param_code.push(quote! {
query_params.push((#key.to_string(), self.#ident.to_string()));
});
}
}
quote! {
impl #name {
pub async fn send(
&self,
2025-07-10 16:34:43 -04:00
client: std::sync::Arc<awc::Client>,
2025-07-08 20:09:08 -04:00
headers: Option<Vec<(&str, &str)>>,
api_key: Option<&str>,
2025-07-03 15:03:18 -04:00
) -> Result<awc::ClientResponse, awc::error::SendRequestError> {
use urlencoding::encode;
let mut query_params: Vec<(String, String)> = Vec::new();
#(#query_param_code)*
2025-07-08 20:09:08 -04:00
if let Some(key) = api_key {
query_params.push(("api_key".to_string(), key.to_string()));
}
2025-07-10 16:34:43 -04:00
let mut url = #base_url.to_string();
2025-07-03 15:03:18 -04:00
if !query_params.is_empty() {
2025-07-10 16:34:43 -04:00
let query_parts: Vec<String> = query_params.iter()
.map(|(k, v)| format!("{}={}", k, encode(v)))
.collect();
2025-07-03 15:03:18 -04:00
url.push('?');
url.push_str(&query_parts.join("&"));
}
let mut request = client.get(url);
if let Some(hdrs) = headers {
for (k, v) in hdrs {
request = request.append_header((k, v));
}
}
let response = request.send().await?;
Ok(response)
}
}
}
}
Data::Enum(data_enum) => {
let mut variant_arms = Vec::new();
for variant in &data_enum.variants {
let vname = &variant.ident;
match &variant.fields {
Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
variant_arms.push(quote! {
2025-07-10 16:34:43 -04:00
#name::#vname(inner) => inner.send(client.clone(), headers.clone(), api_key).await,
2025-07-03 15:03:18 -04:00
});
}
_ => panic!("#[derive(HttpRequest)] enum variants must have a single unnamed field"),
2025-07-03 15:03:18 -04:00
}
}
quote! {
impl #name {
2025-07-08 20:09:08 -04:00
pub async fn send(
&self,
2025-07-10 16:34:43 -04:00
client: std::sync::Arc<awc::Client>,
2025-07-08 20:09:08 -04:00
headers: Option<Vec<(&str, &str)>>,
api_key: Option<&str>,
) -> Result<awc::ClientResponse, awc::error::SendRequestError> {
2025-07-03 15:03:18 -04:00
match self {
#(#variant_arms)*
}
}
}
}
}
_ => panic!("#[derive(HttpRequest)] only supports structs and enums"),
};
TokenStream::from(expanded)
}
#[proc_macro_derive(HttpResponse)]
pub fn derive_http_response(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
let expanded = quote! {
impl #name {
pub async fn receive(resp: awc::ClientResponse) -> Result<Self, awc::error::JsonPayloadError> {
resp.json().await
}
}
2025-07-03 15:03:18 -04:00
};
TokenStream::from(expanded)
}
2025-07-10 17:10:31 -04:00
#[proc_macro_derive(SendVec)]
pub fn derive_send_vec(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
let expanded = quote! {
impl #name {
/// Sends all items in the vec sequentially, awaiting each.
pub async fn send_vec(
items: Vec<Self>,
client: std::sync::Arc<awc::Client>,
headers: Option<Vec<(&str, &str)>>,
api_key: Option<&str>,
) -> Result<Vec<awc::ClientResponse>, awc::error::SendRequestError> {
let mut responses = Vec::with_capacity(items.len());
for item in items {
let resp = item.send(client.clone(), headers.clone(), api_key).await?;
responses.push(resp);
}
Ok(responses)
}
}
};
TokenStream::from(expanded)
}
#[proc_macro_derive(ResponseVec)]
pub fn derive_response_vec(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
let expanded = quote! {
impl #name {
/// Deserializes all responses sequentially into a Vec<Self>.
/// Assumes `Self` implements `DeserializeOwned`.
pub async fn response_vec(
responses: Vec<awc::ClientResponse>,
) -> Result<Vec<Self>, awc::error::JsonPayloadError>
where
Self: Sized + serde::de::DeserializeOwned,
{
let mut results = Vec::with_capacity(responses.len());
for resp in responses {
let item = resp.json::<Self>().await?;
results.push(item);
}
Ok(results)
}
}
};
TokenStream::from(expanded)
}