Skip to main content

reinhardt_conf/settings/
profile.rs

1//! Profile and environment support
2//!
3//! Provides Django-style environment profiles (development, staging, production)
4//! with cascading configuration support.
5
6use serde::{Deserialize, Serialize};
7use std::env;
8use std::fmt;
9
10/// Application profile/environment
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
12#[serde(rename_all = "lowercase")]
13#[derive(Default)]
14pub enum Profile {
15	/// Development environment (default)
16	#[default]
17	Development,
18	/// Staging/testing environment
19	Staging,
20	/// Production environment
21	Production,
22	/// Custom profile
23	Custom,
24}
25
26impl Profile {
27	/// Get the profile from environment variable
28	///
29	/// Checks REINHARDT_ENV, REINHARDT_SETTINGS_MODULE, and ENVIRONMENT variables
30	///
31	/// # Examples
32	///
33	/// ```
34	/// use reinhardt_conf::settings::profile::Profile;
35	///
36	/// // Returns the profile from environment variables
37	/// if let Some(profile) = Profile::from_env() {
38	///     println!("Running in {:?} mode", profile);
39	/// }
40	/// ```
41	pub fn from_env() -> Option<Self> {
42		// Try REINHARDT_ENV first
43		if let Ok(env_val) = env::var("REINHARDT_ENV") {
44			return Some(Self::parse(&env_val));
45		}
46
47		// Try ENVIRONMENT
48		if let Ok(env_val) = env::var("ENVIRONMENT") {
49			return Some(Self::parse(&env_val));
50		}
51
52		// Try to detect from REINHARDT_SETTINGS_MODULE
53		if let Ok(settings_module) = env::var("REINHARDT_SETTINGS_MODULE") {
54			if settings_module.contains("production") {
55				return Some(Profile::Production);
56			} else if settings_module.contains("staging") {
57				return Some(Profile::Staging);
58			} else if settings_module.contains("development") || settings_module.contains("dev") {
59				return Some(Profile::Development);
60			}
61		}
62
63		None
64	}
65	/// Parse profile from string
66	///
67	/// Returns Custom for unknown profile names.
68	///
69	/// # Examples
70	///
71	/// ```
72	/// use reinhardt_conf::settings::profile::Profile;
73	///
74	/// assert_eq!(Profile::parse("production"), Profile::Production);
75	/// assert_eq!(Profile::parse("dev"), Profile::Development);
76	/// assert_eq!(Profile::parse("staging"), Profile::Staging);
77	/// assert_eq!(Profile::parse("unknown"), Profile::Custom);
78	/// ```
79	pub fn parse(s: &str) -> Self {
80		match s.to_lowercase().as_str() {
81			"development" | "dev" | "develop" => Profile::Development,
82			"staging" | "stage" | "test" => Profile::Staging,
83			"production" | "prod" => Profile::Production,
84			_ => Profile::Custom,
85		}
86	}
87	/// Get the profile name as a string
88	///
89	/// # Examples
90	///
91	/// ```
92	/// use reinhardt_conf::settings::profile::Profile;
93	///
94	/// assert_eq!(Profile::Development.as_str(), "development");
95	/// assert_eq!(Profile::Production.as_str(), "production");
96	/// ```
97	pub fn as_str(&self) -> &'static str {
98		match self {
99			Profile::Development => "development",
100			Profile::Staging => "staging",
101			Profile::Production => "production",
102			Profile::Custom => "custom",
103		}
104	}
105	/// Check if this is a production environment
106	///
107	/// # Examples
108	///
109	/// ```
110	/// use reinhardt_conf::settings::profile::Profile;
111	///
112	/// assert!(Profile::Production.is_production());
113	/// assert!(!Profile::Development.is_production());
114	/// ```
115	pub fn is_production(&self) -> bool {
116		matches!(self, Profile::Production)
117	}
118	/// Check if this is a development environment
119	///
120	/// # Examples
121	///
122	/// ```
123	/// use reinhardt_conf::settings::profile::Profile;
124	///
125	/// assert!(Profile::Development.is_development());
126	/// assert!(!Profile::Production.is_development());
127	/// ```
128	pub fn is_development(&self) -> bool {
129		matches!(self, Profile::Development)
130	}
131	/// Check if debug mode should be enabled by default
132	///
133	/// # Examples
134	///
135	/// ```
136	/// use reinhardt_conf::settings::profile::Profile;
137	///
138	/// assert!(Profile::Development.default_debug());
139	/// assert!(!Profile::Production.default_debug());
140	/// ```
141	pub fn default_debug(&self) -> bool {
142		!self.is_production()
143	}
144	/// Get the .env file name for this profile
145	///
146	/// # Examples
147	///
148	/// ```
149	/// use reinhardt_conf::settings::profile::Profile;
150	///
151	/// let profile = Profile::Development;
152	/// assert_eq!(profile.env_file_name(), ".env.development");
153	/// ```
154	pub fn env_file_name(&self) -> String {
155		match self {
156			Profile::Custom => ".env".to_string(),
157			_ => format!(".env.{}", self.as_str()),
158		}
159	}
160}
161
162impl fmt::Display for Profile {
163	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
164		write!(f, "{}", self.as_str())
165	}
166}
167
168#[cfg(test)]
169mod tests {
170	use super::*;
171
172	#[test]
173	fn test_profile_parse() {
174		assert_eq!(Profile::parse("development"), Profile::Development);
175		assert_eq!(Profile::parse("dev"), Profile::Development);
176		assert_eq!(Profile::parse("staging"), Profile::Staging);
177		assert_eq!(Profile::parse("stage"), Profile::Staging);
178		assert_eq!(Profile::parse("production"), Profile::Production);
179		assert_eq!(Profile::parse("prod"), Profile::Production);
180		assert_eq!(Profile::parse("unknown"), Profile::Custom);
181	}
182
183	#[test]
184	fn test_profile_as_str() {
185		assert_eq!(Profile::Development.as_str(), "development");
186		assert_eq!(Profile::Staging.as_str(), "staging");
187		assert_eq!(Profile::Production.as_str(), "production");
188	}
189
190	#[test]
191	fn test_profile_checks() {
192		assert!(Profile::Production.is_production());
193		assert!(!Profile::Development.is_production());
194
195		assert!(Profile::Development.is_development());
196		assert!(!Profile::Production.is_development());
197	}
198
199	#[test]
200	fn test_default_debug() {
201		assert!(Profile::Development.default_debug());
202		assert!(Profile::Staging.default_debug());
203		assert!(!Profile::Production.default_debug());
204	}
205
206	#[test]
207	fn test_env_file_name() {
208		assert_eq!(Profile::Development.env_file_name(), ".env.development");
209		assert_eq!(Profile::Staging.env_file_name(), ".env.staging");
210		assert_eq!(Profile::Production.env_file_name(), ".env.production");
211		assert_eq!(Profile::Custom.env_file_name(), ".env");
212	}
213
214	#[test]
215	fn test_settings_profile_from_env() {
216		// SAFETY: Setting environment variables is unsafe in multi-threaded programs.
217		// This test uses #[serial] to ensure exclusive access to environment variables.
218		unsafe {
219			env::set_var("REINHARDT_ENV", "production");
220		}
221		assert_eq!(Profile::from_env().unwrap(), Profile::Production);
222		// SAFETY: Removing environment variables is unsafe in multi-threaded programs.
223		// This test uses #[serial] to ensure exclusive access to environment variables.
224		unsafe {
225			env::remove_var("REINHARDT_ENV");
226		}
227
228		// SAFETY: Setting environment variables is unsafe in multi-threaded programs.
229		// This test uses #[serial] to ensure exclusive access to environment variables.
230		unsafe {
231			env::set_var("ENVIRONMENT", "development");
232		}
233		assert_eq!(Profile::from_env().unwrap(), Profile::Development);
234		// SAFETY: Removing environment variables is unsafe in multi-threaded programs.
235		// This test uses #[serial] to ensure exclusive access to environment variables.
236		unsafe {
237			env::remove_var("ENVIRONMENT");
238		}
239	}
240}