diff --git a/Cargo.lock b/Cargo.lock index 6e4eebe..5e270a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21,9 +21,9 @@ dependencies = [ [[package]] name = "actix-http" -version = "3.11.0" +version = "3.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44dfe5c9e0004c623edc65391dfd51daa201e7e30ebd9c9bedf873048ec32bc2" +checksum = "44cceded2fb55f3c4b67068fa64962e2ca59614edc5b03167de9ff82ae803da0" dependencies = [ "actix-codec", "actix-rt", @@ -60,9 +60,9 @@ dependencies = [ [[package]] name = "actix-rt" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eda4e2a6e042aa4e55ac438a2ae052d3b5da0ecf83d7411e1a368946925208" +checksum = "92589714878ca59a7626ea19734f0e07a6a875197eec751bb5d3f99e64998c63" dependencies = [ "futures-core", "tokio", @@ -210,9 +210,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" -version = "2.9.2" +version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a65b545ab31d687cff52899d4890855fec459eb6afe0da6417b8a18da87aa29" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" [[package]] name = "block-buffer" @@ -261,10 +261,11 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.33" +version = "1.2.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ee0f8803222ba5a7e2777dd72ca451868909b1ac410621b676adf07280e9b5f" +checksum = "5252b3d2648e5eedbc1a6f501e3c795e07025c1e93bbf8bbdd6eef7f447a6d54" dependencies = [ + "find-msvc-tools", "jobserver", "libc", "shlex", @@ -317,9 +318,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.4.0" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +checksum = "d630bccd429a5bb5a64b5e94f693bfc48c9f8566418fda4c494cc94f911f87cc" dependencies = [ "powerfmt", ] @@ -370,6 +371,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "find-msvc-tools" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fd99930f64d146689264c637b5af2f0233a933bef0d8570e2526bf9e083192d" + [[package]] name = "flate2" version = "1.1.2" @@ -394,9 +401,9 @@ checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "form_urlencoded" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ "percent-encoding", ] @@ -451,7 +458,7 @@ dependencies = [ "cfg-if", "libc", "r-efi", - "wasi 0.14.2+wasi-0.2.4", + "wasi 0.14.4+wasi-0.2.4", ] [[package]] @@ -549,9 +556,9 @@ checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" [[package]] name = "indexmap" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9" dependencies = [ "equivalent", "hashbrown", @@ -559,9 +566,9 @@ dependencies = [ [[package]] name = "io-uring" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" +checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" dependencies = [ "bitflags", "cfg-if", @@ -576,9 +583,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jobserver" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ "getrandom", "libc", @@ -625,9 +632,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.27" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] name = "memchr" @@ -707,9 +714,9 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pin-project-lite" @@ -938,12 +945,11 @@ dependencies = [ [[package]] name = "time" -version = "0.3.41" +version = "0.3.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +checksum = "83bde6f1ec10e72d583d91623c939f623002284ef622b87de38cfd546cbf2031" dependencies = [ "deranged", - "itoa", "num-conv", "powerfmt", "serde", @@ -953,15 +959,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.4" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" +checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" [[package]] name = "time-macros" -version = "0.2.22" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" dependencies = [ "num-conv", "time-core", @@ -1096,11 +1102,11 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" -version = "0.14.2+wasi-0.2.4" +version = "0.14.4+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +checksum = "88a5f4a424faf49c3c2c344f166f0662341d470ea185e939657aaff130f0ec4a" dependencies = [ - "wit-bindgen-rt", + "wit-bindgen", ] [[package]] @@ -1178,33 +1184,30 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.7.12" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" [[package]] -name = "wit-bindgen-rt" -version = "0.39.0" +name = "wit-bindgen" +version = "0.45.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags", -] +checksum = "5c573471f125075647d03df72e026074b7203790d41351cd6edc96f46bcccd36" [[package]] name = "zerocopy" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", @@ -1231,9 +1234,9 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.15+zstd.1.5.7" +version = "2.0.16+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" dependencies = [ "cc", "pkg-config", diff --git a/src/lib.rs b/src/lib.rs index b6cecf2..e1e22d6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,12 +5,12 @@ use quote::format_ident; use http_core::{Queryable, ApiDispatch, HasHttp, Keys}; #[proc_macro_derive(HttpRequest, attributes(http_response, http_error_type))] -pub fn derive_http_get_request(input: TokenStream) -> TokenStream { +pub fn derive_http_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(); - // Parse optional #[http_response = "..."] attribute + // Parse optional #[http_response = "..."] let mut response_name_opt: Option = None; for attr in &input.attrs { if attr.path().is_ident("http_response") { @@ -22,15 +22,14 @@ pub fn derive_http_get_request(input: TokenStream) -> TokenStream { } } Ok(()) - }).unwrap_or_else(|e| panic!("Error parsing http_response attribute: {}", e)); + }).unwrap(); } } - let response_name_str = response_name_opt.unwrap_or_else(|| format!("{}Resp", query_name_str)); let response_name = format_ident!("{}", response_name_str); - // Parse optional #[http_error_type = "..."] attribute (default to `E`) - let mut error_type = syn::Path::from(syn::Ident::new("E", proc_macro2::Span::call_site())); + // Parse optional #[http_error_type = "..."] (default to Box) + let mut error_type = syn::parse_str::("Box").unwrap(); for attr in &input.attrs { if attr.path().is_ident("http_error_type") { attr.parse_nested_meta(|meta| { @@ -41,11 +40,11 @@ pub fn derive_http_get_request(input: TokenStream) -> TokenStream { } } Ok(()) - }).unwrap_or_else(|e| panic!("Error parsing http_error_type attribute: {}", e)); + }).unwrap(); } } - // Collect query parameters from lnk_p_* fields + // Collect query parameters from fields prefixed with lnk_p_ 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| { @@ -58,51 +57,62 @@ pub fn derive_http_get_request(input: TokenStream) -> TokenStream { query_params.push((#key.to_string(), val.to_string())); } }) - } else { - None - } + } else { None } }).collect::>() - } else { - Vec::new() - } - } else { - Vec::new() - }; + } else { Vec::new() } + } else { Vec::new() }; let expanded = quote! { #[async_trait::async_trait] - impl Queryable for #query_name { + impl http_core::Queryable for #query_name { type R = #response_name; + type E = #error_type; async fn send( &self, - base_url: &str, + override_url: Option<&str>, + sandbox: bool, + method_override: Option<&str>, headers: Option>, - ) -> Result { + ) -> Result { use awc::Client; use urlencoding::encode; + use http_core::HasHttp; + // collect lnk_p_* query params let mut query_params: Vec<(String, String)> = Vec::new(); #(#query_param_code)* - let mut url = base_url.to_string(); + // pick URL + let mut url = if let Some(u) = override_url { + u.to_string() + } else if sandbox { + ::sandbox_url().to_string() + } else { + ::live_url().to_string() + }; + if !query_params.is_empty() { - 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))); - } + let query_string = query_params.into_iter() + .map(|(k,v)| format!("{}={}", k, encode(&v))) + .collect::>() + .join("&"); url.push('?'); url.push_str(&query_string); } + // choose method let client = Client::default(); - let mut request = client.get(url); + let mut request = match method_override.unwrap_or("GET") { + "GET" => client.get(url), + "POST" => client.post(url), + "PUT" => client.put(url), + "DELETE" => client.delete(url), + "PATCH" => client.patch(url), + m => panic!("Unsupported method override: {}", m), + }; + // add headers if let Some(hdrs) = headers { for (k, v) in hdrs { request = request.append_header((k, v)); @@ -120,7 +130,6 @@ pub fn derive_http_get_request(input: TokenStream) -> TokenStream { TokenStream::from(expanded) } - #[proc_macro_attribute] pub fn alpaca_cli(_attr: TokenStream, item: TokenStream) -> TokenStream { let input_enum = parse_macro_input!(item as ItemEnum); @@ -159,31 +168,19 @@ pub fn alpaca_cli(_attr: TokenStream, item: TokenStream) -> TokenStream { let client = Arc::new(awc::Client::default()); let keys = Arc::new(crate::load_api_keys()?); - const THREADS: usize = 4; - let total = queries.len(); - let per_thread = std::cmp::max(1, total / THREADS); - let shared_queries = Arc::new(queries); - + // Spawn all queries as async tasks let mut handles = Vec::new(); - for i in 0..THREADS { - let queries = Arc::clone(&shared_queries); + for q in queries { let client = Arc::clone(&client); let keys = Arc::clone(&keys); - let start = i * per_thread; - let end = if i == THREADS - 1 { total } else { start + per_thread }; - - let handle = std::thread::spawn(move || { - let rt = tokio::runtime::Runtime::new().unwrap(); - for q in &queries[start..end] { - rt.block_on(q.send_all(&client, &keys)).unwrap(); - } - }); - - handles.push(handle); + handles.push(tokio::spawn(async move { + q.send_all(&client, &keys).await + })); } + // Await all results and propagate first error (if any) for h in handles { - h.join().expect("Thread panicked"); + h.await??; } } other => { @@ -212,71 +209,6 @@ pub fn alpaca_cli(_attr: TokenStream, item: TokenStream) -> TokenStream { } Ok(()) } - - // Trait for dispatching API calls - pub trait ApiDispatch { - fn send_all( - &self, - client: &awc::Client, - keys: &std::collections::HashMap, - ) -> std::pin::Pin>> + Send>>; - } - }; - - TokenStream::from(expanded) -} - -#[proc_macro_attribute] -pub fn api_dispatch(attr: TokenStream, item: TokenStream) -> TokenStream { - let input = parse_macro_input!(item as syn::ItemEnum); - let enum_ident = &input.ident; - - // Parse attribute input: input = "MyQuery" - let meta_args = attr.to_string(); - let input_type: syn::Ident = { - let cleaned = meta_args.trim().replace("input", "").replace('=', "").replace('"', "").trim().to_string(); - syn::Ident::new(&cleaned, proc_macro2::Span::call_site()) - }; - - let expanded = quote! { - #input - - impl ApiDispatch for #enum_ident { - fn send_all( - &self, - client: &awc::Client, - keys: &std::collections::HashMap, - ) -> std::pin::Pin>> + Send>> { - Box::pin(async move { - match self { - #enum_ident::Single { query } => { - let parsed: #input_type = serde_json::from_str(query)?; - parsed.send(client, keys).await?; - } - #enum_ident::Bulk { input } => { - let json = if let Some(raw) = input { - if std::path::Path::new(&raw).exists() { - std::fs::read_to_string(raw)? - } else { - raw.clone() - } - } else { - use std::io::Read; - let mut buf = String::new(); - std::io::stdin().read_to_string(&mut buf)?; - buf - }; - - let items: Vec<#input_type> = serde_json::from_str(&json)?; - for item in items { - item.send(client, keys).await?; - } - } - } - Ok(()) - }) - } - } }; TokenStream::from(expanded)