1use std::future::pending as pending_forever;
4
5use serde::de::DeserializeOwned;
6use tokio::sync::broadcast;
7use tracing::warn;
8
9use crate::dynamic::ConfigChangeEvent;
10
11pub struct FieldUpdateWatcher {
18 pub(crate) key: String,
20 pub(crate) rx: Option<broadcast::Receiver<ConfigChangeEvent>>,
22}
23
24impl FieldUpdateWatcher {
25 pub async fn changed<T>(&mut self) -> (Option<T>, Option<T>)
27 where
28 T: DeserializeOwned,
29 {
30 if self.rx.is_none() {
31 pending_forever::<()>().await;
32 unreachable!();
33 }
34
35 let rx = self.rx.as_mut().unwrap();
36 loop {
37 match rx.recv().await {
38 Ok(event) if event.key == self.key => {
39 let old_ref = event.old_value.as_ref();
40 let new_ref = event.new_value.as_ref();
41
42 let old_t = old_ref.and_then(|ov| serde_json::from_value::<T>(ov.clone()).ok());
43 let new_t = new_ref.and_then(|nv| serde_json::from_value::<T>(nv.clone()).ok());
44
45 if new_t.is_some() || old_t.is_some() {
46 return (old_t, new_t);
47 }
48
49 if let Some(new_ref) = new_ref {
51 warn!(
52 key = %self.key,
53 expected = %std::any::type_name::<T>(),
54 actual = %get_type_name(new_ref),
55 "FieldUpdateWatcher failed to deserialize new value. Skipping update."
56 );
57 }
58 }
59 Ok(_) => continue,
61 Err(broadcast::error::RecvError::Lagged(_)) => {
62 saluki_antithesis::unreachable!(
63 "config filter update dropped (broadcast Lagged); live filtering may stay stale",
64 { "key": self.key.to_string() }
65 );
66 warn!(
67 "FieldUpdateWatcher dropped events for key: {}. Continuing to wait for the next event.",
68 self.key
69 );
70 continue;
71 }
72 Err(broadcast::error::RecvError::Closed) => {
73 pending_forever::<()>().await;
75 unreachable!();
76 }
77 }
78 }
79 }
80}
81
82fn get_type_name(value: &serde_json::Value) -> &'static str {
83 match value {
84 serde_json::Value::Null => "null",
85 serde_json::Value::Bool(_) => "bool",
86 serde_json::Value::Number(_) => "number",
87 serde_json::Value::String(_) => "string",
88 serde_json::Value::Array(_) => "array",
89 serde_json::Value::Object(_) => "object",
90 }
91}
92
93#[cfg(test)]
94mod tests {
95 use std::time::Duration;
96
97 use serde_json::json;
98 use tokio::sync::broadcast;
99
100 use super::FieldUpdateWatcher;
101 use crate::dynamic::event::{ConfigChangeEvent, ConfigUpdate};
102 use crate::ConfigurationLoader;
103
104 fn change_event(key: &str, new_value: serde_json::Value) -> ConfigChangeEvent {
105 ConfigChangeEvent {
106 key: key.to_string(),
107 old_value: None,
108 new_value: Some(new_value),
109 }
110 }
111
112 fn watcher_over(key: &str, capacity: usize) -> (broadcast::Sender<ConfigChangeEvent>, FieldUpdateWatcher) {
113 let (tx, rx) = broadcast::channel(capacity);
114 (
115 tx,
116 FieldUpdateWatcher {
117 key: key.to_string(),
118 rx: Some(rx),
119 },
120 )
121 }
122
123 #[tokio::test]
124 async fn changed_returns_partial_update_for_watched_key() {
125 let (cfg, sender) = ConfigurationLoader::for_tests(
126 Some(serde_json::json!({ "foobar": { "a": false, "b": "c" } })),
127 None,
128 true,
129 )
130 .await;
131 let sender = sender.expect("sender should exist");
132
133 sender
134 .send(ConfigUpdate::Snapshot(serde_json::json!({})))
135 .await
136 .unwrap();
137 cfg.ready().await;
138
139 let mut watcher = cfg.watch_for_updates("watched_key");
140
141 sender
142 .send(ConfigUpdate::Partial {
143 key: "watched_key".to_string(),
144 value: serde_json::json!("hello"),
145 })
146 .await
147 .unwrap();
148
149 let (old, new) = tokio::time::timeout(std::time::Duration::from_secs(2), watcher.changed::<String>())
150 .await
151 .expect("timed out waiting for watched_key update");
152
153 assert_eq!(old, None);
154 assert_eq!(new, Some("hello".to_string()));
155 }
156
157 #[tokio::test]
158 async fn changed_returns_nested_key_update() {
159 let (cfg, sender) = ConfigurationLoader::for_tests(
160 Some(serde_json::json!({ "foobar": { "a": false, "b": "c" } })),
161 None,
162 true,
163 )
164 .await;
165 let sender = sender.expect("sender should exist");
166
167 sender
168 .send(ConfigUpdate::Snapshot(serde_json::json!({})))
169 .await
170 .unwrap();
171 cfg.ready().await;
172
173 let mut watcher = cfg.watch_for_updates("foobar.a");
174
175 sender
177 .send(ConfigUpdate::Partial {
178 key: "foobar.a".to_string(),
179 value: serde_json::json!(true),
180 })
181 .await
182 .unwrap();
183
184 let (old, new) = tokio::time::timeout(std::time::Duration::from_secs(2), watcher.changed::<bool>())
185 .await
186 .expect("timed out waiting for foobar.a update");
187
188 assert_eq!(old, Some(false));
189 assert_eq!(new, Some(true));
190 assert!(cfg.get_typed::<bool>("foobar.a").unwrap());
191
192 assert_eq!(cfg.get_typed::<String>("foobar.b").unwrap(), "c");
194 }
195
196 #[tokio::test]
197 async fn changed_returns_update_when_parent_object_changes() {
198 let (cfg, sender) = ConfigurationLoader::for_tests(
199 Some(serde_json::json!({ "foobar": { "a": false, "b": "c" } })),
200 None,
201 true,
202 )
203 .await;
204 let sender = sender.expect("sender should exist");
205
206 sender
207 .send(ConfigUpdate::Snapshot(serde_json::json!({})))
208 .await
209 .unwrap();
210 cfg.ready().await;
211
212 let mut watcher = cfg.watch_for_updates("foobar.a");
213
214 sender
216 .send(ConfigUpdate::Partial {
217 key: "foobar".to_string(),
218 value: serde_json::json!({ "a": true }),
219 })
220 .await
221 .unwrap();
222
223 let (old, new) = tokio::time::timeout(std::time::Duration::from_secs(2), watcher.changed::<bool>())
224 .await
225 .expect("timed out waiting for foobar.a update");
226
227 assert_eq!(old, Some(false));
228 assert_eq!(new, Some(true));
229 assert!(cfg.get_typed::<bool>("foobar.a").unwrap());
230
231 assert_eq!(cfg.get_typed::<String>("foobar.b").unwrap(), "c");
233 }
234
235 #[tokio::test]
236 async fn changed_waits_forever_when_dynamic_configuration_disabled() {
237 let mut watcher = FieldUpdateWatcher {
239 key: "watched_key".to_string(),
240 rx: None,
241 };
242
243 let result = tokio::time::timeout(Duration::from_millis(250), watcher.changed::<String>()).await;
244 assert!(
245 result.is_err(),
246 "changed() must stay pending when dynamic config is disabled"
247 );
248 }
249
250 #[tokio::test]
251 async fn changed_waits_forever_after_channel_closes() {
252 let (tx, mut watcher) = watcher_over("watched_key", 4);
255 drop(tx);
256
257 let result = tokio::time::timeout(Duration::from_millis(250), watcher.changed::<String>()).await;
258 assert!(result.is_err(), "changed() must stay pending after the channel closes");
259 }
260
261 #[tokio::test]
262 async fn changed_skips_lagged_errors_and_returns_next_event() {
263 let (tx, mut watcher) = watcher_over("watched_key", 1);
266 tx.send(change_event("watched_key", json!("first"))).unwrap();
267 tx.send(change_event("watched_key", json!("second"))).unwrap();
268 tx.send(change_event("watched_key", json!("third"))).unwrap();
269
270 let (old, new) = tokio::time::timeout(Duration::from_secs(2), watcher.changed::<String>())
271 .await
272 .expect("changed() should resolve after skipping the lagged error");
273
274 assert_eq!(old, None);
275 assert_eq!(new, Some("third".to_string()));
276 }
277
278 #[tokio::test]
279 async fn changed_skips_events_that_fail_to_deserialize() {
280 let (tx, mut watcher) = watcher_over("watched_key", 8);
283 tx.send(change_event("watched_key", json!("not-a-number"))).unwrap();
284 tx.send(change_event("watched_key", json!(42))).unwrap();
285
286 let (old, new) = tokio::time::timeout(Duration::from_secs(2), watcher.changed::<u32>())
287 .await
288 .expect("changed() should resolve once a well-typed value arrives");
289
290 assert_eq!(old, None);
291 assert_eq!(new, Some(42));
292 }
293}