reinhardt_tasks/backends/
metadata_store.rs1use crate::{TaskId, TaskStatus, registry::SerializedTask};
42use async_trait::async_trait;
43use serde::{Deserialize, Serialize};
44use std::collections::HashMap;
45use std::sync::Arc;
46use tokio::sync::RwLock;
47
48#[derive(Debug, Clone)]
50pub enum MetadataStoreError {
51 NotFound(TaskId),
53 StorageError(String),
55 SerializationError(String),
57}
58
59impl std::fmt::Display for MetadataStoreError {
60 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61 match self {
62 MetadataStoreError::NotFound(id) => {
63 write!(f, "Task {} not found in metadata store", id)
64 }
65 MetadataStoreError::StorageError(msg) => write!(f, "Metadata storage error: {}", msg),
66 MetadataStoreError::SerializationError(msg) => {
67 write!(f, "Metadata serialization error: {}", msg)
68 }
69 }
70 }
71}
72
73impl std::error::Error for MetadataStoreError {}
74
75#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct TaskMetadata {
80 pub id: TaskId,
82 pub name: String,
84 pub status: TaskStatus,
86 pub created_at: i64,
88 pub updated_at: i64,
90 pub task_data: Option<SerializedTask>,
92}
93
94impl TaskMetadata {
95 pub fn new(id: TaskId, name: String) -> Self {
97 let now = chrono::Utc::now().timestamp();
98 Self {
99 id,
100 name,
101 status: TaskStatus::Pending,
102 created_at: now,
103 updated_at: now,
104 task_data: None,
105 }
106 }
107
108 pub fn with_task_data(id: TaskId, name: String, task_data: SerializedTask) -> Self {
110 let now = chrono::Utc::now().timestamp();
111 Self {
112 id,
113 name,
114 status: TaskStatus::Pending,
115 created_at: now,
116 updated_at: now,
117 task_data: Some(task_data),
118 }
119 }
120
121 pub fn set_status(&mut self, status: TaskStatus) {
123 self.status = status;
124 self.updated_at = chrono::Utc::now().timestamp();
125 }
126}
127
128#[async_trait]
171pub trait MetadataStore: Send + Sync {
172 async fn store(&self, metadata: TaskMetadata) -> Result<(), MetadataStoreError>;
174
175 async fn get(&self, task_id: TaskId) -> Result<Option<TaskMetadata>, MetadataStoreError>;
177
178 async fn update_status(
180 &self,
181 task_id: TaskId,
182 status: TaskStatus,
183 ) -> Result<(), MetadataStoreError>;
184
185 async fn delete(&self, task_id: TaskId) -> Result<(), MetadataStoreError>;
187}
188
189pub struct InMemoryMetadataStore {
214 store: Arc<RwLock<HashMap<TaskId, TaskMetadata>>>,
215}
216
217impl InMemoryMetadataStore {
218 pub fn new() -> Self {
220 Self {
221 store: Arc::new(RwLock::new(HashMap::new())),
222 }
223 }
224
225 pub async fn len(&self) -> usize {
227 self.store.read().await.len()
228 }
229
230 pub async fn is_empty(&self) -> bool {
232 self.store.read().await.is_empty()
233 }
234
235 pub async fn clear(&self) {
237 self.store.write().await.clear();
238 }
239}
240
241impl Default for InMemoryMetadataStore {
242 fn default() -> Self {
243 Self::new()
244 }
245}
246
247impl Clone for InMemoryMetadataStore {
248 fn clone(&self) -> Self {
249 Self {
250 store: Arc::clone(&self.store),
251 }
252 }
253}
254
255#[async_trait]
256impl MetadataStore for InMemoryMetadataStore {
257 async fn store(&self, metadata: TaskMetadata) -> Result<(), MetadataStoreError> {
258 let mut store = self.store.write().await;
259 store.insert(metadata.id, metadata);
260 Ok(())
261 }
262
263 async fn get(&self, task_id: TaskId) -> Result<Option<TaskMetadata>, MetadataStoreError> {
264 let store = self.store.read().await;
265 Ok(store.get(&task_id).cloned())
266 }
267
268 async fn update_status(
269 &self,
270 task_id: TaskId,
271 status: TaskStatus,
272 ) -> Result<(), MetadataStoreError> {
273 let mut store = self.store.write().await;
274
275 if let Some(metadata) = store.get_mut(&task_id) {
276 metadata.set_status(status);
277 Ok(())
278 } else {
279 Err(MetadataStoreError::NotFound(task_id))
280 }
281 }
282
283 async fn delete(&self, task_id: TaskId) -> Result<(), MetadataStoreError> {
284 let mut store = self.store.write().await;
285 store.remove(&task_id);
286 Ok(())
287 }
288}
289
290#[cfg(test)]
291mod tests {
292 use super::*;
293
294 #[tokio::test]
295 async fn test_in_memory_store_basic_operations() {
296 let store = InMemoryMetadataStore::new();
297
298 let task_id = TaskId::new();
299 let metadata = TaskMetadata::new(task_id, "test_task".to_string());
300
301 store.store(metadata.clone()).await.unwrap();
303 assert_eq!(store.len().await, 1);
304
305 let retrieved = store.get(task_id).await.unwrap().unwrap();
307 assert_eq!(retrieved.id, task_id);
308 assert_eq!(retrieved.name, "test_task");
309 assert_eq!(retrieved.status, TaskStatus::Pending);
310
311 store
313 .update_status(task_id, TaskStatus::Running)
314 .await
315 .unwrap();
316 let updated = store.get(task_id).await.unwrap().unwrap();
317 assert_eq!(updated.status, TaskStatus::Running);
318
319 store.delete(task_id).await.unwrap();
321 assert!(store.get(task_id).await.unwrap().is_none());
322 }
323
324 #[tokio::test]
325 async fn test_in_memory_store_update_nonexistent() {
326 let store = InMemoryMetadataStore::new();
327 let task_id = TaskId::new();
328
329 let result = store.update_status(task_id, TaskStatus::Running).await;
330 assert!(matches!(result, Err(MetadataStoreError::NotFound(_))));
331 }
332
333 #[tokio::test]
334 async fn test_in_memory_store_clear() {
335 let store = InMemoryMetadataStore::new();
336
337 for _ in 0..10 {
338 let metadata = TaskMetadata::new(TaskId::new(), "test".to_string());
339 store.store(metadata).await.unwrap();
340 }
341
342 assert_eq!(store.len().await, 10);
343 store.clear().await;
344 assert!(store.is_empty().await);
345 }
346
347 #[tokio::test]
348 async fn test_task_metadata_with_task_data() {
349 let task_id = TaskId::new();
350 let task_data =
351 SerializedTask::new("test_task".to_string(), r#"{"key": "value"}"#.to_string());
352 let metadata = TaskMetadata::with_task_data(task_id, "test_task".to_string(), task_data);
353
354 assert!(metadata.task_data.is_some());
355 assert_eq!(metadata.task_data.as_ref().unwrap().name(), "test_task");
356 }
357
358 #[test]
359 fn test_metadata_store_error_display() {
360 let task_id = TaskId::new();
361 let not_found = MetadataStoreError::NotFound(task_id);
362 assert!(not_found.to_string().contains("not found"));
363
364 let storage_error = MetadataStoreError::StorageError("connection failed".to_string());
365 assert!(storage_error.to_string().contains("connection failed"));
366 }
367}