use async_trait::async_trait;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct MemoryMetadata {
pub context: Option<String>,
pub tags: Vec<String>,
pub expires_at: Option<DateTime<Utc>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MemoryEntry {
pub id: String,
pub content: String,
pub score: f64,
pub metadata: MemoryMetadata,
pub created_at: DateTime<Utc>,
}
#[async_trait]
pub trait MemoryProvider: Send + Sync {
async fn store(&self, content: &str, metadata: MemoryMetadata) -> anyhow::Result<String>;
async fn search(&self, query: &str, limit: usize) -> anyhow::Result<Vec<MemoryEntry>>;
async fn forget(&self, id: &str) -> anyhow::Result<()>;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn memory_metadata_default() {
let meta = MemoryMetadata::default();
assert!(meta.context.is_none());
assert!(meta.tags.is_empty());
assert!(meta.expires_at.is_none());
}
#[test]
fn memory_entry_serde_round_trip() {
let entry = MemoryEntry {
id: "mem-123".into(),
content: "the user prefers dark mode".into(),
score: 0.95,
metadata: MemoryMetadata {
context: Some("preferences".into()),
tags: vec!["preference".into(), "ui".into()],
expires_at: None,
},
created_at: Utc::now(),
};
let json = serde_json::to_string(&entry).unwrap();
let deserialized: MemoryEntry = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.id, "mem-123");
assert_eq!(deserialized.content, "the user prefers dark mode");
assert!((deserialized.score - 0.95).abs() < f64::EPSILON);
assert_eq!(deserialized.metadata.tags.len(), 2);
}
#[test]
fn memory_metadata_with_expiry() {
let expiry = Utc::now() + chrono::Duration::hours(24);
let meta = MemoryMetadata {
context: None,
tags: vec!["ephemeral".into()],
expires_at: Some(expiry),
};
assert!(meta.expires_at.unwrap() > Utc::now());
}
}