mockforge_graphql/
schema_watcher.rs1use notify::{Event, RecommendedWatcher, RecursiveMode, Watcher};
6use std::path::PathBuf;
7use std::sync::Arc;
8use tokio::sync::RwLock;
9use tracing::{error, info, warn};
10
11pub struct SchemaWatcher {
13 schema_path: PathBuf,
15 schema_sdl: Arc<RwLock<String>>,
17 _watcher: Option<RecommendedWatcher>,
19}
20
21impl SchemaWatcher {
22 pub async fn new(
24 schema_path: PathBuf,
25 ) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
26 let initial_sdl = tokio::fs::read_to_string(&schema_path).await?;
28 let schema_sdl = Arc::new(RwLock::new(initial_sdl));
29
30 Ok(Self {
31 schema_path,
32 schema_sdl,
33 _watcher: None,
34 })
35 }
36
37 pub fn start_watching(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
39 let schema_path = self.schema_path.clone();
40 let schema_sdl = Arc::clone(&self.schema_sdl);
41
42 let mut watcher = notify::recommended_watcher(move |res: Result<Event, notify::Error>| {
43 match res {
44 Ok(event) => {
45 if event.kind.is_modify() {
46 info!("Schema file changed, reloading...");
47 let path = schema_path.clone();
48 let sdl = Arc::clone(&schema_sdl);
49
50 tokio::spawn(async move {
52 match tokio::fs::read_to_string(&path).await {
53 Ok(new_sdl) => {
54 let mut sdl_lock = sdl.write().await;
55 *sdl_lock = new_sdl;
56 info!("✓ Schema reloaded successfully");
57 }
58 Err(e) => {
59 error!("Failed to reload schema: {}", e);
60 }
61 }
62 });
63 }
64 }
65 Err(e) => warn!("Watch error: {:?}", e),
66 }
67 })?;
68
69 watcher.watch(&self.schema_path, RecursiveMode::NonRecursive)?;
70 self._watcher = Some(watcher);
71
72 info!("👀 Watching schema file: {:?}", self.schema_path);
73 Ok(())
74 }
75
76 pub async fn get_schema(&self) -> String {
78 self.schema_sdl.read().await.clone()
79 }
80
81 pub async fn reload(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
83 let new_sdl = tokio::fs::read_to_string(&self.schema_path).await?;
84 let mut sdl_lock = self.schema_sdl.write().await;
85 *sdl_lock = new_sdl;
86 info!("Schema manually reloaded");
87 Ok(())
88 }
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94 use std::io::Write;
95 use tempfile::NamedTempFile;
96
97 #[tokio::test]
98 async fn test_schema_watcher_creation() {
99 let mut temp_file = NamedTempFile::new().unwrap();
100 writeln!(temp_file, "type Query {{ hello: String }}").unwrap();
101
102 let watcher = SchemaWatcher::new(temp_file.path().to_path_buf()).await;
103 assert!(watcher.is_ok());
104 }
105
106 #[tokio::test]
107 async fn test_get_schema() {
108 let mut temp_file = NamedTempFile::new().unwrap();
109 let schema_content = "type Query { hello: String }";
110 writeln!(temp_file, "{}", schema_content).unwrap();
111
112 let watcher = SchemaWatcher::new(temp_file.path().to_path_buf()).await.unwrap();
113 let sdl = watcher.get_schema().await;
114 assert!(sdl.contains("type Query"));
115 }
116
117 #[tokio::test]
118 async fn test_manual_reload() {
119 let mut temp_file = NamedTempFile::new().unwrap();
120 writeln!(temp_file, "type Query {{ hello: String }}").unwrap();
121
122 let watcher = SchemaWatcher::new(temp_file.path().to_path_buf()).await.unwrap();
123
124 writeln!(temp_file, "type Query {{ world: String }}").unwrap();
126
127 let result = watcher.reload().await;
129 assert!(result.is_ok());
130
131 let sdl = watcher.get_schema().await;
132 assert!(sdl.contains("world"));
133 }
134}