Skip to main content

toku_core/
config.rs

1use std::path::{Path, PathBuf};
2
3use serde::{Deserialize, Serialize};
4
5use crate::TokuError;
6
7/// Application configuration, persisted as `config.toml` in the data directory.
8#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
9#[serde(default)]
10pub struct TokuConfig {
11    /// Default output format: table, json, csv
12    pub default_format: String,
13    /// Whether to use colors (auto, always, never)
14    pub color: String,
15    /// Primary metadata source (openlibrary, google)
16    pub metadata_source: String,
17}
18
19impl Default for TokuConfig {
20    fn default() -> Self {
21        Self {
22            default_format: "table".to_string(),
23            color: "auto".to_string(),
24            metadata_source: "openlibrary".to_string(),
25        }
26    }
27}
28
29const CONFIG_FILENAME: &str = "config.toml";
30
31impl TokuConfig {
32    /// Returns the path to `config.toml` inside the given data directory.
33    pub fn config_path(data_dir: &Path) -> PathBuf {
34        data_dir.join(CONFIG_FILENAME)
35    }
36
37    /// Load configuration from `config.toml` in `data_dir`.
38    /// Returns defaults if the file does not exist.
39    pub fn load(data_dir: &Path) -> Result<Self, TokuError> {
40        let path = Self::config_path(data_dir);
41        if !path.exists() {
42            return Ok(Self::default());
43        }
44        let contents =
45            std::fs::read_to_string(&path).map_err(|e| TokuError::Config(e.to_string()))?;
46        let config: TokuConfig =
47            toml::from_str(&contents).map_err(|e| TokuError::Config(e.to_string()))?;
48        Ok(config)
49    }
50
51    /// Save configuration to `config.toml` in `data_dir`.
52    /// Creates the data directory if it does not exist.
53    pub fn save(&self, data_dir: &Path) -> Result<(), TokuError> {
54        std::fs::create_dir_all(data_dir).map_err(|e| TokuError::Config(e.to_string()))?;
55        let contents =
56            toml::to_string_pretty(self).map_err(|e| TokuError::Config(e.to_string()))?;
57        let path = Self::config_path(data_dir);
58        std::fs::write(&path, contents).map_err(|e| TokuError::Config(e.to_string()))?;
59        Ok(())
60    }
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66
67    #[test]
68    fn default_has_sensible_values() {
69        let cfg = TokuConfig::default();
70        assert_eq!(cfg.default_format, "table");
71        assert_eq!(cfg.color, "auto");
72        assert_eq!(cfg.metadata_source, "openlibrary");
73    }
74
75    #[test]
76    fn load_missing_file_returns_defaults() {
77        let dir = std::env::temp_dir().join("toku-test-config-missing");
78        // Ensure the directory doesn't have a config file
79        let _ = std::fs::remove_file(TokuConfig::config_path(&dir));
80        let cfg = TokuConfig::load(&dir).expect("load should succeed");
81        assert_eq!(cfg, TokuConfig::default());
82    }
83
84    #[test]
85    fn save_and_load_roundtrip() {
86        let dir = std::env::temp_dir().join("toku-test-config-roundtrip");
87        let _ = std::fs::remove_dir_all(&dir);
88
89        let cfg = TokuConfig {
90            default_format: "json".to_string(),
91            color: "never".to_string(),
92            metadata_source: "google".to_string(),
93        };
94        cfg.save(&dir).expect("save should succeed");
95
96        let loaded = TokuConfig::load(&dir).expect("load should succeed");
97        assert_eq!(loaded, cfg);
98
99        // Clean up
100        let _ = std::fs::remove_dir_all(&dir);
101    }
102}