Skip to main content

fastskill_core/core/registry/
config.rs

1//! Registry configuration management
2
3use crate::core::service::ServiceError;
4use serde::{Deserialize, Serialize};
5use std::fs;
6use std::path::PathBuf;
7
8/// Registry configuration manager
9pub struct RegistryConfigManager {
10    config_path: PathBuf,
11    config: Option<RegistriesConfig>,
12}
13
14/// Main registries configuration
15#[derive(Debug, Clone, Serialize, Deserialize, Default)]
16pub struct RegistriesConfig {
17    /// Default registry configuration
18    #[serde(default)]
19    pub registry: Option<DefaultRegistryConfig>,
20
21    /// Additional named registries
22    #[serde(default)]
23    pub registries: Vec<RegistryConfig>,
24}
25
26/// Default registry configuration
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct DefaultRegistryConfig {
29    pub name: String,
30    #[serde(rename = "type")]
31    pub registry_type: String,
32    pub index_url: String,
33    #[serde(default)]
34    pub auth: Option<AuthConfig>,
35    #[serde(default)]
36    pub storage: Option<StorageConfig>,
37}
38
39/// Registry configuration
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct RegistryConfig {
42    pub name: String,
43    #[serde(rename = "type")]
44    pub registry_type: String,
45    pub index_url: String,
46    #[serde(default)]
47    pub auth: Option<AuthConfig>,
48    #[serde(default)]
49    pub storage: Option<StorageConfig>,
50}
51
52/// Authentication configuration
53#[derive(Debug, Clone, Serialize, Deserialize)]
54#[serde(tag = "type")]
55pub enum AuthConfig {
56    #[serde(rename = "pat")]
57    Pat { env_var: String },
58    #[serde(rename = "ssh")]
59    Ssh { key_path: PathBuf },
60    #[serde(rename = "api_key")]
61    ApiKey { env_var: String },
62}
63
64/// Storage backend configuration
65#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct StorageConfig {
67    #[serde(rename = "type")]
68    pub storage_type: String,
69    #[serde(default)]
70    pub repository: Option<String>,
71    #[serde(default)]
72    pub bucket: Option<String>,
73    #[serde(default)]
74    pub region: Option<String>,
75    #[serde(default)]
76    pub endpoint: Option<String>,
77    #[serde(default)]
78    pub base_url: Option<String>,
79}
80
81impl RegistryConfigManager {
82    /// Create a new registry config manager
83    pub fn new(config_path: PathBuf) -> Self {
84        Self {
85            config_path,
86            config: None,
87        }
88    }
89
90    /// Load configuration from file
91    pub fn load(&mut self) -> Result<(), ServiceError> {
92        if !self.config_path.exists() {
93            // Return empty config if file doesn't exist
94            self.config = Some(RegistriesConfig {
95                registry: None,
96                registries: Vec::new(),
97            });
98            return Ok(());
99        }
100
101        let content = fs::read_to_string(&self.config_path).map_err(ServiceError::Io)?;
102
103        let config: RegistriesConfig = toml::from_str(&content).map_err(|e| {
104            ServiceError::Custom(format!("Failed to parse registries config: {}", e))
105        })?;
106
107        self.config = Some(config);
108        Ok(())
109    }
110
111    /// Get default registry configuration
112    pub fn get_default_registry(&self) -> Result<Option<RegistryConfig>, ServiceError> {
113        let config = self
114            .config
115            .as_ref()
116            .ok_or_else(|| ServiceError::Custom("Registry config not loaded".to_string()))?;
117
118        if let Some(ref default) = config.registry {
119            Ok(Some(RegistryConfig {
120                name: default.name.clone(),
121                registry_type: default.registry_type.clone(),
122                index_url: default.index_url.clone(),
123                auth: default.auth.clone(),
124                storage: default.storage.clone(),
125            }))
126        } else {
127            Ok(None)
128        }
129    }
130
131    /// Get registry by name
132    pub fn get_registry(&self, name: &str) -> Result<Option<RegistryConfig>, ServiceError> {
133        let config = self
134            .config
135            .as_ref()
136            .ok_or_else(|| ServiceError::Custom("Registry config not loaded".to_string()))?;
137
138        // Check if it's the default registry
139        if let Some(ref default) = config.registry {
140            if default.name == name {
141                return Ok(Some(RegistryConfig {
142                    name: default.name.clone(),
143                    registry_type: default.registry_type.clone(),
144                    index_url: default.index_url.clone(),
145                    auth: default.auth.clone(),
146                    storage: default.storage.clone(),
147                }));
148            }
149        }
150
151        // Check named registries
152        for registry in &config.registries {
153            if registry.name == name {
154                return Ok(Some(registry.clone()));
155            }
156        }
157
158        Ok(None)
159    }
160
161    /// List all configured registries
162    pub fn list_registries(&self) -> Result<Vec<String>, ServiceError> {
163        let config = self
164            .config
165            .as_ref()
166            .ok_or_else(|| ServiceError::Custom("Registry config not loaded".to_string()))?;
167
168        let mut names = Vec::new();
169
170        if let Some(ref default) = config.registry {
171            names.push(default.name.clone());
172        }
173
174        for registry in &config.registries {
175            names.push(registry.name.clone());
176        }
177
178        Ok(names)
179    }
180}
181
182#[cfg(test)]
183#[allow(clippy::unwrap_used)]
184mod tests {
185    use super::*;
186    use tempfile::TempDir;
187
188    #[test]
189    fn test_load_registry_config() {
190        let temp_dir = TempDir::new().unwrap();
191        let config_path = temp_dir.path().join("registries.toml");
192
193        let config_content = r#"
194[registry]
195name = "primary"
196type = "github"
197index_url = "https://github.com/org/registry-index"
198
199[[registries]]
200name = "enterprise"
201type = "github"
202index_url = "https://github.com/org/enterprise-registry"
203"#;
204
205        std::fs::write(&config_path, config_content).unwrap();
206
207        let mut manager = RegistryConfigManager::new(config_path);
208        manager.load().unwrap();
209
210        let default = manager.get_default_registry().unwrap();
211        assert!(default.is_some());
212        assert_eq!(default.unwrap().name, "primary");
213
214        let enterprise = manager.get_registry("enterprise").unwrap();
215        assert!(enterprise.is_some());
216        assert_eq!(enterprise.unwrap().name, "enterprise");
217    }
218}