envelope_cli/config/
settings.rs1use serde::{Deserialize, Serialize};
7
8use super::paths::EnvelopePaths;
9use crate::crypto::key_derivation::KeyDerivationParams;
10use crate::error::EnvelopeError;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
14#[serde(rename_all = "lowercase")]
15pub enum BudgetPeriodType {
16 #[default]
18 Monthly,
19 Weekly,
21 BiWeekly,
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct BackupRetention {
28 pub daily_count: u32,
30 pub monthly_count: u32,
32}
33
34impl Default for BackupRetention {
35 fn default() -> Self {
36 Self {
37 daily_count: 30,
38 monthly_count: 12,
39 }
40 }
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize, Default)]
45pub struct EncryptionSettings {
46 #[serde(default)]
48 pub enabled: bool,
49
50 #[serde(default, skip_serializing_if = "Option::is_none")]
52 pub key_params: Option<KeyDerivationParams>,
53
54 #[serde(default, skip_serializing_if = "Option::is_none")]
57 pub verification_hash: Option<String>,
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct Settings {
63 #[serde(default = "default_schema_version")]
65 pub schema_version: u32,
66
67 #[serde(default)]
69 pub budget_period_type: BudgetPeriodType,
70
71 #[serde(default)]
73 pub encryption_enabled: bool,
74
75 #[serde(default)]
77 pub encryption: EncryptionSettings,
78
79 #[serde(default)]
81 pub backup_retention: BackupRetention,
82
83 #[serde(default = "default_currency")]
85 pub currency_symbol: String,
86
87 #[serde(default = "default_date_format")]
89 pub date_format: String,
90
91 #[serde(default = "default_first_day_of_week")]
93 pub first_day_of_week: u8,
94
95 #[serde(default)]
97 pub setup_completed: bool,
98}
99
100fn default_schema_version() -> u32 {
101 1
102}
103
104fn default_currency() -> String {
105 "$".to_string()
106}
107
108fn default_date_format() -> String {
109 "%Y-%m-%d".to_string()
110}
111
112fn default_first_day_of_week() -> u8 {
113 0 }
115
116impl Default for Settings {
117 fn default() -> Self {
118 Self {
119 schema_version: default_schema_version(),
120 budget_period_type: BudgetPeriodType::default(),
121 encryption_enabled: false,
122 encryption: EncryptionSettings::default(),
123 backup_retention: BackupRetention::default(),
124 currency_symbol: default_currency(),
125 date_format: default_date_format(),
126 first_day_of_week: default_first_day_of_week(),
127 setup_completed: false,
128 }
129 }
130}
131
132impl Settings {
133 pub fn is_encryption_enabled(&self) -> bool {
135 self.encryption.enabled || self.encryption_enabled
136 }
137
138 pub fn load_or_create(paths: &EnvelopePaths) -> Result<Self, EnvelopeError> {
140 let settings_path = paths.settings_file();
141
142 if settings_path.exists() {
143 let contents = std::fs::read_to_string(&settings_path)
144 .map_err(|e| EnvelopeError::Io(format!("Failed to read settings file: {}", e)))?;
145
146 let settings: Settings = serde_json::from_str(&contents).map_err(|e| {
147 EnvelopeError::Config(format!("Failed to parse settings file: {}", e))
148 })?;
149
150 Ok(settings)
151 } else {
152 let settings = Settings::default();
154 Ok(settings)
156 }
157 }
158
159 pub fn save(&self, paths: &EnvelopePaths) -> Result<(), EnvelopeError> {
161 paths.ensure_directories()?;
163
164 let settings_path = paths.settings_file();
165 let contents = serde_json::to_string_pretty(self)
166 .map_err(|e| EnvelopeError::Config(format!("Failed to serialize settings: {}", e)))?;
167
168 std::fs::write(&settings_path, contents)
169 .map_err(|e| EnvelopeError::Io(format!("Failed to write settings file: {}", e)))?;
170
171 Ok(())
172 }
173}
174
175#[cfg(test)]
176mod tests {
177 use super::*;
178 use tempfile::TempDir;
179
180 #[test]
181 fn test_default_settings() {
182 let settings = Settings::default();
183 assert_eq!(settings.budget_period_type, BudgetPeriodType::Monthly);
184 assert!(!settings.encryption_enabled);
185 assert_eq!(settings.backup_retention.daily_count, 30);
186 assert_eq!(settings.backup_retention.monthly_count, 12);
187 }
188
189 #[test]
190 fn test_save_and_load() {
191 let temp_dir = TempDir::new().unwrap();
192 let paths = EnvelopePaths::with_base_dir(temp_dir.path().to_path_buf());
193
194 let settings = Settings {
195 budget_period_type: BudgetPeriodType::Weekly,
196 encryption_enabled: true,
197 ..Default::default()
198 };
199
200 settings.save(&paths).unwrap();
201
202 let loaded = Settings::load_or_create(&paths).unwrap();
203 assert_eq!(loaded.budget_period_type, BudgetPeriodType::Weekly);
204 assert!(loaded.encryption_enabled);
205 }
206
207 #[test]
208 fn test_serde_round_trip() {
209 let settings = Settings::default();
210 let json = serde_json::to_string(&settings).unwrap();
211 let deserialized: Settings = serde_json::from_str(&json).unwrap();
212 assert_eq!(settings.budget_period_type, deserialized.budget_period_type);
213 }
214}