1use serde::{Deserialize, Serialize};
4use std::fs;
5use std::path::Path;
6use thiserror::Error;
7
8#[derive(Debug, Error)]
10pub enum ConfigError {
11 #[error("IO error: {0}")]
13 IoError(#[from] std::io::Error),
14
15 #[error("Parse error: {0}")]
17 ParseError(#[from] toml::de::Error),
18
19 #[error("Serialize error: {0}")]
21 SerializeError(#[from] toml::ser::Error),
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct Config {
27 pub rust_bucket_version: String,
29 pub test_timeout: u32,
31 pub project_name: String,
33}
34
35impl Config {
41 pub fn load(path: &Path) -> Result<Config, ConfigError> {
43 let contents = fs::read_to_string(path)?;
44 let config = toml::from_str(&contents)?;
45 Ok(config)
46 }
47
48 pub fn save(&self, path: &Path) -> Result<(), ConfigError> {
50 let toml_string = toml::to_string_pretty(self)?;
51 let version = env!("CARGO_PKG_VERSION");
52 let header = format!(
53 "# Generated by rust-bucket v{}. DO NOT EDIT BY HAND.\n\n",
54 version
55 );
56 let contents = header + &toml_string;
57 fs::write(path, contents)?;
58 Ok(())
59 }
60}
61
62#[cfg(test)]
63mod tests {
64 use super::*;
65 use std::path::PathBuf;
66
67 #[test]
68 fn test_save_and_load_roundtrip() -> Result<(), Box<dyn std::error::Error>> {
69 let temp_dir = tempfile::tempdir()?;
70 let config_path = temp_dir.path().join("rust-bucket.toml");
71
72 let original_config = Config {
74 rust_bucket_version: "0.1.0".to_string(),
75 test_timeout: 300,
76 project_name: "test-project".to_string(),
77 };
78
79 original_config.save(&config_path)?;
81
82 let file_contents = fs::read_to_string(&config_path)?;
84 assert!(file_contents.starts_with("# Generated by rust-bucket v"));
85 assert!(file_contents.contains("DO NOT EDIT BY HAND"));
86
87 let loaded_config = Config::load(&config_path)?;
89
90 assert_eq!(loaded_config.rust_bucket_version, "0.1.0");
92 assert_eq!(loaded_config.test_timeout, 300);
93 assert_eq!(loaded_config.project_name, "test-project");
94 Ok(())
95 }
96
97 #[test]
98 fn test_load_nonexistent_file() {
99 let path = PathBuf::from("/nonexistent/path/config.toml");
100 let result = Config::load(&path);
101 assert!(result.is_err());
102 assert!(matches!(result.unwrap_err(), ConfigError::IoError(_)));
103 }
104
105 #[test]
106 fn test_load_invalid_toml() -> Result<(), Box<dyn std::error::Error>> {
107 let temp_dir = tempfile::tempdir()?;
108 let config_path = temp_dir.path().join("invalid.toml");
109
110 fs::write(&config_path, "this is not valid toml { [ }")?;
112
113 let result = Config::load(&config_path);
114 assert!(result.is_err());
115 assert!(matches!(result.unwrap_err(), ConfigError::ParseError(_)));
116 Ok(())
117 }
118
119 #[test]
120 fn test_save_to_readonly_directory() {
121 }
125}