ricecoder_storage/config/
loader.rs1use super::Config;
7use crate::error::{StorageError, StorageResult};
8use crate::types::ConfigFormat;
9use std::path::Path;
10
11pub struct ConfigLoader;
13
14impl ConfigLoader {
15 pub fn load_from_file<P: AsRef<Path>>(path: P) -> StorageResult<Config> {
20 let path = path.as_ref();
21 let content = std::fs::read_to_string(path).map_err(|e| {
22 StorageError::io_error(path.to_path_buf(), crate::error::IoOperation::Read, e)
23 })?;
24
25 let extension = path
26 .extension()
27 .and_then(|ext| ext.to_str())
28 .ok_or_else(|| {
29 StorageError::parse_error(path.to_path_buf(), "unknown", "File has no extension")
30 })?;
31
32 let format = ConfigFormat::from_extension(extension).ok_or_else(|| {
33 StorageError::parse_error(
34 path.to_path_buf(),
35 "unknown",
36 format!("Unsupported file format: {}", extension),
37 )
38 })?;
39
40 Self::load_from_string(&content, format, path)
41 }
42
43 pub fn load_from_string<P: AsRef<Path>>(
45 content: &str,
46 format: ConfigFormat,
47 path: P,
48 ) -> StorageResult<Config> {
49 let path = path.as_ref();
50 match format {
51 ConfigFormat::Yaml => Self::parse_yaml(content, path),
52 ConfigFormat::Toml => Self::parse_toml(content, path),
53 ConfigFormat::Json => Self::parse_json(content, path),
54 }
55 }
56
57 fn parse_yaml<P: AsRef<Path>>(content: &str, path: P) -> StorageResult<Config> {
59 let path = path.as_ref();
60 serde_yaml::from_str(content)
61 .map_err(|e| StorageError::parse_error(path.to_path_buf(), "YAML", e.to_string()))
62 }
63
64 fn parse_toml<P: AsRef<Path>>(content: &str, path: P) -> StorageResult<Config> {
66 let path = path.as_ref();
67 toml::from_str(content)
68 .map_err(|e| StorageError::parse_error(path.to_path_buf(), "TOML", e.to_string()))
69 }
70
71 fn parse_json<P: AsRef<Path>>(content: &str, path: P) -> StorageResult<Config> {
73 let path = path.as_ref();
74 serde_json::from_str(content)
75 .map_err(|e| StorageError::parse_error(path.to_path_buf(), "JSON", e.to_string()))
76 }
77
78 pub fn serialize(config: &Config, format: ConfigFormat) -> StorageResult<String> {
80 match format {
81 ConfigFormat::Yaml => serde_yaml::to_string(config)
82 .map_err(|e| StorageError::Internal(format!("Failed to serialize to YAML: {}", e))),
83 ConfigFormat::Toml => toml::to_string_pretty(config)
84 .map_err(|e| StorageError::Internal(format!("Failed to serialize to TOML: {}", e))),
85 ConfigFormat::Json => serde_json::to_string_pretty(config)
86 .map_err(|e| StorageError::Internal(format!("Failed to serialize to JSON: {}", e))),
87 }
88 }
89
90 pub fn save_to_file<P: AsRef<Path>>(
92 config: &Config,
93 path: P,
94 format: ConfigFormat,
95 ) -> StorageResult<()> {
96 let path = path.as_ref();
97 let content = Self::serialize(config, format)?;
98 std::fs::write(path, content).map_err(|e| {
99 StorageError::io_error(path.to_path_buf(), crate::error::IoOperation::Write, e)
100 })
101 }
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107
108 #[test]
109 fn test_load_yaml_config() {
110 let yaml_content = r#"
111providers:
112 default_provider: openai
113 api_keys:
114 openai: test-key
115defaults:
116 model: gpt-4
117 temperature: 0.7
118steering: []
119"#;
120 let config = ConfigLoader::load_from_string(yaml_content, ConfigFormat::Yaml, "test.yaml")
121 .expect("Failed to parse YAML");
122 assert_eq!(
123 config.providers.default_provider,
124 Some("openai".to_string())
125 );
126 assert_eq!(config.defaults.model, Some("gpt-4".to_string()));
127 }
128
129 #[test]
130 fn test_load_toml_config() {
131 let toml_content = r#"[providers]
132default_provider = "openai"
133api_keys = { openai = "test-key" }
134endpoints = {}
135
136[defaults]
137model = "gpt-4"
138temperature = 0.7
139
140steering = []
141custom = {}
142"#;
143 let config = ConfigLoader::load_from_string(toml_content, ConfigFormat::Toml, "test.toml")
144 .expect("Failed to parse TOML");
145 assert_eq!(
146 config.providers.default_provider,
147 Some("openai".to_string())
148 );
149 assert_eq!(config.defaults.model, Some("gpt-4".to_string()));
150 }
151
152 #[test]
153 fn test_load_json_config() {
154 let json_content = r#"{
155 "providers": {
156 "default_provider": "openai",
157 "api_keys": {
158 "openai": "test-key"
159 },
160 "endpoints": {}
161 },
162 "defaults": {
163 "model": "gpt-4",
164 "temperature": 0.7
165 },
166 "steering": []
167}"#;
168 let config = ConfigLoader::load_from_string(json_content, ConfigFormat::Json, "test.json")
169 .expect("Failed to parse JSON");
170 assert_eq!(
171 config.providers.default_provider,
172 Some("openai".to_string())
173 );
174 assert_eq!(config.defaults.model, Some("gpt-4".to_string()));
175 }
176
177 #[test]
178 fn test_serialize_yaml() {
179 let config = Config::default();
180 let yaml = ConfigLoader::serialize(&config, ConfigFormat::Yaml)
181 .expect("Failed to serialize to YAML");
182 assert!(yaml.contains("providers:"));
183 assert!(yaml.contains("defaults:"));
184 }
185
186 #[test]
187 fn test_serialize_toml() {
188 let config = Config::default();
189 let toml = ConfigLoader::serialize(&config, ConfigFormat::Toml)
190 .expect("Failed to serialize to TOML");
191 assert!(toml.contains("providers") || toml.contains("[providers]"));
192 assert!(toml.contains("defaults") || toml.contains("[defaults]"));
193 }
194
195 #[test]
196 fn test_serialize_json() {
197 let config = Config::default();
198 let json = ConfigLoader::serialize(&config, ConfigFormat::Json)
199 .expect("Failed to serialize to JSON");
200 assert!(json.contains("\"providers\""));
201 assert!(json.contains("\"defaults\""));
202 }
203
204 #[test]
205 fn test_save_and_load_yaml() {
206 let temp_dir = tempfile::TempDir::new().expect("Failed to create temp dir");
207 let file_path = temp_dir.path().join("config.yaml");
208 let config = Config::default();
209
210 ConfigLoader::save_to_file(&config, &file_path, ConfigFormat::Yaml)
211 .expect("Failed to save config");
212
213 let loaded = ConfigLoader::load_from_file(&file_path).expect("Failed to load config");
214
215 assert_eq!(config, loaded);
216 }
217
218 #[test]
219 fn test_save_and_load_toml() {
220 let temp_dir = tempfile::TempDir::new().expect("Failed to create temp dir");
221 let file_path = temp_dir.path().join("config.toml");
222 let config = Config::default();
223
224 ConfigLoader::save_to_file(&config, &file_path, ConfigFormat::Toml)
225 .expect("Failed to save config");
226
227 let loaded = ConfigLoader::load_from_file(&file_path).expect("Failed to load config");
228
229 assert_eq!(config, loaded);
230 }
231
232 #[test]
233 fn test_save_and_load_json() {
234 let temp_dir = tempfile::TempDir::new().expect("Failed to create temp dir");
235 let file_path = temp_dir.path().join("config.json");
236 let config = Config::default();
237
238 ConfigLoader::save_to_file(&config, &file_path, ConfigFormat::Json)
239 .expect("Failed to save config");
240
241 let loaded = ConfigLoader::load_from_file(&file_path).expect("Failed to load config");
242
243 assert_eq!(config, loaded);
244 }
245}