fixed macro and made take response type attr optionally
This commit is contained in:
53
src/lib.rs
53
src/lib.rs
@ -3,17 +3,37 @@ use quote::quote;
|
|||||||
use syn::{parse_macro_input, Lit, ItemEnum, DeriveInput, Fields, Data};
|
use syn::{parse_macro_input, Lit, ItemEnum, DeriveInput, Fields, Data};
|
||||||
use quote::format_ident;
|
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 {
|
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 query_name = &input.ident;
|
let query_name = &input.ident;
|
||||||
let query_name_str = query_name.to_string();
|
let query_name_str = query_name.to_string();
|
||||||
|
|
||||||
// Derive response enum name by replacing "Q" suffix with "R"
|
// Parse optional #[http_response = "..."] attribute via parse_nested_meta
|
||||||
let response_name_str = if query_name_str.ends_with("Q") {
|
let mut response_name_opt: Option<String> = None;
|
||||||
query_name_str.trim_end_matches("Q").to_string() + "R"
|
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 {
|
} 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);
|
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") {
|
if attr.path().is_ident("http_get") {
|
||||||
attr.parse_nested_meta(|meta| {
|
attr.parse_nested_meta(|meta| {
|
||||||
if meta.path.is_ident("url") {
|
if meta.path.is_ident("url") {
|
||||||
let value: Lit = meta.value()?.parse()?;
|
let lit: Lit = meta.value()?.parse()?;
|
||||||
if let Lit::Str(litstr) = value {
|
if let Lit::Str(litstr) = lit {
|
||||||
base_url = Some(litstr.value());
|
base_url = Some(litstr.value());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
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 = base_url.expect("Missing #[http_get(url = \"...\")] attribute");
|
||||||
let base_url_lit = syn::LitStr::new(&base_url, proc_macro2::Span::call_site());
|
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 {
|
let query_param_code = if let Data::Struct(data_struct) = &input.data {
|
||||||
if let Fields::Named(fields_named) = &data_struct.fields {
|
if let Fields::Named(fields_named) = &data_struct.fields {
|
||||||
fields_named.named.iter().filter_map(|field| {
|
fields_named.named.iter().filter_map(|field| {
|
||||||
@ -57,7 +77,6 @@ pub fn derive_http_get_request(input: TokenStream) -> TokenStream {
|
|||||||
Vec::new()
|
Vec::new()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Generate the impl
|
|
||||||
let expanded = quote! {
|
let expanded = quote! {
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl Queryable for #query_name {
|
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();
|
let mut url = #base_url_lit.to_string();
|
||||||
if !query_params.is_empty() {
|
if !query_params.is_empty() {
|
||||||
let query_string: String = query_params.iter()
|
let mut query_string = String::new();
|
||||||
.map(|(k, v)| format!("{}={}", k, encode(v)))
|
let mut first = true;
|
||||||
.collect::<Vec<_>>()
|
for (k, v) in &query_params {
|
||||||
.join("&");
|
if !first {
|
||||||
|
query_string.push('&');
|
||||||
|
}
|
||||||
|
first = false;
|
||||||
|
query_string.push_str(&format!("{}={}", k, encode(v)));
|
||||||
|
}
|
||||||
url.push('?');
|
url.push('?');
|
||||||
url.push_str(&query_string);
|
url.push_str(&query_string);
|
||||||
}
|
}
|
||||||
@ -95,7 +119,6 @@ pub fn derive_http_get_request(input: TokenStream) -> TokenStream {
|
|||||||
let response = request.send().await?;
|
let response = request.send().await?;
|
||||||
let bytes = response.body().await?;
|
let bytes = response.body().await?;
|
||||||
|
|
||||||
// Deserialize into associated R type
|
|
||||||
let parsed: Self::R = serde_json::from_slice(&bytes)?;
|
let parsed: Self::R = serde_json::from_slice(&bytes)?;
|
||||||
Ok(parsed)
|
Ok(parsed)
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user