Skip to main content

polyoxide_data/
client.rs

1use polyoxide_core::{
2    HttpClient, HttpClientBuilder, RateLimiter, RetryConfig, DEFAULT_POOL_SIZE, DEFAULT_TIMEOUT_MS,
3};
4
5use crate::{
6    api::{
7        builders::BuildersApi,
8        health::Health,
9        holders::Holders,
10        live_volume::LiveVolumeApi,
11        open_interest::OpenInterestApi,
12        trades::Trades,
13        users::{UserApi, UserTraded},
14    },
15    error::DataApiError,
16};
17
18const DEFAULT_BASE_URL: &str = "https://data-api.polymarket.com";
19
20/// Main Data API client
21#[derive(Clone)]
22pub struct DataApi {
23    pub(crate) http_client: HttpClient,
24}
25
26impl DataApi {
27    /// Create a new Data API client with default configuration
28    pub fn new() -> Result<Self, DataApiError> {
29        Self::builder().build()
30    }
31
32    /// Create a builder for configuring the client
33    pub fn builder() -> DataApiBuilder {
34        DataApiBuilder::new()
35    }
36
37    /// Get health namespace
38    pub fn health(&self) -> Health {
39        Health {
40            http_client: self.http_client.clone(),
41        }
42    }
43
44    /// Get user namespace for user-specific operations
45    pub fn user(&self, user_address: impl Into<String>) -> UserApi {
46        UserApi {
47            http_client: self.http_client.clone(),
48            user_address: user_address.into(),
49        }
50    }
51
52    /// Alias for `user()` - for backwards compatibility
53    pub fn positions(&self, user_address: impl Into<String>) -> UserApi {
54        self.user(user_address)
55    }
56
57    /// Get traded namespace for backwards compatibility
58    pub fn traded(&self, user_address: impl Into<String>) -> Traded {
59        Traded {
60            user_api: self.user(user_address),
61        }
62    }
63
64    /// Get trades namespace
65    pub fn trades(&self) -> Trades {
66        Trades {
67            http_client: self.http_client.clone(),
68        }
69    }
70
71    /// Get holders namespace
72    pub fn holders(&self) -> Holders {
73        Holders {
74            http_client: self.http_client.clone(),
75        }
76    }
77
78    /// Get open interest namespace
79    pub fn open_interest(&self) -> OpenInterestApi {
80        OpenInterestApi {
81            http_client: self.http_client.clone(),
82        }
83    }
84
85    /// Get live volume namespace
86    pub fn live_volume(&self) -> LiveVolumeApi {
87        LiveVolumeApi {
88            http_client: self.http_client.clone(),
89        }
90    }
91
92    /// Get builders namespace
93    pub fn builders(&self) -> BuildersApi {
94        BuildersApi {
95            http_client: self.http_client.clone(),
96        }
97    }
98}
99
100/// Builder for configuring Data API client
101pub struct DataApiBuilder {
102    base_url: String,
103    timeout_ms: u64,
104    pool_size: usize,
105    retry_config: Option<RetryConfig>,
106}
107
108impl DataApiBuilder {
109    fn new() -> Self {
110        Self {
111            base_url: DEFAULT_BASE_URL.to_string(),
112            timeout_ms: DEFAULT_TIMEOUT_MS,
113            pool_size: DEFAULT_POOL_SIZE,
114            retry_config: None,
115        }
116    }
117
118    /// Set base URL for the API
119    pub fn base_url(mut self, url: impl Into<String>) -> Self {
120        self.base_url = url.into();
121        self
122    }
123
124    /// Set request timeout in milliseconds
125    pub fn timeout_ms(mut self, timeout: u64) -> Self {
126        self.timeout_ms = timeout;
127        self
128    }
129
130    /// Set connection pool size
131    pub fn pool_size(mut self, size: usize) -> Self {
132        self.pool_size = size;
133        self
134    }
135
136    /// Set retry configuration for 429 responses
137    pub fn with_retry_config(mut self, config: RetryConfig) -> Self {
138        self.retry_config = Some(config);
139        self
140    }
141
142    /// Build the Data API client
143    pub fn build(self) -> Result<DataApi, DataApiError> {
144        let mut builder = HttpClientBuilder::new(&self.base_url)
145            .timeout_ms(self.timeout_ms)
146            .pool_size(self.pool_size)
147            .with_rate_limiter(RateLimiter::data_default());
148        if let Some(config) = self.retry_config {
149            builder = builder.with_retry_config(config);
150        }
151        let http_client = builder.build()?;
152
153        Ok(DataApi { http_client })
154    }
155}
156
157impl Default for DataApiBuilder {
158    fn default() -> Self {
159        Self::new()
160    }
161}
162
163/// Wrapper for backwards compatibility with traded() API
164pub struct Traded {
165    user_api: UserApi,
166}
167
168impl Traded {
169    /// Get total markets traded by the user
170    pub async fn get(self) -> std::result::Result<UserTraded, DataApiError> {
171        self.user_api.traded().await
172    }
173}
174
175#[cfg(test)]
176mod tests {
177    use super::*;
178
179    #[test]
180    fn test_builder_default() {
181        let builder = DataApiBuilder::default();
182        assert_eq!(builder.base_url, DEFAULT_BASE_URL);
183    }
184
185    #[test]
186    fn test_builder_custom_retry_config() {
187        let config = RetryConfig {
188            max_retries: 5,
189            initial_backoff_ms: 1000,
190            max_backoff_ms: 30_000,
191        };
192        let builder = DataApiBuilder::new().with_retry_config(config);
193        let config = builder.retry_config.unwrap();
194        assert_eq!(config.max_retries, 5);
195        assert_eq!(config.initial_backoff_ms, 1000);
196    }
197
198    #[test]
199    fn test_builder_build_success() {
200        let data = DataApi::builder().build();
201        assert!(data.is_ok());
202    }
203}