diff --git a/src/lib.rs b/src/lib.rs index c233447..31a13f5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,144 +1,27 @@ use proc_macro::TokenStream; use quote::quote; -use syn::{parse_macro_input, ItemEnum, Lit, DeriveInput, Fields, Data}; +use syn::{parse_macro_input, ItemEnum, DeriveInput, Data}; -#[proc_macro_derive(HttpRequest, attributes(http_get))] -pub fn derive_http_get_request(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - let name = &input.ident; +#[proc_macro_attribute] +pub fn http_response(_attr: TokenStream, item: TokenStream) -> TokenStream { + let item_for_ast = item.clone(); // for parse_macro_input + let item_for_quote = item.clone(); // for syn::parse to quote - // Extract #[http_get(url = "...")] - let mut base_url_lit = None; - for attr in &input.attrs { - if attr.path().is_ident("http_get") { - 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_lit = Some(litstr); - } - } - Ok(()) - }); - } - } - - let base_url_lit = base_url_lit.expect("Missing #[http_get(url = \"...\")] attribute"); - - 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"), - }; - - let query_param_code: Vec<_> = fields.iter().filter_map(|field| { - 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()..]; - Some(quote! { - query_params.push((#key.to_string(), self.#ident.to_string())); - }) - } else { - None - } - }).collect(); + let ast: DeriveInput = parse_macro_input!(item_for_ast as DeriveInput); + let name = &ast.ident; + let impl_block = match &ast.data { + Data::Struct(_) => { quote! { - impl ::Queryable for #name + impl Responsable for #name where Self: serde::de::DeserializeOwned, { - fn send( - &self, - headers: Option>, - ) -> Result { - use urlencoding::encode; - use awc::Client; - - let mut query_params: Vec<(String, String)> = Vec::new(); - #(#query_param_code)* - - let mut url = #base_url_lit.to_string(); - if !query_params.is_empty() { - let query_parts: Vec = query_params.iter() - .map(|(k, v)| format!("{}={}", k, encode(v))) - .collect(); - url.push('?'); - url.push_str(&query_parts.join("&")); - } - - let client = Client::default(); - let mut request = client.get(url); - - if let Some(hdrs) = headers { - for (k, v) in hdrs { - request = request.append_header((k, v)); - } - } - - let parsed = actix_web::rt::System::new() - .block_on(async { - request - .send() - .await? - .json::() - .await - }) - .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?; - - Ok(Response::receive(parsed)?) - } - } - } - } - - Data::Enum(data_enum) => { - let variant_arms: Vec<_> = data_enum.variants.iter().map(|variant| { - let vname = &variant.ident; - match &variant.fields { - Fields::Unnamed(fields) if fields.unnamed.len() == 1 => { - quote! { - #name::#vname(inner) => inner.send(headers.clone()), - } - } - _ => panic!("#[derive(HttpRequest)] enum variants must have a single unnamed field"), - } - }).collect(); - - quote! { - impl ::Queryable for #name { - fn send( - &self, - headers: Option>, - ) -> Result { - 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 = match &input.data { - Data::Struct(_) => { - quote! { - impl Responsable for #name { fn receive(resp: actix_web::ClientResponse) -> Result { let parsed = actix_web::rt::System::new() - .block_on(async { resp.json::<#name>().await }) + .block_on(async { + resp.json::().await + }) .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?; Ok(parsed) } @@ -146,15 +29,15 @@ pub fn derive_http_response(input: TokenStream) -> TokenStream { } } - Data::Enum(enum_data) => { - let match_arms = enum_data.variants.iter().filter_map(|v| { - let variant_name = &v.ident; - match &v.fields { + Data::Enum(data_enum) => { + let variant_arms = data_enum.variants.iter().filter_map(|variant| { + let vname = &variant.ident; + match &variant.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)); + return Ok(#name::#vname(inner)); } }) } @@ -165,48 +48,28 @@ pub fn derive_http_response(input: TokenStream) -> TokenStream { quote! { impl Responsable for #name { fn receive(resp: actix_web::ClientResponse) -> Result { - #(#match_arms)* + #(#variant_arms)* Err(std::io::Error::new( std::io::ErrorKind::Other, - format!("No matching variant found for {}", stringify!(#name)), + concat!("No matching enum variant in ", stringify!(#name)) ).into()) } } } } - _ => panic!("#[derive(HttpResponse)] only supports structs and enums with tuple variants"), + _ => panic!("#[HttpResponse] only supports structs and tuple-style enum variants"), }; - TokenStream::from(expanded) -} + let original: syn::Item = syn::parse(item_for_quote).expect("Failed to parse item as syn::Item"); -#[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, - client: std::sync::Arc, - headers: Option>, - api_key: Option<&str>, - ) -> Result, actix_web::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) - } - } + let output = quote! { + #original + #impl_block }; - TokenStream::from(expanded) + output.into() } #[proc_macro_derive(ResponseVec)]