use polyoxide_core::{HttpClient, QueryBuilder, Request};
use serde::{Deserialize, Serialize};
use crate::{
error::DataApiError,
types::{
Activity, ActivitySortBy, ActivityType, ClosedPosition, ClosedPositionSortBy, Position,
PositionSortBy, SortDirection, Trade, TradeFilterType, TradeSide, UserValue,
},
};
#[derive(Clone)]
pub struct UserApi {
pub(crate) http_client: HttpClient,
pub(crate) user_address: String,
}
impl UserApi {
pub fn list_positions(&self) -> ListPositions {
let mut request = Request::new(self.http_client.clone(), "/positions");
request = request.query("user", &self.user_address);
ListPositions { request }
}
pub fn positions_value(&self) -> GetPositionValue {
let mut request = Request::new(self.http_client.clone(), "/value");
request = request.query("user", &self.user_address);
GetPositionValue { request }
}
pub fn closed_positions(&self) -> ListClosedPositions {
let mut request = Request::new(self.http_client.clone(), "/closed-positions");
request = request.query("user", &self.user_address);
ListClosedPositions { request }
}
pub fn trades(&self) -> ListUserTrades {
let mut request = Request::new(self.http_client.clone(), "/trades");
request = request.query("user", &self.user_address);
ListUserTrades { request }
}
pub fn activity(&self) -> ListActivity {
let mut request = Request::new(self.http_client.clone(), "/activity");
request = request.query("user", &self.user_address);
ListActivity { request }
}
pub async fn traded(&self) -> Result<UserTraded, DataApiError> {
Request::<UserTraded, DataApiError>::new(self.http_client.clone(), "/traded")
.query("user", &self.user_address)
.send()
.await
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserTraded {
pub user: String,
pub traded: u64,
}
pub struct ListPositions {
request: Request<Vec<Position>, DataApiError>,
}
impl ListPositions {
pub fn market(mut self, condition_ids: impl IntoIterator<Item = impl ToString>) -> Self {
let ids: Vec<String> = condition_ids.into_iter().map(|s| s.to_string()).collect();
if !ids.is_empty() {
self.request = self.request.query("market", ids.join(","));
}
self
}
pub fn event_id(mut self, event_ids: impl IntoIterator<Item = impl ToString>) -> Self {
let ids: Vec<String> = event_ids.into_iter().map(|s| s.to_string()).collect();
if !ids.is_empty() {
self.request = self.request.query("eventId", ids.join(","));
}
self
}
pub fn size_threshold(mut self, threshold: f64) -> Self {
self.request = self.request.query("sizeThreshold", threshold);
self
}
pub fn redeemable(mut self, redeemable: bool) -> Self {
self.request = self.request.query("redeemable", redeemable);
self
}
pub fn mergeable(mut self, mergeable: bool) -> Self {
self.request = self.request.query("mergeable", mergeable);
self
}
pub fn limit(mut self, limit: u32) -> Self {
self.request = self.request.query("limit", limit);
self
}
pub fn offset(mut self, offset: u32) -> Self {
self.request = self.request.query("offset", offset);
self
}
pub fn sort_by(mut self, sort_by: PositionSortBy) -> Self {
self.request = self.request.query("sortBy", sort_by);
self
}
pub fn sort_direction(mut self, direction: SortDirection) -> Self {
self.request = self.request.query("sortDirection", direction);
self
}
pub fn title(mut self, title: impl Into<String>) -> Self {
self.request = self.request.query("title", title.into());
self
}
pub async fn send(self) -> Result<Vec<Position>, DataApiError> {
self.request.send().await
}
}
pub struct GetPositionValue {
request: Request<Vec<UserValue>, DataApiError>,
}
impl GetPositionValue {
pub fn market(mut self, condition_ids: impl IntoIterator<Item = impl ToString>) -> Self {
let ids: Vec<String> = condition_ids.into_iter().map(|s| s.to_string()).collect();
if !ids.is_empty() {
self.request = self.request.query("market", ids.join(","));
}
self
}
pub async fn send(self) -> Result<Vec<UserValue>, DataApiError> {
self.request.send().await
}
}
pub struct ListClosedPositions {
request: Request<Vec<ClosedPosition>, DataApiError>,
}
impl ListClosedPositions {
pub fn market(mut self, condition_ids: impl IntoIterator<Item = impl ToString>) -> Self {
let ids: Vec<String> = condition_ids.into_iter().map(|s| s.to_string()).collect();
if !ids.is_empty() {
self.request = self.request.query("market", ids.join(","));
}
self
}
pub fn event_id(mut self, event_ids: impl IntoIterator<Item = impl ToString>) -> Self {
let ids: Vec<String> = event_ids.into_iter().map(|s| s.to_string()).collect();
if !ids.is_empty() {
self.request = self.request.query("eventId", ids.join(","));
}
self
}
pub fn title(mut self, title: impl Into<String>) -> Self {
self.request = self.request.query("title", title.into());
self
}
pub fn limit(mut self, limit: u32) -> Self {
self.request = self.request.query("limit", limit);
self
}
pub fn offset(mut self, offset: u32) -> Self {
self.request = self.request.query("offset", offset);
self
}
pub fn sort_by(mut self, sort_by: ClosedPositionSortBy) -> Self {
self.request = self.request.query("sortBy", sort_by);
self
}
pub fn sort_direction(mut self, direction: SortDirection) -> Self {
self.request = self.request.query("sortDirection", direction);
self
}
pub async fn send(self) -> Result<Vec<ClosedPosition>, DataApiError> {
self.request.send().await
}
}
pub struct ListUserTrades {
request: Request<Vec<Trade>, DataApiError>,
}
impl ListUserTrades {
pub fn market(mut self, condition_ids: impl IntoIterator<Item = impl ToString>) -> Self {
let ids: Vec<String> = condition_ids.into_iter().map(|s| s.to_string()).collect();
if !ids.is_empty() {
self.request = self.request.query("market", ids.join(","));
}
self
}
pub fn event_id(mut self, event_ids: impl IntoIterator<Item = impl ToString>) -> Self {
let ids: Vec<String> = event_ids.into_iter().map(|s| s.to_string()).collect();
if !ids.is_empty() {
self.request = self.request.query("eventId", ids.join(","));
}
self
}
pub fn side(mut self, side: TradeSide) -> Self {
self.request = self.request.query("side", side);
self
}
pub fn taker_only(mut self, taker_only: bool) -> Self {
self.request = self.request.query("takerOnly", taker_only);
self
}
pub fn filter_type(mut self, filter_type: TradeFilterType) -> Self {
self.request = self.request.query("filterType", filter_type);
self
}
pub fn filter_amount(mut self, amount: f64) -> Self {
self.request = self.request.query("filterAmount", amount);
self
}
pub fn limit(mut self, limit: u32) -> Self {
self.request = self.request.query("limit", limit);
self
}
pub fn offset(mut self, offset: u32) -> Self {
self.request = self.request.query("offset", offset);
self
}
pub async fn send(self) -> Result<Vec<Trade>, DataApiError> {
self.request.send().await
}
}
pub struct ListActivity {
request: Request<Vec<Activity>, DataApiError>,
}
impl ListActivity {
pub fn market(mut self, condition_ids: impl IntoIterator<Item = impl ToString>) -> Self {
let ids: Vec<String> = condition_ids.into_iter().map(|s| s.to_string()).collect();
if !ids.is_empty() {
self.request = self.request.query("market", ids.join(","));
}
self
}
pub fn event_id(mut self, event_ids: impl IntoIterator<Item = impl ToString>) -> Self {
let ids: Vec<String> = event_ids.into_iter().map(|s| s.to_string()).collect();
if !ids.is_empty() {
self.request = self.request.query("eventId", ids.join(","));
}
self
}
pub fn activity_type(mut self, types: impl IntoIterator<Item = ActivityType>) -> Self {
let type_strs: Vec<String> = types.into_iter().map(|t| t.to_string()).collect();
if !type_strs.is_empty() {
self.request = self.request.query("type", type_strs.join(","));
}
self
}
pub fn side(mut self, side: TradeSide) -> Self {
self.request = self.request.query("side", side);
self
}
pub fn start(mut self, timestamp: i64) -> Self {
self.request = self.request.query("start", timestamp);
self
}
pub fn end(mut self, timestamp: i64) -> Self {
self.request = self.request.query("end", timestamp);
self
}
pub fn limit(mut self, limit: u32) -> Self {
self.request = self.request.query("limit", limit);
self
}
pub fn offset(mut self, offset: u32) -> Self {
self.request = self.request.query("offset", offset);
self
}
pub fn sort_by(mut self, sort_by: ActivitySortBy) -> Self {
self.request = self.request.query("sortBy", sort_by);
self
}
pub fn sort_direction(mut self, direction: SortDirection) -> Self {
self.request = self.request.query("sortDirection", direction);
self
}
pub async fn send(self) -> Result<Vec<Activity>, DataApiError> {
self.request.send().await
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn deserialize_user_traded() {
let json = r#"{"user": "0xabcdef1234567890", "traded": 42}"#;
let ut: UserTraded = serde_json::from_str(json).unwrap();
assert_eq!(ut.user, "0xabcdef1234567890");
assert_eq!(ut.traded, 42);
}
#[test]
fn deserialize_user_traded_zero() {
let json = r#"{"user": "0x0000000000000000000000000000000000000001", "traded": 0}"#;
let ut: UserTraded = serde_json::from_str(json).unwrap();
assert_eq!(ut.traded, 0);
}
#[test]
fn user_traded_roundtrip() {
let original = UserTraded {
user: "0x1234".to_string(),
traded: 100,
};
let json = serde_json::to_string(&original).unwrap();
let deserialized: UserTraded = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.user, original.user);
assert_eq!(deserialized.traded, original.traded);
}
}