vex_persist/
context_store.rs1use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use std::sync::Arc;
6use uuid::Uuid;
7
8use crate::backend::{StorageBackend, StorageError, StorageExt};
9use vex_core::ContextPacket;
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct ContextState {
14 pub id: Uuid,
16 pub packet: ContextPacket,
18 pub agent_id: Option<Uuid>,
20 pub stored_at: DateTime<Utc>,
22}
23
24#[derive(Debug)]
26pub struct ContextStore<B: StorageBackend + ?Sized> {
27 backend: Arc<B>,
28 prefix: String,
29}
30
31impl<B: StorageBackend + ?Sized> ContextStore<B> {
32 pub fn new(backend: Arc<B>) -> Self {
34 Self {
35 backend,
36 prefix: "context:".to_string(),
37 }
38 }
39
40 fn key(&self, tenant_id: &str, id: Uuid) -> String {
41 format!("{}tenant:{}:{}", self.prefix, tenant_id, id)
42 }
43
44 fn agent_key(&self, tenant_id: &str, agent_id: Uuid) -> String {
45 format!("{}tenant:{}:agent:{}", self.prefix, tenant_id, agent_id)
46 }
47
48 pub async fn save(
50 &self,
51 tenant_id: &str,
52 packet: &ContextPacket,
53 ) -> Result<Uuid, StorageError> {
54 let id = Uuid::new_v4();
55 let state = ContextState {
56 id,
57 packet: packet.clone(),
58 agent_id: packet.source_agent,
59 stored_at: Utc::now(),
60 };
61 self.backend.set(&self.key(tenant_id, id), &state).await?;
62
63 if let Some(agent_id) = packet.source_agent {
65 let mut agent_contexts: Vec<Uuid> = self
66 .backend
67 .get(&self.agent_key(tenant_id, agent_id))
68 .await?
69 .unwrap_or_default();
70 agent_contexts.push(id);
71 self.backend
72 .set(&self.agent_key(tenant_id, agent_id), &agent_contexts)
73 .await?;
74 }
75
76 Ok(id)
77 }
78
79 pub async fn load(
81 &self,
82 tenant_id: &str,
83 id: Uuid,
84 ) -> Result<Option<ContextPacket>, StorageError> {
85 let state: Option<ContextState> = self.backend.get(&self.key(tenant_id, id)).await?;
86 Ok(state.map(|s| s.packet))
87 }
88
89 pub async fn load_by_agent(
91 &self,
92 tenant_id: &str,
93 agent_id: Uuid,
94 ) -> Result<Vec<ContextPacket>, StorageError> {
95 let context_ids: Vec<Uuid> = self
96 .backend
97 .get(&self.agent_key(tenant_id, agent_id))
98 .await?
99 .unwrap_or_default();
100
101 let mut contexts = Vec::new();
102 for id in context_ids {
103 if let Some(ctx) = self.load(tenant_id, id).await? {
104 contexts.push(ctx);
105 }
106 }
107 Ok(contexts)
108 }
109
110 pub async fn delete(&self, tenant_id: &str, id: Uuid) -> Result<bool, StorageError> {
112 self.backend.delete(&self.key(tenant_id, id)).await
113 }
114
115 pub async fn count(&self, tenant_id: &str) -> Result<usize, StorageError> {
117 let tenant_prefix = format!("{}tenant:{}:", self.prefix, tenant_id);
118 let keys = self.backend.list_keys(&tenant_prefix).await?;
119 Ok(keys.iter().filter(|k| !k.contains(":agent:")).count())
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126 use crate::backend::MemoryBackend;
127
128 #[tokio::test]
129 async fn test_context_store() {
130 let backend = Arc::new(MemoryBackend::new());
131 let store = ContextStore::new(backend);
132 let tenant_id = "test-tenant";
133
134 let mut packet = ContextPacket::new("Test content");
135 let agent_id = Uuid::new_v4();
136 packet.source_agent = Some(agent_id);
137
138 let id = store.save(tenant_id, &packet).await.unwrap();
140
141 let loaded = store.load(tenant_id, id).await.unwrap().unwrap();
143 assert_eq!(loaded.content, "Test content");
144
145 let agent_contexts = store.load_by_agent(tenant_id, agent_id).await.unwrap();
147 assert_eq!(agent_contexts.len(), 1);
148
149 assert_eq!(store.count(tenant_id).await.unwrap(), 1);
151 }
152}