1use std::collections::HashMap;
12use std::sync::{Arc, Mutex};
13use std::time::{Duration, Instant};
14
15use async_trait::async_trait;
16use serde_json::Value;
17
18#[async_trait]
19pub trait State: Send + Sync {
20 async fn append(&self, key: &str, item: Value, max_len: Option<usize>, ttl: Option<Duration>);
24
25 async fn set(&self, key: &str, value: Value, ttl: Option<Duration>);
27
28 async fn get(&self, key: &str) -> Option<Value>;
30}
31
32pub struct NamespacedState {
35 inner: Arc<dyn State>,
36 prefix: String,
37}
38
39impl NamespacedState {
40 pub fn new(inner: Arc<dyn State>, namespace: impl std::fmt::Display) -> Self {
41 Self {
42 inner,
43 prefix: format!("{namespace}."),
44 }
45 }
46
47 pub fn prefix(&self) -> &str {
48 &self.prefix
49 }
50
51 fn key(&self, key: &str) -> String {
52 format!("{}{key}", self.prefix)
53 }
54}
55
56#[async_trait]
57impl State for NamespacedState {
58 async fn append(&self, key: &str, item: Value, max_len: Option<usize>, ttl: Option<Duration>) {
59 self.inner.append(&self.key(key), item, max_len, ttl).await;
60 }
61
62 async fn set(&self, key: &str, value: Value, ttl: Option<Duration>) {
63 self.inner.set(&self.key(key), value, ttl).await;
64 }
65
66 async fn get(&self, key: &str) -> Option<Value> {
67 self.inner.get(&self.key(key)).await
68 }
69}
70
71struct Entry {
72 value: Value,
73 expires_at: Option<Instant>,
74}
75
76impl Entry {
77 fn is_expired(&self, now: Instant) -> bool {
78 self.expires_at.map(|e| now >= e).unwrap_or(false)
79 }
80}
81
82#[derive(Default)]
84pub struct MemoryState {
85 map: Mutex<HashMap<String, Entry>>,
86}
87
88impl MemoryState {
89 pub fn new() -> Self {
90 Self::default()
91 }
92
93 fn expiry(ttl: Option<Duration>) -> Option<Instant> {
94 ttl.map(|d| Instant::now() + d)
95 }
96}
97
98#[async_trait]
99impl State for MemoryState {
100 async fn append(&self, key: &str, item: Value, max_len: Option<usize>, ttl: Option<Duration>) {
101 let now = Instant::now();
102 let mut map = self.map.lock().unwrap();
103
104 if let Some(e) = map.get(key) {
106 if e.is_expired(now) {
107 map.remove(key);
108 }
109 }
110
111 let entry = map.entry(key.to_string()).or_insert_with(|| Entry {
112 value: Value::Array(Vec::new()),
113 expires_at: Self::expiry(ttl),
114 });
115
116 if !entry.value.is_array() {
117 entry.value = Value::Array(Vec::new());
118 }
119 if let Value::Array(list) = &mut entry.value {
120 list.push(item);
121 if let Some(cap) = max_len {
122 while list.len() > cap {
123 list.remove(0);
124 }
125 }
126 }
127 if ttl.is_some() {
128 entry.expires_at = Self::expiry(ttl);
129 }
130 }
131
132 async fn set(&self, key: &str, value: Value, ttl: Option<Duration>) {
133 let mut map = self.map.lock().unwrap();
134 map.insert(
135 key.to_string(),
136 Entry {
137 value,
138 expires_at: Self::expiry(ttl),
139 },
140 );
141 }
142
143 async fn get(&self, key: &str) -> Option<Value> {
144 let now = Instant::now();
145 let mut map = self.map.lock().unwrap();
146 match map.get(key) {
147 Some(e) if e.is_expired(now) => {
148 map.remove(key);
149 None
150 }
151 Some(e) => Some(e.value.clone()),
152 None => None,
153 }
154 }
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160 use serde_json::json;
161
162 #[tokio::test]
163 async fn append_respects_max_len() {
164 let s = MemoryState::new();
165 for i in 0..5 {
166 s.append("k", json!(i), Some(3), None).await;
167 }
168 let v = s.get("k").await.unwrap();
169 assert_eq!(v, json!([2, 3, 4]));
170 }
171
172 #[tokio::test]
173 async fn set_and_get() {
174 let s = MemoryState::new();
175 s.set("k", json!({"a": 1}), None).await;
176 assert_eq!(s.get("k").await.unwrap(), json!({"a": 1}));
177 assert!(s.get("missing").await.is_none());
178 }
179
180 #[tokio::test]
181 async fn namespaces_isolate_instances() {
182 let shared: Arc<dyn State> = Arc::new(MemoryState::new());
183 let first = NamespacedState::new(shared.clone(), "first");
184 let second = NamespacedState::new(shared, "second");
185 first.set("branch.value", json!(1), None).await;
186 second.set("branch.value", json!(2), None).await;
187 assert_eq!(first.get("branch.value").await, Some(json!(1)));
188 assert_eq!(second.get("branch.value").await, Some(json!(2)));
189 }
190}