Skip to main content

vespertide_loader/
config.rs

1use std::fs;
2use std::path::PathBuf;
3
4use anyhow::{Context, Result};
5use vespertide_config::VespertideConfig;
6
7/// Load vespertide.json config from current directory.
8pub fn load_config() -> Result<VespertideConfig> {
9    let path = PathBuf::from("vespertide.json");
10    if !path.exists() {
11        anyhow::bail!("vespertide.json not found. Run 'vespertide init' first.");
12    }
13
14    let content = fs::read_to_string(&path).context("read vespertide.json")?;
15    let config: VespertideConfig =
16        serde_json::from_str(&content).context("parse vespertide.json")?;
17    Ok(config)
18}
19
20/// Load config from a specific path.
21pub fn load_config_from_path(path: PathBuf) -> Result<VespertideConfig> {
22    let path = path.into_boxed_path();
23    if !path.exists() {
24        anyhow::bail!("vespertide.json not found at: {}", path.display());
25    }
26
27    let content = fs::read_to_string(&path).context("read vespertide.json")?;
28    let config: VespertideConfig =
29        serde_json::from_str(&content).context("parse vespertide.json")?;
30    Ok(config)
31}
32
33/// Load config from project root, with fallback to defaults.
34pub fn load_config_or_default(project_root: Option<PathBuf>) -> Result<VespertideConfig> {
35    let config_path = if let Some(root) = project_root {
36        root.join("vespertide.json")
37    } else {
38        PathBuf::from("vespertide.json")
39    };
40
41    if config_path.exists() {
42        load_config_from_path(config_path)
43    } else {
44        Ok(VespertideConfig::default())
45    }
46}
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51    use serial_test::serial;
52    use std::fs;
53    use tempfile::tempdir;
54
55    struct CwdGuard {
56        original: PathBuf,
57    }
58
59    impl CwdGuard {
60        fn new(dir: &PathBuf) -> Self {
61            let original = std::env::current_dir().unwrap();
62            std::env::set_current_dir(dir).unwrap();
63            Self { original }
64        }
65    }
66
67    impl Drop for CwdGuard {
68        fn drop(&mut self) {
69            let _ = std::env::set_current_dir(&self.original);
70        }
71    }
72
73    fn write_config(path: &PathBuf) {
74        let cfg = VespertideConfig::default();
75        let text = serde_json::to_string_pretty(&cfg).unwrap();
76        fs::write(path, text).unwrap();
77    }
78
79    #[test]
80    #[serial]
81    fn test_load_config_from_path_success() {
82        let tmp = tempdir().unwrap();
83        let config_path = tmp.path().join("vespertide.json");
84        write_config(&config_path);
85
86        let result = load_config_from_path(config_path);
87        assert!(result.is_ok());
88        let config = result.unwrap();
89        assert_eq!(config.models_dir, PathBuf::from("models"));
90    }
91
92    #[test]
93    #[serial]
94    fn test_load_config_from_path_not_found() {
95        let tmp = tempdir().unwrap();
96        let config_path = tmp.path().join("nonexistent.json");
97
98        let result = load_config_from_path(config_path.clone());
99        assert!(result.is_err());
100        let err_msg = result.unwrap_err().to_string();
101        assert!(err_msg.contains("vespertide.json not found at:"));
102        assert!(err_msg.contains(&config_path.display().to_string()));
103    }
104
105    #[test]
106    #[serial]
107    fn test_load_config_or_default_with_root() {
108        let tmp = tempdir().unwrap();
109        let config_path = tmp.path().join("vespertide.json");
110        write_config(&config_path);
111
112        let result = load_config_or_default(Some(tmp.path().to_path_buf()));
113        assert!(result.is_ok());
114        let config = result.unwrap();
115        assert_eq!(config.models_dir, PathBuf::from("models"));
116    }
117
118    #[test]
119    #[serial]
120    fn test_load_config_or_default_without_root() {
121        let tmp = tempdir().unwrap();
122        let _guard = CwdGuard::new(&tmp.path().to_path_buf());
123        let config_path = PathBuf::from("vespertide.json");
124        write_config(&config_path);
125
126        let result = load_config_or_default(None);
127        assert!(result.is_ok());
128        let config = result.unwrap();
129        assert_eq!(config.models_dir, PathBuf::from("models"));
130    }
131
132    #[test]
133    #[serial]
134    fn test_load_config_or_default_fallback_to_default() {
135        let tmp = tempdir().unwrap();
136        let _guard = CwdGuard::new(&tmp.path().to_path_buf());
137
138        let result = load_config_or_default(None);
139        assert!(result.is_ok());
140        let config = result.unwrap();
141        assert_eq!(config.models_dir, PathBuf::from("models"));
142    }
143
144    #[test]
145    #[serial]
146    fn test_load_config_success() {
147        let tmp = tempdir().unwrap();
148        let _guard = CwdGuard::new(&tmp.path().to_path_buf());
149        let config_path = PathBuf::from("vespertide.json");
150        write_config(&config_path);
151
152        let result = load_config();
153        assert!(result.is_ok());
154        let config = result.unwrap();
155        assert_eq!(config.models_dir, PathBuf::from("models"));
156    }
157
158    #[test]
159    #[serial]
160    fn test_load_config_not_found() {
161        let tmp = tempdir().unwrap();
162        let _guard = CwdGuard::new(&tmp.path().to_path_buf());
163
164        let result = load_config();
165        assert!(result.is_err());
166        let err_msg = result.unwrap_err().to_string();
167        assert!(err_msg.contains("vespertide.json not found"));
168    }
169}