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 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 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 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 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 original_config.save(&config_path).unwrap();
94
95 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 let loaded_config = Config::load(&config_path).unwrap();
102
103 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 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 }
136}