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