Skip to main content

fncc_core/
config.rs

1use serde::Deserialize;
2use std::path::Path;
3
4#[derive(Debug, Deserialize)]
5#[serde(default)]
6pub struct FnccConfig {
7    #[serde(rename = "ui")]
8    pub ui_dir: String,
9    #[serde(rename = "lib")]
10    pub lib_dir: String,
11    #[serde(rename = "output")]
12    pub output_dir: String,
13    #[serde(rename = "entry")]
14    pub entry: String,
15}
16
17impl Default for FnccConfig {
18    fn default() -> Self {
19        Self {
20            ui_dir: "src/ui".into(),
21            lib_dir: String::new(),
22            output_dir: "target/fncc".into(),
23            entry: String::new(),
24        }
25    }
26}
27
28pub fn load(path: &Path) -> Result<FnccConfig, String> {
29    let content = std::fs::read_to_string(path).map_err(|e| format!("failed to read config: {e}"))?;
30
31    let mut config: FnccConfig = toml::from_str(&content).map_err(|e| format!("failed to parse config: {e}"))?;
32
33    if config.ui_dir.is_empty() {
34        config.ui_dir = "src/ui".into();
35    }
36    if config.output_dir.is_empty() {
37        config.output_dir = "target/fncc".into();
38    }
39
40    Ok(config)
41}
42
43#[cfg(test)]
44mod tests {
45    use super::*;
46    use std::io::Write;
47    use std::path::PathBuf;
48    use std::sync::atomic::{AtomicUsize, Ordering};
49
50    static CONFIG_TEST_COUNTER: AtomicUsize = AtomicUsize::new(0);
51
52    fn write_config(content: &str) -> PathBuf {
53        let dir = std::env::temp_dir();
54        let id = CONFIG_TEST_COUNTER.fetch_add(1, Ordering::Relaxed);
55        let path = dir.join(format!("fncc_test_config_{}.toml", id));
56        let mut f = std::fs::File::create(&path).unwrap();
57        write!(f, "{content}").unwrap();
58        path
59    }
60
61    // --- Happy path ---
62
63    #[test]
64    fn test_loads_all_fields() {
65        let path = write_config("ui=\"src/myui\"\nlib=\"src/mylib\"\noutput=\"out\"\nentry=\"main\"");
66        let cfg = load(&path).unwrap();
67        assert_eq!(cfg.ui_dir, "src/myui");
68        assert_eq!(cfg.lib_dir, "src/mylib");
69        assert_eq!(cfg.output_dir, "out");
70        assert_eq!(cfg.entry, "main");
71        let _ = std::fs::remove_file(&path);
72    }
73
74    #[test]
75    fn test_loads_empty_config_with_defaults() {
76        let path = write_config("");
77        let cfg = load(&path).unwrap();
78        assert_eq!(cfg.ui_dir, "src/ui");
79        assert_eq!(cfg.output_dir, "target/fncc");
80        assert_eq!(cfg.lib_dir, "");
81        assert_eq!(cfg.entry, "");
82        let _ = std::fs::remove_file(&path);
83    }
84
85    #[test]
86    fn test_unknown_keys_ignored() {
87        let path = write_config("unknown=true\nui=\"custom\"");
88        let cfg = load(&path).unwrap();
89        assert_eq!(cfg.ui_dir, "custom");
90        let _ = std::fs::remove_file(&path);
91    }
92
93    // --- Edge cases ---
94
95    #[test]
96    fn test_ui_dir_default_when_empty() {
97        let path = write_config("ui=\"\"");
98        let cfg = load(&path).unwrap();
99        assert_eq!(cfg.ui_dir, "src/ui");
100        let _ = std::fs::remove_file(&path);
101    }
102
103    #[test]
104    fn test_output_dir_default_when_empty() {
105        let path = write_config("output=\"\"");
106        let cfg = load(&path).unwrap();
107        assert_eq!(cfg.output_dir, "target/fncc");
108        let _ = std::fs::remove_file(&path);
109    }
110
111    #[test]
112    fn test_values_with_extra_whitespace_preserved() {
113        let path = write_config(r#"ui = "  spacey  ""#);
114        let cfg = load(&path).unwrap();
115        // TOML preserves whitespace inside quoted strings
116        assert_eq!(cfg.ui_dir, "  spacey  ");
117        let _ = std::fs::remove_file(&path);
118    }
119
120    // --- Error handling ---
121
122    #[test]
123    fn test_nonexistent_file_returns_error() {
124        let result = load(Path::new("/tmp/__fncc_nonexistent_file_12345.toml"));
125        assert!(result.is_err());
126    }
127
128    #[test]
129    fn test_comments_are_ignored() {
130        let path = write_config("ui=\"ok\"\n# this is a comment\nlib=\"val\"");
131        let cfg = load(&path).unwrap();
132        assert_eq!(cfg.ui_dir, "ok");
133        assert_eq!(cfg.lib_dir, "val");
134        let _ = std::fs::remove_file(&path);
135    }
136}