1use polyoxide_core::{HttpClient, QueryBuilder, Request};
2use serde::{Deserialize, Serialize};
3
4use crate::{
5 error::DataApiError,
6 types::{
7 Activity, ActivitySortBy, ActivityType, ClosedPosition, ClosedPositionSortBy, Position,
8 PositionSortBy, SortDirection, Trade, TradeFilterType, TradeSide, UserValue,
9 },
10};
11
12#[derive(Clone)]
14pub struct UserApi {
15 pub(crate) http_client: HttpClient,
16 pub(crate) user_address: String,
17}
18
19impl UserApi {
20 pub fn list_positions(&self) -> ListPositions {
22 let mut request = Request::new(self.http_client.clone(), "/positions");
23 request = request.query("user", &self.user_address);
24
25 ListPositions { request }
26 }
27
28 pub fn positions_value(&self) -> GetPositionValue {
30 let mut request = Request::new(self.http_client.clone(), "/value");
31 request = request.query("user", &self.user_address);
32
33 GetPositionValue { request }
34 }
35
36 pub fn closed_positions(&self) -> ListClosedPositions {
38 let mut request = Request::new(self.http_client.clone(), "/closed-positions");
39 request = request.query("user", &self.user_address);
40
41 ListClosedPositions { request }
42 }
43
44 pub fn trades(&self) -> ListUserTrades {
46 let mut request = Request::new(self.http_client.clone(), "/trades");
47 request = request.query("user", &self.user_address);
48
49 ListUserTrades { request }
50 }
51
52 pub fn activity(&self) -> ListActivity {
54 let mut request = Request::new(self.http_client.clone(), "/activity");
55 request = request.query("user", &self.user_address);
56
57 ListActivity { request }
58 }
59
60 pub async fn traded(&self) -> Result<UserTraded, DataApiError> {
62 Request::<UserTraded, DataApiError>::new(self.http_client.clone(), "/traded")
63 .query("user", &self.user_address)
64 .send()
65 .await
66 }
67}
68
69#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct UserTraded {
72 pub user: String,
74 pub traded: u64,
76}
77
78pub struct ListPositions {
80 request: Request<Vec<Position>, DataApiError>,
81}
82
83impl ListPositions {
84 pub fn market(mut self, condition_ids: impl IntoIterator<Item = impl ToString>) -> Self {
86 let ids: Vec<String> = condition_ids.into_iter().map(|s| s.to_string()).collect();
87 if !ids.is_empty() {
88 self.request = self.request.query("market", ids.join(","));
89 }
90 self
91 }
92
93 pub fn event_id(mut self, event_ids: impl IntoIterator<Item = impl ToString>) -> Self {
95 let ids: Vec<String> = event_ids.into_iter().map(|s| s.to_string()).collect();
96 if !ids.is_empty() {
97 self.request = self.request.query("eventId", ids.join(","));
98 }
99 self
100 }
101
102 pub fn size_threshold(mut self, threshold: f64) -> Self {
104 self.request = self.request.query("sizeThreshold", threshold);
105 self
106 }
107
108 pub fn redeemable(mut self, redeemable: bool) -> Self {
110 self.request = self.request.query("redeemable", redeemable);
111 self
112 }
113
114 pub fn mergeable(mut self, mergeable: bool) -> Self {
116 self.request = self.request.query("mergeable", mergeable);
117 self
118 }
119
120 pub fn limit(mut self, limit: u32) -> Self {
122 self.request = self.request.query("limit", limit);
123 self
124 }
125
126 pub fn offset(mut self, offset: u32) -> Self {
128 self.request = self.request.query("offset", offset);
129 self
130 }
131
132 pub fn sort_by(mut self, sort_by: PositionSortBy) -> Self {
134 self.request = self.request.query("sortBy", sort_by);
135 self
136 }
137
138 pub fn sort_direction(mut self, direction: SortDirection) -> Self {
140 self.request = self.request.query("sortDirection", direction);
141 self
142 }
143
144 pub fn title(mut self, title: impl Into<String>) -> Self {
146 self.request = self.request.query("title", title.into());
147 self
148 }
149
150 pub async fn send(self) -> Result<Vec<Position>, DataApiError> {
152 self.request.send().await
153 }
154}
155
156pub struct GetPositionValue {
158 request: Request<Vec<UserValue>, DataApiError>,
159}
160
161impl GetPositionValue {
162 pub fn market(mut self, condition_ids: impl IntoIterator<Item = impl ToString>) -> Self {
164 let ids: Vec<String> = condition_ids.into_iter().map(|s| s.to_string()).collect();
165 if !ids.is_empty() {
166 self.request = self.request.query("market", ids.join(","));
167 }
168 self
169 }
170
171 pub async fn send(self) -> Result<Vec<UserValue>, DataApiError> {
173 self.request.send().await
174 }
175}
176
177pub struct ListClosedPositions {
179 request: Request<Vec<ClosedPosition>, DataApiError>,
180}
181
182impl ListClosedPositions {
183 pub fn market(mut self, condition_ids: impl IntoIterator<Item = impl ToString>) -> Self {
185 let ids: Vec<String> = condition_ids.into_iter().map(|s| s.to_string()).collect();
186 if !ids.is_empty() {
187 self.request = self.request.query("market", ids.join(","));
188 }
189 self
190 }
191
192 pub fn event_id(mut self, event_ids: impl IntoIterator<Item = impl ToString>) -> Self {
194 let ids: Vec<String> = event_ids.into_iter().map(|s| s.to_string()).collect();
195 if !ids.is_empty() {
196 self.request = self.request.query("eventId", ids.join(","));
197 }
198 self
199 }
200
201 pub fn title(mut self, title: impl Into<String>) -> Self {
203 self.request = self.request.query("title", title.into());
204 self
205 }
206
207 pub fn limit(mut self, limit: u32) -> Self {
214 self.request = self.request.query("limit", limit);
215 self
216 }
217
218 pub fn offset(mut self, offset: u32) -> Self {
220 self.request = self.request.query("offset", offset);
221 self
222 }
223
224 pub fn sort_by(mut self, sort_by: ClosedPositionSortBy) -> Self {
226 self.request = self.request.query("sortBy", sort_by);
227 self
228 }
229
230 pub fn sort_direction(mut self, direction: SortDirection) -> Self {
232 self.request = self.request.query("sortDirection", direction);
233 self
234 }
235
236 pub async fn send(self) -> Result<Vec<ClosedPosition>, DataApiError> {
238 self.request.send().await
239 }
240}
241
242pub struct ListUserTrades {
244 request: Request<Vec<Trade>, DataApiError>,
245}
246
247impl ListUserTrades {
248 pub fn market(mut self, condition_ids: impl IntoIterator<Item = impl ToString>) -> Self {
251 let ids: Vec<String> = condition_ids.into_iter().map(|s| s.to_string()).collect();
252 if !ids.is_empty() {
253 self.request = self.request.query("market", ids.join(","));
254 }
255 self
256 }
257
258 pub fn event_id(mut self, event_ids: impl IntoIterator<Item = impl ToString>) -> Self {
261 let ids: Vec<String> = event_ids.into_iter().map(|s| s.to_string()).collect();
262 if !ids.is_empty() {
263 self.request = self.request.query("eventId", ids.join(","));
264 }
265 self
266 }
267
268 pub fn side(mut self, side: TradeSide) -> Self {
270 self.request = self.request.query("side", side);
271 self
272 }
273
274 pub fn taker_only(mut self, taker_only: bool) -> Self {
276 self.request = self.request.query("takerOnly", taker_only);
277 self
278 }
279
280 pub fn filter_type(mut self, filter_type: TradeFilterType) -> Self {
282 self.request = self.request.query("filterType", filter_type);
283 self
284 }
285
286 pub fn filter_amount(mut self, amount: f64) -> Self {
288 self.request = self.request.query("filterAmount", amount);
289 self
290 }
291
292 pub fn limit(mut self, limit: u32) -> Self {
294 self.request = self.request.query("limit", limit);
295 self
296 }
297
298 pub fn offset(mut self, offset: u32) -> Self {
300 self.request = self.request.query("offset", offset);
301 self
302 }
303
304 pub async fn send(self) -> Result<Vec<Trade>, DataApiError> {
306 self.request.send().await
307 }
308}
309
310pub struct ListActivity {
312 request: Request<Vec<Activity>, DataApiError>,
313}
314
315impl ListActivity {
316 pub fn market(mut self, condition_ids: impl IntoIterator<Item = impl ToString>) -> Self {
318 let ids: Vec<String> = condition_ids.into_iter().map(|s| s.to_string()).collect();
319 if !ids.is_empty() {
320 self.request = self.request.query("market", ids.join(","));
321 }
322 self
323 }
324
325 pub fn event_id(mut self, event_ids: impl IntoIterator<Item = impl ToString>) -> Self {
327 let ids: Vec<String> = event_ids.into_iter().map(|s| s.to_string()).collect();
328 if !ids.is_empty() {
329 self.request = self.request.query("eventId", ids.join(","));
330 }
331 self
332 }
333
334 pub fn activity_type(mut self, types: impl IntoIterator<Item = ActivityType>) -> Self {
337 let type_strs: Vec<String> = types
338 .into_iter()
339 .filter(|t| *t != ActivityType::Unknown)
340 .map(|t| t.to_string())
341 .collect();
342 if !type_strs.is_empty() {
343 self.request = self.request.query("type", type_strs.join(","));
344 }
345 self
346 }
347
348 pub fn side(mut self, side: TradeSide) -> Self {
350 self.request = self.request.query("side", side);
351 self
352 }
353
354 pub fn start(mut self, timestamp: i64) -> Self {
361 self.request = self.request.query("start", timestamp);
362 self
363 }
364
365 pub fn end(mut self, timestamp: i64) -> Self {
369 self.request = self.request.query("end", timestamp);
370 self
371 }
372
373 pub fn limit(mut self, limit: u32) -> Self {
377 self.request = self.request.query("limit", limit);
378 self
379 }
380
381 pub fn offset(mut self, offset: u32) -> Self {
388 self.request = self.request.query("offset", offset);
389 self
390 }
391
392 pub fn sort_by(mut self, sort_by: ActivitySortBy) -> Self {
394 self.request = self.request.query("sortBy", sort_by);
395 self
396 }
397
398 pub fn sort_direction(mut self, direction: SortDirection) -> Self {
400 self.request = self.request.query("sortDirection", direction);
401 self
402 }
403
404 pub async fn send(self) -> Result<Vec<Activity>, DataApiError> {
406 self.request.send().await
407 }
408}
409
410#[cfg(test)]
411mod tests {
412 use super::*;
413
414 #[test]
415 fn deserialize_user_traded() {
416 let json = r#"{"user": "0xabcdef1234567890", "traded": 42}"#;
417 let ut: UserTraded = serde_json::from_str(json).unwrap();
418 assert_eq!(ut.user, "0xabcdef1234567890");
419 assert_eq!(ut.traded, 42);
420 }
421
422 #[test]
423 fn deserialize_user_traded_zero() {
424 let json = r#"{"user": "0x0000000000000000000000000000000000000001", "traded": 0}"#;
425 let ut: UserTraded = serde_json::from_str(json).unwrap();
426 assert_eq!(ut.traded, 0);
427 }
428
429 #[test]
430 fn user_traded_roundtrip() {
431 let original = UserTraded {
432 user: "0x1234".to_string(),
433 traded: 100,
434 };
435 let json = serde_json::to_string(&original).unwrap();
436 let deserialized: UserTraded = serde_json::from_str(&json).unwrap();
437 assert_eq!(deserialized.user, original.user);
438 assert_eq!(deserialized.traded, original.traded);
439 }
440}