omni/
api_client.rs

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    // Store arbitrary key-value pairs for different parts of the app
14    #[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        // Initialize with defaults
34        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            // Ensure the app config directory exists
43            Self::ensure_config_dir(&app_config_dir);
44            
45            // Load config if it exists, otherwise create default
46            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                    // Write default config
53                    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    // Helper methods for configuration management
79    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    // Renamed to avoid collision with instance method
98    fn write_config(path: &Path, config: &AppConfig) -> io::Result<()> {
99        let json = serde_json::to_string_pretty(config)?;
100        
101        // Ensure parent directory exists
102        if let Some(parent) = path.parent() {
103            fs::create_dir_all(parent)?;
104        }
105        
106        fs::write(path, json)
107    }
108    
109    // Key-value storage methods
110    
111    /// Get a setting value by key
112    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    /// Get a setting with default fallback
118    pub fn get_setting_or<T: DeserializeOwned>(&self, key: &str, default: T) -> T {
119        self.get_setting(key).unwrap_or(default)
120    }
121    
122    /// Set a setting value
123    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        // Save the updated config
128        self.save_config()
129    }
130    
131    /// Remove a setting
132    pub fn remove_setting(&mut self, key: &str) -> bool {
133        let removed = self.config.settings.remove(key).is_some();
134        if removed {
135            // Only save if something was actually removed
136            let _ = self.save_config();
137        }
138        removed
139    }
140    
141    /// Save the current configuration to disk
142    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    /// Get a section of settings with a common prefix
153    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    // Builder methods
161    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        // Ignore errors during chain building
165        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        // Recreate client with new timeout
172        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        // Store API key in settings
187        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    // HTTP Request methods (unchanged)
200    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    // Convenience methods for common HTTP verbs
229    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}