1use serde::{Deserialize, Serialize};
7
8use crate::config::ConfigManager;
9use crate::error::{Error, Result};
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct RetryConfig {
14 #[serde(default = "default_max_attempts")]
16 pub max_attempts: u32,
17
18 #[serde(default = "default_initial_backoff")]
20 pub initial_backoff_ms: u64,
21
22 #[serde(default = "default_max_backoff")]
24 pub max_backoff_ms: u64,
25}
26
27fn default_max_attempts() -> u32 {
28 3
29}
30
31fn default_initial_backoff() -> u64 {
32 100
33}
34
35fn default_max_backoff() -> u64 {
36 10000
37}
38
39impl Default for RetryConfig {
40 fn default() -> Self {
41 Self {
42 max_attempts: default_max_attempts(),
43 initial_backoff_ms: default_initial_backoff(),
44 max_backoff_ms: default_max_backoff(),
45 }
46 }
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct TimeoutConfig {
52 #[serde(default = "default_connect_timeout")]
54 pub connect_ms: u64,
55
56 #[serde(default = "default_read_timeout")]
58 pub read_ms: u64,
59}
60
61fn default_connect_timeout() -> u64 {
62 5000
63}
64
65fn default_read_timeout() -> u64 {
66 30000
67}
68
69impl Default for TimeoutConfig {
70 fn default() -> Self {
71 Self {
72 connect_ms: default_connect_timeout(),
73 read_ms: default_read_timeout(),
74 }
75 }
76}
77
78#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct Alias {
81 pub name: String,
83
84 pub endpoint: String,
86
87 pub access_key: String,
89
90 pub secret_key: String,
92
93 #[serde(default = "default_region")]
95 pub region: String,
96
97 #[serde(default = "default_signature")]
99 pub signature: String,
100
101 #[serde(default = "default_bucket_lookup")]
103 pub bucket_lookup: String,
104
105 #[serde(default)]
107 pub insecure: bool,
108
109 #[serde(default, skip_serializing_if = "Option::is_none")]
111 pub ca_bundle: Option<String>,
112
113 #[serde(default, skip_serializing_if = "Option::is_none")]
115 pub retry: Option<RetryConfig>,
116
117 #[serde(default, skip_serializing_if = "Option::is_none")]
119 pub timeout: Option<TimeoutConfig>,
120}
121
122fn default_region() -> String {
123 "us-east-1".to_string()
124}
125
126fn default_signature() -> String {
127 "v4".to_string()
128}
129
130fn default_bucket_lookup() -> String {
131 "auto".to_string()
132}
133
134impl Alias {
135 pub fn new(
137 name: impl Into<String>,
138 endpoint: impl Into<String>,
139 access_key: impl Into<String>,
140 secret_key: impl Into<String>,
141 ) -> Self {
142 Self {
143 name: name.into(),
144 endpoint: endpoint.into(),
145 access_key: access_key.into(),
146 secret_key: secret_key.into(),
147 region: default_region(),
148 signature: default_signature(),
149 bucket_lookup: default_bucket_lookup(),
150 insecure: false,
151 ca_bundle: None,
152 retry: None,
153 timeout: None,
154 }
155 }
156
157 pub fn retry_config(&self) -> RetryConfig {
159 self.retry.clone().unwrap_or_default()
160 }
161
162 pub fn timeout_config(&self) -> TimeoutConfig {
164 self.timeout.clone().unwrap_or_default()
165 }
166}
167
168pub struct AliasManager {
170 config_manager: ConfigManager,
171}
172
173impl AliasManager {
174 pub fn with_config_manager(config_manager: ConfigManager) -> Self {
176 Self { config_manager }
177 }
178
179 pub fn new() -> Result<Self> {
181 let config_manager = ConfigManager::new()?;
182 Ok(Self { config_manager })
183 }
184
185 pub fn list(&self) -> Result<Vec<Alias>> {
187 let config = self.config_manager.load()?;
188 Ok(config.aliases)
189 }
190
191 pub fn get(&self, name: &str) -> Result<Alias> {
193 let config = self.config_manager.load()?;
194 config
195 .aliases
196 .into_iter()
197 .find(|a| a.name == name)
198 .ok_or_else(|| Error::AliasNotFound(name.to_string()))
199 }
200
201 pub fn set(&self, alias: Alias) -> Result<()> {
203 let mut config = self.config_manager.load()?;
204
205 config.aliases.retain(|a| a.name != alias.name);
207 config.aliases.push(alias);
208
209 self.config_manager.save(&config)
210 }
211
212 pub fn remove(&self, name: &str) -> Result<()> {
214 let mut config = self.config_manager.load()?;
215 let original_len = config.aliases.len();
216
217 config.aliases.retain(|a| a.name != name);
218
219 if config.aliases.len() == original_len {
220 return Err(Error::AliasNotFound(name.to_string()));
221 }
222
223 self.config_manager.save(&config)
224 }
225
226 pub fn exists(&self, name: &str) -> Result<bool> {
228 let config = self.config_manager.load()?;
229 Ok(config.aliases.iter().any(|a| a.name == name))
230 }
231}
232
233#[cfg(test)]
234mod tests {
235 use super::*;
236 use tempfile::TempDir;
237
238 fn temp_alias_manager() -> (AliasManager, TempDir) {
239 let temp_dir = TempDir::new().unwrap();
240 let config_path = temp_dir.path().join("config.toml");
241 let config_manager = ConfigManager::with_path(config_path);
242 let alias_manager = AliasManager::with_config_manager(config_manager);
243 (alias_manager, temp_dir)
244 }
245
246 #[test]
247 fn test_alias_new() {
248 let alias = Alias::new("test", "http://localhost:9000", "access", "secret");
249 assert_eq!(alias.name, "test");
250 assert_eq!(alias.endpoint, "http://localhost:9000");
251 assert_eq!(alias.region, "us-east-1");
252 assert_eq!(alias.signature, "v4");
253 assert_eq!(alias.bucket_lookup, "auto");
254 assert!(!alias.insecure);
255 }
256
257 #[test]
258 fn test_alias_manager_set_and_get() {
259 let (manager, _temp_dir) = temp_alias_manager();
260
261 let alias = Alias::new("local", "http://localhost:9000", "accesskey", "secretkey");
262 manager.set(alias).unwrap();
263
264 let retrieved = manager.get("local").unwrap();
265 assert_eq!(retrieved.name, "local");
266 assert_eq!(retrieved.endpoint, "http://localhost:9000");
267 }
268
269 #[test]
270 fn test_alias_manager_list() {
271 let (manager, _temp_dir) = temp_alias_manager();
272
273 manager
274 .set(Alias::new("a", "http://a:9000", "a", "a"))
275 .unwrap();
276 manager
277 .set(Alias::new("b", "http://b:9000", "b", "b"))
278 .unwrap();
279
280 let aliases = manager.list().unwrap();
281 assert_eq!(aliases.len(), 2);
282 }
283
284 #[test]
285 fn test_alias_manager_remove() {
286 let (manager, _temp_dir) = temp_alias_manager();
287
288 manager
289 .set(Alias::new("test", "http://localhost:9000", "a", "b"))
290 .unwrap();
291 assert!(manager.exists("test").unwrap());
292
293 manager.remove("test").unwrap();
294 assert!(!manager.exists("test").unwrap());
295 }
296
297 #[test]
298 fn test_alias_manager_remove_not_found() {
299 let (manager, _temp_dir) = temp_alias_manager();
300
301 let result = manager.remove("nonexistent");
302 assert!(result.is_err());
303 assert!(matches!(result.unwrap_err(), Error::AliasNotFound(_)));
304 }
305
306 #[test]
307 fn test_alias_manager_get_not_found() {
308 let (manager, _temp_dir) = temp_alias_manager();
309
310 let result = manager.get("nonexistent");
311 assert!(result.is_err());
312 assert!(matches!(result.unwrap_err(), Error::AliasNotFound(_)));
313 }
314
315 #[test]
316 fn test_alias_update_existing() {
317 let (manager, _temp_dir) = temp_alias_manager();
318
319 manager
320 .set(Alias::new("test", "http://old:9000", "a", "b"))
321 .unwrap();
322 manager
323 .set(Alias::new("test", "http://new:9000", "c", "d"))
324 .unwrap();
325
326 let aliases = manager.list().unwrap();
327 assert_eq!(aliases.len(), 1);
328 assert_eq!(aliases[0].endpoint, "http://new:9000");
329 }
330}