use serde::{Deserialize, Serialize};
use crate::config::ConfigManager;
use crate::error::{Error, Result};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RetryConfig {
#[serde(default = "default_max_attempts")]
pub max_attempts: u32,
#[serde(default = "default_initial_backoff")]
pub initial_backoff_ms: u64,
#[serde(default = "default_max_backoff")]
pub max_backoff_ms: u64,
}
fn default_max_attempts() -> u32 {
3
}
fn default_initial_backoff() -> u64 {
100
}
fn default_max_backoff() -> u64 {
10000
}
impl Default for RetryConfig {
fn default() -> Self {
Self {
max_attempts: default_max_attempts(),
initial_backoff_ms: default_initial_backoff(),
max_backoff_ms: default_max_backoff(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TimeoutConfig {
#[serde(default = "default_connect_timeout")]
pub connect_ms: u64,
#[serde(default = "default_read_timeout")]
pub read_ms: u64,
}
fn default_connect_timeout() -> u64 {
5000
}
fn default_read_timeout() -> u64 {
30000
}
impl Default for TimeoutConfig {
fn default() -> Self {
Self {
connect_ms: default_connect_timeout(),
read_ms: default_read_timeout(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Alias {
pub name: String,
pub endpoint: String,
pub access_key: String,
pub secret_key: String,
#[serde(default = "default_region")]
pub region: String,
#[serde(default = "default_signature")]
pub signature: String,
#[serde(default = "default_bucket_lookup")]
pub bucket_lookup: String,
#[serde(default)]
pub insecure: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ca_bundle: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub retry: Option<RetryConfig>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub timeout: Option<TimeoutConfig>,
}
fn default_region() -> String {
"us-east-1".to_string()
}
fn default_signature() -> String {
"v4".to_string()
}
fn default_bucket_lookup() -> String {
"auto".to_string()
}
impl Alias {
pub fn new(
name: impl Into<String>,
endpoint: impl Into<String>,
access_key: impl Into<String>,
secret_key: impl Into<String>,
) -> Self {
Self {
name: name.into(),
endpoint: endpoint.into(),
access_key: access_key.into(),
secret_key: secret_key.into(),
region: default_region(),
signature: default_signature(),
bucket_lookup: default_bucket_lookup(),
insecure: false,
ca_bundle: None,
retry: None,
timeout: None,
}
}
pub fn retry_config(&self) -> RetryConfig {
self.retry.clone().unwrap_or_default()
}
pub fn timeout_config(&self) -> TimeoutConfig {
self.timeout.clone().unwrap_or_default()
}
}
pub struct AliasManager {
config_manager: ConfigManager,
}
impl AliasManager {
pub fn with_config_manager(config_manager: ConfigManager) -> Self {
Self { config_manager }
}
pub fn new() -> Result<Self> {
let config_manager = ConfigManager::new()?;
Ok(Self { config_manager })
}
pub fn list(&self) -> Result<Vec<Alias>> {
let config = self.config_manager.load()?;
Ok(config.aliases)
}
pub fn get(&self, name: &str) -> Result<Alias> {
let config = self.config_manager.load()?;
config
.aliases
.into_iter()
.find(|a| a.name == name)
.ok_or_else(|| Error::AliasNotFound(name.to_string()))
}
pub fn set(&self, alias: Alias) -> Result<()> {
let mut config = self.config_manager.load()?;
config.aliases.retain(|a| a.name != alias.name);
config.aliases.push(alias);
self.config_manager.save(&config)
}
pub fn remove(&self, name: &str) -> Result<()> {
let mut config = self.config_manager.load()?;
let original_len = config.aliases.len();
config.aliases.retain(|a| a.name != name);
if config.aliases.len() == original_len {
return Err(Error::AliasNotFound(name.to_string()));
}
self.config_manager.save(&config)
}
pub fn exists(&self, name: &str) -> Result<bool> {
let config = self.config_manager.load()?;
Ok(config.aliases.iter().any(|a| a.name == name))
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
fn temp_alias_manager() -> (AliasManager, TempDir) {
let temp_dir = TempDir::new().unwrap();
let config_path = temp_dir.path().join("config.toml");
let config_manager = ConfigManager::with_path(config_path);
let alias_manager = AliasManager::with_config_manager(config_manager);
(alias_manager, temp_dir)
}
#[test]
fn test_alias_new() {
let alias = Alias::new("test", "http://localhost:9000", "access", "secret");
assert_eq!(alias.name, "test");
assert_eq!(alias.endpoint, "http://localhost:9000");
assert_eq!(alias.region, "us-east-1");
assert_eq!(alias.signature, "v4");
assert_eq!(alias.bucket_lookup, "auto");
assert!(!alias.insecure);
}
#[test]
fn test_alias_manager_set_and_get() {
let (manager, _temp_dir) = temp_alias_manager();
let alias = Alias::new("local", "http://localhost:9000", "accesskey", "secretkey");
manager.set(alias).unwrap();
let retrieved = manager.get("local").unwrap();
assert_eq!(retrieved.name, "local");
assert_eq!(retrieved.endpoint, "http://localhost:9000");
}
#[test]
fn test_alias_manager_list() {
let (manager, _temp_dir) = temp_alias_manager();
manager
.set(Alias::new("a", "http://a:9000", "a", "a"))
.unwrap();
manager
.set(Alias::new("b", "http://b:9000", "b", "b"))
.unwrap();
let aliases = manager.list().unwrap();
assert_eq!(aliases.len(), 2);
}
#[test]
fn test_alias_manager_remove() {
let (manager, _temp_dir) = temp_alias_manager();
manager
.set(Alias::new("test", "http://localhost:9000", "a", "b"))
.unwrap();
assert!(manager.exists("test").unwrap());
manager.remove("test").unwrap();
assert!(!manager.exists("test").unwrap());
}
#[test]
fn test_alias_manager_remove_not_found() {
let (manager, _temp_dir) = temp_alias_manager();
let result = manager.remove("nonexistent");
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), Error::AliasNotFound(_)));
}
#[test]
fn test_alias_manager_get_not_found() {
let (manager, _temp_dir) = temp_alias_manager();
let result = manager.get("nonexistent");
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), Error::AliasNotFound(_)));
}
#[test]
fn test_alias_update_existing() {
let (manager, _temp_dir) = temp_alias_manager();
manager
.set(Alias::new("test", "http://old:9000", "a", "b"))
.unwrap();
manager
.set(Alias::new("test", "http://new:9000", "c", "d"))
.unwrap();
let aliases = manager.list().unwrap();
assert_eq!(aliases.len(), 1);
assert_eq!(aliases[0].endpoint, "http://new:9000");
}
}