Skip to main content

algocline_core/
custom.rs

1use std::collections::HashMap;
2
3/// KV store written from Lua via alc.stats.record(key, value).
4pub struct CustomMetrics {
5    entries: HashMap<String, serde_json::Value>,
6}
7
8impl CustomMetrics {
9    pub fn new() -> Self {
10        Self {
11            entries: HashMap::new(),
12        }
13    }
14
15    pub fn record(&mut self, key: String, value: serde_json::Value) {
16        self.entries.insert(key, value);
17    }
18
19    pub fn get(&self, key: &str) -> Option<&serde_json::Value> {
20        self.entries.get(key)
21    }
22
23    pub fn to_json(&self) -> serde_json::Value {
24        serde_json::to_value(&self.entries).unwrap_or(serde_json::Value::Null)
25    }
26}
27
28impl Default for CustomMetrics {
29    fn default() -> Self {
30        Self::new()
31    }
32}
33
34#[cfg(test)]
35mod tests {
36    use super::*;
37    use serde_json::json;
38
39    #[test]
40    fn record_and_get() {
41        let mut cm = CustomMetrics::new();
42        cm.record("key".into(), json!(42));
43        assert_eq!(cm.get("key"), Some(&json!(42)));
44    }
45
46    #[test]
47    fn get_missing_returns_none() {
48        let cm = CustomMetrics::new();
49        assert_eq!(cm.get("missing"), None);
50    }
51
52    #[test]
53    fn record_overwrites() {
54        let mut cm = CustomMetrics::new();
55        cm.record("key".into(), json!(1));
56        cm.record("key".into(), json!(2));
57        assert_eq!(cm.get("key"), Some(&json!(2)));
58    }
59
60    #[test]
61    fn to_json_includes_all_entries() {
62        let mut cm = CustomMetrics::new();
63        cm.record("a".into(), json!(1));
64        cm.record("b".into(), json!("two"));
65        let json = cm.to_json();
66        assert_eq!(json.get("a").unwrap(), 1);
67        assert_eq!(json.get("b").unwrap(), "two");
68    }
69}
70
71#[cfg(test)]
72mod proptests {
73    use super::*;
74    use proptest::prelude::*;
75
76    proptest! {
77        #[test]
78        fn record_then_get_consistent(key in "[a-zA-Z_]{1,30}", val in any::<i64>()) {
79            let mut cm = CustomMetrics::new();
80            let json_val = serde_json::json!(val);
81            cm.record(key.clone(), json_val.clone());
82            prop_assert_eq!(cm.get(&key), Some(&json_val));
83        }
84
85        #[test]
86        fn last_write_wins(key in "[a-zA-Z_]{1,30}", v1 in any::<i64>(), v2 in any::<i64>()) {
87            let mut cm = CustomMetrics::new();
88            cm.record(key.clone(), serde_json::json!(v1));
89            cm.record(key.clone(), serde_json::json!(v2));
90            prop_assert_eq!(cm.get(&key), Some(&serde_json::json!(v2)));
91        }
92
93        #[test]
94        fn to_json_contains_all_recorded(
95            entries in proptest::collection::vec(
96                ("[a-z]{1,10}", any::<i64>()),
97                1..20,
98            )
99        ) {
100            let mut cm = CustomMetrics::new();
101            for (k, v) in &entries {
102                cm.record(k.clone(), serde_json::json!(v));
103            }
104            let json = cm.to_json();
105            // last-write-wins: check final value for each key
106            let mut expected = std::collections::HashMap::new();
107            for (k, v) in &entries {
108                expected.insert(k.clone(), serde_json::json!(v));
109            }
110            for (k, v) in &expected {
111                prop_assert_eq!(json.get(k), Some(v));
112            }
113        }
114    }
115}