1use crate::errors::ClobError;
2use reqwest::Client;
3use serde::de::DeserializeOwned;
4use serde::Serialize;
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 if let Ok(b) = serde_json::to_string(&body) {
68 debug_body = Some(b);
69 }
70 }
71 req = req.json(&body);
72 }
73 if let Some(params) = opts.params {
74 if std::env::var("CLOB_DEBUG_FULL").is_ok() || std::env::var("CLOB_DEBUG_RAW").is_ok() {
75 debug_params = Some(params.clone());
76 }
77 req = req.query(¶ms);
78 }
79 }
80 if std::env::var("CLOB_DEBUG_FULL").is_ok() || std::env::var("CLOB_DEBUG_RAW").is_ok() {
81 eprintln!("[HTTP DEBUG] POST {}", endpoint);
82 if let Some(h) = &debug_headers {
83 eprintln!(" headers={:?}", h);
84 }
85 if let Some(p) = &debug_params {
86 eprintln!(" params={:?}", p);
87 }
88 if let Some(b) = &debug_body {
89 let preview = if b.len() > 800 {
90 format!("{}... ({} bytes)", &b[..800], b.len())
91 } else {
92 b.clone()
93 };
94 eprintln!(" body={}", preview);
95 }
96 }
97 let resp = req
98 .send()
99 .await
100 .map_err(|e| ClobError::Other(format!("HTTP request failed: {}", e)))?;
101
102 let status = resp.status();
103
104 if !status.is_success() {
106 let body_text = resp
108 .text()
109 .await
110 .unwrap_or_else(|_| "<unable to read response body>".to_string());
111
112 eprintln!("❌ HTTP Error Response:");
113 eprintln!(" Status: {}", status);
114 eprintln!(" Endpoint: {}", endpoint);
115 eprintln!(" Response Body: {}", body_text);
116
117 return Err(ClobError::Other(format!(
118 "HTTP {} error from {}: {}",
119 status, endpoint, body_text
120 )));
121 }
122
123 let body_text = resp
125 .text()
126 .await
127 .map_err(|e| ClobError::Other(format!("Failed to read response body: {}", e)))?;
128
129 match serde_json::from_str::<R>(&body_text) {
130 Ok(val) => Ok(val),
131 Err(e) => {
132 eprintln!("❌ JSON Parse Error:");
133 eprintln!(" Endpoint: {}", endpoint);
134 eprintln!(" Error: {}", e);
135 eprintln!(" Response Body: {}", body_text);
136
137 Err(ClobError::Other(format!(
138 "Failed to parse JSON response from {}: {}. Response body: {}",
139 endpoint,
140 e,
141 if body_text.len() > 500 {
142 format!(
143 "{}... (truncated, {} bytes total)",
144 &body_text[..500],
145 body_text.len()
146 )
147 } else {
148 body_text
149 }
150 )))
151 }
152 }
153}
154
155pub async fn post(endpoint: &str, options: Option<RequestOptions>) -> Result<Value, ClobError> {
156 post_typed::<Value, Value>(endpoint, options).await
157}
158
159pub async fn put_typed<R, B>(
160 endpoint: &str,
161 options: Option<RequestOptions<B>>,
162) -> Result<R, ClobError>
163where
164 R: DeserializeOwned,
165 B: Serialize,
166{
167 let client = Client::new();
168 let mut req = client.put(endpoint);
169 let mut debug_headers: Option<std::collections::HashMap<String, String>> = None;
170 let mut debug_body: Option<String> = None;
171 let mut debug_params: Option<QueryParams> = None;
172 if let Some(opts) = options {
173 if let Some(h) = opts.headers {
174 if std::env::var("CLOB_DEBUG_FULL").is_ok() || std::env::var("CLOB_DEBUG_RAW").is_ok() {
175 let mut masked = std::collections::HashMap::new();
176 for (k, v) in h.iter() {
177 let key = k.to_string();
178 let val = if key.contains("PASSPHRASE") || key.contains("API_KEY") {
179 if v.len() > 6 {
180 format!("{}***", &v[..6])
181 } else {
182 "***".to_string()
183 }
184 } else if key.contains("SIGNATURE") {
185 if v.len() > 12 {
186 format!("{}...", &v[..12])
187 } else {
188 "***".to_string()
189 }
190 } else {
191 v.to_string()
192 };
193 masked.insert(key, val);
194 }
195 debug_headers = Some(masked);
196 }
197 for (k, v) in h.iter() {
198 req = req.header(k, v);
199 }
200 }
201 if let Some(body) = opts.data {
202 if std::env::var("CLOB_DEBUG_FULL").is_ok() || std::env::var("CLOB_DEBUG_RAW").is_ok() {
203 if let Ok(b) = serde_json::to_string(&body) {
204 debug_body = Some(b);
205 }
206 }
207 req = req.json(&body);
208 }
209 if let Some(params) = opts.params {
210 if std::env::var("CLOB_DEBUG_FULL").is_ok() || std::env::var("CLOB_DEBUG_RAW").is_ok() {
211 debug_params = Some(params.clone());
212 }
213 req = req.query(¶ms);
214 }
215 }
216 if std::env::var("CLOB_DEBUG_FULL").is_ok() || std::env::var("CLOB_DEBUG_RAW").is_ok() {
217 eprintln!("[HTTP DEBUG] PUT {}", endpoint);
218 if let Some(h) = &debug_headers {
219 eprintln!(" headers={:?}", h);
220 }
221 if let Some(p) = &debug_params {
222 eprintln!(" params={:?}", p);
223 }
224 if let Some(b) = &debug_body {
225 let preview = if b.len() > 800 {
226 format!("{}... ({} bytes)", &b[..800], b.len())
227 } else {
228 b.clone()
229 };
230 eprintln!(" body={}", preview);
231 }
232 }
233 let resp = req
234 .send()
235 .await
236 .map_err(|e| ClobError::Other(format!("HTTP request failed: {}", e)))?;
237
238 let status = resp.status();
239 if !status.is_success() {
240 let body_text = resp
241 .text()
242 .await
243 .unwrap_or_else(|_| "<unable to read response body>".to_string());
244
245 eprintln!("❌ HTTP Error Response:");
246 eprintln!(" Status: {}", status);
247 eprintln!(" Endpoint: {}", endpoint);
248 eprintln!(" Response Body: {}", body_text);
249
250 return Err(ClobError::Other(format!(
251 "HTTP {} error from {}: {}",
252 status, endpoint, body_text
253 )));
254 }
255
256 let body_text = resp
257 .text()
258 .await
259 .map_err(|e| ClobError::Other(format!("Failed to read response body: {}", e)))?;
260
261 match serde_json::from_str::<R>(&body_text) {
262 Ok(val) => Ok(val),
263 Err(e) => {
264 eprintln!("❌ JSON Parse Error:");
265 eprintln!(" Endpoint: {}", endpoint);
266 eprintln!(" Error: {}", e);
267 eprintln!(" Response Body: {}", body_text);
268
269 Err(ClobError::Other(format!(
270 "Failed to parse JSON response from {}: {}. Response body: {}",
271 endpoint,
272 e,
273 if body_text.len() > 500 {
274 format!(
275 "{}... (truncated, {} bytes total)",
276 &body_text[..500],
277 body_text.len()
278 )
279 } else {
280 body_text
281 }
282 )))
283 }
284 }
285}
286
287pub async fn put(endpoint: &str, options: Option<RequestOptions>) -> Result<Value, ClobError> {
288 put_typed::<Value, Value>(endpoint, options).await
289}
290
291pub async fn get_typed<R, B>(
292 endpoint: &str,
293 options: Option<RequestOptions<B>>,
294) -> Result<R, ClobError>
295where
296 R: DeserializeOwned,
297 B: Serialize,
298{
299 let client = Client::new();
300 let mut req = client.get(endpoint);
301 let mut debug_headers: Option<std::collections::HashMap<String, String>> = None;
302 let mut debug_params: Option<QueryParams> = None;
303 if let Some(opts) = options {
304 if let Some(h) = opts.headers {
305 if std::env::var("CLOB_DEBUG_FULL").is_ok() || std::env::var("CLOB_DEBUG_RAW").is_ok() {
306 let mut masked = std::collections::HashMap::new();
307 for (k, v) in h.iter() {
308 let key = k.to_string();
309 let val = if key.contains("PASSPHRASE") || key.contains("API_KEY") {
310 if v.len() > 6 {
311 format!("{}***", &v[..6])
312 } else {
313 "***".to_string()
314 }
315 } else if key.contains("SIGNATURE") {
316 if v.len() > 12 {
317 format!("{}...", &v[..12])
318 } else {
319 "***".to_string()
320 }
321 } else {
322 v.to_string()
323 };
324 masked.insert(key, val);
325 }
326 debug_headers = Some(masked);
327 }
328 for (k, v) in h.iter() {
329 req = req.header(k, v);
330 }
331 }
332 if let Some(params) = opts.params {
333 if std::env::var("CLOB_DEBUG_FULL").is_ok() || std::env::var("CLOB_DEBUG_RAW").is_ok() {
334 debug_params = Some(params.clone());
335 }
336 req = req.query(¶ms);
337 }
338 }
339 if std::env::var("CLOB_DEBUG_FULL").is_ok() || std::env::var("CLOB_DEBUG_RAW").is_ok() {
340 eprintln!("[HTTP DEBUG] GET {}", endpoint);
341 if let Some(h) = &debug_headers {
342 eprintln!(" headers={:?}", h);
343 }
344 if let Some(p) = &debug_params {
345 eprintln!(" params={:?}", p);
346 }
347 }
348 let resp = req
349 .send()
350 .await
351 .map_err(|e| ClobError::Other(format!("HTTP request failed: {}", e)))?;
352
353 let status = resp.status();
354
355 if !status.is_success() {
357 let body_text = resp
359 .text()
360 .await
361 .unwrap_or_else(|_| "<unable to read response body>".to_string());
362
363 eprintln!("❌ HTTP Error Response:");
364 eprintln!(" Status: {}", status);
365 eprintln!(" Endpoint: {}", endpoint);
366 eprintln!(" Response Body: {}", body_text);
367
368 return Err(ClobError::Other(format!(
369 "HTTP {} error from {}: {}",
370 status, endpoint, body_text
371 )));
372 }
373
374 let body_text = resp
376 .text()
377 .await
378 .map_err(|e| ClobError::Other(format!("Failed to read response body: {}", e)))?;
379
380 match serde_json::from_str::<R>(&body_text) {
381 Ok(val) => Ok(val),
382 Err(e) => {
383 eprintln!("❌ JSON Parse Error:");
384 eprintln!(" Endpoint: {}", endpoint);
385 eprintln!(" Error: {}", e);
386 eprintln!(" Response Body: {}", body_text);
387
388 Err(ClobError::Other(format!(
389 "Failed to parse JSON response from {}: {}. Response body: {}",
390 endpoint,
391 e,
392 if body_text.len() > 500 {
393 format!(
394 "{}... (truncated, {} bytes total)",
395 &body_text[..500],
396 body_text.len()
397 )
398 } else {
399 body_text
400 }
401 )))
402 }
403 }
404}
405
406pub async fn get(endpoint: &str, options: Option<RequestOptions>) -> Result<Value, ClobError> {
407 get_typed::<Value, Value>(endpoint, options).await
408}
409
410pub async fn del_typed<R, B>(
411 endpoint: &str,
412 options: Option<RequestOptions<B>>,
413) -> Result<R, ClobError>
414where
415 R: DeserializeOwned,
416 B: Serialize,
417{
418 let client = Client::new();
419 let mut req = client.delete(endpoint);
420 let mut debug_headers: Option<std::collections::HashMap<String, String>> = None;
421 let mut debug_body: Option<String> = None;
422 let mut debug_params: Option<QueryParams> = None;
423 if let Some(opts) = options {
424 if let Some(h) = opts.headers {
425 if std::env::var("CLOB_DEBUG_FULL").is_ok() || std::env::var("CLOB_DEBUG_RAW").is_ok() {
426 let mut masked = std::collections::HashMap::new();
427 for (k, v) in h.iter() {
428 let key = k.to_string();
429 let val = if key.contains("PASSPHRASE") || key.contains("API_KEY") {
430 if v.len() > 6 {
431 format!("{}***", &v[..6])
432 } else {
433 "***".to_string()
434 }
435 } else if key.contains("SIGNATURE") {
436 if v.len() > 12 {
437 format!("{}...", &v[..12])
438 } else {
439 "***".to_string()
440 }
441 } else {
442 v.to_string()
443 };
444 masked.insert(key, val);
445 }
446 debug_headers = Some(masked);
447 }
448 for (k, v) in h.iter() {
449 req = req.header(k, v);
450 }
451 }
452 if let Some(body) = opts.data {
453 if std::env::var("CLOB_DEBUG_FULL").is_ok() || std::env::var("CLOB_DEBUG_RAW").is_ok() {
454 if let Ok(b) = serde_json::to_string(&body) {
455 debug_body = Some(b);
456 }
457 }
458 req = req.json(&body);
459 }
460 if let Some(params) = opts.params {
461 if std::env::var("CLOB_DEBUG_FULL").is_ok() || std::env::var("CLOB_DEBUG_RAW").is_ok() {
462 debug_params = Some(params.clone());
463 }
464 req = req.query(¶ms);
465 }
466 }
467 if std::env::var("CLOB_DEBUG_FULL").is_ok() || std::env::var("CLOB_DEBUG_RAW").is_ok() {
468 eprintln!("[HTTP DEBUG] DELETE {}", endpoint);
469 if let Some(h) = &debug_headers {
470 eprintln!(" headers={:?}", h);
471 }
472 if let Some(p) = &debug_params {
473 eprintln!(" params={:?}", p);
474 }
475 if let Some(b) = &debug_body {
476 let preview = if b.len() > 800 {
477 format!("{}... ({} bytes)", &b[..800], b.len())
478 } else {
479 b.clone()
480 };
481 eprintln!(" body={}", preview);
482 }
483 }
484 let resp = req
485 .send()
486 .await
487 .map_err(|e| ClobError::Other(format!("HTTP request failed: {}", e)))?;
488
489 let status = resp.status();
490
491 if !status.is_success() {
493 let body_text = resp
495 .text()
496 .await
497 .unwrap_or_else(|_| "<unable to read response body>".to_string());
498
499 eprintln!("❌ HTTP Error Response:");
500 eprintln!(" Status: {}", status);
501 eprintln!(" Endpoint: {}", endpoint);
502 eprintln!(" Response Body: {}", body_text);
503
504 return Err(ClobError::Other(format!(
505 "HTTP {} error from {}: {}",
506 status, endpoint, body_text
507 )));
508 }
509
510 let body_text = resp
512 .text()
513 .await
514 .map_err(|e| ClobError::Other(format!("Failed to read response body: {}", e)))?;
515
516 match serde_json::from_str::<R>(&body_text) {
517 Ok(val) => Ok(val),
518 Err(e) => {
519 eprintln!("❌ JSON Parse Error:");
520 eprintln!(" Endpoint: {}", endpoint);
521 eprintln!(" Error: {}", e);
522 eprintln!(" Response Body: {}", body_text);
523
524 Err(ClobError::Other(format!(
525 "Failed to parse JSON response from {}: {}. Response body: {}",
526 endpoint,
527 e,
528 if body_text.len() > 500 {
529 format!(
530 "{}... (truncated, {} bytes total)",
531 &body_text[..500],
532 body_text.len()
533 )
534 } else {
535 body_text
536 }
537 )))
538 }
539 }
540}
541
542pub async fn del(endpoint: &str, options: Option<RequestOptions>) -> Result<Value, ClobError> {
543 del_typed::<Value, Value>(endpoint, options).await
544}
545
546pub fn parse_orders_scoring_params(order_ids: Option<&Vec<String>>) -> QueryParams {
547 let mut params = QueryParams::new();
548 if let Some(ids) = order_ids {
549 params.insert("order_ids".to_string(), ids.join(","));
550 }
551 params
552}
553
554pub fn parse_drop_notification_params(ids: Option<&Vec<String>>) -> QueryParams {
555 let mut params = QueryParams::new();
556 if let Some(arr) = ids {
557 params.insert("ids".to_string(), arr.join(","));
558 }
559 params
560}