1use std::collections::VecDeque;
8use std::sync::Mutex;
9
10pub trait HasId {
11 fn id(&self) -> &str;
12}
13
14#[derive(Debug)]
15pub struct LogStore<T> {
16 entries: Mutex<VecDeque<T>>,
17 capacity: usize,
18}
19
20impl<T: HasId + Clone> LogStore<T> {
21 pub fn new(capacity: usize) -> Self {
22 Self {
23 entries: Mutex::new(VecDeque::with_capacity(capacity)),
24 capacity,
25 }
26 }
27
28 pub fn push(&self, entry: T) {
29 let mut entries = self.entries.lock().unwrap();
30 if entries.len() >= self.capacity {
31 entries.pop_front();
32 }
33 entries.push_back(entry);
34 }
35
36 pub fn get(&self, id: &str) -> Option<T> {
37 let entries = self.entries.lock().unwrap();
38 entries.iter().find(|e| e.id() == id).cloned()
39 }
40
41 pub fn recent(&self, n: usize) -> Vec<T> {
42 let entries = self.entries.lock().unwrap();
43 entries.iter().rev().take(n).cloned().collect()
44 }
45}
46
47#[cfg(test)]
48mod tests {
49 use super::*;
50
51 #[derive(Debug, Clone)]
52 struct Entry {
53 id: String,
54 value: i32,
55 }
56
57 impl HasId for Entry {
58 fn id(&self) -> &str {
59 &self.id
60 }
61 }
62
63 #[test]
64 fn push_and_get() {
65 let store = LogStore::new(10);
66 store.push(Entry {
67 id: "a".into(),
68 value: 1,
69 });
70 store.push(Entry {
71 id: "b".into(),
72 value: 2,
73 });
74 assert_eq!(store.get("a").unwrap().value, 1);
75 assert_eq!(store.get("b").unwrap().value, 2);
76 }
77
78 #[test]
79 fn eviction_at_capacity() {
80 let store = LogStore::new(2);
81 store.push(Entry {
82 id: "a".into(),
83 value: 1,
84 });
85 store.push(Entry {
86 id: "b".into(),
87 value: 2,
88 });
89 store.push(Entry {
90 id: "c".into(),
91 value: 3,
92 });
93 assert!(store.get("a").is_none());
94 assert_eq!(store.get("b").unwrap().value, 2);
95 assert_eq!(store.get("c").unwrap().value, 3);
96 }
97
98 #[test]
99 fn recent_newest_first() {
100 let store = LogStore::new(10);
101 store.push(Entry {
102 id: "a".into(),
103 value: 1,
104 });
105 store.push(Entry {
106 id: "b".into(),
107 value: 2,
108 });
109 store.push(Entry {
110 id: "c".into(),
111 value: 3,
112 });
113 let recent = store.recent(2);
114 assert_eq!(recent.len(), 2);
115 assert_eq!(recent[0].id, "c");
116 assert_eq!(recent[1].id, "b");
117 }
118
119 #[test]
120 fn get_missing_returns_none() {
121 let store: LogStore<Entry> = LogStore::new(10);
122 assert!(store.get("nonexistent").is_none());
123 }
124}