fastskill_core/core/registry/
config.rs1use crate::core::service::ServiceError;
4use serde::{Deserialize, Serialize};
5use std::fs;
6use std::path::PathBuf;
7
8pub struct RegistryConfigManager {
10 config_path: PathBuf,
11 config: Option<RegistriesConfig>,
12}
13
14#[derive(Debug, Clone, Serialize, Deserialize, Default)]
16pub struct RegistriesConfig {
17 #[serde(default)]
19 pub registry: Option<DefaultRegistryConfig>,
20
21 #[serde(default)]
23 pub registries: Vec<RegistryConfig>,
24}
25
26#[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#[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#[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#[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 pub fn new(config_path: PathBuf) -> Self {
84 Self {
85 config_path,
86 config: None,
87 }
88 }
89
90 pub fn load(&mut self) -> Result<(), ServiceError> {
92 if !self.config_path.exists() {
93 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 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 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 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 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 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}