1use reqwest::Client;
4use url::Url;
5
6use crate::error::{PrecolatorError, Result};
7use crate::models::*;
8
9#[derive(Debug, Clone)]
23pub struct PrecolatorClient {
24 http: Client,
25 base_url: Url,
26}
27
28impl PrecolatorClient {
29 pub fn new(base_url: &str) -> Self {
35 let base = Url::parse(base_url).expect("invalid base URL");
36 Self {
37 http: Client::builder()
38 .user_agent(format!("precolator-sdk/{}", crate::SDK_VERSION))
39 .build()
40 .expect("failed to build HTTP client"),
41 base_url: base,
42 }
43 }
44
45 pub fn with_client(base_url: &str, http: Client) -> Result<Self> {
47 Ok(Self {
48 http,
49 base_url: Url::parse(base_url)?,
50 })
51 }
52
53 fn url(&self, path: &str) -> Result<Url> {
56 self.base_url
57 .join(path)
58 .map_err(PrecolatorError::InvalidUrl)
59 }
60
61 async fn get<T: serde::de::DeserializeOwned>(&self, path: &str) -> Result<T> {
62 let url = self.url(path)?;
63 let resp = self.http.get(url).send().await?;
64
65 let status = resp.status().as_u16();
66 if status == 429 {
67 return Err(PrecolatorError::RateLimited {
68 retry_after_secs: 15,
69 });
70 }
71 if status == 404 {
72 return Err(PrecolatorError::NotFound(path.to_string()));
73 }
74 if !resp.status().is_success() {
75 let body = resp.text().await.unwrap_or_default();
76 return Err(PrecolatorError::Api {
77 status,
78 message: body,
79 });
80 }
81
82 let wrapper: ApiResponse<T> = resp.json().await?;
83 Ok(wrapper.data)
84 }
85
86 pub async fn health(&self) -> Result<HealthStatus> {
90 self.get("/api/v1/health").await
91 }
92
93 pub async fn health_detailed(&self) -> Result<DetailedHealth> {
95 self.get("/api/v1/health/detailed").await
96 }
97
98 pub async fn get_markets(&self) -> Result<Vec<Market>> {
102 self.get("/api/v1/markets").await
103 }
104
105 pub async fn get_market(&self, id: &str) -> Result<Market> {
107 self.get(&format!("/api/v1/markets/{id}")).await
108 }
109
110 pub async fn get_market_stats(&self, id: &str) -> Result<MarketStats> {
112 self.get(&format!("/api/v1/markets/{id}/stats")).await
113 }
114
115 pub async fn get_trades(&self) -> Result<Vec<Trade>> {
119 self.get("/api/v1/trades").await
120 }
121
122 pub async fn get_trade(&self, trade_id: &str) -> Result<Trade> {
124 self.get(&format!("/api/v1/trades/{trade_id}")).await
125 }
126
127 pub async fn get_user_trades(&self, wallet: &str) -> Result<Vec<Trade>> {
129 self.get(&format!("/api/v1/trades/user/{wallet}")).await
130 }
131
132 pub async fn get_portfolio(&self, wallet: &str) -> Result<Portfolio> {
136 self.get(&format!("/api/v1/portfolio/{wallet}")).await
137 }
138
139 pub async fn get_portfolio_history(&self, wallet: &str) -> Result<PortfolioHistory> {
141 self.get(&format!("/api/v1/portfolio/{wallet}/history"))
142 .await
143 }
144
145 pub async fn get_portfolio_stats(&self, wallet: &str) -> Result<PortfolioStats> {
147 self.get(&format!("/api/v1/portfolio/{wallet}/stats"))
148 .await
149 }
150
151 pub async fn get_leaderboard(&self) -> Result<Vec<LeaderboardEntry>> {
155 self.get("/api/v1/leaderboard").await
156 }
157
158 pub async fn get_leaderboard_by_timeframe(
160 &self,
161 timeframe: &str,
162 ) -> Result<Vec<LeaderboardEntry>> {
163 self.get(&format!("/api/v1/leaderboard/{timeframe}"))
164 .await
165 }
166
167 pub async fn get_tokens(&self) -> Result<Vec<Token>> {
171 self.get("/api/v1/tokens").await
172 }
173
174 pub async fn get_token(&self, symbol: &str) -> Result<Token> {
176 self.get(&format!("/api/v1/tokens/{symbol}")).await
177 }
178
179 pub async fn get_platform_stats(&self) -> Result<PlatformStats> {
183 self.get("/api/v1/stats").await
184 }
185
186 pub async fn get_market_overview(&self) -> Result<MarketOverview> {
188 self.get("/api/v1/stats/markets/overview").await
189 }
190
191 pub async fn get_exchange_metrics(&self) -> Result<ExchangeMetrics> {
193 self.get("/api/v1/stats/exchange/metrics").await
194 }
195}