codesynapse_core/
config.rs1use serde::{Deserialize, Serialize};
2use std::path::Path;
3
4#[derive(Debug, Clone, Serialize, Deserialize, Default)]
5pub struct CodeSynapseConfig {
6 pub output: Option<String>,
7 #[serde(default)]
8 pub no_llm: bool,
9 #[serde(default)]
10 pub code_only: bool,
11 pub formats: Option<Vec<String>>,
12 pub llm: Option<LlmConfig>,
13 pub embeddings: Option<EmbeddingsConfig>,
14}
15
16#[derive(Debug, Clone, Serialize, Deserialize, Default)]
17pub struct EmbeddingsConfig {
18 pub model_path: Option<String>,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct LlmConfig {
25 pub provider: Option<String>,
26 pub model: Option<String>,
27 pub api_key: Option<String>,
28 pub base_url: Option<String>,
29}
30
31impl CodeSynapseConfig {
32 pub fn from_file(path: &Path) -> Result<Self, String> {
33 let content =
34 std::fs::read_to_string(path).map_err(|e| format!("Failed to read config: {}", e))?;
35 toml::from_str(&content).map_err(|e| format!("Failed to parse config: {}", e))
36 }
37
38 pub fn discover(start: &Path) -> Result<Self, String> {
39 let candidates = [
40 start.join("codesynapse.toml"),
41 start.join("codesynapse.toml"),
42 start.join(".codesynapse.toml"),
43 start.join(".codesynapse.toml"),
44 ];
45 for candidate in &candidates {
46 if candidate.exists() {
47 return Self::from_file(candidate);
48 }
49 }
50 Err("No codesynapse.toml found".to_string())
51 }
52
53 pub fn write_default(path: &Path) -> Result<(), String> {
54 let config = CodeSynapseConfig::default();
55 let content = toml::to_string_pretty(&config)
56 .map_err(|e| format!("Failed to serialize config: {}", e))?;
57 std::fs::write(path, content).map_err(|e| format!("Failed to write config: {}", e))?;
58 Ok(())
59 }
60}
61
62#[cfg(test)]
63mod tests {
64 use super::*;
65
66 #[test]
67 fn test_config_default() {
68 let config = CodeSynapseConfig::default();
69 assert!(!config.no_llm);
70 assert!(!config.code_only);
71 assert!(config.output.is_none());
72 assert!(config.formats.is_none());
73 assert!(config.llm.is_none());
74 }
75
76 #[test]
77 fn test_config_parse_full() {
78 let toml_str = r#"
79output = "codesynapse-out"
80no_llm = true
81code_only = true
82formats = ["json", "html"]
83
84[llm]
85provider = "anthropic"
86model = "claude-sonnet-4-20250514"
87"#;
88 let config: CodeSynapseConfig = toml::from_str(toml_str).unwrap();
89 assert_eq!(config.output, Some("codesynapse-out".to_string()));
90 assert!(config.no_llm);
91 assert!(config.code_only);
92 assert_eq!(
93 config.formats,
94 Some(vec!["json".to_string(), "html".to_string()])
95 );
96 let llm = config.llm.unwrap();
97 assert_eq!(llm.provider, Some("anthropic".to_string()));
98 assert_eq!(llm.model, Some("claude-sonnet-4-20250514".to_string()));
99 }
100
101 #[test]
102 fn test_config_parse_minimal() {
103 let toml_str = r#"no_llm = true"#;
104 let config: CodeSynapseConfig = toml::from_str(toml_str).unwrap();
105 assert!(!config.code_only);
106 assert!(config.no_llm);
107 assert!(config.output.is_none());
108 assert!(config.formats.is_none());
109 }
110
111 #[test]
112 fn test_config_write_default() {
113 let dir = std::env::temp_dir().join("codesynapse_test_config");
114 let _ = std::fs::remove_dir_all(&dir);
115 std::fs::create_dir_all(&dir).unwrap();
116
117 let path = dir.join("codesynapse.toml");
118 CodeSynapseConfig::write_default(&path).unwrap();
119 assert!(path.exists());
120
121 let loaded = CodeSynapseConfig::from_file(&path).unwrap();
122 assert!(!loaded.no_llm);
123 assert!(!loaded.code_only);
124
125 let _ = std::fs::remove_dir_all(&dir);
126 }
127
128 #[test]
129 fn test_config_discover() {
130 let dir = std::env::temp_dir().join("codesynapse_test_discover");
131 let _ = std::fs::remove_dir_all(&dir);
132 std::fs::create_dir_all(&dir).unwrap();
133
134 let path = dir.join("codesynapse.toml");
135 std::fs::write(
136 &path,
137 r#"no_llm = true
138code_only = true
139"#,
140 )
141 .unwrap();
142
143 let config = CodeSynapseConfig::discover(&dir).unwrap();
144 assert!(config.no_llm);
145 assert!(config.code_only);
146
147 let _ = std::fs::remove_dir_all(&dir);
148 }
149
150 #[test]
151 fn test_config_discover_not_found() {
152 let dir = std::env::temp_dir().join("codesynapse_test_no_config");
153 let _ = std::fs::remove_dir_all(&dir);
154 std::fs::create_dir_all(&dir).unwrap();
155
156 let result = CodeSynapseConfig::discover(&dir);
157 assert!(result.is_err());
158
159 let _ = std::fs::remove_dir_all(&dir);
160 }
161
162 #[test]
163 fn test_config_parse_empty() {
164 let config: CodeSynapseConfig = toml::from_str("").unwrap();
165 assert!(!config.no_llm);
166 assert!(!config.code_only);
167 }
168}