1use serde::{Deserialize, Serialize};
11
12#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
17#[serde(rename_all = "lowercase")]
18pub enum MemoryType {
19 #[default]
21 Pattern,
22 Decision,
24 Fix,
26 Context,
28}
29
30impl MemoryType {
31 #[must_use]
35 pub fn section_name(&self) -> &'static str {
36 match self {
37 Self::Pattern => "Patterns",
38 Self::Decision => "Decisions",
39 Self::Fix => "Fixes",
40 Self::Context => "Context",
41 }
42 }
43
44 #[must_use]
48 pub fn from_section(s: &str) -> Option<Self> {
49 match s {
50 "Patterns" => Some(Self::Pattern),
51 "Decisions" => Some(Self::Decision),
52 "Fixes" => Some(Self::Fix),
53 "Context" => Some(Self::Context),
54 _ => None,
55 }
56 }
57
58 #[must_use]
62 pub fn emoji(&self) -> &'static str {
63 match self {
64 Self::Pattern => "🔄",
65 Self::Decision => "⚖️",
66 Self::Fix => "🔧",
67 Self::Context => "📍",
68 }
69 }
70
71 #[must_use]
73 pub fn all() -> &'static [Self] {
74 &[Self::Pattern, Self::Decision, Self::Fix, Self::Context]
75 }
76}
77
78impl std::fmt::Display for MemoryType {
79 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80 match self {
81 Self::Pattern => write!(f, "pattern"),
82 Self::Decision => write!(f, "decision"),
83 Self::Fix => write!(f, "fix"),
84 Self::Context => write!(f, "context"),
85 }
86 }
87}
88
89impl std::str::FromStr for MemoryType {
90 type Err = String;
91
92 fn from_str(s: &str) -> Result<Self, Self::Err> {
93 match s.to_lowercase().as_str() {
94 "pattern" => Ok(Self::Pattern),
95 "decision" => Ok(Self::Decision),
96 "fix" => Ok(Self::Fix),
97 "context" => Ok(Self::Context),
98 _ => Err(format!(
99 "Invalid memory type: '{}'. Valid types: pattern, decision, fix, context",
100 s
101 )),
102 }
103 }
104}
105
106#[derive(Debug, Clone, Serialize, Deserialize)]
116pub struct Memory {
117 pub id: String,
119
120 pub memory_type: MemoryType,
122
123 pub content: String,
125
126 pub tags: Vec<String>,
128
129 pub created: String,
131}
132
133impl Memory {
134 #[must_use]
138 pub fn new(memory_type: MemoryType, content: String, tags: Vec<String>) -> Self {
139 Self {
140 id: Self::generate_id(),
141 memory_type,
142 content,
143 tags,
144 created: chrono::Utc::now().format("%Y-%m-%d").to_string(),
145 }
146 }
147
148 #[must_use]
156 pub fn generate_id() -> String {
157 use std::time::{SystemTime, UNIX_EPOCH};
158
159 let duration = SystemTime::now()
160 .duration_since(UNIX_EPOCH)
161 .expect("Time went backwards");
162
163 let timestamp = duration.as_secs();
164 let micros = duration.subsec_micros();
166 let hex_suffix = format!("{:04x}", micros % 0x10000);
167
168 format!("mem-{}-{}", timestamp, hex_suffix)
169 }
170
171 #[must_use]
175 pub fn matches_query(&self, query: &str) -> bool {
176 let query_lower = query.to_lowercase();
177 self.content.to_lowercase().contains(&query_lower)
178 || self
179 .tags
180 .iter()
181 .any(|tag| tag.to_lowercase().contains(&query_lower))
182 }
183
184 #[must_use]
186 pub fn has_any_tag(&self, tags: &[String]) -> bool {
187 let tags_lower: Vec<String> = tags.iter().map(|t| t.to_lowercase()).collect();
188 self.tags
189 .iter()
190 .any(|t| tags_lower.contains(&t.to_lowercase()))
191 }
192}
193
194#[cfg(test)]
195mod tests {
196 use super::*;
197
198 #[test]
199 fn test_memory_type_section_names() {
200 assert_eq!(MemoryType::Pattern.section_name(), "Patterns");
201 assert_eq!(MemoryType::Decision.section_name(), "Decisions");
202 assert_eq!(MemoryType::Fix.section_name(), "Fixes");
203 assert_eq!(MemoryType::Context.section_name(), "Context");
204 }
205
206 #[test]
207 fn test_memory_type_from_section() {
208 assert_eq!(
209 MemoryType::from_section("Patterns"),
210 Some(MemoryType::Pattern)
211 );
212 assert_eq!(
213 MemoryType::from_section("Decisions"),
214 Some(MemoryType::Decision)
215 );
216 assert_eq!(MemoryType::from_section("Fixes"), Some(MemoryType::Fix));
217 assert_eq!(
218 MemoryType::from_section("Context"),
219 Some(MemoryType::Context)
220 );
221 assert_eq!(MemoryType::from_section("Unknown"), None);
222 }
223
224 #[test]
225 fn test_memory_type_emojis() {
226 assert_eq!(MemoryType::Pattern.emoji(), "🔄");
227 assert_eq!(MemoryType::Decision.emoji(), "⚖️");
228 assert_eq!(MemoryType::Fix.emoji(), "🔧");
229 assert_eq!(MemoryType::Context.emoji(), "📍");
230 }
231
232 #[test]
233 fn test_memory_type_from_str() {
234 assert_eq!(
235 "pattern".parse::<MemoryType>().unwrap(),
236 MemoryType::Pattern
237 );
238 assert_eq!(
239 "DECISION".parse::<MemoryType>().unwrap(),
240 MemoryType::Decision
241 );
242 assert_eq!("Fix".parse::<MemoryType>().unwrap(), MemoryType::Fix);
243 assert_eq!(
244 "context".parse::<MemoryType>().unwrap(),
245 MemoryType::Context
246 );
247 assert!("invalid".parse::<MemoryType>().is_err());
248 }
249
250 #[test]
251 fn test_memory_type_display() {
252 assert_eq!(format!("{}", MemoryType::Pattern), "pattern");
253 assert_eq!(format!("{}", MemoryType::Decision), "decision");
254 assert_eq!(format!("{}", MemoryType::Fix), "fix");
255 assert_eq!(format!("{}", MemoryType::Context), "context");
256 }
257
258 #[test]
259 fn test_memory_new() {
260 let memory = Memory::new(
261 MemoryType::Pattern,
262 "Uses barrel exports".to_string(),
263 vec!["imports".to_string(), "structure".to_string()],
264 );
265
266 assert!(memory.id.starts_with("mem-"));
267 assert_eq!(memory.memory_type, MemoryType::Pattern);
268 assert_eq!(memory.content, "Uses barrel exports");
269 assert_eq!(memory.tags, vec!["imports", "structure"]);
270 let today = chrono::Utc::now().format("%Y-%m-%d").to_string();
272 assert_eq!(memory.created, today);
273 }
274
275 #[test]
276 fn test_memory_id_format() {
277 let id = Memory::generate_id();
278 assert!(id.starts_with("mem-"));
279
280 let parts: Vec<&str> = id.split('-').collect();
282 assert_eq!(parts.len(), 3);
283 assert_eq!(parts[0], "mem");
284 assert!(parts[1].parse::<u64>().is_ok()); assert_eq!(parts[2].len(), 4); }
287
288 #[test]
289 fn test_memory_matches_query() {
290 let memory = Memory {
291 id: "mem-123-abcd".to_string(),
292 memory_type: MemoryType::Pattern,
293 content: "Uses barrel exports for modules".to_string(),
294 tags: vec!["imports".to_string(), "structure".to_string()],
295 created: "2025-01-20".to_string(),
296 };
297
298 assert!(memory.matches_query("barrel"));
300 assert!(memory.matches_query("BARREL")); assert!(memory.matches_query("imports"));
304 assert!(memory.matches_query("STRUCTURE"));
305
306 assert!(!memory.matches_query("authentication"));
308 }
309
310 #[test]
311 fn test_memory_has_any_tag() {
312 let memory = Memory {
313 id: "mem-123-abcd".to_string(),
314 memory_type: MemoryType::Fix,
315 content: "Docker fix".to_string(),
316 tags: vec!["docker".to_string(), "debugging".to_string()],
317 created: "2025-01-20".to_string(),
318 };
319
320 assert!(memory.has_any_tag(&["docker".to_string()]));
321 assert!(memory.has_any_tag(&["DEBUGGING".to_string()])); assert!(memory.has_any_tag(&["other".to_string(), "docker".to_string()]));
323 assert!(!memory.has_any_tag(&["unrelated".to_string()]));
324 }
325
326 #[test]
327 fn test_memory_type_all() {
328 let all = MemoryType::all();
329 assert_eq!(all.len(), 4);
330 assert_eq!(all[0], MemoryType::Pattern);
331 assert_eq!(all[1], MemoryType::Decision);
332 assert_eq!(all[2], MemoryType::Fix);
333 assert_eq!(all[3], MemoryType::Context);
334 }
335
336 #[test]
337 fn test_memory_type_default() {
338 assert_eq!(MemoryType::default(), MemoryType::Pattern);
339 }
340
341 #[test]
342 fn test_memory_serde_roundtrip() {
343 let memory = Memory {
344 id: "mem-123-abcd".to_string(),
345 memory_type: MemoryType::Decision,
346 content: "Chose Postgres".to_string(),
347 tags: vec!["database".to_string()],
348 created: "2025-01-20".to_string(),
349 };
350
351 let json = serde_json::to_string(&memory).unwrap();
352 let deserialized: Memory = serde_json::from_str(&json).unwrap();
353
354 assert_eq!(deserialized.id, memory.id);
355 assert_eq!(deserialized.memory_type, memory.memory_type);
356 assert_eq!(deserialized.content, memory.content);
357 assert_eq!(deserialized.tags, memory.tags);
358 assert_eq!(deserialized.created, memory.created);
359 }
360
361 #[test]
362 fn test_memory_type_serde() {
363 let mt = MemoryType::Decision;
365 let json = serde_json::to_string(&mt).unwrap();
366 assert_eq!(json, "\"decision\"");
367
368 let deserialized: MemoryType = serde_json::from_str("\"fix\"").unwrap();
370 assert_eq!(deserialized, MemoryType::Fix);
371 }
372}