1use crate::{CurrencyError, Result, config::Config};
4use reqwest::{Client, Response, StatusCode};
5use serde::de::DeserializeOwned;
6use std::time::Duration;
7use tokio::time::sleep;
8
9#[derive(Debug, Clone)]
11pub struct CurrencyClient {
12 client: Client,
14 config: Config,
16}
17
18impl CurrencyClient {
19 pub fn new() -> Result<Self> {
21 let config = Config::new();
22 Self::with_config(config)
23 }
24
25 pub fn with_config(config: Config) -> Result<Self> {
27 let client = Client::builder()
29 .timeout(config.timeout)
30 .user_agent(&config.user_agent)
31 .build()
32 .map_err(|e| {
33 CurrencyError::configuration(format!("Failed to create HTTP client: {}", e))
34 })?;
35
36 Ok(CurrencyClient { client, config })
37 }
38
39 pub fn from_env() -> Result<Self> {
41 let config = Config::from_env()?;
42 Self::with_config(config)
43 }
44
45 pub async fn get<T>(&self, endpoint: &str) -> Result<T>
47 where
48 T: DeserializeOwned,
49 {
50 let url = format!(
51 "{}/{}",
52 self.config.base_url,
53 endpoint.trim_start_matches('/')
54 );
55
56 let mut last_error = None;
58
59 for attempt in 1..=self.config.max_retries {
60 match self.make_request(&url).await {
61 Ok(response) => {
62 return self.handle_response(response).await;
63 }
64 Err(e) => {
65 last_error = Some(e);
66
67 if attempt < self.config.max_retries {
68 let delay = Duration::from_millis(1000 * 2_u64.pow(attempt - 1));
70 sleep(delay).await;
71 }
72 }
73 }
74 }
75
76 Err(last_error.unwrap_or_else(|| CurrencyError::api("All retries exhausted")))
78 }
79
80 async fn make_request(&self, url: &str) -> Result<Response> {
82 let response = self.client.get(url).send().await?; Ok(response)
85 }
86
87 async fn handle_response<T>(&self, response: Response) -> Result<T>
89 where
90 T: DeserializeOwned,
91 {
92 let status = response.status();
93
94 match status {
96 StatusCode::OK => {
97 let json_text = response.text().await?;
99
100 serde_json::from_str(&json_text).map_err(|e| CurrencyError::from(e))
102 }
103 StatusCode::UNAUTHORIZED => {
104 Err(CurrencyError::api("Invalid API key or unauthorized access"))
105 }
106 StatusCode::TOO_MANY_REQUESTS => Err(CurrencyError::api(
107 "Rate limit exceeded. Please try again later",
108 )),
109 StatusCode::NOT_FOUND => Err(CurrencyError::api("API endpoint not found")),
110 StatusCode::BAD_REQUEST => {
111 let error_text = response
113 .text()
114 .await
115 .unwrap_or_else(|_| "Bad request".to_string());
116 Err(CurrencyError::api(format!("Bad request: {}", error_text)))
117 }
118 _ => {
119 let error_text = response
120 .text()
121 .await
122 .unwrap_or_else(|_| "Unknown error".to_string());
123 Err(CurrencyError::api(format!(
124 "HTTP error {}: {}",
125 status.as_u16(),
126 error_text
127 )))
128 }
129 }
130 }
131
132 pub fn base_url(&self) -> &str {
134 &self.config.base_url
135 }
136
137 pub fn has_api_key(&self) -> bool {
139 self.config.api_key.is_some()
140 }
141}