1use crate::{ConfigError, ConfigResult, PropertySource, Value};
12use indexmap::IndexMap;
13use std::fmt;
14use std::sync::{Arc, RwLock};
15
16#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
28pub struct Profile(String);
29
30impl Profile {
31 pub fn new(name: impl Into<String>) -> Self {
34 Profile(name.into())
35 }
36
37 pub fn dev() -> Self {
40 Profile("dev".to_string())
41 }
42
43 pub fn test() -> Self {
46 Profile("test".to_string())
47 }
48
49 pub fn staging() -> Self {
52 Profile("staging".to_string())
53 }
54
55 pub fn prod() -> Self {
58 Profile("prod".to_string())
59 }
60
61 pub fn name(&self) -> &str {
64 &self.0
65 }
66
67 pub fn is_default(&self) -> bool {
70 self.0 == "default"
71 }
72}
73
74impl fmt::Display for Profile {
75 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76 write!(f, "{}", self.0)
77 }
78}
79
80impl From<String> for Profile {
81 fn from(s: String) -> Self {
82 Profile(s)
83 }
84}
85
86impl From<&str> for Profile {
87 fn from(s: &str) -> Self {
88 Profile(s.to_string())
89 }
90}
91
92#[derive(Debug, Clone)]
98pub struct ActiveProfiles {
99 profiles: Vec<Profile>,
100 default_profiles: Vec<Profile>,
101}
102
103impl ActiveProfiles {
104 pub fn new() -> Self {
107 Self {
108 profiles: vec![Profile::dev()],
109 default_profiles: vec![Profile("default".to_string())],
110 }
111 }
112
113 pub fn set_active(&mut self, profiles: Vec<Profile>) {
116 self.profiles = profiles;
117 }
118
119 pub fn add_active(&mut self, profile: Profile) {
122 if !self.profiles.contains(&profile) {
123 self.profiles.push(profile);
124 }
125 }
126
127 pub fn active(&self) -> &[Profile] {
130 &self.profiles
131 }
132
133 pub fn is_active(&self, profile: &Profile) -> bool {
136 self.profiles.contains(profile) || self.default_profiles.contains(profile)
137 }
138
139 pub fn set_defaults(&mut self, profiles: Vec<Profile>) {
142 self.default_profiles = profiles;
143 }
144
145 pub fn defaults(&self) -> &[Profile] {
148 &self.default_profiles
149 }
150}
151
152impl Default for ActiveProfiles {
153 fn default() -> Self {
154 Self::new()
155 }
156}
157
158#[derive(Debug, Clone)]
167pub struct Environment {
168 property_sources: Arc<RwLock<Vec<PropertySource>>>,
171
172 active_profiles: Arc<RwLock<ActiveProfiles>>,
175
176 system_env: IndexMap<String, String>,
179}
180
181impl Environment {
182 pub fn new() -> Self {
185 Self {
186 property_sources: Arc::new(RwLock::new(Vec::new())),
187 active_profiles: Arc::new(RwLock::new(ActiveProfiles::new())),
188 system_env: std::env::vars().collect(),
189 }
190 }
191
192 pub fn add_property_source(&self, source: PropertySource) {
195 let mut sources = self
196 .property_sources
197 .write()
198 .unwrap_or_else(std::sync::PoisonError::into_inner);
199 sources.push(source);
200 }
201
202 pub fn add_property_source_first(&self, source: PropertySource) {
205 let mut sources = self
206 .property_sources
207 .write()
208 .unwrap_or_else(std::sync::PoisonError::into_inner);
209 sources.insert(0, source);
210 }
211
212 pub fn get_property(&self, key: &str) -> Option<Value> {
215 let sources = self
216 .property_sources
217 .read()
218 .unwrap_or_else(std::sync::PoisonError::into_inner);
219 for source in sources.iter() {
220 if let Some(value) = source.get(key) {
221 return Some(value);
222 }
223 }
224 None
225 }
226
227 pub fn get_property_as<T>(&self, key: &str) -> ConfigResult<T>
230 where
231 T: serde::de::DeserializeOwned,
232 {
233 let value = self
234 .get_property(key)
235 .ok_or_else(|| ConfigError::MissingProperty(key.to_string()))?;
236
237 value.into::<T>()
238 }
239
240 pub fn get_required_property(&self, key: &str) -> ConfigResult<Value> {
243 self.get_property(key)
244 .ok_or_else(|| ConfigError::MissingProperty(key.to_string()))
245 }
246
247 pub fn get_required_property_as<T>(&self, key: &str) -> ConfigResult<T>
250 where
251 T: serde::de::DeserializeOwned,
252 {
253 let value = self.get_required_property(key)?;
254 value.into::<T>()
255 }
256
257 pub fn contains_property(&self, key: &str) -> bool {
260 self.get_property(key).is_some()
261 }
262
263 pub fn resolve_placeholders(&self, input: &str) -> String {
266 let mut result = input.to_string();
267
268 let mut start = 0;
270 while let Some(pos) = result[start..].find("${") {
271 let absolute_pos = start + pos;
272 if let Some(end) = result[absolute_pos..].find('}') {
273 let key = &result[absolute_pos + 2..absolute_pos + end];
274 if let Some(value) = self.get_property(key) {
275 let value_str = value.as_str().unwrap_or_default();
276 result.replace_range(absolute_pos..=(absolute_pos + end), value_str);
277 }
278 start = absolute_pos + 1;
279 } else {
280 break;
281 }
282 }
283
284 result
285 }
286
287 pub fn get_active_profiles(&self) -> Vec<Profile> {
290 let profiles = self
291 .active_profiles
292 .read()
293 .unwrap_or_else(std::sync::PoisonError::into_inner);
294 profiles.active().to_vec()
295 }
296
297 pub fn set_active_profiles(&self, profiles: Vec<Profile>) {
300 let mut active = self
301 .active_profiles
302 .write()
303 .unwrap_or_else(std::sync::PoisonError::into_inner);
304 active.set_active(profiles);
305 }
306
307 pub fn add_active_profile(&self, profile: Profile) {
310 let mut active = self
311 .active_profiles
312 .write()
313 .unwrap_or_else(std::sync::PoisonError::into_inner);
314 active.add_active(profile);
315 }
316
317 pub fn accepts_profiles(&self, profiles: &[Profile]) -> bool {
320 let active = self
321 .active_profiles
322 .read()
323 .unwrap_or_else(std::sync::PoisonError::into_inner);
324 profiles.iter().any(|p| active.is_active(p))
325 }
326
327 pub fn get_property_sources(&self) -> Vec<PropertySource> {
330 let sources = self
331 .property_sources
332 .read()
333 .unwrap_or_else(std::sync::PoisonError::into_inner);
334 sources.clone()
335 }
336
337 pub fn get_env(&self, key: &str) -> Option<String> {
340 self.system_env.get(key).cloned()
341 }
342}
343
344impl Default for Environment {
345 fn default() -> Self {
346 Self::new()
347 }
348}
349
350#[cfg(test)]
351mod tests {
352 use super::*;
353
354 #[test]
361 fn test_profile_new() {
362 let p = Profile::new("custom");
363 assert_eq!(p.name(), "custom");
364 assert!(!p.is_default());
365 }
366
367 #[test]
370 fn test_profile_presets() {
371 assert_eq!(Profile::dev().name(), "dev");
372 assert_eq!(Profile::test().name(), "test");
373 assert_eq!(Profile::staging().name(), "staging");
374 assert_eq!(Profile::prod().name(), "prod");
375 }
376
377 #[test]
380 fn test_profile_is_default() {
381 assert!(Profile::new("default").is_default());
382 assert!(!Profile::dev().is_default());
383 }
384
385 #[test]
388 fn test_profile_display() {
389 assert_eq!(format!("{}", Profile::dev()), "dev");
390 assert_eq!(format!("{}", Profile::new("staging")), "staging");
391 }
392
393 #[test]
396 fn test_profile_from() {
397 let p1: Profile = "test".into();
398 let p2: Profile = String::from("prod").into();
399 assert_eq!(p1.name(), "test");
400 assert_eq!(p2.name(), "prod");
401 }
402
403 #[test]
406 fn test_profile_eq_and_ord() {
407 assert_eq!(Profile::dev(), Profile::new("dev"));
408 assert_ne!(Profile::dev(), Profile::prod());
409 assert!(Profile::dev() < Profile::prod());
410 }
411
412 #[test]
419 fn test_active_profiles_default() {
420 let ap = ActiveProfiles::new();
421 assert_eq!(ap.active().len(), 1);
422 assert_eq!(ap.active()[0], Profile::dev());
423 }
424
425 #[test]
428 fn test_active_profiles_set_active() {
429 let mut ap = ActiveProfiles::new();
430 ap.set_active(vec![Profile::prod()]);
431 assert_eq!(ap.active().len(), 1);
432 assert_eq!(ap.active()[0], Profile::prod());
433 }
434
435 #[test]
438 fn test_active_profiles_add_no_duplicate() {
439 let mut ap = ActiveProfiles::new();
440 ap.add_active(Profile::dev());
441 assert_eq!(ap.active().len(), 1); }
443
444 #[test]
447 fn test_active_profiles_add_new() {
448 let mut ap = ActiveProfiles::new();
449 ap.add_active(Profile::prod());
450 assert_eq!(ap.active().len(), 2);
451 }
452
453 #[test]
456 fn test_active_profiles_is_active() {
457 let ap = ActiveProfiles::new();
458 assert!(ap.is_active(&Profile::dev()));
459 assert!(ap.is_active(&Profile::new("default"))); assert!(!ap.is_active(&Profile::prod()));
461 }
462
463 #[test]
466 fn test_active_profiles_defaults() {
467 let mut ap = ActiveProfiles::new();
468 assert_eq!(ap.defaults().len(), 1);
469 assert_eq!(ap.defaults()[0], Profile::new("default"));
470
471 ap.set_defaults(vec![Profile::new("base")]);
472 assert_eq!(ap.defaults().len(), 1);
473 assert_eq!(ap.defaults()[0], Profile::new("base"));
474 }
475
476 #[test]
483 fn test_environment_new() {
484 let env = Environment::new();
485 assert!(env.get_active_profiles().len() >= 1); assert!(env.get_property_sources().is_empty());
487 }
488
489 #[test]
492 fn test_environment_add_and_get() {
493 let env = Environment::new();
494 let mut source = PropertySource::new("test");
495 source.put("server.port", Value::integer(8080));
496 source.put("server.host", Value::string("localhost"));
497 env.add_property_source(source);
498
499 assert_eq!(env.get_property("server.port").unwrap().as_i64(), Some(8080));
500 assert_eq!(env.get_property("server.host").unwrap().as_str(), Some("localhost"));
501 assert!(env.get_property("nonexistent").is_none());
502 }
503
504 #[test]
507 fn test_environment_add_first_priority() {
508 let env = Environment::new();
509
510 let mut source1 = PropertySource::new("source1");
511 source1.put("key", Value::string("from_source1"));
512 env.add_property_source(source1);
513
514 let mut source2 = PropertySource::new("source2");
515 source2.put("key", Value::string("from_source2"));
516 env.add_property_source_first(source2);
517
518 assert_eq!(env.get_property("key").unwrap().as_str(), Some("from_source2"));
520 }
521
522 #[test]
525 fn test_environment_get_property_as() {
526 let env = Environment::new();
527 let mut source = PropertySource::new("test");
528 source.put("count", Value::integer(42));
529 env.add_property_source(source);
530
531 let result: i64 = env.get_property_as("count").unwrap();
532 assert_eq!(result, 42);
533 }
534
535 #[test]
538 fn test_environment_get_property_as_missing() {
539 let env = Environment::new();
540 let result: Result<i64, _> = env.get_property_as("missing");
541 assert!(result.is_err());
542 }
543
544 #[test]
547 fn test_environment_required_property() {
548 let env = Environment::new();
549 let mut source = PropertySource::new("test");
550 source.put("present", Value::string("here"));
551 env.add_property_source(source);
552
553 assert!(env.get_required_property("present").is_ok());
554 assert!(env.get_required_property("absent").is_err());
555 }
556
557 #[test]
560 fn test_environment_contains_property() {
561 let env = Environment::new();
562 assert!(!env.contains_property("key"));
563
564 let mut source = PropertySource::new("test");
565 source.put("key", Value::string("value"));
566 env.add_property_source(source);
567 assert!(env.contains_property("key"));
568 }
569
570 #[test]
573 fn test_environment_resolve_placeholders() {
574 let env = Environment::new();
575 let mut source = PropertySource::new("test");
576 source.put("host", Value::string("localhost"));
577 source.put("port", Value::string("8080"));
578 env.add_property_source(source);
579
580 let result = env.resolve_placeholders("server at ${host}:${port}");
581 assert_eq!(result, "server at localhost:8080");
582 }
583
584 #[test]
587 fn test_environment_resolve_placeholders_unresolved() {
588 let env = Environment::new();
589 let result = env.resolve_placeholders("missing ${no.key} stays");
590 assert_eq!(result, "missing ${no.key} stays");
591 }
592
593 #[test]
596 fn test_environment_profiles() {
597 let env = Environment::new();
598 env.set_active_profiles(vec![Profile::prod(), Profile::staging()]);
599
600 let profiles = env.get_active_profiles();
601 assert_eq!(profiles.len(), 2);
602 assert!(profiles.contains(&Profile::prod()));
603 assert!(profiles.contains(&Profile::staging()));
604 }
605
606 #[test]
609 fn test_environment_add_profile() {
610 let env = Environment::new();
611 env.add_active_profile(Profile::test());
612
613 let profiles = env.get_active_profiles();
614 assert!(profiles.contains(&Profile::test()));
615 }
616
617 #[test]
620 fn test_environment_accepts_profiles() {
621 let env = Environment::new();
622 assert!(env.accepts_profiles(&[Profile::dev()]));
623 assert!(!env.accepts_profiles(&[Profile::prod()]));
624 }
625
626 #[test]
629 fn test_environment_get_property_sources() {
630 let env = Environment::new();
631 let source1 = PropertySource::new("s1");
632 let source2 = PropertySource::new("s2");
633 env.add_property_source(source1);
634 env.add_property_source(source2);
635
636 let sources = env.get_property_sources();
637 assert_eq!(sources.len(), 2);
638 }
639
640 #[test]
643 fn test_environment_get_env() {
644 let env = Environment::new();
645 assert!(env.get_env("PATH").is_some());
647 assert!(env.get_env("HIVER_TEST_NONEXISTENT_VAR_12345").is_none());
649 }
650
651 #[test]
654 fn test_environment_get_required_property_as() {
655 let env = Environment::new();
656 let mut source = PropertySource::new("test");
657 source.put("ratio", Value::float(2.5));
658 env.add_property_source(source);
659
660 let result: f64 = env.get_required_property_as("ratio").unwrap();
661 assert!((result - 2.5).abs() < f64::EPSILON);
662 }
663}