Skip to main content

hiver_config/
refresh.rs

1//! RefreshScope — configuration refresh support
2//! RefreshScope — 配置刷新支持
3//!
4//! Equivalent to Spring Cloud's `@RefreshScope` annotation.
5//! Provides dynamic configuration refresh without restarting the application.
6//! 等价于 Spring Cloud 的 `@RefreshScope` 注解。
7//! 提供无需重启应用的动态配置刷新。
8//!
9//! # Spring Equivalent / Spring等价物
10//!
11//! ```java
12//! @RefreshScope
13//! @Bean
14//! public DataSource dataSource() {
15//!     return DataSourceBuilder.create().url(config.get("db.url")).build();
16//! }
17//! ```
18//!
19//! # Example / 示例
20//!
21//! ```rust,ignore
22//! use hiver_config::refresh::{RefreshScope, ConfigChangeEvent, Refreshable};
23//!
24//! let mut scope = RefreshScope::new();
25//! let refreshable = Refreshable::new("db.url", "localhost:5432".to_string());
26//! scope.register("db.url", refreshable);
27//!
28//! // Simulate config change
29//! let event = ConfigChangeEvent::new("db.url", Some("localhost:5432"), "db.example.com:5432");
30//! scope.fire_event(&event);
31//! ```
32
33use std::collections::HashMap;
34use std::sync::{Arc, RwLock};
35
36/// Event fired when a configuration property changes.
37/// 当配置属性发生更改时触发的事件。
38///
39/// Equivalent to Spring Cloud's `EnvironmentChangeEvent`.
40/// 等价于 Spring Cloud 的 `EnvironmentChangeEvent`。
41#[derive(Debug, Clone)]
42pub struct ConfigChangeEvent {
43    /// The key of the changed property / 已更改属性的键
44    pub key: String,
45    /// The old value (None if the property is new) / 旧值(如果属性是新的则为 None)
46    pub old_value: Option<String>,
47    /// The new value / 新值
48    pub new_value: String,
49}
50
51impl ConfigChangeEvent {
52    /// Create a new config change event / 创建新的配置变更事件
53    pub fn new(
54        key: impl Into<String>,
55        old_value: Option<impl Into<String>>,
56        new_value: impl Into<String>,
57    ) -> Self {
58        Self {
59            key: key.into(),
60            old_value: old_value.map(Into::into),
61            new_value: new_value.into(),
62        }
63    }
64
65    /// Whether this event represents a new property / 此事件是否代表新属性
66    pub fn is_new(&self) -> bool {
67        self.old_value.is_none()
68    }
69
70    /// Whether the value was removed (empty new value) / 值是否被移除(新值为空)
71    pub fn is_removed(&self) -> bool {
72        self.new_value.is_empty()
73    }
74}
75
76/// Callback type for config change listeners / 配置变更监听器的回调类型
77pub(crate) type ChangeListener = Box<dyn Fn(&ConfigChangeEvent) + Send + Sync>;
78
79/// RefreshScope — marks beans that should be refreshed when config changes.
80/// RefreshScope — 标记在配置更改时应刷新的 bean。
81///
82/// Equivalent to Spring Cloud's `@RefreshScope`.
83/// 等价于 Spring Cloud 的 `@RefreshScope`。
84///
85/// Manages refreshable values and fires events when configuration changes.
86/// 管理可刷新值,并在配置更改时触发事件。
87pub struct RefreshScope {
88    /// Registered refreshables by key / 按键注册的可刷新值
89    refreshables: Arc<RwLock<HashMap<String, Arc<RwLock<RefreshableValue>>>>>,
90
91    /// Change listeners / 变更监听器
92    listeners: Arc<RwLock<Vec<ChangeListener>>>,
93}
94
95impl std::fmt::Debug for RefreshScope {
96    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
97        let count = self.refreshables.read().map_or(0, |m| m.len());
98        let listener_count = self.listeners.read().map_or(0, |v| v.len());
99        f.debug_struct("RefreshScope")
100            .field("refreshable_count", &count)
101            .field("listener_count", &listener_count)
102            .finish()
103    }
104}
105
106/// Internal storage for a refreshable value / 可刷新值的内部存储
107#[derive(Debug)]
108struct RefreshableValue {
109    /// Current value / 当前值
110    value: String,
111}
112
113impl RefreshScope {
114    /// Create a new empty RefreshScope / 创建新的空 RefreshScope
115    pub fn new() -> Self {
116        Self {
117            refreshables: Arc::new(RwLock::new(HashMap::new())),
118            listeners: Arc::new(RwLock::new(Vec::new())),
119        }
120    }
121
122    /// Register a refreshable value / 注册可刷新值
123    pub fn register(&self, key: impl Into<String>, initial_value: impl Into<String>) {
124        let mut map = self
125            .refreshables
126            .write()
127            .unwrap_or_else(std::sync::PoisonError::into_inner);
128        map.insert(
129            key.into(),
130            Arc::new(RwLock::new(RefreshableValue {
131                value: initial_value.into(),
132            })),
133        );
134    }
135
136    /// Get the current value of a refreshable / 获取可刷新值的当前值
137    pub fn get(&self, key: &str) -> Option<String> {
138        let map = self
139            .refreshables
140            .read()
141            .unwrap_or_else(std::sync::PoisonError::into_inner);
142        map.get(key).map(|v| {
143            let guard = v.read().unwrap_or_else(std::sync::PoisonError::into_inner);
144            guard.value.clone()
145        })
146    }
147
148    /// Add a change listener / 添加变更监听器
149    pub fn add_listener(&self, listener: impl Fn(&ConfigChangeEvent) + Send + Sync + 'static) {
150        let mut listeners = self.listeners.write().unwrap_or_else(|e| e.into_inner());
151        listeners.push(Box::new(listener));
152    }
153
154    /// Fire a config change event and update the registered refreshables.
155    /// 触发配置变更事件并更新已注册的可刷新值。
156    ///
157    /// This method updates the refreshable value if registered, and notifies
158    /// all registered listeners.
159    /// 此方法更新已注册的可刷新值,并通知所有已注册的监听器。
160    pub fn fire_event(&self, event: &ConfigChangeEvent) {
161        // Update the refreshable value if registered
162        // 如果已注册则更新可刷新值
163        let map = self
164            .refreshables
165            .read()
166            .unwrap_or_else(std::sync::PoisonError::into_inner);
167        if let Some(refreshable) = map.get(&event.key) {
168            let mut guard = refreshable
169                .write()
170                .unwrap_or_else(std::sync::PoisonError::into_inner);
171            guard.value.clone_from(&event.new_value);
172        }
173
174        // Notify all listeners
175        // 通知所有监听器
176        let listeners = self.listeners.read().unwrap_or_else(|e| e.into_inner());
177        for listener in listeners.iter() {
178            listener(event);
179        }
180    }
181
182    /// Refresh all registered values with a provided getter function.
183    /// 使用提供的 getter 函数刷新所有已注册的值。
184    ///
185    /// The getter is called for each registered key to obtain the new value.
186    /// 对每个已注册的键调用 getter 以获取新值。
187    pub fn refresh_all(&self, getter: impl Fn(&str) -> Option<String>) {
188        let map = self
189            .refreshables
190            .read()
191            .unwrap_or_else(std::sync::PoisonError::into_inner);
192        for (key, refreshable) in map.iter() {
193            if let Some(new_value) = getter(key) {
194                let old_value = {
195                    let guard = refreshable
196                        .read()
197                        .unwrap_or_else(std::sync::PoisonError::into_inner);
198                    Some(guard.value.clone())
199                };
200
201                let event = ConfigChangeEvent::new(key.as_str(), old_value, &new_value);
202
203                {
204                    let mut guard = refreshable
205                        .write()
206                        .unwrap_or_else(std::sync::PoisonError::into_inner);
207                    guard.value = new_value;
208                }
209
210                // Notify listeners
211                let listeners = self.listeners.read().unwrap_or_else(|e| e.into_inner());
212                for listener in listeners.iter() {
213                    listener(&event);
214                }
215            }
216        }
217    }
218
219    /// Get the number of registered refreshables / 获取已注册的可刷新值数量
220    pub fn len(&self) -> usize {
221        let map = self
222            .refreshables
223            .read()
224            .unwrap_or_else(std::sync::PoisonError::into_inner);
225        map.len()
226    }
227
228    /// Check if there are no refreshables / 检查是否没有可刷新值
229    pub fn is_empty(&self) -> bool {
230        self.len() == 0
231    }
232}
233
234impl Default for RefreshScope {
235    fn default() -> Self {
236        Self::new()
237    }
238}
239
240/// ConfigWatcher — watches config sources for changes.
241/// ConfigWatcher — 监视配置源的更改。
242///
243/// Supports file modification watching and HTTP polling.
244/// Equivalent to Spring Cloud Config's watch mechanism.
245/// 支持文件修改监视和 HTTP 轮询。
246/// 等价于 Spring Cloud Config 的监视机制。
247#[derive(Debug)]
248pub struct ConfigWatcher {
249    /// File paths being watched / 正在监视的文件路径
250    watched_files: Vec<std::path::PathBuf>,
251
252    /// Last known modification times / 上次已知的修改时间
253    last_modified: HashMap<std::path::PathBuf, std::time::SystemTime>,
254
255    /// Refresh scope to notify on changes / 配置更改时通知的刷新作用域
256    scope: RefreshScope,
257}
258
259impl ConfigWatcher {
260    /// Create a new config watcher / 创建新配置监视器
261    pub fn new(scope: RefreshScope) -> Self {
262        Self {
263            watched_files: Vec::new(),
264            last_modified: HashMap::new(),
265            scope,
266        }
267    }
268
269    /// Add a file to watch / 添加要监视的文件
270    pub fn watch_file(&mut self, path: impl Into<std::path::PathBuf>) {
271        let path = path.into();
272        if let Ok(metadata) = std::fs::metadata(&path)
273            && let Ok(modified) = metadata.modified()
274        {
275            self.last_modified.insert(path.clone(), modified);
276        }
277        self.watched_files.push(path);
278    }
279
280    /// Check for file changes and fire events / 检查文件更改并触发事件
281    ///
282    /// Returns a list of keys that changed (empty if no changes detected).
283    /// 返回已更改的键列表(如果未检测到更改则为空)。
284    pub fn check_changes(&mut self) -> Vec<String> {
285        let mut changed = Vec::new();
286
287        for path in &self.watched_files {
288            if let Ok(metadata) = std::fs::metadata(path)
289                && let Ok(modified) = metadata.modified()
290            {
291                let prev = self.last_modified.get(path).copied();
292                if prev != Some(modified) {
293                    let path_str = path.to_string_lossy().to_string();
294                    let event = ConfigChangeEvent::new(&path_str, prev.map(|_| "old"), "updated");
295                    self.scope.fire_event(&event);
296                    self.last_modified.insert(path.clone(), modified);
297                    changed.push(path_str);
298                }
299            }
300        }
301
302        changed
303    }
304
305    /// Get the underlying RefreshScope / 获取底层 RefreshScope
306    pub fn scope(&self) -> &RefreshScope {
307        &self.scope
308    }
309
310    /// Get the number of watched files / 获取被监视文件的数量
311    pub fn watched_count(&self) -> usize {
312        self.watched_files.len()
313    }
314}
315
316/// A wrapper that auto-refreshes its value when configuration changes.
317/// 在配置更改时自动刷新其值的包装器。
318///
319/// Equivalent to Spring Cloud's `@RefreshScope` bean behavior.
320/// 等价于 Spring Cloud 的 `@RefreshScope` bean 行为。
321///
322/// # Example / 示例
323///
324/// ```rust,ignore
325/// use hiver_config::refresh::Refreshable;
326///
327/// let mut port = Refreshable::new("server.port", 8080);
328/// assert_eq!(*port.get(), 8080);
329///
330/// port.update("9090".to_string());
331/// assert_eq!(*port.get(), 9090);
332/// ```
333#[derive(Debug)]
334pub struct Refreshable<T> {
335    /// Configuration key / 配置键
336    key: String,
337    /// Current value / 当前值
338    value: Arc<RwLock<T>>,
339}
340
341impl<T: Clone> Refreshable<T> {
342    /// Create a new refreshable / 创建新可刷新值
343    pub fn new(key: impl Into<String>, value: T) -> Self {
344        Self {
345            key: key.into(),
346            value: Arc::new(RwLock::new(value)),
347        }
348    }
349
350    /// Get the current value / 获取当前值
351    pub fn get(&self) -> std::sync::RwLockReadGuard<'_, T> {
352        self.value
353            .read()
354            .unwrap_or_else(std::sync::PoisonError::into_inner)
355    }
356
357    /// Update the value / 更新值
358    pub fn update(&self, new_value: T) {
359        let mut guard = self
360            .value
361            .write()
362            .unwrap_or_else(std::sync::PoisonError::into_inner);
363        *guard = new_value;
364    }
365
366    /// Get the configuration key / 获取配置键
367    pub fn key(&self) -> &str {
368        &self.key
369    }
370
371    /// Create a cloned copy of the current value / 创建当前值的克隆副本
372    pub fn value(&self) -> T {
373        self.get().clone()
374    }
375}
376
377impl<T: Clone> Clone for Refreshable<T> {
378    fn clone(&self) -> Self {
379        Self {
380            key: self.key.clone(),
381            value: self.value.clone(),
382        }
383    }
384}
385
386#[cfg(test)]
387mod tests {
388    use super::*;
389    use std::sync::atomic::{AtomicUsize, Ordering};
390
391    #[test]
392    fn test_config_change_event() {
393        let event = ConfigChangeEvent::new("db.url", Some("old_host"), "new_host");
394        assert_eq!(event.key, "db.url");
395        assert_eq!(event.old_value, Some("old_host".to_string()));
396        assert_eq!(event.new_value, "new_host");
397        assert!(!event.is_new());
398        assert!(!event.is_removed());
399    }
400
401    #[test]
402    fn test_config_change_event_new_property() {
403        let event = ConfigChangeEvent::new("new.key", None::<String>, "value");
404        assert!(event.is_new());
405        assert!(!event.is_removed());
406    }
407
408    #[test]
409    fn test_config_change_event_removed() {
410        let event = ConfigChangeEvent::new("old.key", Some("value"), "");
411        assert!(event.is_removed());
412    }
413
414    #[test]
415    fn test_refresh_scope_register_and_get() {
416        let scope = RefreshScope::new();
417        scope.register("db.url", "localhost:5432");
418        scope.register("server.port", "8080");
419
420        assert_eq!(scope.get("db.url"), Some("localhost:5432".to_string()));
421        assert_eq!(scope.get("server.port"), Some("8080".to_string()));
422        assert_eq!(scope.get("missing"), None);
423        assert_eq!(scope.len(), 2);
424    }
425
426    #[test]
427    fn test_refresh_scope_fire_event() {
428        let scope = RefreshScope::new();
429        scope.register("db.url", "localhost:5432");
430
431        let event = ConfigChangeEvent::new("db.url", Some("localhost:5432"), "db.example.com:5432");
432        scope.fire_event(&event);
433
434        assert_eq!(scope.get("db.url"), Some("db.example.com:5432".to_string()));
435    }
436
437    #[test]
438    fn test_refresh_scope_listener() {
439        let scope = RefreshScope::new();
440        scope.register("db.url", "old");
441
442        let call_count = Arc::new(AtomicUsize::new(0));
443        let count_clone = call_count.clone();
444        scope.add_listener(move |_event| {
445            count_clone.fetch_add(1, Ordering::SeqCst);
446        });
447
448        let event = ConfigChangeEvent::new("db.url", Some("old"), "new");
449        scope.fire_event(&event);
450
451        assert_eq!(call_count.load(Ordering::SeqCst), 1);
452    }
453
454    #[test]
455    fn test_refresh_scope_refresh_all() {
456        let scope = RefreshScope::new();
457        scope.register("a", "1");
458        scope.register("b", "2");
459
460        let new_values: HashMap<String, String> = {
461            let mut m = HashMap::new();
462            m.insert("a".to_string(), "100".to_string());
463            m.insert("b".to_string(), "200".to_string());
464            m
465        };
466
467        scope.refresh_all(|key| new_values.get(key).cloned());
468
469        assert_eq!(scope.get("a"), Some("100".to_string()));
470        assert_eq!(scope.get("b"), Some("200".to_string()));
471    }
472
473    #[test]
474    fn test_refresh_scope_default() {
475        let scope = RefreshScope::default();
476        assert!(scope.is_empty());
477    }
478
479    #[test]
480    fn test_config_watcher() {
481        let scope = RefreshScope::new();
482        let mut watcher = ConfigWatcher::new(scope);
483        assert_eq!(watcher.watched_count(), 0);
484
485        // Watching a nonexistent file should not panic
486        watcher.watch_file("/nonexistent/config.yaml");
487        assert_eq!(watcher.watched_count(), 1);
488
489        // Checking changes on nonexistent files should return empty
490        let changes = watcher.check_changes();
491        assert!(changes.is_empty());
492    }
493
494    #[test]
495    fn test_refreshable() {
496        let refreshable = Refreshable::new("server.port", 8080);
497        assert_eq!(refreshable.key(), "server.port");
498        assert_eq!(*refreshable.get(), 8080);
499        assert_eq!(refreshable.value(), 8080);
500
501        refreshable.update(9090);
502        assert_eq!(*refreshable.get(), 9090);
503    }
504
505    #[test]
506    fn test_refreshable_clone() {
507        let r1 = Refreshable::new("key", "value");
508        let r2 = r1.clone();
509        r1.update("new_value");
510        // Both point to the same underlying value
511        assert_eq!(*r2.get(), "new_value");
512    }
513}