envvault/config/
settings.rs1use std::path::{Path, PathBuf};
2
3use serde::{Deserialize, Serialize};
4
5use crate::errors::{EnvVaultError, Result};
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct Settings {
13 #[serde(default = "default_environment")]
15 pub default_environment: String,
16
17 #[serde(default = "default_vault_dir")]
19 pub vault_dir: String,
20
21 #[serde(default = "default_argon2_memory_kib")]
23 pub argon2_memory_kib: u32,
24
25 #[serde(default = "default_argon2_iterations")]
27 pub argon2_iterations: u32,
28
29 #[serde(default = "default_argon2_parallelism")]
31 pub argon2_parallelism: u32,
32}
33
34fn default_environment() -> String {
37 "dev".to_string()
38}
39
40fn default_vault_dir() -> String {
41 ".envvault".to_string()
42}
43
44fn default_argon2_memory_kib() -> u32 {
45 65_536 }
47
48fn default_argon2_iterations() -> u32 {
49 3
50}
51
52fn default_argon2_parallelism() -> u32 {
53 4
54}
55
56impl Default for Settings {
59 fn default() -> Self {
60 Self {
61 default_environment: default_environment(),
62 vault_dir: default_vault_dir(),
63 argon2_memory_kib: default_argon2_memory_kib(),
64 argon2_iterations: default_argon2_iterations(),
65 argon2_parallelism: default_argon2_parallelism(),
66 }
67 }
68}
69
70impl Settings {
71 const FILE_NAME: &'static str = ".envvault.toml";
73
74 pub fn load(project_dir: &Path) -> Result<Self> {
79 let config_path = project_dir.join(Self::FILE_NAME);
80
81 if !config_path.exists() {
82 return Ok(Self::default());
83 }
84
85 let contents = std::fs::read_to_string(&config_path)?;
86
87 let settings: Settings = toml::from_str(&contents).map_err(|e| {
88 EnvVaultError::ConfigError(format!("Failed to parse {}: {e}", config_path.display()))
89 })?;
90
91 Ok(settings)
92 }
93
94 pub fn vault_path(&self, project_dir: &Path, env_name: &str) -> PathBuf {
98 project_dir
99 .join(&self.vault_dir)
100 .join(format!("{env_name}.vault"))
101 }
102
103 pub fn argon2_params(&self) -> crate::crypto::kdf::Argon2Params {
105 crate::crypto::kdf::Argon2Params {
106 memory_kib: self.argon2_memory_kib,
107 iterations: self.argon2_iterations,
108 parallelism: self.argon2_parallelism,
109 }
110 }
111}
112
113#[cfg(test)]
116mod tests {
117 use super::*;
118 use std::fs;
119 use tempfile::TempDir;
120
121 #[test]
122 fn default_settings_are_sensible() {
123 let s = Settings::default();
124 assert_eq!(s.default_environment, "dev");
125 assert_eq!(s.vault_dir, ".envvault");
126 assert_eq!(s.argon2_memory_kib, 65_536);
127 assert_eq!(s.argon2_iterations, 3);
128 assert_eq!(s.argon2_parallelism, 4);
129 }
130
131 #[test]
132 fn load_returns_defaults_when_no_config_file() {
133 let tmp = TempDir::new().unwrap();
134 let settings = Settings::load(tmp.path()).unwrap();
135 assert_eq!(settings.default_environment, "dev");
136 }
137
138 #[test]
139 fn load_parses_toml_file() {
140 let tmp = TempDir::new().unwrap();
141 let config = r#"
142default_environment = "staging"
143vault_dir = "secrets"
144argon2_memory_kib = 131072
145argon2_iterations = 5
146argon2_parallelism = 8
147"#;
148 fs::write(tmp.path().join(".envvault.toml"), config).unwrap();
149
150 let settings = Settings::load(tmp.path()).unwrap();
151 assert_eq!(settings.default_environment, "staging");
152 assert_eq!(settings.vault_dir, "secrets");
153 assert_eq!(settings.argon2_memory_kib, 131_072);
154 assert_eq!(settings.argon2_iterations, 5);
155 assert_eq!(settings.argon2_parallelism, 8);
156 }
157
158 #[test]
159 fn load_uses_defaults_for_missing_fields() {
160 let tmp = TempDir::new().unwrap();
161 let config = "default_environment = \"prod\"\n";
162 fs::write(tmp.path().join(".envvault.toml"), config).unwrap();
163
164 let settings = Settings::load(tmp.path()).unwrap();
165 assert_eq!(settings.default_environment, "prod");
166 assert_eq!(settings.vault_dir, ".envvault");
168 assert_eq!(settings.argon2_iterations, 3);
169 }
170
171 #[test]
172 fn load_errors_on_invalid_toml() {
173 let tmp = TempDir::new().unwrap();
174 fs::write(tmp.path().join(".envvault.toml"), "not valid {{toml").unwrap();
175
176 let result = Settings::load(tmp.path());
177 assert!(result.is_err());
178 }
179
180 #[test]
181 fn vault_path_builds_correct_path() {
182 let s = Settings::default();
183 let project = Path::new("/home/user/myproject");
184 let path = s.vault_path(project, "dev");
185 assert_eq!(
186 path,
187 PathBuf::from("/home/user/myproject/.envvault/dev.vault")
188 );
189 }
190
191 #[test]
192 fn vault_path_respects_custom_vault_dir() {
193 let s = Settings {
194 vault_dir: "secrets".to_string(),
195 ..Settings::default()
196 };
197 let project = Path::new("/home/user/myproject");
198 let path = s.vault_path(project, "staging");
199 assert_eq!(
200 path,
201 PathBuf::from("/home/user/myproject/secrets/staging.vault")
202 );
203 }
204}