1use std::collections::HashMap;
8use std::sync::Arc;
9use std::time::Duration;
10
11use serde::ser::{Serialize, SerializeMap, Serializer};
12use serde_json::Value;
13use tokio::sync::{Notify, RwLock};
14
15use crate::AdkError;
16
17const MIN_TIMEOUT: Duration = Duration::from_millis(1);
19const MAX_TIMEOUT: Duration = Duration::from_secs(300);
21const MAX_KEY_LEN: usize = 256;
23
24#[derive(Debug, thiserror::Error)]
26pub enum SharedStateError {
27 #[error("shared state key must not be empty")]
29 EmptyKey,
30
31 #[error("shared state key exceeds 256 bytes: {len} bytes")]
33 KeyTooLong {
34 len: usize,
36 },
37
38 #[error("wait_for_key timed out after {timeout:?} for key \"{key}\"")]
40 Timeout {
41 key: String,
43 timeout: Duration,
45 },
46
47 #[error("invalid timeout {timeout:?}: must be between 1ms and 300s")]
49 InvalidTimeout {
50 timeout: Duration,
52 },
53}
54
55impl From<SharedStateError> for AdkError {
56 fn from(err: SharedStateError) -> Self {
57 AdkError::agent(err.to_string())
58 }
59}
60
61#[derive(Debug)]
82pub struct SharedState {
83 data: RwLock<HashMap<String, Value>>,
84 notifiers: RwLock<HashMap<String, Arc<Notify>>>,
85}
86
87impl SharedState {
88 #[must_use]
90 pub fn new() -> Self {
91 Self { data: RwLock::new(HashMap::new()), notifiers: RwLock::new(HashMap::new()) }
92 }
93
94 pub async fn set_shared(
101 &self,
102 key: impl Into<String>,
103 value: Value,
104 ) -> Result<(), SharedStateError> {
105 let key = key.into();
106 validate_key(&key)?;
107
108 self.data.write().await.insert(key.clone(), value);
109
110 let notifiers = self.notifiers.read().await;
112 if let Some(notify) = notifiers.get(&key) {
113 notify.notify_waiters();
114 }
115
116 Ok(())
117 }
118
119 pub async fn get_shared(&self, key: &str) -> Option<Value> {
121 self.data.read().await.get(key).cloned()
122 }
123
124 pub async fn wait_for_key(
135 &self,
136 key: &str,
137 timeout: Duration,
138 ) -> Result<Value, SharedStateError> {
139 self.wait_for_key_with(key, timeout, || async {}).await
140 }
141
142 async fn wait_for_key_with<F, Fut>(
146 &self,
147 key: &str,
148 timeout: Duration,
149 mut after_miss: F,
150 ) -> Result<Value, SharedStateError>
151 where
152 F: FnMut() -> Fut,
153 Fut: std::future::Future<Output = ()>,
154 {
155 if timeout < MIN_TIMEOUT || timeout > MAX_TIMEOUT {
156 return Err(SharedStateError::InvalidTimeout { timeout });
157 }
158
159 if let Some(value) = self.data.read().await.get(key).cloned() {
161 return Ok(value);
162 }
163
164 let notify = {
165 let mut notifiers = self.notifiers.write().await;
166 notifiers.entry(key.to_string()).or_insert_with(|| Arc::new(Notify::new())).clone()
167 };
168
169 let deadline = tokio::time::Instant::now() + timeout;
170 loop {
171 let notified = notify.notified();
175 tokio::pin!(notified);
176 notified.as_mut().enable();
177
178 if let Some(value) = self.data.read().await.get(key).cloned() {
179 return Ok(value);
180 }
181 after_miss().await;
182
183 if tokio::time::timeout_at(deadline, notified).await.is_err() {
184 return Err(SharedStateError::Timeout { key: key.to_string(), timeout });
185 }
186 }
187 }
188
189 pub async fn snapshot(&self) -> HashMap<String, Value> {
191 self.data.read().await.clone()
192 }
193}
194
195impl Default for SharedState {
196 fn default() -> Self {
197 Self::new()
198 }
199}
200
201impl Serialize for SharedState {
202 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
203 match self.data.try_read() {
206 Ok(data) => {
207 let mut map = serializer.serialize_map(Some(data.len()))?;
208 for (k, v) in data.iter() {
209 map.serialize_entry(k, v)?;
210 }
211 map.end()
212 }
213 Err(_) => serializer.serialize_map(Some(0))?.end(),
214 }
215 }
216}
217
218fn validate_key(key: &str) -> Result<(), SharedStateError> {
220 if key.is_empty() {
221 return Err(SharedStateError::EmptyKey);
222 }
223 if key.len() > MAX_KEY_LEN {
224 return Err(SharedStateError::KeyTooLong { len: key.len() });
225 }
226 Ok(())
227}
228
229#[cfg(test)]
230mod tests {
231 use super::*;
232
233 #[tokio::test]
234 async fn new_shared_state_is_empty() {
235 let state = SharedState::new();
236 assert!(state.snapshot().await.is_empty());
237 }
238
239 #[tokio::test]
240 async fn set_and_get() {
241 let state = SharedState::new();
242 state.set_shared("key", serde_json::json!("value")).await.unwrap();
243 assert_eq!(state.get_shared("key").await, Some(serde_json::json!("value")));
244 }
245
246 #[tokio::test]
247 async fn get_missing_returns_none() {
248 let state = SharedState::new();
249 assert_eq!(state.get_shared("missing").await, None);
250 }
251
252 #[tokio::test]
253 async fn overwrite_replaces_value() {
254 let state = SharedState::new();
255 state.set_shared("key", serde_json::json!(1)).await.unwrap();
256 state.set_shared("key", serde_json::json!(2)).await.unwrap();
257 assert_eq!(state.get_shared("key").await, Some(serde_json::json!(2)));
258 }
259
260 #[tokio::test]
261 async fn empty_key_rejected() {
262 let state = SharedState::new();
263 let err = state.set_shared("", serde_json::json!(1)).await.unwrap_err();
264 assert!(matches!(err, SharedStateError::EmptyKey));
265 }
266
267 #[tokio::test]
268 async fn long_key_rejected() {
269 let state = SharedState::new();
270 let long_key = "x".repeat(257);
271 let err = state.set_shared(long_key, serde_json::json!(1)).await.unwrap_err();
272 assert!(matches!(err, SharedStateError::KeyTooLong { .. }));
273 }
274
275 #[tokio::test]
276 async fn key_at_256_bytes_accepted() {
277 let state = SharedState::new();
278 let key = "x".repeat(256);
279 state.set_shared(key.clone(), serde_json::json!(1)).await.unwrap();
280 assert_eq!(state.get_shared(&key).await, Some(serde_json::json!(1)));
281 }
282
283 #[tokio::test]
284 async fn wait_for_existing_key_returns_immediately() {
285 let state = SharedState::new();
286 state.set_shared("key", serde_json::json!("val")).await.unwrap();
287 let val = state.wait_for_key("key", Duration::from_secs(1)).await.unwrap();
288 assert_eq!(val, serde_json::json!("val"));
289 }
290
291 #[tokio::test]
292 async fn wait_for_key_timeout() {
293 let state = SharedState::new();
294 let err = state.wait_for_key("missing", Duration::from_millis(10)).await.unwrap_err();
295 assert!(matches!(err, SharedStateError::Timeout { .. }));
296 }
297
298 #[tokio::test]
299 async fn wait_for_key_invalid_timeout_too_small() {
300 let state = SharedState::new();
301 let err = state.wait_for_key("key", Duration::from_nanos(1)).await.unwrap_err();
302 assert!(matches!(err, SharedStateError::InvalidTimeout { .. }));
303 }
304
305 #[tokio::test]
306 async fn wait_for_key_invalid_timeout_too_large() {
307 let state = SharedState::new();
308 let err = state.wait_for_key("key", Duration::from_secs(301)).await.unwrap_err();
309 assert!(matches!(err, SharedStateError::InvalidTimeout { .. }));
310 }
311
312 #[tokio::test(start_paused = true)]
315 async fn wait_for_key_sees_a_set_between_its_check_and_its_wait() {
316 let state = SharedState::new();
317 let state_ref = &state;
318 let mut misses = 0;
319 let value = state
320 .wait_for_key_with("raced", MAX_TIMEOUT, || {
321 misses += 1;
322 let first_miss = misses == 1;
323 async move {
324 if first_miss {
325 state_ref.set_shared("raced", serde_json::json!("val")).await.unwrap();
326 }
327 }
328 })
329 .await
330 .unwrap();
331 assert_eq!(value, serde_json::json!("val"));
332 assert_eq!(misses, 1);
333 }
334
335 #[tokio::test]
336 async fn error_converts_to_adk_error() {
337 let err = SharedStateError::EmptyKey;
338 let adk_err: AdkError = err.into();
339 assert!(adk_err.to_string().contains("empty"));
340 }
341}