Skip to main content

rust_bucket/
config.rs

1// Configuration file parsing and management
2
3use serde::{Deserialize, Serialize};
4use std::fs;
5use std::path::Path;
6use thiserror::Error;
7
8/// Errors that can occur when working with configuration files
9#[derive(Debug, Error)]
10pub enum ConfigError {
11    /// IO error when reading or writing the config file
12    #[error("IO error: {0}")]
13    IoError(#[from] std::io::Error),
14
15    /// Error parsing the TOML config file
16    #[error("Parse error: {0}")]
17    ParseError(#[from] toml::de::Error),
18
19    /// Error serializing the config to TOML
20    #[error("Serialize error: {0}")]
21    SerializeError(#[from] toml::ser::Error),
22}
23
24/// Configuration for rust-bucket
25#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct Config {
27    /// Version of rust-bucket that generated this config
28    pub rust_bucket_version: String,
29    /// Test timeout in seconds (default: 120)
30    pub test_timeout: u32,
31    /// Name of the project
32    pub project_name: String,
33}
34
35impl Default for Config {
36    fn default() -> Self {
37        Self {
38            rust_bucket_version: env!("CARGO_PKG_VERSION").to_string(),
39            test_timeout: 120,
40            project_name: env!("CARGO_PKG_NAME").to_string(),
41        }
42    }
43}
44
45impl Config {
46    /// Load configuration from a TOML file
47    pub fn load(path: &Path) -> Result<Config, ConfigError> {
48        let contents = fs::read_to_string(path)?;
49        let config = toml::from_str(&contents)?;
50        Ok(config)
51    }
52
53    /// Save configuration to a TOML file with a header comment
54    pub fn save(&self, path: &Path) -> Result<(), ConfigError> {
55        let toml_string = toml::to_string_pretty(self)?;
56        let version = env!("CARGO_PKG_VERSION");
57        let header = format!(
58            "# Generated by rust-bucket v{}. DO NOT EDIT BY HAND.\n\n",
59            version
60        );
61        let contents = header + &toml_string;
62        fs::write(path, contents)?;
63        Ok(())
64    }
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70    use std::path::PathBuf;
71
72    #[test]
73    fn test_default_config() {
74        let config = Config::default();
75        assert_eq!(config.rust_bucket_version, env!("CARGO_PKG_VERSION"));
76        assert_eq!(config.test_timeout, 120);
77        assert_eq!(config.project_name, env!("CARGO_PKG_NAME"));
78    }
79
80    #[test]
81    fn test_save_and_load_roundtrip() {
82        let temp_dir = tempfile::tempdir().unwrap();
83        let config_path = temp_dir.path().join("rust-bucket.toml");
84
85        // Create a config with custom values
86        let original_config = Config {
87            rust_bucket_version: "0.1.0".to_string(),
88            test_timeout: 300,
89            project_name: "test-project".to_string(),
90        };
91
92        // Save the config
93        original_config.save(&config_path).unwrap();
94
95        // Verify the file contains the header comment
96        let file_contents = fs::read_to_string(&config_path).unwrap();
97        assert!(file_contents.starts_with("# Generated by rust-bucket v"));
98        assert!(file_contents.contains("DO NOT EDIT BY HAND"));
99
100        // Load the config back
101        let loaded_config = Config::load(&config_path).unwrap();
102
103        // Verify the values match
104        assert_eq!(loaded_config.rust_bucket_version, "0.1.0");
105        assert_eq!(loaded_config.test_timeout, 300);
106        assert_eq!(loaded_config.project_name, "test-project");
107    }
108
109    #[test]
110    fn test_load_nonexistent_file() {
111        let path = PathBuf::from("/nonexistent/path/config.toml");
112        let result = Config::load(&path);
113        assert!(result.is_err());
114        assert!(matches!(result.unwrap_err(), ConfigError::IoError(_)));
115    }
116
117    #[test]
118    fn test_load_invalid_toml() {
119        let temp_dir = tempfile::tempdir().unwrap();
120        let config_path = temp_dir.path().join("invalid.toml");
121
122        // Write invalid TOML
123        fs::write(&config_path, "this is not valid toml { [ }").unwrap();
124
125        let result = Config::load(&config_path);
126        assert!(result.is_err());
127        assert!(matches!(result.unwrap_err(), ConfigError::ParseError(_)));
128    }
129
130    #[test]
131    fn test_save_to_readonly_directory() {
132        // This test would require setting up a read-only directory
133        // which is platform-dependent and may require elevated privileges.
134        // Skipping for simplicity, but the error handling is in place.
135    }
136}