diff --git a/src/clob/client.rs b/src/clob/client.rs index 2697bf02..99aebb7e 100644 --- a/src/clob/client.rs +++ b/src/clob/client.rs @@ -368,6 +368,16 @@ pub struct Config { /// This is primarily useful for testing. #[builder(into)] geoblock_host: Option, + /// A pre-configured [`reqwest::Client`] to use for HTTP requests. When provided, + /// the supplied client is used as-is — callers should include appropriate default + /// headers (e.g. `User-Agent`, `Content-Type: application/json`) on their client. + /// + /// This is useful for latency-sensitive use cases that need control over connection + /// pooling, TCP keep-alive, HTTP/2 window sizing, TLS, proxies, or timeouts. + /// + /// If `None` (the default), the client builds a standard [`reqwest::Client`] + /// internally. + http_client: Option, #[cfg(feature = "heartbeats")] #[builder(default = Duration::from_secs(5))] /// How often the [`Client`] will automatically submit heartbeats. The default is five (5) seconds. @@ -379,6 +389,7 @@ impl Default for Config { Self { use_server_time: false, geoblock_host: None, + http_client: None, #[cfg(feature = "heartbeats")] heartbeat_interval: Duration::from_secs(5), } @@ -1194,7 +1205,10 @@ impl Client { headers.insert("Connection", HeaderValue::from_static("keep-alive")); headers.insert("Content-Type", HeaderValue::from_static("application/json")); - let client = ReqwestClient::builder().default_headers(headers).build()?; + let client = match config.http_client.clone() { + Some(custom) => custom, + None => ReqwestClient::builder().default_headers(headers).build()?, + }; let geoblock_host = Url::parse( config @@ -1563,7 +1577,7 @@ impl Client> { let request = self .client() .request(Method::DELETE, format!("{}order", self.host())) - .json(&json!({ "orderId": order_id })) + .json(&json!({ "orderID": order_id })) .build()?; let headers = self.create_headers(&request).await?; diff --git a/tests/clob.rs b/tests/clob.rs index f4bacadb..46f50837 100644 --- a/tests/clob.rs +++ b/tests/clob.rs @@ -49,6 +49,36 @@ mod unauthenticated { use super::*; + #[tokio::test] + async fn custom_http_client_should_be_used() -> anyhow::Result<()> { + let server = MockServer::start(); + + let custom = reqwest::Client::builder() + .tcp_nodelay(true) + .pool_max_idle_per_host(10) + .default_headers({ + let mut h = reqwest::header::HeaderMap::new(); + h.insert("User-Agent", "rs_clob_client".parse().unwrap()); + h.insert("Content-Type", "application/json".parse().unwrap()); + h + }) + .build()?; + + let config = Config::builder().http_client(custom).build(); + let client = Client::new(&server.base_url(), config)?; + + let mock = server.mock(|when, then| { + when.method(httpmock::Method::GET).path("/"); + then.status(StatusCode::OK).body("\"OK\""); + }); + + let response = client.ok().await?; + assert_eq!(response, "OK"); + mock.assert(); + + Ok(()) + } + #[tokio::test] async fn ok_should_succeed() -> anyhow::Result<()> { let server = MockServer::start(); @@ -1826,7 +1856,7 @@ mod authenticated { .header(POLY_ADDRESS, client.address().to_string().to_lowercase()) .header(POLY_API_KEY, API_KEY) .header(POLY_PASSPHRASE, PASSPHRASE) - .json_body(json!({ "orderId": "1" })); + .json_body(json!({ "orderID": "1" })); then.status(StatusCode::OK).json_body(json!({ "canceled": [], "notCanceled": { @@ -1862,7 +1892,7 @@ mod authenticated { .header(POLY_ADDRESS, client.address().to_string().to_lowercase()) .header(POLY_API_KEY, API_KEY) .header(POLY_PASSPHRASE, PASSPHRASE) - .json_body(json!({ "orderId": "1" })); + .json_body(json!({ "orderID": "1" })); then.status(StatusCode::OK).json_body(json!({ "canceled": [], "not_canceled": {