use crate::core::service::ServiceError;
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::PathBuf;
pub struct RegistryConfigManager {
config_path: PathBuf,
config: Option<RegistriesConfig>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct RegistriesConfig {
#[serde(default)]
pub registry: Option<DefaultRegistryConfig>,
#[serde(default)]
pub registries: Vec<RegistryConfig>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DefaultRegistryConfig {
pub name: String,
#[serde(rename = "type")]
pub registry_type: String,
pub index_url: String,
#[serde(default)]
pub auth: Option<AuthConfig>,
#[serde(default)]
pub storage: Option<StorageConfig>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RegistryConfig {
pub name: String,
#[serde(rename = "type")]
pub registry_type: String,
pub index_url: String,
#[serde(default)]
pub auth: Option<AuthConfig>,
#[serde(default)]
pub storage: Option<StorageConfig>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum AuthConfig {
#[serde(rename = "pat")]
Pat { env_var: String },
#[serde(rename = "ssh")]
Ssh { key_path: PathBuf },
#[serde(rename = "api_key")]
ApiKey { env_var: String },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StorageConfig {
#[serde(rename = "type")]
pub storage_type: String,
#[serde(default)]
pub repository: Option<String>,
#[serde(default)]
pub bucket: Option<String>,
#[serde(default)]
pub region: Option<String>,
#[serde(default)]
pub endpoint: Option<String>,
#[serde(default)]
pub base_url: Option<String>,
}
impl RegistryConfigManager {
pub fn new(config_path: PathBuf) -> Self {
Self {
config_path,
config: None,
}
}
pub fn load(&mut self) -> Result<(), ServiceError> {
if !self.config_path.exists() {
self.config = Some(RegistriesConfig {
registry: None,
registries: Vec::new(),
});
return Ok(());
}
let content = fs::read_to_string(&self.config_path).map_err(ServiceError::Io)?;
let config: RegistriesConfig = toml::from_str(&content).map_err(|e| {
ServiceError::Custom(format!("Failed to parse registries config: {}", e))
})?;
self.config = Some(config);
Ok(())
}
pub fn get_default_registry(&self) -> Result<Option<RegistryConfig>, ServiceError> {
let config = self
.config
.as_ref()
.ok_or_else(|| ServiceError::Custom("Registry config not loaded".to_string()))?;
if let Some(ref default) = config.registry {
Ok(Some(RegistryConfig {
name: default.name.clone(),
registry_type: default.registry_type.clone(),
index_url: default.index_url.clone(),
auth: default.auth.clone(),
storage: default.storage.clone(),
}))
} else {
Ok(None)
}
}
pub fn get_registry(&self, name: &str) -> Result<Option<RegistryConfig>, ServiceError> {
let config = self
.config
.as_ref()
.ok_or_else(|| ServiceError::Custom("Registry config not loaded".to_string()))?;
if let Some(ref default) = config.registry {
if default.name == name {
return Ok(Some(RegistryConfig {
name: default.name.clone(),
registry_type: default.registry_type.clone(),
index_url: default.index_url.clone(),
auth: default.auth.clone(),
storage: default.storage.clone(),
}));
}
}
for registry in &config.registries {
if registry.name == name {
return Ok(Some(registry.clone()));
}
}
Ok(None)
}
pub fn list_registries(&self) -> Result<Vec<String>, ServiceError> {
let config = self
.config
.as_ref()
.ok_or_else(|| ServiceError::Custom("Registry config not loaded".to_string()))?;
let mut names = Vec::new();
if let Some(ref default) = config.registry {
names.push(default.name.clone());
}
for registry in &config.registries {
names.push(registry.name.clone());
}
Ok(names)
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
use tempfile::TempDir;
#[test]
fn test_load_registry_config() {
let temp_dir = TempDir::new().unwrap();
let config_path = temp_dir.path().join("registries.toml");
let config_content = r#"
[registry]
name = "primary"
type = "github"
index_url = "https://github.com/org/registry-index"
[[registries]]
name = "enterprise"
type = "github"
index_url = "https://github.com/org/enterprise-registry"
"#;
std::fs::write(&config_path, config_content).unwrap();
let mut manager = RegistryConfigManager::new(config_path);
manager.load().unwrap();
let default = manager.get_default_registry().unwrap();
assert!(default.is_some());
assert_eq!(default.unwrap().name, "primary");
let enterprise = manager.get_registry("enterprise").unwrap();
assert!(enterprise.is_some());
assert_eq!(enterprise.unwrap().name, "enterprise");
}
}