1use crate::errors::ClobError;
2use reqwest::Client;
3use serde::Serialize;
4use serde::de::DeserializeOwned;
5use serde_json::Value;
6use std::collections::HashMap;
7
8pub const GET: &str = "GET";
9pub const POST: &str = "POST";
10pub const DELETE: &str = "DELETE";
11pub const PUT: &str = "PUT";
12
13pub type QueryParams = HashMap<String, String>;
14
15pub struct RequestOptions<B = Value> {
16 pub headers: Option<HashMap<String, String>>,
17 pub data: Option<B>,
18 pub params: Option<QueryParams>,
19}
20
21pub async fn post_typed<R, B>(
22 endpoint: &str,
23 options: Option<RequestOptions<B>>,
24) -> Result<R, ClobError>
25where
26 R: DeserializeOwned,
27 B: Serialize,
28{
29 let client = Client::new();
30 let mut req = client.post(endpoint);
31 let mut debug_headers: Option<std::collections::HashMap<String, String>> = None;
32 let mut debug_body: Option<String> = None;
33 let mut debug_params: Option<QueryParams> = None;
34 if let Some(opts) = options {
35 if let Some(h) = opts.headers {
36 if std::env::var("CLOB_DEBUG_FULL").is_ok() || std::env::var("CLOB_DEBUG_RAW").is_ok() {
38 let mut masked = std::collections::HashMap::new();
39 for (k, v) in h.iter() {
40 let key = k.to_string();
41 let val = if key.contains("PASSPHRASE") || key.contains("API_KEY") {
42 if v.len() > 6 {
43 format!("{}***", &v[..6])
44 } else {
45 "***".to_string()
46 }
47 } else if key.contains("SIGNATURE") {
48 if v.len() > 12 {
49 format!("{}...", &v[..12])
50 } else {
51 "***".to_string()
52 }
53 } else {
54 v.to_string()
55 };
56 masked.insert(key, val);
57 }
58 debug_headers = Some(masked);
59 }
60 for (k, v) in h.iter() {
61 req = req.header(k, v);
62 }
63 }
64 if let Some(body) = opts.data {
65 if (std::env::var("CLOB_DEBUG_FULL").is_ok() || std::env::var("CLOB_DEBUG_RAW").is_ok())
67 && let Ok(b) = serde_json::to_string(&body) {
68 debug_body = Some(b);
69 }
70 req = req.json(&body);
71 }
72 if let Some(params) = opts.params {
73 if std::env::var("CLOB_DEBUG_FULL").is_ok() || std::env::var("CLOB_DEBUG_RAW").is_ok() {
74 debug_params = Some(params.clone());
75 }
76 req = req.query(¶ms);
77 }
78 }
79 if std::env::var("CLOB_DEBUG_FULL").is_ok() || std::env::var("CLOB_DEBUG_RAW").is_ok() {
80 eprintln!("[HTTP DEBUG] POST {}", endpoint);
81 if let Some(h) = &debug_headers {
82 eprintln!(" headers={:?}", h);
83 }
84 if let Some(p) = &debug_params {
85 eprintln!(" params={:?}", p);
86 }
87 if let Some(b) = &debug_body {
88 let preview = if b.len() > 800 {
89 format!("{}... ({} bytes)", &b[..800], b.len())
90 } else {
91 b.clone()
92 };
93 eprintln!(" body={}", preview);
94 }
95 }
96 let resp = req
97 .send()
98 .await
99 .map_err(|e| ClobError::Other(format!("HTTP request failed: {}", e)))?;
100
101 let status = resp.status();
102
103 if !status.is_success() {
105 let body_text = resp
107 .text()
108 .await
109 .unwrap_or_else(|_| "<unable to read response body>".to_string());
110
111 eprintln!("❌ HTTP Error Response:");
112 eprintln!(" Status: {}", status);
113 eprintln!(" Endpoint: {}", endpoint);
114 eprintln!(" Response Body: {}", body_text);
115
116 return Err(ClobError::Other(format!(
117 "HTTP {} error from {}: {}",
118 status, endpoint, body_text
119 )));
120 }
121
122 let body_text = resp
124 .text()
125 .await
126 .map_err(|e| ClobError::Other(format!("Failed to read response body: {}", e)))?;
127
128 match serde_json::from_str::<R>(&body_text) {
129 Ok(val) => Ok(val),
130 Err(e) => {
131 eprintln!("❌ JSON Parse Error:");
132 eprintln!(" Endpoint: {}", endpoint);
133 eprintln!(" Error: {}", e);
134 eprintln!(" Response Body: {}", body_text);
135
136 Err(ClobError::Other(format!(
137 "Failed to parse JSON response from {}: {}. Response body: {}",
138 endpoint,
139 e,
140 if body_text.len() > 500 {
141 format!(
142 "{}... (truncated, {} bytes total)",
143 &body_text[..500],
144 body_text.len()
145 )
146 } else {
147 body_text
148 }
149 )))
150 }
151 }
152}
153
154pub async fn post(endpoint: &str, options: Option<RequestOptions>) -> Result<Value, ClobError> {
155 post_typed::<Value, Value>(endpoint, options).await
156}
157
158pub async fn get_typed<R, B>(
159 endpoint: &str,
160 options: Option<RequestOptions<B>>,
161) -> Result<R, ClobError>
162where
163 R: DeserializeOwned,
164 B: Serialize,
165{
166 let client = Client::new();
167 let mut req = client.get(endpoint);
168 let mut debug_headers: Option<std::collections::HashMap<String, String>> = None;
169 let mut debug_params: Option<QueryParams> = None;
170 if let Some(opts) = options {
171 if let Some(h) = opts.headers {
172 if std::env::var("CLOB_DEBUG_FULL").is_ok() || std::env::var("CLOB_DEBUG_RAW").is_ok() {
173 let mut masked = std::collections::HashMap::new();
174 for (k, v) in h.iter() {
175 let key = k.to_string();
176 let val = if key.contains("PASSPHRASE") || key.contains("API_KEY") {
177 if v.len() > 6 {
178 format!("{}***", &v[..6])
179 } else {
180 "***".to_string()
181 }
182 } else if key.contains("SIGNATURE") {
183 if v.len() > 12 {
184 format!("{}...", &v[..12])
185 } else {
186 "***".to_string()
187 }
188 } else {
189 v.to_string()
190 };
191 masked.insert(key, val);
192 }
193 debug_headers = Some(masked);
194 }
195 for (k, v) in h.iter() {
196 req = req.header(k, v);
197 }
198 }
199 if let Some(params) = opts.params {
200 if std::env::var("CLOB_DEBUG_FULL").is_ok() || std::env::var("CLOB_DEBUG_RAW").is_ok() {
201 debug_params = Some(params.clone());
202 }
203 req = req.query(¶ms);
204 }
205 }
206 if std::env::var("CLOB_DEBUG_FULL").is_ok() || std::env::var("CLOB_DEBUG_RAW").is_ok() {
207 eprintln!("[HTTP DEBUG] GET {}", endpoint);
208 if let Some(h) = &debug_headers {
209 eprintln!(" headers={:?}", h);
210 }
211 if let Some(p) = &debug_params {
212 eprintln!(" params={:?}", p);
213 }
214 }
215 let resp = req
216 .send()
217 .await
218 .map_err(|e| ClobError::Other(format!("HTTP request failed: {}", e)))?;
219
220 let status = resp.status();
221
222 if !status.is_success() {
224 let body_text = resp
226 .text()
227 .await
228 .unwrap_or_else(|_| "<unable to read response body>".to_string());
229
230 eprintln!("❌ HTTP Error Response:");
231 eprintln!(" Status: {}", status);
232 eprintln!(" Endpoint: {}", endpoint);
233 eprintln!(" Response Body: {}", body_text);
234
235 return Err(ClobError::Other(format!(
236 "HTTP {} error from {}: {}",
237 status, endpoint, body_text
238 )));
239 }
240
241 let body_text = resp
243 .text()
244 .await
245 .map_err(|e| ClobError::Other(format!("Failed to read response body: {}", e)))?;
246
247 match serde_json::from_str::<R>(&body_text) {
248 Ok(val) => Ok(val),
249 Err(e) => {
250 eprintln!("❌ JSON Parse Error:");
251 eprintln!(" Endpoint: {}", endpoint);
252 eprintln!(" Error: {}", e);
253 eprintln!(" Response Body: {}", body_text);
254
255 Err(ClobError::Other(format!(
256 "Failed to parse JSON response from {}: {}. Response body: {}",
257 endpoint,
258 e,
259 if body_text.len() > 500 {
260 format!(
261 "{}... (truncated, {} bytes total)",
262 &body_text[..500],
263 body_text.len()
264 )
265 } else {
266 body_text
267 }
268 )))
269 }
270 }
271}
272
273pub async fn get(endpoint: &str, options: Option<RequestOptions>) -> Result<Value, ClobError> {
274 get_typed::<Value, Value>(endpoint, options).await
275}
276
277pub async fn del_typed<R, B>(
278 endpoint: &str,
279 options: Option<RequestOptions<B>>,
280) -> Result<R, ClobError>
281where
282 R: DeserializeOwned,
283 B: Serialize,
284{
285 let client = Client::new();
286 let mut req = client.delete(endpoint);
287 let mut debug_headers: Option<std::collections::HashMap<String, String>> = None;
288 let mut debug_body: Option<String> = None;
289 let mut debug_params: Option<QueryParams> = None;
290 if let Some(opts) = options {
291 if let Some(h) = opts.headers {
292 if std::env::var("CLOB_DEBUG_FULL").is_ok() || std::env::var("CLOB_DEBUG_RAW").is_ok() {
293 let mut masked = std::collections::HashMap::new();
294 for (k, v) in h.iter() {
295 let key = k.to_string();
296 let val = if key.contains("PASSPHRASE") || key.contains("API_KEY") {
297 if v.len() > 6 {
298 format!("{}***", &v[..6])
299 } else {
300 "***".to_string()
301 }
302 } else if key.contains("SIGNATURE") {
303 if v.len() > 12 {
304 format!("{}...", &v[..12])
305 } else {
306 "***".to_string()
307 }
308 } else {
309 v.to_string()
310 };
311 masked.insert(key, val);
312 }
313 debug_headers = Some(masked);
314 }
315 for (k, v) in h.iter() {
316 req = req.header(k, v);
317 }
318 }
319 if let Some(body) = opts.data {
320 if (std::env::var("CLOB_DEBUG_FULL").is_ok() || std::env::var("CLOB_DEBUG_RAW").is_ok())
321 && let Ok(b) = serde_json::to_string(&body) {
322 debug_body = Some(b);
323 }
324 req = req.json(&body);
325 }
326 if let Some(params) = opts.params {
327 if std::env::var("CLOB_DEBUG_FULL").is_ok() || std::env::var("CLOB_DEBUG_RAW").is_ok() {
328 debug_params = Some(params.clone());
329 }
330 req = req.query(¶ms);
331 }
332 }
333 if std::env::var("CLOB_DEBUG_FULL").is_ok() || std::env::var("CLOB_DEBUG_RAW").is_ok() {
334 eprintln!("[HTTP DEBUG] DELETE {}", endpoint);
335 if let Some(h) = &debug_headers {
336 eprintln!(" headers={:?}", h);
337 }
338 if let Some(p) = &debug_params {
339 eprintln!(" params={:?}", p);
340 }
341 if let Some(b) = &debug_body {
342 let preview = if b.len() > 800 {
343 format!("{}... ({} bytes)", &b[..800], b.len())
344 } else {
345 b.clone()
346 };
347 eprintln!(" body={}", preview);
348 }
349 }
350 let resp = req
351 .send()
352 .await
353 .map_err(|e| ClobError::Other(format!("HTTP request failed: {}", e)))?;
354
355 let status = resp.status();
356
357 if !status.is_success() {
359 let body_text = resp
361 .text()
362 .await
363 .unwrap_or_else(|_| "<unable to read response body>".to_string());
364
365 eprintln!("❌ HTTP Error Response:");
366 eprintln!(" Status: {}", status);
367 eprintln!(" Endpoint: {}", endpoint);
368 eprintln!(" Response Body: {}", body_text);
369
370 return Err(ClobError::Other(format!(
371 "HTTP {} error from {}: {}",
372 status, endpoint, body_text
373 )));
374 }
375
376 let body_text = resp
378 .text()
379 .await
380 .map_err(|e| ClobError::Other(format!("Failed to read response body: {}", e)))?;
381
382 match serde_json::from_str::<R>(&body_text) {
383 Ok(val) => Ok(val),
384 Err(e) => {
385 eprintln!("❌ JSON Parse Error:");
386 eprintln!(" Endpoint: {}", endpoint);
387 eprintln!(" Error: {}", e);
388 eprintln!(" Response Body: {}", body_text);
389
390 Err(ClobError::Other(format!(
391 "Failed to parse JSON response from {}: {}. Response body: {}",
392 endpoint,
393 e,
394 if body_text.len() > 500 {
395 format!(
396 "{}... (truncated, {} bytes total)",
397 &body_text[..500],
398 body_text.len()
399 )
400 } else {
401 body_text
402 }
403 )))
404 }
405 }
406}
407
408pub async fn del(endpoint: &str, options: Option<RequestOptions>) -> Result<Value, ClobError> {
409 del_typed::<Value, Value>(endpoint, options).await
410}
411
412pub fn parse_orders_scoring_params(order_ids: Option<&Vec<String>>) -> QueryParams {
413 let mut params = QueryParams::new();
414 if let Some(ids) = order_ids {
415 params.insert("order_ids".to_string(), ids.join(","));
416 }
417 params
418}
419
420pub fn parse_drop_notification_params(ids: Option<&Vec<String>>) -> QueryParams {
421 let mut params = QueryParams::new();
422 if let Some(arr) = ids {
423 params.insert("ids".to_string(), arr.join(","));
424 }
425 params
426}