Skip to main content

moonpool_sim/chaos/
state_handle.rs

1//! Type-safe shared state for workload communication and invariant checking.
2//!
3//! `StateHandle` replaces the JSON-based `StateRegistry` with a type-safe
4//! `Rc<RefCell<HashMap>>` backed store using `Box<dyn Any>`.
5//!
6//! # Usage
7//!
8//! ```ignore
9//! use moonpool_sim::StateHandle;
10//!
11//! let state = StateHandle::new();
12//! state.publish("counter", 42u64);
13//! let val: Option<u64> = state.get("counter");
14//! assert_eq!(val, Some(42));
15//! ```
16
17use std::any::Any;
18use std::cell::RefCell;
19use std::collections::HashMap;
20use std::rc::Rc;
21
22/// Shared state handle for cross-workload communication and invariant checking.
23///
24/// Provides type-safe publish/get semantics using `std::any::Any` for type erasure.
25/// Clone is cheap (Rc-based) — all clones share the same underlying storage.
26#[derive(Clone, Default)]
27pub struct StateHandle {
28    inner: Rc<RefCell<HashMap<String, Box<dyn Any>>>>,
29}
30
31impl StateHandle {
32    /// Create a new empty state handle.
33    pub fn new() -> Self {
34        Self::default()
35    }
36
37    /// Publish a value under a key, replacing any existing value.
38    pub fn publish<T: Any + 'static>(&self, key: &str, value: T) {
39        self.inner
40            .borrow_mut()
41            .insert(key.to_string(), Box::new(value));
42    }
43
44    /// Get a cloned copy of the value under a key, if it exists and matches the type.
45    ///
46    /// Returns `None` if the key doesn't exist or the type doesn't match.
47    pub fn get<T: Any + Clone + 'static>(&self, key: &str) -> Option<T> {
48        self.inner
49            .borrow()
50            .get(key)
51            .and_then(|v| v.downcast_ref::<T>())
52            .cloned()
53    }
54
55    /// Check whether a key exists in the state.
56    pub fn contains(&self, key: &str) -> bool {
57        self.inner.borrow().contains_key(key)
58    }
59}
60
61impl std::fmt::Debug for StateHandle {
62    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63        let count = self.inner.borrow().len();
64        f.debug_struct("StateHandle")
65            .field("key_count", &count)
66            .finish()
67    }
68}
69
70#[cfg(test)]
71mod tests {
72    use super::*;
73
74    #[test]
75    fn test_publish_and_get() {
76        let state = StateHandle::new();
77        state.publish("count", 42u64);
78        assert_eq!(state.get::<u64>("count"), Some(42));
79    }
80
81    #[test]
82    fn test_get_wrong_type_returns_none() {
83        let state = StateHandle::new();
84        state.publish("count", 42u64);
85        assert_eq!(state.get::<String>("count"), None);
86    }
87
88    #[test]
89    fn test_get_missing_key_returns_none() {
90        let state = StateHandle::new();
91        assert_eq!(state.get::<u64>("missing"), None);
92    }
93
94    #[test]
95    fn test_contains() {
96        let state = StateHandle::new();
97        assert!(!state.contains("key"));
98        state.publish("key", "value".to_string());
99        assert!(state.contains("key"));
100    }
101
102    #[test]
103    fn test_publish_replaces() {
104        let state = StateHandle::new();
105        state.publish("x", 1u64);
106        state.publish("x", 2u64);
107        assert_eq!(state.get::<u64>("x"), Some(2));
108    }
109
110    #[test]
111    fn test_clone_shares_state() {
112        let state = StateHandle::new();
113        let clone = state.clone();
114        state.publish("shared", true);
115        assert_eq!(clone.get::<bool>("shared"), Some(true));
116    }
117}