obsidian_cli_inspector/
config.rs1use serde::{Deserialize, Serialize};
2use std::path::PathBuf;
3
4#[derive(Debug, Clone, Serialize, Deserialize)]
5pub struct Config {
6 pub vault_path: PathBuf,
7 #[serde(default)]
8 pub database_path: Option<PathBuf>,
9 #[serde(default)]
10 pub log_path: Option<PathBuf>,
11 #[serde(default)]
12 pub exclude: ExcludeConfig,
13 #[serde(default)]
14 pub search: SearchConfig,
15 #[serde(default)]
16 pub graph: GraphConfig,
17 #[serde(default)]
18 pub llm: Option<LlmConfig>,
19}
20
21#[derive(Debug, Clone, Serialize, Deserialize, Default)]
22pub struct ExcludeConfig {
23 #[serde(default = "default_exclude_patterns")]
24 pub patterns: Vec<String>,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct SearchConfig {
29 #[serde(default = "default_search_limit")]
30 pub default_limit: usize,
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct GraphConfig {
35 #[serde(default = "default_max_depth")]
36 pub max_depth: usize,
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct LlmConfig {
41 pub api_url: String,
42 pub model: String,
43 #[serde(default = "default_timeout")]
44 pub timeout_seconds: u64,
45}
46
47fn default_exclude_patterns() -> Vec<String> {
48 vec![
49 ".obsidian/".to_string(),
50 ".git/".to_string(),
51 ".trash/".to_string(),
52 ]
53}
54
55fn default_search_limit() -> usize {
56 20
57}
58
59fn default_max_depth() -> usize {
60 3
61}
62
63fn default_timeout() -> u64 {
64 30
65}
66
67impl Default for SearchConfig {
68 fn default() -> Self {
69 Self {
70 default_limit: default_search_limit(),
71 }
72 }
73}
74
75impl Default for GraphConfig {
76 fn default() -> Self {
77 Self {
78 max_depth: default_max_depth(),
79 }
80 }
81}
82
83impl Config {
84 pub fn from_file(path: &PathBuf) -> anyhow::Result<Self> {
85 let content = std::fs::read_to_string(path)?;
86 let config: Config = toml::from_str(&content)?;
87 Ok(config)
88 }
89
90 pub fn database_path(&self) -> PathBuf {
91 self.database_path.clone().unwrap_or_else(|| {
92 let mut path = self.config_dir();
94 path.push("vault.db");
95 path
96 })
97 }
98
99 pub fn config_dir(&self) -> PathBuf {
100 dirs::config_dir()
101 .unwrap_or_else(|| PathBuf::from("."))
102 .join("obsidian-cli-inspector")
103 }
104
105 pub fn log_dir(&self) -> PathBuf {
106 self.log_path
107 .clone()
108 .unwrap_or_else(|| self.config_dir().join("logs"))
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115
116 #[test]
117 fn test_config_database_path_with_custom_path() {
118 let config = Config {
119 vault_path: PathBuf::from("/test/vault"),
120 database_path: Some(PathBuf::from("/custom/path/db.db")),
121 log_path: None,
122 exclude: Default::default(),
123 search: Default::default(),
124 graph: Default::default(),
125 llm: None,
126 };
127
128 assert_eq!(config.database_path(), PathBuf::from("/custom/path/db.db"));
129 }
130
131 #[test]
132 fn test_config_database_path_default() {
133 let config = Config {
134 vault_path: PathBuf::from("/test/vault"),
135 database_path: None,
136 log_path: None,
137 exclude: Default::default(),
138 search: Default::default(),
139 graph: Default::default(),
140 llm: None,
141 };
142
143 let db_path = config.database_path();
144 assert!(db_path.to_string_lossy().ends_with("vault.db"));
145 }
146
147 #[test]
148 fn test_config_log_dir_with_custom_path() {
149 let config = Config {
150 vault_path: PathBuf::from("/test/vault"),
151 database_path: None,
152 log_path: Some(PathBuf::from("/custom/logs")),
153 exclude: Default::default(),
154 search: Default::default(),
155 graph: Default::default(),
156 llm: None,
157 };
158
159 assert_eq!(config.log_dir(), PathBuf::from("/custom/logs"));
160 }
161
162 #[test]
163 fn test_config_log_dir_default() {
164 let config = Config {
165 vault_path: PathBuf::from("/test/vault"),
166 database_path: None,
167 log_path: None,
168 exclude: Default::default(),
169 search: Default::default(),
170 graph: Default::default(),
171 llm: None,
172 };
173
174 let log_path = config.log_dir();
175 assert!(log_path.to_string_lossy().contains("logs"));
176 }
177
178 #[test]
179 fn test_llm_config_creation() {
180 let llm = LlmConfig {
181 api_url: "http://api.example.com".to_string(),
182 model: "gpt-4".to_string(),
183 timeout_seconds: 60,
184 };
185
186 assert_eq!(llm.api_url, "http://api.example.com");
187 assert_eq!(llm.timeout_seconds, 60);
188 }
189
190 #[test]
191 fn test_config_default_search_limit() {
192 assert_eq!(super::default_search_limit(), 20);
193 }
194
195 #[test]
196 fn test_config_default_max_depth() {
197 assert_eq!(super::default_max_depth(), 3);
198 }
199
200 #[test]
201 fn test_config_default_timeout() {
202 assert_eq!(super::default_timeout(), 30);
203 }
204
205 #[test]
206 fn test_config_default_exclude_patterns() {
207 let patterns = super::default_exclude_patterns();
208 assert!(patterns.contains(&".obsidian/".to_string()));
209 assert!(patterns.contains(&".git/".to_string()));
210 assert!(patterns.contains(&".trash/".to_string()));
211 }
212
213 #[test]
214 fn test_config_struct_creation() {
215 let config = Config {
216 vault_path: PathBuf::from("/test/vault"),
217 database_path: Some(PathBuf::from("/test/db.db")),
218 log_path: Some(PathBuf::from("/test/logs")),
219 exclude: ExcludeConfig::default(),
220 search: SearchConfig::default(),
221 graph: GraphConfig::default(),
222 llm: None,
223 };
224
225 assert_eq!(config.vault_path, PathBuf::from("/test/vault"));
226 assert!(config.database_path.is_some());
227 assert!(config.log_path.is_some());
228 }
229
230 #[test]
231 fn test_search_config_default_implementation() {
232 let search = SearchConfig::default();
233 assert_eq!(search.default_limit, 20);
234 }
235
236 #[test]
237 fn test_graph_config_default_implementation() {
238 let graph = GraphConfig::default();
239 assert_eq!(graph.max_depth, 3);
240 }
241}