1use std::{
2 convert::Infallible,
3 error::Error,
4 fmt::Debug,
5 marker::PhantomData,
6 time::{Duration, Instant},
7};
8
9use reqwest::{
10 Method, StatusCode,
11 header::{HeaderMap, HeaderName, HeaderValue},
12};
13use serde::{Deserialize, Serialize, de::DeserializeOwned};
14
15use crate::{
16 AuthConfig, HttpClient, HttpClientError, HttpClientResult, is_cn,
17 signature::{SignatureParams, signature},
18 timestamp::Timestamp,
19};
20
21const HTTP_URL: &str = "https://openapi.longbridge.com";
22const HTTP_URL_CN: &str = "https://openapi.longportapp.cn";
23
24const USER_AGENT: &str = "openapi-sdk";
25const REQUEST_TIMEOUT: Duration = Duration::from_secs(30);
26const RETRY_COUNT: usize = 5;
27const RETRY_INITIAL_DELAY: Duration = Duration::from_millis(100);
28const RETRY_FACTOR: f32 = 2.0;
29
30#[derive(Debug)]
32pub struct Json<T>(pub T);
33
34pub trait FromPayload: Sized + Send + Sync + 'static {
36 type Err: Error;
38
39 fn parse_from_bytes(data: &[u8]) -> Result<Self, Self::Err>;
41}
42
43pub trait ToPayload: Debug + Sized + Send + Sync + 'static {
45 type Err: Error;
47
48 fn to_bytes(&self) -> Result<Vec<u8>, Self::Err>;
50}
51
52impl<T> FromPayload for Json<T>
53where
54 T: DeserializeOwned + Send + Sync + 'static,
55{
56 type Err = serde_json::Error;
57
58 #[inline]
59 fn parse_from_bytes(data: &[u8]) -> Result<Self, Self::Err> {
60 Ok(Json(serde_json::from_slice(data)?))
61 }
62}
63
64impl<T> ToPayload for Json<T>
65where
66 T: Debug + Serialize + Send + Sync + 'static,
67{
68 type Err = serde_json::Error;
69
70 #[inline]
71 fn to_bytes(&self) -> Result<Vec<u8>, Self::Err> {
72 serde_json::to_vec(&self.0)
73 }
74}
75
76impl FromPayload for String {
77 type Err = std::string::FromUtf8Error;
78
79 #[inline]
80 fn parse_from_bytes(data: &[u8]) -> Result<Self, Self::Err> {
81 String::from_utf8(data.to_vec())
82 }
83}
84
85impl ToPayload for String {
86 type Err = std::string::FromUtf8Error;
87
88 #[inline]
89 fn to_bytes(&self) -> Result<Vec<u8>, Self::Err> {
90 Ok(self.clone().into_bytes())
91 }
92}
93
94impl FromPayload for () {
95 type Err = Infallible;
96
97 #[inline]
98 fn parse_from_bytes(_data: &[u8]) -> Result<Self, Self::Err> {
99 Ok(())
100 }
101}
102
103impl ToPayload for () {
104 type Err = Infallible;
105
106 #[inline]
107 fn to_bytes(&self) -> Result<Vec<u8>, Self::Err> {
108 Ok(vec![])
109 }
110}
111
112#[derive(Deserialize)]
113struct OpenApiResponse {
114 code: i32,
115 message: String,
116 data: Option<Box<serde_json::value::RawValue>>,
117}
118
119pub struct RequestBuilder<'a, T, Q, R> {
121 client: &'a HttpClient,
122 method: Method,
123 path: String,
124 headers: HeaderMap,
125 body: Option<T>,
126 query_params: Option<Q>,
127 mark_resp: PhantomData<R>,
128}
129
130impl<'a> RequestBuilder<'a, (), (), ()> {
131 pub(crate) fn new(client: &'a HttpClient, method: Method, path: impl Into<String>) -> Self {
132 Self {
133 client,
134 method,
135 path: path.into(),
136 headers: Default::default(),
137 body: None,
138 query_params: None,
139 mark_resp: PhantomData,
140 }
141 }
142}
143
144impl<'a, T, Q, R> RequestBuilder<'a, T, Q, R> {
145 #[must_use]
147 pub fn body<T2>(self, body: T2) -> RequestBuilder<'a, T2, Q, R>
148 where
149 T2: ToPayload,
150 {
151 RequestBuilder {
152 client: self.client,
153 method: self.method,
154 path: self.path,
155 headers: self.headers,
156 body: Some(body),
157 query_params: self.query_params,
158 mark_resp: self.mark_resp,
159 }
160 }
161
162 #[must_use]
164 pub fn header<K, V>(mut self, key: K, value: V) -> Self
165 where
166 K: TryInto<HeaderName>,
167 V: TryInto<HeaderValue>,
168 {
169 let key = key.try_into();
170 let value = value.try_into();
171 if let (Ok(key), Ok(value)) = (key, value) {
172 self.headers.insert(key, value);
173 }
174 self
175 }
176
177 #[must_use]
179 pub fn query_params<Q2>(self, params: Q2) -> RequestBuilder<'a, T, Q2, R>
180 where
181 Q2: Serialize + Send + Sync,
182 {
183 RequestBuilder {
184 client: self.client,
185 method: self.method,
186 path: self.path,
187 headers: self.headers,
188 body: self.body,
189 query_params: Some(params),
190 mark_resp: self.mark_resp,
191 }
192 }
193
194 #[must_use]
196 pub fn response<R2>(self) -> RequestBuilder<'a, T, Q, R2>
197 where
198 R2: FromPayload,
199 {
200 RequestBuilder {
201 client: self.client,
202 method: self.method,
203 path: self.path,
204 headers: self.headers,
205 body: self.body,
206 query_params: self.query_params,
207 mark_resp: PhantomData,
208 }
209 }
210}
211
212impl<T, Q, R> RequestBuilder<'_, T, Q, R>
213where
214 T: ToPayload,
215 Q: Serialize + Send,
216 R: FromPayload,
217{
218 async fn http_url(&self) -> &str {
219 if let Some(url) = self.client.config.http_url.as_deref() {
220 return url;
221 }
222
223 if is_cn().await { HTTP_URL_CN } else { HTTP_URL }
224 }
225
226 async fn do_send(&self) -> HttpClientResult<R> {
227 let HttpClient {
228 http_cli,
229 config,
230 default_headers,
231 } = &self.client;
232 let timestamp = self
233 .headers
234 .get("X-Timestamp")
235 .and_then(|value| value.to_str().ok())
236 .and_then(|value| value.parse().ok())
237 .unwrap_or_else(Timestamp::now);
238
239 let (app_key, access_token, app_secret) = match &config.auth {
241 AuthConfig::ApiKey {
242 app_key,
243 app_secret,
244 access_token,
245 } => (
246 app_key.clone(),
247 access_token.clone(),
248 Some(app_secret.clone()),
249 ),
250 AuthConfig::OAuth(oauth) => {
251 let token = oauth
252 .access_token()
253 .await
254 .map_err(|e| HttpClientError::OAuth(e.to_string()))?;
255 (
256 oauth.client_id().to_string(),
257 format!("Bearer {token}"),
258 None,
259 )
260 }
261 };
262
263 let app_key_value =
264 HeaderValue::from_str(&app_key).map_err(|_| HttpClientError::InvalidApiKey)?;
265 let access_token_value = HeaderValue::from_str(&access_token)
266 .map_err(|_| HttpClientError::InvalidAccessToken)?;
267
268 let url = self.http_url().await;
269 let mut request_builder = http_cli
270 .request(self.method.clone(), format!("{}{}", url, self.path))
271 .headers(default_headers.clone())
272 .headers(self.headers.clone())
273 .header("User-Agent", USER_AGENT)
274 .header("X-Api-Key", app_key_value)
275 .header("Authorization", access_token_value)
276 .header("X-Timestamp", timestamp.to_string())
277 .header("Content-Type", "application/json; charset=utf-8");
278
279 if let Some(body) = &self.body {
281 let body = body
282 .to_bytes()
283 .map_err(|err| HttpClientError::SerializeRequestBody(err.to_string()))?;
284 request_builder = request_builder.body(body);
285 }
286
287 let mut request = request_builder.build().expect("invalid request");
288
289 if let Some(query_params) = &self.query_params {
291 let query_string = crate::qs::to_string(&query_params)?;
292 request.url_mut().set_query(Some(&query_string));
293 }
294
295 if let Some(secret) = app_secret {
297 let sign = signature(SignatureParams {
298 request: &request,
299 app_key: &app_key,
300 access_token: Some(&access_token),
301 app_secret: &secret,
302 timestamp,
303 });
304 if let Some(signature_value) = sign {
305 request.headers_mut().insert(
306 "X-Api-Signature",
307 HeaderValue::from_maybe_shared(signature_value).expect("valid signature"),
308 );
309 }
310 }
311
312 if let Some(body) = &self.body {
313 tracing::info!(method = %request.method(), url = %request.url(), body = ?body, "http request");
314 } else {
315 tracing::info!(method = %request.method(), url = %request.url(), "http request");
316 }
317
318 let s = Instant::now();
319
320 let (status, trace_id, text) = tokio::time::timeout(REQUEST_TIMEOUT, async move {
322 let resp = http_cli
323 .execute(request)
324 .await
325 .map_err(|err| HttpClientError::Http(err.into()))?;
326 let status = resp.status();
327 let trace_id = resp
328 .headers()
329 .get("x-trace-id")
330 .and_then(|value| value.to_str().ok())
331 .unwrap_or_default()
332 .to_string();
333 let text = resp
334 .text()
335 .await
336 .map_err(|err| HttpClientError::Http(err.into()))?;
337 Ok::<_, HttpClientError>((status, trace_id, text))
338 })
339 .await
340 .map_err(|_| HttpClientError::RequestTimeout)??;
341
342 tracing::info!(duration = ?s.elapsed(), body = %text.as_str(), "http response");
343
344 let resp = match serde_json::from_str::<OpenApiResponse>(&text) {
345 Ok(resp) if resp.code == 0 => resp.data.ok_or(HttpClientError::UnexpectedResponse),
346 Ok(resp) => Err(HttpClientError::OpenApi {
347 code: resp.code,
348 message: resp.message,
349 trace_id,
350 }),
351 Err(err) if status == StatusCode::OK => {
352 Err(HttpClientError::DeserializeResponseBody(err.to_string()))
353 }
354 Err(_) => Err(HttpClientError::BadStatus(status)),
355 }?;
356
357 R::parse_from_bytes(resp.get().as_bytes())
358 .map_err(|err| HttpClientError::DeserializeResponseBody(err.to_string()))
359 }
360
361 pub async fn send(self) -> HttpClientResult<R> {
363 match self.do_send().await {
364 Ok(resp) => Ok(resp),
365 Err(HttpClientError::BadStatus(StatusCode::TOO_MANY_REQUESTS)) => {
366 let mut retry_delay = RETRY_INITIAL_DELAY;
367
368 for _ in 0..RETRY_COUNT {
369 tokio::time::sleep(retry_delay).await;
370
371 match self.do_send().await {
372 Ok(resp) => return Ok(resp),
373 Err(HttpClientError::BadStatus(StatusCode::TOO_MANY_REQUESTS)) => {
374 retry_delay =
375 Duration::from_secs_f32(retry_delay.as_secs_f32() * RETRY_FACTOR);
376 continue;
377 }
378 Err(err) => return Err(err),
379 }
380 }
381
382 Err(HttpClientError::BadStatus(StatusCode::TOO_MANY_REQUESTS))
383 }
384 Err(err) => Err(err),
385 }
386 }
387}