1mod property_resolver;
2
3use std::collections::HashMap;
4use std::path::Path;
5use std::sync::Arc;
6use config::{Config, Environment, File};
7use crate::context::context::Context;
8use crate::core::bean_def::BeanDef;
9use crate::core::Error;
10use crate::core::ty::Type;
11
12pub use property_resolver::PropertyResolver as PropertyResolver;
13
14pub fn get_config_context(config_files: Vec<String>) -> Result<Context, Error> {
15 let config_context = Context::new("config");
16
17 let ty = Type::of::<Config>();
18 ty.add_downcast::<Config>(|b| Ok(Arc::downcast::<Config>(b)?));
19 ty.add_downcast::<dyn PropertyResolver + Send + Sync>(|b| Ok(Arc::downcast::<Config>(b)?));
20
21 let mut config_builder = Config::builder();
22 for config_file in config_files {
23 if Path::new(config_file.as_str()).exists() {
24 log::debug!("Loading config file: {}", config_file);
25 config_builder = config_builder.add_source(File::with_name(&config_file))
26 }
27 }
28
29 log::debug!("Loading environment variables with APP prefix");
31 config_builder = config_builder.add_source(
32 Environment::with_prefix("APP").prefix_separator("_").separator(".").try_parsing(true)
33 );
34
35 log::debug!("Parsing -- style command line arguments");
37 let d_args: HashMap<String, String> = std::env::args()
38 .filter_map(|arg| {
39 if let Some(stripped) = arg.strip_prefix("--") {
40 stripped.split_once('=').map(|(k, v)| (k.to_string(), v.to_string()))
41 } else {
42 None
43 }
44 })
45 .collect();
46
47 for (key, value) in d_args {
48 config_builder = config_builder.set_override(key, value).map_err(|e| {
49 Error::from(format!("configuration error: {:#?}", e))
50 })?;
51 }
52
53 let config = Arc::new(config_builder.build().map_err(|e| {
54 Error::from(format!("configuration error: {:#?}", e))
55 })?);
56 config_context.register(BeanDef::builder()
57 .name("config")
58 .ty(ty)
59 .get(Arc::new(move |_| { Ok(config.clone()) }))
60 .build())?;
61
62 Ok(config_context)
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68 use crate::config::PropertyResolver;
69
70 #[test]
71 fn should_allow_config_downcast_to_property_resolver() {
72 let context = get_config_context(vec![]).unwrap();
73 let resolver = context.get_bean::<dyn PropertyResolver + Send + Sync>("config");
74 assert!(resolver.is_ok());
75 }
76
77 #[test]
78 fn should_load_environment_variables() {
79 std::env::set_var("APP_test_key", "test_value");
80
81 let context = get_config_context(vec![]).unwrap();
82 let resolver = context.get_bean::<dyn PropertyResolver + Send + Sync>("config").unwrap();
83 assert_eq!(resolver.get_string("test_key"), Some("test_value".to_string()));
84
85 std::env::remove_var("APP_test_key");
86 }
87
88 #[test]
89 fn should_compute_template_values() {
90 std::env::set_var("APP_prop1", "value1");
91 std::env::set_var("APP_prop2", "value2");
92
93 let context = get_config_context(vec![]).unwrap();
94 let resolver = context.get_bean::<dyn PropertyResolver + Send + Sync>("config").unwrap();
95
96 let result = resolver.compute_template_value("${prop1}_${prop2}").unwrap();
97 assert_eq!(result, "value1_value2");
98
99 std::env::remove_var("APP_prop1");
100 std::env::remove_var("APP_prop2");
101 }
102
103 #[test]
104 fn should_compute_template_values_with_defaults() {
105 let context = get_config_context(vec![]).unwrap();
106 let resolver = context.get_bean::<dyn PropertyResolver + Send + Sync>("config").unwrap();
107
108 let result = resolver.compute_template_value("${missing_prop:default_value}").unwrap();
109 assert_eq!(result, "default_value");
110 }
111
112 #[test]
113 fn should_compute_typed_template_values() {
114 std::env::set_var("APP_server.port", "8080");
115 std::env::set_var("APP_enabled", "true");
116
117 let context = get_config_context(vec![]).unwrap();
118 let resolver = context.get_bean::<dyn PropertyResolver + Send + Sync>("config").unwrap();
119
120 assert_eq!(resolver.compute_template_value_as_u16("${server.port}").unwrap(), 8080);
121 assert_eq!(resolver.compute_template_value_as_bool("${enabled}").unwrap(), true);
122
123 std::env::remove_var("APP_server_port");
124 std::env::remove_var("APP_enabled");
125 }
126}