rch_common/config/
profiles.rs1use std::env;
9use tracing::debug;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum Profile {
14 Dev,
16 Prod,
18 Test,
20 Custom,
22}
23
24impl Profile {
25 pub fn from_env() -> Option<Self> {
27 let value = env::var("RCH_PROFILE").ok()?;
28 Some(Self::from_string(&value))
29 }
30
31 pub fn from_string(s: &str) -> Self {
33 match s.to_lowercase().as_str() {
34 "dev" | "development" => Profile::Dev,
35 "prod" | "production" => Profile::Prod,
36 "test" | "testing" => Profile::Test,
37 _ => Profile::Custom,
38 }
39 }
40
41 pub fn as_str(&self) -> &'static str {
43 match self {
44 Profile::Dev => "dev",
45 Profile::Prod => "prod",
46 Profile::Test => "test",
47 Profile::Custom => "custom",
48 }
49 }
50
51 pub fn get_defaults(&self) -> Vec<(&'static str, &'static str)> {
57 let candidates = match self {
58 Profile::Dev => {
59 debug!("Getting dev profile defaults");
60 vec![("RCH_LOG_LEVEL", "debug"), ("RCH_LOG_FORMAT", "pretty")]
61 }
62 Profile::Prod => {
63 debug!("Getting prod profile defaults");
64 vec![
65 ("RCH_LOG_LEVEL", "warn"),
66 ("RCH_LOG_FORMAT", "json"),
67 ("RCH_ENABLE_METRICS", "true"),
68 ]
69 }
70 Profile::Test => {
71 debug!("Getting test profile defaults");
72 vec![
73 ("RCH_MOCK_SSH", "1"),
74 ("RCH_LOG_LEVEL", "debug"),
75 ("RCH_TEST_MODE", "1"),
76 ]
77 }
78 Profile::Custom => {
79 debug!("Custom profile - no automatic defaults");
80 vec![]
81 }
82 };
83
84 candidates
86 .into_iter()
87 .filter(|(key, _)| env::var(key).is_err())
88 .collect()
89 }
90
91 pub fn description(&self) -> &'static str {
93 match self {
94 Profile::Dev => "Development mode with verbose logging and relaxed settings",
95 Profile::Prod => "Production mode with minimal logging and strict settings",
96 Profile::Test => "Testing mode with mock SSH and test fixtures enabled",
97 Profile::Custom => "Custom profile with user-defined settings",
98 }
99 }
100
101 pub fn uses_mock_ssh(&self) -> bool {
103 matches!(self, Profile::Test)
104 }
105
106 pub fn is_verbose(&self) -> bool {
108 matches!(self, Profile::Dev | Profile::Test)
109 }
110}
111
112impl std::fmt::Display for Profile {
113 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
114 write!(f, "{}", self.as_str())
115 }
116}
117
118#[cfg(test)]
119#[allow(unsafe_code)]
120mod tests {
121 use super::*;
122
123 fn env_guard() -> std::sync::MutexGuard<'static, ()> {
124 super::super::env_test_lock()
125 }
126
127 fn set_env(key: &str, value: &str) {
129 unsafe { std::env::set_var(key, value) };
131 }
132
133 fn remove_env(key: &str) {
135 unsafe { std::env::remove_var(key) };
137 }
138
139 #[test]
140 fn test_profile_from_string() {
141 assert_eq!(Profile::from_string("dev"), Profile::Dev);
142 assert_eq!(Profile::from_string("DEV"), Profile::Dev);
143 assert_eq!(Profile::from_string("development"), Profile::Dev);
144 assert_eq!(Profile::from_string("prod"), Profile::Prod);
145 assert_eq!(Profile::from_string("production"), Profile::Prod);
146 assert_eq!(Profile::from_string("test"), Profile::Test);
147 assert_eq!(Profile::from_string("testing"), Profile::Test);
148 assert_eq!(Profile::from_string("unknown"), Profile::Custom);
149 }
150
151 #[test]
152 fn test_profile_as_str() {
153 assert_eq!(Profile::Dev.as_str(), "dev");
154 assert_eq!(Profile::Prod.as_str(), "prod");
155 assert_eq!(Profile::Test.as_str(), "test");
156 assert_eq!(Profile::Custom.as_str(), "custom");
157 }
158
159 #[test]
160 fn test_profile_uses_mock_ssh() {
161 assert!(!Profile::Dev.uses_mock_ssh());
162 assert!(!Profile::Prod.uses_mock_ssh());
163 assert!(Profile::Test.uses_mock_ssh());
164 assert!(!Profile::Custom.uses_mock_ssh());
165 }
166
167 #[test]
168 fn test_profile_is_verbose() {
169 assert!(Profile::Dev.is_verbose());
170 assert!(!Profile::Prod.is_verbose());
171 assert!(Profile::Test.is_verbose());
172 assert!(!Profile::Custom.is_verbose());
173 }
174
175 #[test]
176 fn test_dev_profile_defaults() {
177 let _guard = env_guard();
178 remove_env("RCH_LOG_LEVEL");
180 remove_env("RCH_LOG_FORMAT");
181
182 let defaults = Profile::Dev.get_defaults();
183
184 if env::var("RCH_LOG_LEVEL").is_ok() {
186 assert!(!defaults.iter().any(|(k, _)| *k == "RCH_LOG_LEVEL"));
187 } else {
188 assert!(
189 defaults
190 .iter()
191 .any(|(k, v)| *k == "RCH_LOG_LEVEL" && *v == "debug")
192 );
193 }
194
195 if env::var("RCH_LOG_FORMAT").is_ok() {
196 assert!(!defaults.iter().any(|(k, _)| *k == "RCH_LOG_FORMAT"));
197 } else {
198 assert!(
199 defaults
200 .iter()
201 .any(|(k, v)| *k == "RCH_LOG_FORMAT" && *v == "pretty")
202 );
203 }
204 }
205
206 #[test]
207 fn test_test_profile_enables_mock() {
208 let _guard = env_guard();
209 remove_env("RCH_MOCK_SSH");
211 remove_env("RCH_TEST_MODE");
212 remove_env("RCH_LOG_LEVEL");
213
214 let defaults = Profile::Test.get_defaults();
215
216 assert!(
218 defaults
219 .iter()
220 .any(|(k, v)| *k == "RCH_MOCK_SSH" && *v == "1")
221 );
222 assert!(
223 defaults
224 .iter()
225 .any(|(k, v)| *k == "RCH_TEST_MODE" && *v == "1")
226 );
227 }
228
229 #[test]
230 fn test_profile_doesnt_override() {
231 let _guard = env_guard();
232 set_env("RCH_LOG_LEVEL", "error");
234
235 let defaults = Profile::Dev.get_defaults();
236
237 assert!(!defaults.iter().any(|(k, _)| *k == "RCH_LOG_LEVEL"));
239 assert!(
241 defaults
242 .iter()
243 .any(|(k, v)| *k == "RCH_LOG_FORMAT" && *v == "pretty")
244 );
245
246 remove_env("RCH_LOG_LEVEL");
248 }
249}