1use std::collections::HashMap;
2use std::sync::{Arc, Mutex};
3
4pub struct CustomMetrics {
6 entries: HashMap<String, serde_json::Value>,
7}
8
9impl CustomMetrics {
10 pub fn new() -> Self {
11 Self {
12 entries: HashMap::new(),
13 }
14 }
15
16 pub fn record(&mut self, key: String, value: serde_json::Value) {
17 self.entries.insert(key, value);
18 }
19
20 pub fn get(&self, key: &str) -> Option<&serde_json::Value> {
21 self.entries.get(key)
22 }
23
24 pub fn to_json(&self) -> serde_json::Value {
25 serde_json::to_value(&self.entries).unwrap_or(serde_json::Value::Null)
26 }
27}
28
29impl Default for CustomMetrics {
30 fn default() -> Self {
31 Self::new()
32 }
33}
34
35#[derive(Clone)]
45pub struct CustomMetricsHandle {
46 inner: Arc<Mutex<CustomMetrics>>,
47}
48
49impl CustomMetricsHandle {
50 pub(crate) fn new(inner: Arc<Mutex<CustomMetrics>>) -> Self {
51 Self { inner }
52 }
53
54 pub fn record(&self, key: String, value: serde_json::Value) {
56 if let Ok(mut m) = self.inner.lock() {
57 m.record(key, value);
58 }
59 }
60
61 pub fn get(&self, key: &str) -> Option<serde_json::Value> {
63 self.inner.lock().ok().and_then(|m| m.get(key).cloned())
64 }
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70 use serde_json::json;
71
72 #[test]
73 fn record_and_get() {
74 let mut cm = CustomMetrics::new();
75 cm.record("key".into(), json!(42));
76 assert_eq!(cm.get("key"), Some(&json!(42)));
77 }
78
79 #[test]
80 fn get_missing_returns_none() {
81 let cm = CustomMetrics::new();
82 assert_eq!(cm.get("missing"), None);
83 }
84
85 #[test]
86 fn record_overwrites() {
87 let mut cm = CustomMetrics::new();
88 cm.record("key".into(), json!(1));
89 cm.record("key".into(), json!(2));
90 assert_eq!(cm.get("key"), Some(&json!(2)));
91 }
92
93 #[test]
94 fn to_json_includes_all_entries() {
95 let mut cm = CustomMetrics::new();
96 cm.record("a".into(), json!(1));
97 cm.record("b".into(), json!("two"));
98 let json = cm.to_json();
99 assert_eq!(json.get("a").unwrap(), 1);
100 assert_eq!(json.get("b").unwrap(), "two");
101 }
102}
103
104#[cfg(test)]
105mod proptests {
106 use super::*;
107 use proptest::prelude::*;
108
109 proptest! {
110 #[test]
111 fn record_then_get_consistent(key in "[a-zA-Z_]{1,30}", val in any::<i64>()) {
112 let mut cm = CustomMetrics::new();
113 let json_val = serde_json::json!(val);
114 cm.record(key.clone(), json_val.clone());
115 prop_assert_eq!(cm.get(&key), Some(&json_val));
116 }
117
118 #[test]
119 fn last_write_wins(key in "[a-zA-Z_]{1,30}", v1 in any::<i64>(), v2 in any::<i64>()) {
120 let mut cm = CustomMetrics::new();
121 cm.record(key.clone(), serde_json::json!(v1));
122 cm.record(key.clone(), serde_json::json!(v2));
123 prop_assert_eq!(cm.get(&key), Some(&serde_json::json!(v2)));
124 }
125
126 #[test]
127 fn to_json_contains_all_recorded(
128 entries in proptest::collection::vec(
129 ("[a-z]{1,10}", any::<i64>()),
130 1..20,
131 )
132 ) {
133 let mut cm = CustomMetrics::new();
134 for (k, v) in &entries {
135 cm.record(k.clone(), serde_json::json!(v));
136 }
137 let json = cm.to_json();
138 let mut expected = std::collections::HashMap::new();
140 for (k, v) in &entries {
141 expected.insert(k.clone(), serde_json::json!(v));
142 }
143 for (k, v) in &expected {
144 prop_assert_eq!(json.get(k), Some(v));
145 }
146 }
147 }
148}