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 {
209 self.request = self.request.query("limit", limit);
210 self
211 }
212
213 pub fn offset(mut self, offset: u32) -> Self {
215 self.request = self.request.query("offset", offset);
216 self
217 }
218
219 pub fn sort_by(mut self, sort_by: ClosedPositionSortBy) -> Self {
221 self.request = self.request.query("sortBy", sort_by);
222 self
223 }
224
225 pub fn sort_direction(mut self, direction: SortDirection) -> Self {
227 self.request = self.request.query("sortDirection", direction);
228 self
229 }
230
231 pub async fn send(self) -> Result<Vec<ClosedPosition>, DataApiError> {
233 self.request.send().await
234 }
235}
236
237pub struct ListUserTrades {
239 request: Request<Vec<Trade>, DataApiError>,
240}
241
242impl ListUserTrades {
243 pub fn market(mut self, condition_ids: impl IntoIterator<Item = impl ToString>) -> Self {
246 let ids: Vec<String> = condition_ids.into_iter().map(|s| s.to_string()).collect();
247 if !ids.is_empty() {
248 self.request = self.request.query("market", ids.join(","));
249 }
250 self
251 }
252
253 pub fn event_id(mut self, event_ids: impl IntoIterator<Item = impl ToString>) -> Self {
256 let ids: Vec<String> = event_ids.into_iter().map(|s| s.to_string()).collect();
257 if !ids.is_empty() {
258 self.request = self.request.query("eventId", ids.join(","));
259 }
260 self
261 }
262
263 pub fn side(mut self, side: TradeSide) -> Self {
265 self.request = self.request.query("side", side);
266 self
267 }
268
269 pub fn taker_only(mut self, taker_only: bool) -> Self {
271 self.request = self.request.query("takerOnly", taker_only);
272 self
273 }
274
275 pub fn filter_type(mut self, filter_type: TradeFilterType) -> Self {
277 self.request = self.request.query("filterType", filter_type);
278 self
279 }
280
281 pub fn filter_amount(mut self, amount: f64) -> Self {
283 self.request = self.request.query("filterAmount", amount);
284 self
285 }
286
287 pub fn limit(mut self, limit: u32) -> Self {
289 self.request = self.request.query("limit", limit);
290 self
291 }
292
293 pub fn offset(mut self, offset: u32) -> Self {
295 self.request = self.request.query("offset", offset);
296 self
297 }
298
299 pub async fn send(self) -> Result<Vec<Trade>, DataApiError> {
301 self.request.send().await
302 }
303}
304
305pub struct ListActivity {
307 request: Request<Vec<Activity>, DataApiError>,
308}
309
310impl ListActivity {
311 pub fn market(mut self, condition_ids: impl IntoIterator<Item = impl ToString>) -> Self {
313 let ids: Vec<String> = condition_ids.into_iter().map(|s| s.to_string()).collect();
314 if !ids.is_empty() {
315 self.request = self.request.query("market", ids.join(","));
316 }
317 self
318 }
319
320 pub fn event_id(mut self, event_ids: impl IntoIterator<Item = impl ToString>) -> Self {
322 let ids: Vec<String> = event_ids.into_iter().map(|s| s.to_string()).collect();
323 if !ids.is_empty() {
324 self.request = self.request.query("eventId", ids.join(","));
325 }
326 self
327 }
328
329 pub fn activity_type(mut self, types: impl IntoIterator<Item = ActivityType>) -> Self {
331 let type_strs: Vec<String> = types.into_iter().map(|t| t.to_string()).collect();
332 if !type_strs.is_empty() {
333 self.request = self.request.query("type", type_strs.join(","));
334 }
335 self
336 }
337
338 pub fn side(mut self, side: TradeSide) -> Self {
340 self.request = self.request.query("side", side);
341 self
342 }
343
344 pub fn start(mut self, timestamp: i64) -> Self {
346 self.request = self.request.query("start", timestamp);
347 self
348 }
349
350 pub fn end(mut self, timestamp: i64) -> Self {
352 self.request = self.request.query("end", timestamp);
353 self
354 }
355
356 pub fn limit(mut self, limit: u32) -> Self {
358 self.request = self.request.query("limit", limit);
359 self
360 }
361
362 pub fn offset(mut self, offset: u32) -> Self {
364 self.request = self.request.query("offset", offset);
365 self
366 }
367
368 pub fn sort_by(mut self, sort_by: ActivitySortBy) -> Self {
370 self.request = self.request.query("sortBy", sort_by);
371 self
372 }
373
374 pub fn sort_direction(mut self, direction: SortDirection) -> Self {
376 self.request = self.request.query("sortDirection", direction);
377 self
378 }
379
380 pub async fn send(self) -> Result<Vec<Activity>, DataApiError> {
382 self.request.send().await
383 }
384}
385
386#[cfg(test)]
387mod tests {
388 use super::*;
389
390 #[test]
391 fn deserialize_user_traded() {
392 let json = r#"{"user": "0xabcdef1234567890", "traded": 42}"#;
393 let ut: UserTraded = serde_json::from_str(json).unwrap();
394 assert_eq!(ut.user, "0xabcdef1234567890");
395 assert_eq!(ut.traded, 42);
396 }
397
398 #[test]
399 fn deserialize_user_traded_zero() {
400 let json = r#"{"user": "0x0000000000000000000000000000000000000001", "traded": 0}"#;
401 let ut: UserTraded = serde_json::from_str(json).unwrap();
402 assert_eq!(ut.traded, 0);
403 }
404
405 #[test]
406 fn user_traded_roundtrip() {
407 let original = UserTraded {
408 user: "0x1234".to_string(),
409 traded: 100,
410 };
411 let json = serde_json::to_string(&original).unwrap();
412 let deserialized: UserTraded = serde_json::from_str(&json).unwrap();
413 assert_eq!(deserialized.user, original.user);
414 assert_eq!(deserialized.traded, original.traded);
415 }
416}