agentstategraph_reminders/
memory.rs1use std::collections::HashMap;
5use std::sync::RwLock;
6
7use crate::error::ReminderError;
8use crate::store::ReminderStore;
9use crate::types::{Reminder, ReminderFilter};
10
11pub struct MemoryReminderStore {
12 reminders: RwLock<HashMap<String, Reminder>>,
13}
14
15impl Default for MemoryReminderStore {
16 fn default() -> Self {
17 Self::new()
18 }
19}
20
21impl MemoryReminderStore {
22 pub fn new() -> Self {
23 Self {
24 reminders: RwLock::new(HashMap::new()),
25 }
26 }
27}
28
29impl ReminderStore for MemoryReminderStore {
30 fn save(&self, reminder: &Reminder) -> Result<(), ReminderError> {
31 self.reminders
32 .write()
33 .expect("MemoryReminderStore lock poisoned")
34 .insert(reminder.id.clone(), reminder.clone());
35 Ok(())
36 }
37
38 fn get(&self, id: &str) -> Result<Option<Reminder>, ReminderError> {
39 Ok(self
40 .reminders
41 .read()
42 .expect("MemoryReminderStore lock poisoned")
43 .get(id)
44 .cloned())
45 }
46
47 fn update(&self, reminder: &Reminder) -> Result<(), ReminderError> {
48 let mut map = self
49 .reminders
50 .write()
51 .expect("MemoryReminderStore lock poisoned");
52 if map.contains_key(&reminder.id) {
53 map.insert(reminder.id.clone(), reminder.clone());
54 Ok(())
55 } else {
56 Err(ReminderError::NotFound(reminder.id.clone()))
57 }
58 }
59
60 fn update_if_unchanged(
61 &self,
62 expected: &Reminder,
63 new: &Reminder,
64 ) -> Result<bool, ReminderError> {
65 let mut map = self
66 .reminders
67 .write()
68 .expect("MemoryReminderStore lock poisoned");
69 match map.get(&expected.id) {
70 Some(current) if crate::store::same_reminder(current, expected) => {
71 map.insert(new.id.clone(), new.clone());
72 Ok(true)
73 }
74 Some(_) => Ok(false),
75 None => Err(ReminderError::NotFound(expected.id.clone())),
76 }
77 }
78
79 fn delete(&self, id: &str) -> Result<bool, ReminderError> {
80 Ok(self
81 .reminders
82 .write()
83 .expect("MemoryReminderStore lock poisoned")
84 .remove(id)
85 .is_some())
86 }
87
88 fn list(&self, filter: &ReminderFilter) -> Result<Vec<Reminder>, ReminderError> {
89 let map = self
90 .reminders
91 .read()
92 .expect("MemoryReminderStore lock poisoned");
93 let mut results: Vec<Reminder> = map
94 .values()
95 .filter(|r| filter.matches(r))
96 .cloned()
97 .collect();
98 results.sort_by(|a, b| a.priority.cmp(&b.priority).then(a.due_at.cmp(&b.due_at)));
100 Ok(results)
101 }
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107 use crate::types::{CreateReminder, Priority, ReminderStatus};
108 use chrono::{Duration, Utc};
109
110 fn make(title: &str, priority: Priority, due_secs: i64) -> Reminder {
111 CreateReminder::new(
112 title,
113 "instructions",
114 Utc::now() + Duration::seconds(due_secs),
115 "agent/test",
116 )
117 .with_priority(priority)
118 .into_reminder()
119 }
120
121 #[test]
122 fn save_and_get_roundtrip() {
123 let store = MemoryReminderStore::new();
124 let r = make("test", Priority::Medium, 60);
125 store.save(&r).unwrap();
126 let fetched = store.get(&r.id).unwrap().unwrap();
127 assert_eq!(fetched.id, r.id);
128 assert_eq!(fetched.title, "test");
129 }
130
131 #[test]
132 fn get_nonexistent_returns_none() {
133 let store = MemoryReminderStore::new();
134 assert!(store.get("no-such-id").unwrap().is_none());
135 }
136
137 #[test]
138 fn update_mutates_record() {
139 let store = MemoryReminderStore::new();
140 let mut r = make("original", Priority::Low, 60);
141 store.save(&r).unwrap();
142 r.title = "updated".into();
143 store.update(&r).unwrap();
144 assert_eq!(store.get(&r.id).unwrap().unwrap().title, "updated");
145 }
146
147 #[test]
148 fn update_nonexistent_returns_not_found() {
149 let store = MemoryReminderStore::new();
150 let r = make("x", Priority::Low, 60);
151 assert!(matches!(store.update(&r), Err(ReminderError::NotFound(_))));
152 }
153
154 #[test]
155 fn delete_returns_true_on_success_false_when_missing() {
156 let store = MemoryReminderStore::new();
157 let r = make("x", Priority::Low, 60);
158 store.save(&r).unwrap();
159 assert!(store.delete(&r.id).unwrap());
160 assert!(!store.delete(&r.id).unwrap());
161 }
162
163 #[test]
164 fn list_ordered_by_priority_then_due_at() {
165 let store = MemoryReminderStore::new();
166 let a = make("low-later", Priority::Low, 120);
167 let b = make("high-later", Priority::High, 120);
168 let c = make("high-sooner", Priority::High, 60);
169 for r in [&a, &b, &c] {
170 store.save(r).unwrap();
171 }
172
173 let results = store.list(&ReminderFilter::default()).unwrap();
174 assert_eq!(results[0].title, "high-sooner");
175 assert_eq!(results[1].title, "high-later");
176 assert_eq!(results[2].title, "low-later");
177 }
178
179 #[test]
180 fn list_filters_by_status() {
181 let store = MemoryReminderStore::new();
182 let mut r1 = make("due", Priority::Medium, -10);
183 r1.status = ReminderStatus::Due;
184 let r2 = make("pending", Priority::Medium, 60);
185 store.save(&r1).unwrap();
186 store.save(&r2).unwrap();
187
188 let results = store
189 .list(&ReminderFilter {
190 status: Some(ReminderStatus::Due),
191 ..Default::default()
192 })
193 .unwrap();
194 assert_eq!(results.len(), 1);
195 assert_eq!(results[0].title, "due");
196 }
197}