Skip to main content

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}