1use crate::{join_url, ApiError, ClientConfig, OAuthTokenProvider, RequestOptions};
2use futures::{future::BoxFuture, Stream, StreamExt};
3use reqwest::{
4 header::{HeaderMap, HeaderName, HeaderValue},
5 Client, Method, Request, Response,
6};
7use serde::de::DeserializeOwned;
8
9use std::{
10 pin::Pin,
11 str::FromStr,
12 sync::Arc,
13 task::{Context, Poll},
14};
15
16#[derive(Debug)]
19pub struct RawResponse<T> {
20 pub body: T,
22 pub status_code: u16,
24 pub headers: HeaderMap,
26}
27
28pub struct ByteStream {
30 content_length: Option<u64>,
31 inner: Pin<Box<dyn Stream<Item = Result<bytes::Bytes, reqwest::Error>> + Send>>,
32}
33
34impl ByteStream {
35 pub(crate) fn new(response: Response) -> Self {
37 let content_length = response.content_length();
38 let stream = response.bytes_stream();
39
40 Self {
41 content_length,
42 inner: Box::pin(stream),
43 }
44 }
45
46 pub async fn collect(mut self) -> Result<Vec<u8>, ApiError> {
57 let mut result = Vec::new();
58 while let Some(chunk) = self.inner.next().await {
59 result.extend_from_slice(&chunk.map_err(ApiError::Network)?);
60 }
61 Ok(result)
62 }
63
64 pub async fn try_next(&mut self) -> Result<Option<bytes::Bytes>, ApiError> {
77 match self.inner.next().await {
78 Some(Ok(bytes)) => Ok(Some(bytes)),
79 Some(Err(e)) => Err(ApiError::Network(e)),
80 None => Ok(None),
81 }
82 }
83
84 pub fn content_length(&self) -> Option<u64> {
86 self.content_length
87 }
88}
89
90impl Stream for ByteStream {
91 type Item = Result<bytes::Bytes, ApiError>;
92
93 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
94 match self.inner.as_mut().poll_next(cx) {
95 Poll::Ready(Some(Ok(bytes))) => Poll::Ready(Some(Ok(bytes))),
96 Poll::Ready(Some(Err(e))) => Poll::Ready(Some(Err(ApiError::Network(e)))),
97 Poll::Ready(None) => Poll::Ready(None),
98 Poll::Pending => Poll::Pending,
99 }
100 }
101}
102
103#[doc(hidden)]
110pub trait RequestExecutor: Send + Sync {
111 fn execute(
112 &self,
113 request: Request,
114 ) -> BoxFuture<'_, Result<Response, Box<dyn std::error::Error + Send + Sync>>>;
115}
116
117struct ReqwestExecutor {
119 client: Client,
120}
121
122impl RequestExecutor for ReqwestExecutor {
123 fn execute(
124 &self,
125 request: Request,
126 ) -> BoxFuture<'_, Result<Response, Box<dyn std::error::Error + Send + Sync>>> {
127 Box::pin(async move {
128 self.client
129 .execute(request)
130 .await
131 .map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send + Sync>)
132 })
133 }
134}
135
136#[derive(Clone)]
141pub struct OAuthConfig {
142 pub token_provider: Arc<OAuthTokenProvider>,
144 pub token_endpoint: String,
146}
147
148#[derive(Debug, Clone, serde::Deserialize)]
150struct OAuthTokenResponse {
151 access_token: String,
152 #[serde(default)]
153 expires_in: Option<i64>,
154}
155
156#[derive(Clone)]
158pub struct HttpClient {
159 client: Client,
160 executor: Option<Arc<dyn RequestExecutor>>,
161 config: ClientConfig,
162 oauth_config: Option<OAuthConfig>,
164}
165
166impl HttpClient {
167 pub fn new(config: ClientConfig) -> Result<Self, ApiError> {
169 Self::new_with_oauth(config, None)
170 }
171
172 pub fn new_with_oauth(
177 config: ClientConfig,
178 oauth_config: Option<OAuthConfig>,
179 ) -> Result<Self, ApiError> {
180 let client = Client::builder()
181 .timeout(config.timeout)
182 .user_agent(&config.user_agent)
183 .build()
184 .map_err(ApiError::Network)?;
185
186 Ok(Self {
187 client,
188 executor: None,
189 config,
190 oauth_config,
191 })
192 }
193
194 #[doc(hidden)]
202 pub fn with_executor(executor: Arc<dyn RequestExecutor>, config: ClientConfig) -> Self {
203 let client = Client::new();
204 Self {
205 client,
206 executor: Some(executor),
207 config,
208 oauth_config: None,
209 }
210 }
211
212 pub fn base_url(&self) -> &str {
214 &self.config.base_url
215 }
216
217 pub fn config(&self) -> &ClientConfig {
219 &self.config
220 }
221
222 pub async fn execute_request_raw<T>(
228 &self,
229 method: Method,
230 path: &str,
231 body: Option<serde_json::Value>,
232 query_params: Option<Vec<(String, String)>>,
233 options: Option<RequestOptions>,
234 ) -> Result<RawResponse<T>, ApiError>
235 where
236 T: DeserializeOwned,
237 {
238 let url = join_url(&self.config.base_url, path);
239 let mut request = self.client.request(method, &url);
240
241 if let Some(params) = query_params {
242 request = request.query(¶ms);
243 }
244
245 if let Some(opts) = &options {
246 if !opts.additional_query_params.is_empty() {
247 request = request.query(&opts.additional_query_params);
248 }
249 }
250
251 if let Some(body) = body {
252 request = request.json(&body);
253 }
254
255 let req = request.build().map_err(|e| ApiError::Network(e))?;
256
257 let response = self.send_request(req, &options).await?;
258 self.parse_response_raw(response).await
259 }
260
261 pub async fn execute_request<T>(
263 &self,
264 method: Method,
265 path: &str,
266 body: Option<serde_json::Value>,
267 query_params: Option<Vec<(String, String)>>,
268 options: Option<RequestOptions>,
269 ) -> Result<T, ApiError>
270 where
271 T: DeserializeOwned,
272 {
273 let url = join_url(&self.config.base_url, path);
274 let mut request = self.client.request(method, &url);
275
276 if let Some(params) = query_params {
277 request = request.query(¶ms);
278 }
279
280 if let Some(opts) = &options {
281 if !opts.additional_query_params.is_empty() {
282 request = request.query(&opts.additional_query_params);
283 }
284 }
285
286 if let Some(body) = body {
287 request = request.json(&body);
288 }
289
290 let req = request.build().map_err(|e| ApiError::Network(e))?;
291
292 let response = self.send_request(req, &options).await?;
293 self.parse_response(response).await
294 }
295
296 pub async fn execute_request_with_base_url<T>(
301 &self,
302 base_url: &str,
303 method: Method,
304 path: &str,
305 body: Option<serde_json::Value>,
306 query_params: Option<Vec<(String, String)>>,
307 options: Option<RequestOptions>,
308 ) -> Result<T, ApiError>
309 where
310 T: DeserializeOwned,
311 {
312 let url = join_url(base_url, path);
313 let mut request = self.client.request(method, &url);
314
315 if let Some(params) = query_params {
316 request = request.query(¶ms);
317 }
318
319 if let Some(opts) = &options {
320 if !opts.additional_query_params.is_empty() {
321 request = request.query(&opts.additional_query_params);
322 }
323 }
324
325 if let Some(body) = body {
326 request = request.json(&body);
327 }
328
329 let req = request.build().map_err(|e| ApiError::Network(e))?;
330
331 let response = self.send_request(req, &options).await?;
332 self.parse_response(response).await
333 }
334
335 async fn send_request(
339 &self,
340 req: Request,
341 options: &Option<RequestOptions>,
342 ) -> Result<Response, ApiError> {
343 if let Some(executor) = &self.executor {
344 executor.execute(req).await.map_err(ApiError::Executor)
345 } else {
346 let mut req = req;
347 self.apply_auth_headers(&mut req, options).await?;
348 self.apply_custom_headers(&mut req, options)?;
349 self.execute_with_retries(req, options).await
350 }
351 }
352
353 async fn apply_auth_headers(
354 &self,
355 request: &mut Request,
356 options: &Option<RequestOptions>,
357 ) -> Result<(), ApiError> {
358 let headers = request.headers_mut();
359
360 let api_key = options
362 .as_ref()
363 .and_then(|opts| opts.api_key.as_ref())
364 .or(self.config.api_key.as_ref());
365
366 if let Some(key) = api_key {
367 let header_value = key.to_string();
368 headers.insert(
369 "api_key",
370 header_value.parse().map_err(|_| ApiError::InvalidHeader)?,
371 );
372 }
373
374 let token = if let Some(opts) = options.as_ref() {
376 if opts.token.is_some() {
377 opts.token.clone()
378 } else {
379 None
380 }
381 } else {
382 None
383 };
384
385 let token = match token {
386 Some(t) => Some(t),
387 None => {
388 if let Some(oauth_config) = &self.oauth_config {
390 Some(self.get_oauth_token(oauth_config).await?)
391 } else {
392 self.config.token.clone()
394 }
395 }
396 };
397
398 if let Some(token) = token {
399 let auth_value = format!("Bearer {}", token);
400 headers.insert(
401 "Authorization",
402 auth_value.parse().map_err(|_| ApiError::InvalidHeader)?,
403 );
404 }
405
406 Ok(())
407 }
408
409 async fn get_oauth_token(&self, oauth_config: &OAuthConfig) -> Result<String, ApiError> {
411 let token_provider = &oauth_config.token_provider;
412 let token_endpoint = &oauth_config.token_endpoint;
413 let client_id = token_provider.client_id().to_string();
414 let client_secret = token_provider.client_secret().to_string();
415 let base_url = self.config.base_url.clone();
416
417 token_provider
419 .get_or_fetch_async(|| async {
420 self.fetch_oauth_token(&base_url, token_endpoint, &client_id, &client_secret)
421 .await
422 })
423 .await
424 }
425
426 async fn fetch_oauth_token(
428 &self,
429 base_url: &str,
430 token_endpoint: &str,
431 client_id: &str,
432 client_secret: &str,
433 ) -> Result<(String, u64), ApiError> {
434 let url = join_url(base_url, token_endpoint);
435
436 let body = serde_json::json!({
438 "client_id": client_id,
439 "client_secret": client_secret,
440 "grant_type": "client_credentials"
441 });
442
443 let response = self
444 .client
445 .request(Method::POST, &url)
446 .json(&body)
447 .send()
448 .await
449 .map_err(ApiError::Network)?;
450
451 if !response.status().is_success() {
452 let status_code = response.status().as_u16();
453 let body = response.text().await.ok();
454 return Err(ApiError::from_response(status_code, body.as_deref()));
455 }
456
457 let token_response: OAuthTokenResponse =
459 response.json().await.map_err(ApiError::Network)?;
460
461 let expires_in = token_response.expires_in.unwrap_or(3600) as u64;
462 Ok((token_response.access_token, expires_in))
463 }
464
465 fn apply_custom_headers(
466 &self,
467 request: &mut Request,
468 options: &Option<RequestOptions>,
469 ) -> Result<(), ApiError> {
470 let headers = request.headers_mut();
471
472 for (key, value) in &self.config.custom_headers {
474 headers.insert(
475 HeaderName::from_str(key).map_err(|_| ApiError::InvalidHeader)?,
476 HeaderValue::from_str(value).map_err(|_| ApiError::InvalidHeader)?,
477 );
478 }
479
480 if let Some(options) = options {
482 for (key, value) in &options.additional_headers {
483 headers.insert(
484 HeaderName::from_str(key).map_err(|_| ApiError::InvalidHeader)?,
485 HeaderValue::from_str(value).map_err(|_| ApiError::InvalidHeader)?,
486 );
487 }
488 }
489
490 Ok(())
491 }
492
493 async fn execute_with_retries(
494 &self,
495 request: Request,
496 options: &Option<RequestOptions>,
497 ) -> Result<Response, ApiError> {
498 let max_retries = options
499 .as_ref()
500 .and_then(|opts| opts.max_retries)
501 .unwrap_or(self.config.max_retries);
502
503 let mut last_error = None;
504
505 for attempt in 0..=max_retries {
506 let cloned_request = request.try_clone().ok_or(ApiError::RequestClone)?;
507
508 match self.client.execute(cloned_request).await {
509 Ok(response) if response.status().is_success() => return Ok(response),
510 Ok(response)
511 if attempt < max_retries
512 && Self::is_retryable_status(response.status().as_u16()) =>
513 {
514 let delay = std::time::Duration::from_millis(100 * 2_u64.pow(attempt));
516 tokio::time::sleep(delay).await;
517 }
518 Ok(response) => {
519 let status_code = response.status().as_u16();
520 let body = response.text().await.ok();
521 return Err(ApiError::from_response(status_code, body.as_deref()));
522 }
523 Err(e) if attempt < max_retries => {
524 last_error = Some(e);
525 let delay = std::time::Duration::from_millis(100 * 2_u64.pow(attempt));
527 tokio::time::sleep(delay).await;
528 }
529 Err(e) => return Err(ApiError::Network(e)),
530 }
531 }
532
533 Err(ApiError::Network(last_error.unwrap()))
534 }
535
536 fn is_retryable_status(status_code: u16) -> bool {
537 [408, 429].contains(&status_code) || status_code >= 500
538 }
539
540 async fn parse_response<T>(&self, response: Response) -> Result<T, ApiError>
541 where
542 T: DeserializeOwned,
543 {
544 let status = response.status().as_u16();
545 let text = response.text().await.map_err(ApiError::Network)?;
546
547 if text.is_empty() {
548 if status >= 400 {
549 return Err(ApiError::Http {
550 status,
551 message: String::new(),
552 });
553 }
554 return serde_json::from_value(serde_json::Value::Null).map_err(|_| ApiError::Http {
555 status,
556 message: String::new(),
557 });
558 }
559
560 serde_json::from_str(&text).map_err(ApiError::Serialization)
561 }
562
563 async fn parse_response_raw<T>(&self, response: Response) -> Result<RawResponse<T>, ApiError>
564 where
565 T: DeserializeOwned,
566 {
567 let status_code = response.status().as_u16();
568 let headers = response.headers().clone();
569 let text = response.text().await.map_err(ApiError::Network)?;
570
571 if text.is_empty() {
572 if status_code >= 400 {
573 return Err(ApiError::Http {
574 status: status_code,
575 message: String::new(),
576 });
577 }
578 return serde_json::from_value(serde_json::Value::Null)
579 .map(|body| RawResponse {
580 body,
581 status_code,
582 headers,
583 })
584 .map_err(|_| ApiError::Http {
585 status: status_code,
586 message: String::new(),
587 });
588 }
589
590 let body: T = serde_json::from_str(&text).map_err(ApiError::Serialization)?;
591 Ok(RawResponse {
592 body,
593 status_code,
594 headers,
595 })
596 }
597
598 pub async fn execute_stream_request(
654 &self,
655 method: Method,
656 path: &str,
657 body: Option<serde_json::Value>,
658 query_params: Option<Vec<(String, String)>>,
659 options: Option<RequestOptions>,
660 ) -> Result<ByteStream, ApiError> {
661 let url = join_url(&self.config.base_url, path);
662 let mut request = self.client.request(method, &url);
663
664 if let Some(params) = query_params {
666 request = request.query(¶ms);
667 }
668
669 if let Some(opts) = &options {
671 if !opts.additional_query_params.is_empty() {
672 request = request.query(&opts.additional_query_params);
673 }
674 }
675
676 if let Some(body) = body {
678 request = request.json(&body);
679 }
680
681 let req = request.build().map_err(|e| ApiError::Network(e))?;
683
684 let response = self.send_request(req, &options).await?;
685
686 Ok(ByteStream::new(response))
688 }
689
690 pub async fn execute_stream_request_with_base_url(
692 &self,
693 base_url: &str,
694 method: Method,
695 path: &str,
696 body: Option<serde_json::Value>,
697 query_params: Option<Vec<(String, String)>>,
698 options: Option<RequestOptions>,
699 ) -> Result<ByteStream, ApiError> {
700 let url = join_url(base_url, path);
701 let mut request = self.client.request(method, &url);
702
703 if let Some(params) = query_params {
704 request = request.query(¶ms);
705 }
706
707 if let Some(opts) = &options {
708 if !opts.additional_query_params.is_empty() {
709 request = request.query(&opts.additional_query_params);
710 }
711 }
712
713 if let Some(body) = body {
714 request = request.json(&body);
715 }
716
717 let req = request.build().map_err(|e| ApiError::Network(e))?;
718
719 let response = self.send_request(req, &options).await?;
720
721 Ok(ByteStream::new(response))
722 }
723}
724
725#[cfg(test)]
726mod tests {
727 use super::*;
728
729 #[test]
730 fn test_is_retryable_status() {
731 assert!(HttpClient::is_retryable_status(408));
733 assert!(HttpClient::is_retryable_status(429));
734
735 assert!(HttpClient::is_retryable_status(500));
737 assert!(HttpClient::is_retryable_status(501));
738 assert!(HttpClient::is_retryable_status(502));
739 assert!(HttpClient::is_retryable_status(503));
740 assert!(HttpClient::is_retryable_status(504));
741 assert!(HttpClient::is_retryable_status(599));
742
743 assert!(!HttpClient::is_retryable_status(200));
745 assert!(!HttpClient::is_retryable_status(400));
746 assert!(!HttpClient::is_retryable_status(401));
747 assert!(!HttpClient::is_retryable_status(404));
748 }
749}