reinhardt_tasks/result.rs
1//! Task result types and persistence
2
3use crate::{TaskExecutionError, TaskId, TaskStatus};
4use async_trait::async_trait;
5use serde::{Deserialize, Serialize};
6
7/// Task execution output
8///
9/// # Examples
10///
11/// ```rust
12/// use reinhardt_tasks::{TaskOutput, TaskId};
13///
14/// let output = TaskOutput::new(TaskId::new(), "Processing completed".to_string());
15/// assert_eq!(output.result(), "Processing completed");
16/// ```
17#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct TaskOutput {
19 task_id: TaskId,
20 result: String,
21}
22
23impl TaskOutput {
24 /// Create a new task output
25 ///
26 /// # Examples
27 ///
28 /// ```rust
29 /// use reinhardt_tasks::{TaskOutput, TaskId};
30 ///
31 /// let task_id = TaskId::new();
32 /// let output = TaskOutput::new(task_id, "Success".to_string());
33 /// ```
34 pub fn new(task_id: TaskId, result: String) -> Self {
35 Self { task_id, result }
36 }
37
38 /// Get the task ID
39 ///
40 /// # Examples
41 ///
42 /// ```rust
43 /// use reinhardt_tasks::{TaskOutput, TaskId};
44 ///
45 /// let task_id = TaskId::new();
46 /// let output = TaskOutput::new(task_id, "Done".to_string());
47 /// assert_eq!(output.task_id(), task_id);
48 /// ```
49 pub fn task_id(&self) -> TaskId {
50 self.task_id
51 }
52
53 /// Get the result string
54 ///
55 /// # Examples
56 ///
57 /// ```rust
58 /// use reinhardt_tasks::{TaskOutput, TaskId};
59 ///
60 /// let output = TaskOutput::new(TaskId::new(), "Result data".to_string());
61 /// assert_eq!(output.result(), "Result data");
62 /// ```
63 pub fn result(&self) -> &str {
64 &self.result
65 }
66}
67
68/// Task result type
69pub type TaskResult = Result<TaskOutput, String>;
70
71/// Task result metadata with status information
72///
73/// # Examples
74///
75/// ```rust
76/// use reinhardt_tasks::{TaskResultMetadata, TaskId, TaskStatus};
77///
78/// let metadata = TaskResultMetadata::new(
79/// TaskId::new(),
80/// TaskStatus::Success,
81/// Some("Task completed successfully".to_string()),
82/// );
83///
84/// assert_eq!(metadata.status(), TaskStatus::Success);
85/// ```
86#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct TaskResultMetadata {
88 task_id: TaskId,
89 status: TaskStatus,
90 result: Option<String>,
91 error: Option<String>,
92 created_at: i64,
93}
94
95impl TaskResultMetadata {
96 /// Create a new task result metadata
97 ///
98 /// # Examples
99 ///
100 /// ```rust
101 /// use reinhardt_tasks::{TaskResultMetadata, TaskId, TaskStatus};
102 ///
103 /// let metadata = TaskResultMetadata::new(
104 /// TaskId::new(),
105 /// TaskStatus::Success,
106 /// Some("Done".to_string()),
107 /// );
108 /// ```
109 pub fn new(task_id: TaskId, status: TaskStatus, result: Option<String>) -> Self {
110 Self {
111 task_id,
112 status,
113 result,
114 error: None,
115 created_at: chrono::Utc::now().timestamp(),
116 }
117 }
118
119 /// Create a failed task result
120 ///
121 /// # Examples
122 ///
123 /// ```rust
124 /// use reinhardt_tasks::{TaskResultMetadata, TaskId, TaskStatus};
125 ///
126 /// let metadata = TaskResultMetadata::with_error(
127 /// TaskId::new(),
128 /// "Database connection failed".to_string(),
129 /// );
130 ///
131 /// assert_eq!(metadata.status(), TaskStatus::Failure);
132 /// ```
133 pub fn with_error(task_id: TaskId, error: String) -> Self {
134 Self {
135 task_id,
136 status: TaskStatus::Failure,
137 result: None,
138 error: Some(error),
139 created_at: chrono::Utc::now().timestamp(),
140 }
141 }
142
143 /// Set the error message while preserving result and status fields.
144 pub fn set_error(&mut self, error: String) {
145 self.error = Some(error);
146 }
147
148 /// Get the task ID
149 ///
150 /// # Examples
151 ///
152 /// ```rust
153 /// use reinhardt_tasks::{TaskResultMetadata, TaskId, TaskStatus};
154 ///
155 /// let task_id = TaskId::new();
156 /// let metadata = TaskResultMetadata::new(task_id, TaskStatus::Success, None);
157 /// assert_eq!(metadata.task_id(), task_id);
158 /// ```
159 pub fn task_id(&self) -> TaskId {
160 self.task_id
161 }
162
163 /// Get the task status
164 ///
165 /// # Examples
166 ///
167 /// ```rust
168 /// use reinhardt_tasks::{TaskResultMetadata, TaskId, TaskStatus};
169 ///
170 /// let metadata = TaskResultMetadata::new(
171 /// TaskId::new(),
172 /// TaskStatus::Success,
173 /// None,
174 /// );
175 /// assert_eq!(metadata.status(), TaskStatus::Success);
176 /// ```
177 pub fn status(&self) -> TaskStatus {
178 self.status
179 }
180
181 /// Get the result if available
182 ///
183 /// # Examples
184 ///
185 /// ```rust
186 /// use reinhardt_tasks::{TaskResultMetadata, TaskId, TaskStatus};
187 ///
188 /// let metadata = TaskResultMetadata::new(
189 /// TaskId::new(),
190 /// TaskStatus::Success,
191 /// Some("Result".to_string()),
192 /// );
193 /// assert_eq!(metadata.result(), Some("Result"));
194 /// ```
195 pub fn result(&self) -> Option<&str> {
196 self.result.as_deref()
197 }
198
199 /// Get the error if available
200 ///
201 /// # Examples
202 ///
203 /// ```rust
204 /// use reinhardt_tasks::{TaskResultMetadata, TaskId};
205 ///
206 /// let metadata = TaskResultMetadata::with_error(
207 /// TaskId::new(),
208 /// "Error occurred".to_string(),
209 /// );
210 /// assert_eq!(metadata.error(), Some("Error occurred"));
211 /// ```
212 pub fn error(&self) -> Option<&str> {
213 self.error.as_deref()
214 }
215
216 /// Get the creation timestamp
217 ///
218 /// # Examples
219 ///
220 /// ```rust
221 /// use reinhardt_tasks::{TaskResultMetadata, TaskId, TaskStatus};
222 ///
223 /// let metadata = TaskResultMetadata::new(TaskId::new(), TaskStatus::Success, None);
224 /// let timestamp = metadata.created_at();
225 /// assert!(timestamp > 0);
226 /// ```
227 pub fn created_at(&self) -> i64 {
228 self.created_at
229 }
230}
231
232/// Result backend trait for persisting task results
233///
234/// # Examples
235///
236/// ```
237/// use reinhardt_tasks::{ResultBackend, TaskResultMetadata, TaskId, TaskStatus};
238/// use async_trait::async_trait;
239///
240/// struct MyResultBackend;
241///
242/// #[async_trait]
243/// impl ResultBackend for MyResultBackend {
244/// async fn store_result(
245/// &self,
246/// metadata: TaskResultMetadata,
247/// ) -> Result<(), reinhardt_tasks::TaskExecutionError> {
248/// // Store the result
249/// Ok(())
250/// }
251///
252/// async fn get_result(
253/// &self,
254/// task_id: TaskId,
255/// ) -> Result<Option<TaskResultMetadata>, reinhardt_tasks::TaskExecutionError> {
256/// // Retrieve the result
257/// Ok(None)
258/// }
259///
260/// async fn delete_result(
261/// &self,
262/// task_id: TaskId,
263/// ) -> Result<(), reinhardt_tasks::TaskExecutionError> {
264/// // Delete the result
265/// Ok(())
266/// }
267/// }
268///
269/// # async fn test_backend() {
270/// let backend = MyResultBackend;
271/// let task_id = TaskId::new();
272/// let metadata = TaskResultMetadata::new(
273/// task_id,
274/// TaskStatus::Success,
275/// Some("Task completed".to_string()),
276/// );
277///
278/// // Test store and retrieve
279/// assert!(backend.store_result(metadata.clone()).await.is_ok());
280/// let result = backend.get_result(task_id).await.unwrap();
281/// assert!(result.is_none()); // Our implementation returns None
282/// # }
283/// # tokio::runtime::Runtime::new().unwrap().block_on(test_backend());
284/// ```
285#[async_trait]
286pub trait ResultBackend: Send + Sync {
287 /// Store a task result
288 async fn store_result(&self, metadata: TaskResultMetadata) -> Result<(), TaskExecutionError>;
289
290 /// Get a task result
291 async fn get_result(
292 &self,
293 task_id: TaskId,
294 ) -> Result<Option<TaskResultMetadata>, TaskExecutionError>;
295
296 /// Delete a task result
297 async fn delete_result(&self, task_id: TaskId) -> Result<(), TaskExecutionError>;
298}
299
300/// In-memory result backend for testing
301///
302/// # Examples
303///
304/// ```rust
305/// use reinhardt_tasks::MemoryResultBackend;
306///
307/// let backend = MemoryResultBackend::new();
308/// ```
309pub struct MemoryResultBackend {
310 results:
311 std::sync::Arc<tokio::sync::RwLock<std::collections::HashMap<TaskId, TaskResultMetadata>>>,
312}
313
314impl MemoryResultBackend {
315 /// Create a new in-memory result backend
316 ///
317 /// # Examples
318 ///
319 /// ```rust
320 /// use reinhardt_tasks::MemoryResultBackend;
321 ///
322 /// let backend = MemoryResultBackend::new();
323 /// ```
324 pub fn new() -> Self {
325 Self {
326 results: std::sync::Arc::new(
327 tokio::sync::RwLock::new(std::collections::HashMap::new()),
328 ),
329 }
330 }
331}
332
333impl Default for MemoryResultBackend {
334 fn default() -> Self {
335 Self::new()
336 }
337}
338
339#[async_trait]
340impl ResultBackend for MemoryResultBackend {
341 async fn store_result(&self, metadata: TaskResultMetadata) -> Result<(), TaskExecutionError> {
342 let mut results = self.results.write().await;
343 results.insert(metadata.task_id(), metadata);
344 Ok(())
345 }
346
347 async fn get_result(
348 &self,
349 task_id: TaskId,
350 ) -> Result<Option<TaskResultMetadata>, TaskExecutionError> {
351 let results = self.results.read().await;
352 Ok(results.get(&task_id).cloned())
353 }
354
355 async fn delete_result(&self, task_id: TaskId) -> Result<(), TaskExecutionError> {
356 let mut results = self.results.write().await;
357 results.remove(&task_id);
358 Ok(())
359 }
360}
361
362#[cfg(test)]
363mod tests {
364 use super::*;
365
366 #[test]
367 fn test_task_output() {
368 let task_id = TaskId::new();
369 let output = TaskOutput::new(task_id, "test result".to_string());
370 assert_eq!(output.task_id(), task_id);
371 assert_eq!(output.result(), "test result");
372 }
373
374 #[test]
375 fn test_task_result_metadata() {
376 let task_id = TaskId::new();
377 let metadata =
378 TaskResultMetadata::new(task_id, TaskStatus::Success, Some("success".to_string()));
379
380 assert_eq!(metadata.task_id(), task_id);
381 assert_eq!(metadata.status(), TaskStatus::Success);
382 assert_eq!(metadata.result(), Some("success"));
383 assert_eq!(metadata.error(), None);
384 }
385
386 #[test]
387 fn test_task_result_metadata_with_error() {
388 let task_id = TaskId::new();
389 let metadata = TaskResultMetadata::with_error(task_id, "error occurred".to_string());
390
391 assert_eq!(metadata.status(), TaskStatus::Failure);
392 assert_eq!(metadata.error(), Some("error occurred"));
393 assert_eq!(metadata.result(), None);
394 }
395
396 #[test]
397 fn test_set_error_preserves_result_and_status() {
398 // Arrange
399 let task_id = TaskId::new();
400 let mut metadata =
401 TaskResultMetadata::new(task_id, TaskStatus::Success, Some("my result".to_string()));
402
403 // Act
404 metadata.set_error("something went wrong".to_string());
405
406 // Assert
407 assert_eq!(metadata.status(), TaskStatus::Success);
408 assert_eq!(metadata.result(), Some("my result"));
409 assert_eq!(metadata.error(), Some("something went wrong"));
410 }
411
412 #[tokio::test]
413 async fn test_memory_result_backend() {
414 let backend = MemoryResultBackend::new();
415 let task_id = TaskId::new();
416
417 let metadata =
418 TaskResultMetadata::new(task_id, TaskStatus::Success, Some("result".to_string()));
419
420 // Store result
421 backend.store_result(metadata.clone()).await.unwrap();
422
423 // Get result
424 let retrieved = backend.get_result(task_id).await.unwrap();
425 assert!(retrieved.is_some());
426 assert_eq!(retrieved.unwrap().result(), Some("result"));
427
428 // Delete result
429 backend.delete_result(task_id).await.unwrap();
430 let deleted = backend.get_result(task_id).await.unwrap();
431 assert!(deleted.is_none());
432 }
433}