rust_serv/config_reloader/
watcher.rs1use notify::{Event, RecommendedWatcher, RecursiveMode, Watcher};
4use std::path::Path;
5use std::sync::mpsc::{channel, Receiver};
6
7pub struct ConfigWatcher {
9 watcher: RecommendedWatcher,
10 rx: Receiver<ConfigEvent>,
11}
12
13#[derive(Debug, Clone)]
15pub enum ConfigEvent {
16 Changed(String),
18 Removed(String),
20 Error(String),
22}
23
24impl ConfigWatcher {
25 pub fn new<P: AsRef<Path>>(_path: P) -> Result<Self, Box<dyn std::error::Error>> {
27 let (tx, rx) = channel();
28
29 let watcher = notify::recommended_watcher(move |res: Result<Event, notify::Error>| {
30 match res {
31 Ok(event) => {
32 for path in event.paths {
33 if let Some(path_str) = path.to_str() {
34 let event_type = match event.kind {
35 notify::EventKind::Modify(_) => ConfigEvent::Changed(path_str.to_string()),
36 notify::EventKind::Remove(_) => ConfigEvent::Removed(path_str.to_string()),
37 _ => continue,
38 };
39 let _ = tx.send(event_type);
40 }
41 }
42 }
43 Err(e) => {
44 let _ = tx.send(ConfigEvent::Error(e.to_string()));
45 }
46 }
47 })?;
48
49 Ok(Self { watcher, rx })
50 }
51
52 pub fn watch<P: AsRef<Path>>(&mut self, path: P) -> Result<(), Box<dyn std::error::Error>> {
54 self.watcher.watch(path.as_ref(), RecursiveMode::NonRecursive)?;
55 Ok(())
56 }
57
58 pub fn unwatch<P: AsRef<Path>>(&mut self, path: P) -> Result<(), Box<dyn std::error::Error>> {
60 self.watcher.unwatch(path.as_ref())?;
61 Ok(())
62 }
63
64 pub fn try_recv(&self) -> Option<ConfigEvent> {
66 match self.rx.try_recv() {
67 Ok(event) => Some(event),
68 Err(_) => None,
69 }
70 }
71
72 pub fn recv(&self) -> Option<ConfigEvent> {
74 match self.rx.recv() {
75 Ok(event) => Some(event),
76 Err(_) => None,
77 }
78 }
79}
80
81#[cfg(test)]
82mod tests {
83 use super::*;
84 use std::fs;
85 use std::io::Write;
86 use tempfile::TempDir;
87
88 #[test]
89 fn test_watcher_creation() {
90 let dir = TempDir::new().unwrap();
91 let config_path = dir.path().join("config.toml");
92 fs::write(&config_path, "port = 8080").unwrap();
93
94 let result = ConfigWatcher::new(&config_path);
95 assert!(result.is_ok());
96 }
97
98 #[test]
99 fn test_watcher_watch_unwatch() {
100 let dir = TempDir::new().unwrap();
101 let config_path = dir.path().join("config.toml");
102 fs::write(&config_path, "port = 8080").unwrap();
103
104 let mut watcher = ConfigWatcher::new(&config_path).unwrap();
105
106 assert!(watcher.watch(&config_path).is_ok());
108
109 assert!(watcher.unwatch(&config_path).is_ok());
111 }
112
113 #[test]
114 fn test_watcher_try_recv_no_event() {
115 let dir = TempDir::new().unwrap();
116 let config_path = dir.path().join("config.toml");
117 fs::write(&config_path, "port = 8080").unwrap();
118
119 let mut watcher = ConfigWatcher::new(&config_path).unwrap();
120 watcher.watch(&config_path).unwrap();
121
122 let result = watcher.try_recv();
124 assert!(result.is_none());
125 }
126
127 #[test]
128 fn test_watcher_nonexistent_file() {
129 let dir = TempDir::new().unwrap();
130 let config_path = dir.path().join("nonexistent.toml");
131
132 let result = ConfigWatcher::new(&config_path);
134 assert!(result.is_ok());
135 }
136
137 #[test]
138 fn test_watcher_recv_timeout() {
139 let dir = TempDir::new().unwrap();
140 let config_path = dir.path().join("config.toml");
141 fs::write(&config_path, "port = 8080").unwrap();
142
143 let mut watcher = ConfigWatcher::new(&config_path).unwrap();
144 watcher.watch(&config_path).unwrap();
145
146 let result = watcher.try_recv();
148 assert!(result.is_none());
150 }
151
152 #[test]
153 fn test_config_event_debug() {
154 let event = ConfigEvent::Changed("/test/path.toml".to_string());
155 let debug_str = format!("{:?}", event);
156 assert!(debug_str.contains("Changed"));
157
158 let event = ConfigEvent::Removed("/test/path.toml".to_string());
159 let debug_str = format!("{:?}", event);
160 assert!(debug_str.contains("Removed"));
161
162 let event = ConfigEvent::Error("test error".to_string());
163 let debug_str = format!("{:?}", event);
164 assert!(debug_str.contains("Error"));
165 }
166}