1use crate::{ext::*, *};
2use config::ext::*;
3use config::Configuration;
4use di::{existing, Ref, ServiceCollection};
5use serde::de::DeserializeOwned;
6use std::marker::PhantomData;
7use tokens::ChangeToken;
8
9pub struct ConfigurationChangeTokenSource<T: Value> {
12 name: Option<String>,
13 configuration: Ref<dyn Configuration>,
14 _data: PhantomData<T>,
15}
16
17impl<T: Value> ConfigurationChangeTokenSource<T> {
18 #[inline]
25 pub fn new(name: Option<&str>, configuration: Ref<dyn Configuration>) -> Self {
26 Self {
27 name: name.map(|s| s.to_owned()),
28 configuration,
29 _data: PhantomData,
30 }
31 }
32}
33
34impl<T: Value> OptionsChangeTokenSource<T> for ConfigurationChangeTokenSource<T> {
35 #[inline]
36 fn token(&self) -> Box<dyn ChangeToken> {
37 self.configuration.reload_token()
38 }
39
40 #[inline]
41 fn name(&self) -> Option<&str> {
42 self.name.as_deref()
43 }
44}
45
46pub trait OptionsConfigurationServiceExtensions {
48 fn apply_config<T>(&mut self, configuration: Ref<dyn Configuration>) -> OptionsBuilder<'_, T>
54 where
55 T: Value + Default + DeserializeOwned + 'static;
56
57 fn apply_config_at<T>(
64 &mut self,
65 configuration: Ref<dyn Configuration>,
66 key: impl AsRef<str>,
67 ) -> OptionsBuilder<'_, T>
68 where
69 T: Value + Default + DeserializeOwned + 'static;
70}
71
72impl OptionsConfigurationServiceExtensions for ServiceCollection {
73 fn apply_config<T>(&mut self, configuration: Ref<dyn Configuration>) -> OptionsBuilder<'_, T>
74 where
75 T: Value + Default + DeserializeOwned + 'static,
76 {
77 let source = Box::new(ConfigurationChangeTokenSource::<T>::new(None, configuration.clone()));
78 let descriptor = existing::<dyn OptionsChangeTokenSource<T>, ConfigurationChangeTokenSource<T>>(source);
79
80 self.add(descriptor)
81 .add_options()
82 .configure(move |options: &mut T| configuration.bind(options))
83 }
84
85 fn apply_config_at<T>(
86 &mut self,
87 configuration: Ref<dyn Configuration>,
88 key: impl AsRef<str>,
89 ) -> OptionsBuilder<'_, T>
90 where
91 T: Value + Default + DeserializeOwned + 'static,
92 {
93 let source = Box::new(ConfigurationChangeTokenSource::<T>::new(
94 Some(key.as_ref()),
95 configuration.clone(),
96 ));
97 let descriptor = existing::<dyn OptionsChangeTokenSource<T>, ConfigurationChangeTokenSource<T>>(source);
98 let key = key.as_ref().to_owned();
99
100 self.add(descriptor)
101 .add_named_options(&key)
102 .configure(move |options: &mut T| configuration.bind_at(&key, options))
103 }
104}
105
106#[cfg(test)]
107mod tests {
108
109 use super::*;
110 use config::{ConfigurationBuilder, DefaultConfigurationBuilder};
111 use di::ServiceCollection;
112 use serde::Deserialize;
113 use serde_json::json;
114 use std::env::temp_dir;
115 use std::fs::{remove_file, File};
116 use std::io::Write;
117 use std::sync::{Arc, Condvar, Mutex};
118 use std::time::Duration;
119
120 #[derive(Default, Deserialize)]
121 #[serde(rename_all(deserialize = "PascalCase"))]
122 struct TestOptions {
123 enabled: bool,
124 }
125
126 #[test]
127 fn apply_config_should_bind_configuration_to_options() {
128 let config = Ref::from(
130 DefaultConfigurationBuilder::new()
131 .add_in_memory(&[("Enabled", "true")])
132 .build()
133 .unwrap()
134 .as_config(),
135 );
136 let provider = ServiceCollection::new()
137 .apply_config::<TestOptions>(config)
138 .build_provider()
139 .unwrap();
140
141 let options = provider.get_required::<dyn Options<TestOptions>>();
143
144 assert!(options.value().enabled);
146 }
147
148 #[test]
149 fn apply_config_should_bind_configuration_section_to_options() {
150 let config = DefaultConfigurationBuilder::new()
152 .add_in_memory(&[("Test:Enabled", "true")])
153 .build()
154 .unwrap();
155 let provider = ServiceCollection::new()
156 .apply_config::<TestOptions>(config.section("Test").as_config().into())
157 .build_provider()
158 .unwrap();
159
160 let options = provider.get_required::<dyn Options<TestOptions>>();
162
163 assert!(options.value().enabled);
165 }
166
167 #[test]
168 fn apply_config_at_should_bind_configuration_to_options() {
169 let config = Ref::from(
171 DefaultConfigurationBuilder::new()
172 .add_in_memory(&[("Test:Enabled", "true")])
173 .build()
174 .unwrap()
175 .as_config(),
176 );
177 let provider = ServiceCollection::new()
178 .apply_config_at::<TestOptions>(config, "Test")
179 .build_provider()
180 .unwrap();
181
182 let options = provider.get_required::<dyn OptionsSnapshot<TestOptions>>();
184
185 assert!(options.get(Some("Test")).enabled);
187 }
188
189 #[test]
190 fn options_should_be_updated_after_configuration_change() {
191 let path = temp_dir().join("options_from_json_1.json");
193 let mut json = json!({"enabled": true});
194
195 let mut file = File::create(&path).unwrap();
196 file.write_all(json.to_string().as_bytes()).unwrap();
197 drop(file);
198
199 let config: Ref<dyn Configuration> = Ref::from(
200 DefaultConfigurationBuilder::new()
201 .add_json_file(&path.is().reloadable())
202 .build()
203 .unwrap()
204 .as_config(),
205 );
206 let provider = ServiceCollection::new()
207 .apply_config::<TestOptions>(config.clone())
208 .build_provider()
209 .unwrap();
210
211 let token = config.reload_token();
212 let original = provider
213 .get_required::<dyn OptionsMonitor<TestOptions>>()
214 .current_value();
215 let state = Arc::new((Mutex::new(false), Condvar::new()));
216 let _unused = token.register(
217 Box::new(|s| {
218 let data = s.unwrap();
219 let (reloaded, event) = &*(data.downcast_ref::<(Mutex<bool>, Condvar)>().unwrap());
220 *reloaded.lock().unwrap() = true;
221 event.notify_one();
222 }),
223 Some(state.clone()),
224 );
225
226 json = json!({"enabled": false});
227 file = File::create(&path).unwrap();
228 file.write_all(json.to_string().as_bytes()).unwrap();
229 drop(file);
230
231 let (mutex, event) = &*state;
232 let mut reloaded = mutex.lock().unwrap();
233
234 while !*reloaded {
235 reloaded = event.wait_timeout(reloaded, Duration::from_secs(1)).unwrap().0;
236 }
237
238 let current = provider
240 .get_required::<dyn OptionsMonitor<TestOptions>>()
241 .current_value();
242
243 if path.exists() {
245 remove_file(&path).ok();
246 }
247
248 assert_eq!(original.enabled, true);
249 assert_eq!(current.enabled, false);
250 }
251}