1use anyhow::{Result, anyhow};
2use reqwest::{self, header::{HeaderMap, HeaderName, HeaderValue}, Client, Method, StatusCode};
3use serde::{Serialize, de::DeserializeOwned, Deserialize};
4use std::time::Duration;
5use std::{fs, io, path::{PathBuf, Path}};
6use std::collections::HashMap;
7use dirs;
8
9#[derive(Debug, Serialize, Deserialize, Default)]
10pub struct AppConfig {
11 pub base_url: String,
12 pub timeout_seconds: u64,
13 #[serde(default)]
15 pub settings: HashMap<String, serde_json::Value>,
16}
17
18pub struct ApiClient {
19 pub client: Client,
20 pub base_url: String,
21 pub headers: HeaderMap,
22 pub config_path: Option<PathBuf>,
23 pub config: AppConfig,
24}
25
26impl ApiClient {
27 pub fn new() -> Self {
28 let mut headers = HeaderMap::new();
29 headers.insert("Content-Type", HeaderValue::from_static("application/json"));
30
31 let app_name = env!("CARGO_PKG_NAME");
32
33 let mut config = AppConfig::default();
35 config.base_url = String::from("http://localhost:8002/api/v1");
36 config.timeout_seconds = 30;
37
38 let config_path = dirs::config_dir().map(|config_dir| {
39 let app_config_dir = config_dir.join(app_name);
40 let config_file = app_config_dir.join("config.json");
41
42 Self::ensure_config_dir(&app_config_dir);
44
45 match Self::load_config(&config_file) {
47 Ok(loaded_config) => {
48 config = loaded_config;
49 println!("Loaded configuration from {:?}", config_file);
50 },
51 Err(_) => {
52 if let Err(err) = Self::write_config(&config_file, &config) {
54 eprintln!("Failed to write default config: {}", err);
55 } else {
56 println!("Created default config at {:?}", config_file);
57 }
58 }
59 }
60
61 config_file
62 });
63
64 let client = Client::builder()
65 .timeout(Duration::from_secs(config.timeout_seconds))
66 .build()
67 .expect("Failed to build HTTP client");
68
69 Self {
70 client,
71 base_url: config.base_url.clone(),
72 headers,
73 config_path,
74 config,
75 }
76 }
77
78 fn ensure_config_dir(dir: &Path) {
80 if !dir.exists() {
81 if let Err(err) = fs::create_dir_all(dir) {
82 eprintln!("Failed to create config directory: {}", err);
83 }
84 }
85 }
86
87 fn load_config(path: &Path) -> Result<AppConfig> {
88 if !path.exists() {
89 return Err(anyhow!("Config file doesn't exist"));
90 }
91
92 let content = fs::read_to_string(path)?;
93 let config = serde_json::from_str(&content)?;
94 Ok(config)
95 }
96
97 fn write_config(path: &Path, config: &AppConfig) -> io::Result<()> {
99 let json = serde_json::to_string_pretty(config)?;
100
101 if let Some(parent) = path.parent() {
103 fs::create_dir_all(parent)?;
104 }
105
106 fs::write(path, json)
107 }
108
109 pub fn get_setting<T: DeserializeOwned>(&self, key: &str) -> Option<T> {
113 self.config.settings.get(key)
114 .and_then(|value| serde_json::from_value(value.clone()).ok())
115 }
116
117 pub fn get_setting_or<T: DeserializeOwned>(&self, key: &str, default: T) -> T {
119 self.get_setting(key).unwrap_or(default)
120 }
121
122 pub fn set_setting<T: Serialize>(&mut self, key: &str, value: T) -> Result<()> {
124 let json_value = serde_json::to_value(value)?;
125 self.config.settings.insert(key.to_string(), json_value);
126
127 self.save_config()
129 }
130
131 pub fn remove_setting(&mut self, key: &str) -> bool {
133 let removed = self.config.settings.remove(key).is_some();
134 if removed {
135 let _ = self.save_config();
137 }
138 removed
139 }
140
141 pub fn save_config(&self) -> Result<()> {
143 if let Some(config_path) = &self.config_path {
144 Self::write_config(config_path, &self.config)
145 .map_err(|e| anyhow!("Failed to save config: {}", e))?;
146 Ok(())
147 } else {
148 Err(anyhow!("No config path available"))
149 }
150 }
151
152 pub fn get_settings_section(&self, prefix: &str) -> HashMap<String, serde_json::Value> {
154 self.config.settings.iter()
155 .filter(|(k, _)| k.starts_with(prefix))
156 .map(|(k, v)| (k.clone(), v.clone()))
157 .collect()
158 }
159
160 pub fn with_base_url(mut self, base_url: &str) -> Self {
162 self.base_url = base_url.to_string();
163 self.config.base_url = base_url.to_string();
164 let _ = self.save_config();
166 self
167 }
168
169 pub fn with_timeout(mut self, seconds: u64) -> Self {
170 self.config.timeout_seconds = seconds;
171 self.client = Client::builder()
173 .timeout(Duration::from_secs(seconds))
174 .build()
175 .expect("Failed to build HTTP client");
176 let _ = self.save_config();
177 self
178 }
179
180 pub fn with_api_key(mut self, api_key: &str) -> Self {
181 self.headers.insert(
182 "Authorization",
183 HeaderValue::from_str(&format!("Bearer {}", api_key))
184 .expect("Invalid API key format")
185 );
186 let _ = self.set_setting("api_key", api_key);
188 self
189 }
190
191 pub fn with_header(mut self, key: &str, value: &str) -> Self {
192 self.headers.insert(
193 HeaderName::from_bytes(key.as_bytes()).expect("Invalid header name"),
194 HeaderValue::from_str(value).expect("Invalid header value")
195 );
196 self
197 }
198
199 pub async fn request<T, U>(&self, method: Method, endpoint: &str, body: Option<&T>) -> Result<U>
201 where
202 T: Serialize + ?Sized,
203 U: DeserializeOwned,
204 {
205 let url = format!("{}{}", self.base_url, endpoint);
206
207 let mut request = self.client.request(method, &url);
208 request = request.headers(self.headers.clone());
209
210 if let Some(data) = body {
211 request = request.json(data);
212 }
213
214 let response = request.send().await?;
215
216 match response.status() {
217 StatusCode::OK | StatusCode::CREATED | StatusCode::ACCEPTED => {
218 let data = response.json::<U>().await?;
219 Ok(data)
220 },
221 status => {
222 let error_text = response.text().await?;
223 Err(anyhow!("API error: {} - {}", status, error_text))
224 }
225 }
226 }
227
228 pub async fn get<U>(&self, endpoint: &str) -> Result<U>
230 where
231 U: DeserializeOwned,
232 {
233 self.request::<(), U>(Method::GET, endpoint, None).await
234 }
235
236 pub async fn post<T, U>(&self, endpoint: &str, body: &T) -> Result<U>
237 where
238 T: Serialize + ?Sized,
239 U: DeserializeOwned,
240 {
241 self.request::<T, U>(Method::POST, endpoint, Some(body)).await
242 }
243
244 pub async fn put<T, U>(&self, endpoint: &str, body: &T) -> Result<U>
245 where
246 T: Serialize + ?Sized,
247 U: DeserializeOwned,
248 {
249 self.request::<T, U>(Method::PUT, endpoint, Some(body)).await
250 }
251
252 pub async fn delete<U>(&self, endpoint: &str) -> Result<U>
253 where
254 U: DeserializeOwned,
255 {
256 self.request::<(), U>(Method::DELETE, endpoint, None).await
257 }
258
259 pub async fn patch<T, U>(&self, endpoint: &str, body: &T) -> Result<U>
260 where
261 T: Serialize + ?Sized,
262 U: DeserializeOwned,
263 {
264 self.request::<T, U>(Method::PATCH, endpoint, Some(body)).await
265 }
266}