1use std::sync::Arc;
6
7use reqwest::Client as HttpClient;
8use serde::{de::DeserializeOwned, Serialize};
9
10use crate::config::Config;
11use crate::error::{Error, ErrorCode, Result};
12use crate::models::ApiResponse;
13use crate::token::TokenManager;
14
15#[derive(Debug, Clone)]
17pub struct RequestOptions {
18 pub trade_type: Option<i32>,
20 pub lang: Option<String>,
22}
23
24impl Default for RequestOptions {
25 fn default() -> Self {
26 Self::new()
27 }
28}
29
30impl RequestOptions {
31 pub fn new() -> Self {
33 RequestOptions {
34 trade_type: None,
35 lang: None,
36 }
37 }
38
39 pub fn with_trade_type(mut self, trade_type: i32) -> Self {
41 self.trade_type = Some(trade_type);
42 self
43 }
44
45 pub fn with_lang(mut self, lang: impl Into<String>) -> Self {
47 self.lang = Some(lang.into());
48 self
49 }
50}
51
52#[derive(Debug, Clone)]
54pub struct BaseClient {
55 config: Arc<Config>,
56 http_client: HttpClient,
57 token_manager: Arc<TokenManager>,
58}
59
60impl BaseClient {
61 pub fn new(config: Config, http_client: HttpClient, token_manager: Arc<TokenManager>) -> Self {
63 BaseClient {
64 config: Arc::new(config),
65 http_client,
66 token_manager,
67 }
68 }
69
70 pub async fn do_request<T, R>(
74 &self,
75 method: reqwest::Method,
76 path: &str,
77 body: Option<&T>,
78 opts: Option<RequestOptions>,
79 ) -> Result<R>
80 where
81 T: Serialize,
82 R: DeserializeOwned,
83 {
84 let opts = opts.unwrap_or_default();
85
86 let result = self.execute_request(&method, path, body, &opts).await;
88
89 if let Err(Error::Api { code, .. }) = &result {
91 if *code == ErrorCode::TokenExpired as i32 {
92 self.token_manager.refresh().await?;
94 return self.execute_request(&method, path, body, &opts).await;
95 }
96 }
97
98 result
99 }
100
101 async fn execute_request<T, R>(
103 &self,
104 method: &reqwest::Method,
105 path: &str,
106 body: Option<&T>,
107 opts: &RequestOptions,
108 ) -> Result<R>
109 where
110 T: Serialize,
111 R: DeserializeOwned,
112 {
113 let token = self.token_manager.token().await?;
115
116 let url = format!("{}{}", self.config.base_url, path);
118
119 let mut request = self.http_client.request(method.clone(), &url);
121
122 request = request
124 .header("Content-Type", "application/json")
125 .header("Authorization", format!("Bearer {}", token))
126 .header("apikey", &self.config.api_key)
127 .header(
128 "tradeType",
129 opts.trade_type.unwrap_or(self.config.trade_type).to_string(),
130 );
131
132 if let Some(lang) = opts.lang.as_ref().or(Some(&self.config.lang)) {
133 request = request.header("lang", lang);
134 }
135
136 if let Some(body) = body {
138 request = request.json(body);
139 }
140
141 let response = request.send().await?;
143
144 let resp_text = response.text().await?;
146
147 self.parse_response(&resp_text)
149 }
150
151 fn parse_response<R>(&self, resp_text: &str) -> Result<R>
153 where
154 R: DeserializeOwned,
155 {
156 let api_resp: ApiResponse = serde_json::from_str(resp_text).map_err(|e| {
158 Error::parse(resp_text, format!("failed to parse response: {}", e))
159 })?;
160
161 match ErrorCode::from_code(api_resp.code) {
163 Some(ErrorCode::Success) => {
164 serde_json::from_value(api_resp.data).map_err(|e| {
166 Error::parse(
167 resp_text,
168 format!("failed to deserialize response data: {}", e),
169 )
170 })
171 }
172
173 Some(ErrorCode::ParamError) => {
174 Err(Error::api(ErrorCode::ParamError as i32, api_resp.msg))
176 }
177
178 Some(ErrorCode::NoPermission) => {
179 Err(Error::api(ErrorCode::NoPermission as i32, api_resp.msg))
181 }
182
183 Some(ErrorCode::TokenExpired) => {
184 Err(Error::api(ErrorCode::TokenExpired as i32, api_resp.msg))
186 }
187
188 Some(ErrorCode::ServerError) => {
189 Err(Error::api(ErrorCode::ServerError as i32, api_resp.msg))
191 }
192
193 Some(ErrorCode::RateLimit) => {
194 Err(Error::api(ErrorCode::RateLimit as i32, api_resp.msg))
196 }
197
198 None => {
199 Err(Error::api(api_resp.code, api_resp.msg))
201 }
202 }
203 }
204
205 pub async fn do_get<R>(&self, path: &str, opts: Option<RequestOptions>) -> Result<R>
207 where
208 R: DeserializeOwned,
209 {
210 self.do_request::<(), R>(reqwest::Method::GET, path, None, opts)
211 .await
212 }
213
214 pub async fn do_post<T, R>(
216 &self,
217 path: &str,
218 body: &T,
219 opts: Option<RequestOptions>,
220 ) -> Result<R>
221 where
222 T: Serialize,
223 R: DeserializeOwned,
224 {
225 self.do_request(reqwest::Method::POST, path, Some(body), opts)
226 .await
227 }
228
229 pub fn config(&self) -> &Config {
231 &self.config
232 }
233
234 pub fn token_manager(&self) -> &TokenManager {
236 &self.token_manager
237 }
238}