Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions src/clob/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,16 @@ pub struct Config {
/// This is primarily useful for testing.
#[builder(into)]
geoblock_host: Option<String>,
/// 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<ReqwestClient>,
#[cfg(feature = "heartbeats")]
#[builder(default = Duration::from_secs(5))]
/// How often the [`Client`] will automatically submit heartbeats. The default is five (5) seconds.
Expand All @@ -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),
}
Expand Down Expand Up @@ -1194,7 +1205,10 @@ impl Client<Unauthenticated> {
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()?,
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Default headers built but unused with custom client

Low Severity

The headers HeaderMap is unconditionally constructed and populated with four entries (User-Agent, Accept, Connection, Content-Type), but when config.http_client is Some, the headers variable is never used. The header construction belongs inside the None arm of the match so it's only performed when actually needed.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 90fb110. Configure here.


let geoblock_host = Url::parse(
config
Expand Down Expand Up @@ -1563,7 +1577,7 @@ impl<K: Kind> Client<Authenticated<K>> {
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?;

Expand Down
34 changes: 32 additions & 2 deletions tests/clob.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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": {
Expand Down Expand Up @@ -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": {
Expand Down
Loading