1#![allow(
2 clippy::missing_errors_doc,
3 clippy::missing_panics_doc,
4 clippy::missing_fields_in_debug
5)]
6#![allow(clippy::cast_precision_loss)]
7use crate::state::TaskState;
52use crate::TaskId;
53use async_trait::async_trait;
54use serde_json::Value;
55use std::time::Duration;
56
57#[async_trait]
63pub trait ResultStore: Send + Sync {
64 async fn store_result(&self, task_id: TaskId, result: TaskResultValue) -> crate::Result<()>;
66
67 async fn get_result(&self, task_id: TaskId) -> crate::Result<Option<TaskResultValue>>;
69
70 async fn get_state(&self, task_id: TaskId) -> crate::Result<TaskState>;
72
73 async fn forget(&self, task_id: TaskId) -> crate::Result<()>;
75
76 async fn has_result(&self, task_id: TaskId) -> crate::Result<bool>;
78
79 async fn store_tombstone(&self, _tombstone: crate::ResultTombstone) -> crate::Result<()> {
95 Ok(())
96 }
97
98 async fn get_tombstone(
103 &self,
104 _task_id: TaskId,
105 ) -> crate::Result<Option<crate::ResultTombstone>> {
106 Ok(None)
107 }
108
109 async fn has_tombstone(&self, task_id: TaskId) -> crate::Result<bool> {
114 Ok(self.get_tombstone(task_id).await?.is_some())
115 }
116
117 async fn result_existence(
130 &self,
131 task_id: TaskId,
132 ) -> crate::Result<crate::result_tombstone::ResultExistence> {
133 if self.has_result(task_id).await? {
134 return Ok(crate::result_tombstone::ResultExistence::Present);
135 }
136 match self.get_tombstone(task_id).await? {
137 Some(tombstone) => Ok(crate::result_tombstone::ResultExistence::Tombstoned(
138 tombstone,
139 )),
140 None => Ok(crate::result_tombstone::ResultExistence::Absent),
141 }
142 }
143
144 async fn forget_with_tombstone(&self, tombstone: crate::ResultTombstone) -> crate::Result<()> {
150 let task_id = tombstone.task_id;
151 self.forget(task_id).await?;
152 self.store_tombstone(tombstone).await
153 }
154
155 async fn result_ttl_for(
167 &self,
168 config: &crate::result_ttl::ResultTtlConfig,
169 task_name: &str,
170 ) -> crate::Result<Option<Duration>> {
171 Ok(config.ttl_for(task_name))
172 }
173
174 async fn apply_result_ttl(
181 &self,
182 _task_id: TaskId,
183 _config: &crate::result_ttl::ResultTtlConfig,
184 _task_name: &str,
185 ) -> crate::Result<bool> {
186 Ok(false)
187 }
188}
189
190#[derive(Debug, Clone)]
192pub enum TaskResultValue {
193 Pending,
195
196 Received,
198
199 Started,
201
202 Success(Value),
204
205 Failure {
207 error: String,
208 traceback: Option<String>,
209 },
210
211 Revoked,
213
214 Retry { attempt: u32, max_retries: u32 },
216
217 Rejected { reason: String },
219}
220
221impl TaskResultValue {
222 #[inline]
224 #[must_use]
225 pub const fn is_terminal(&self) -> bool {
226 matches!(
227 self,
228 TaskResultValue::Success(_)
229 | TaskResultValue::Failure { .. }
230 | TaskResultValue::Revoked
231 | TaskResultValue::Rejected { .. }
232 )
233 }
234
235 #[inline]
237 #[must_use]
238 pub const fn is_pending(&self) -> bool {
239 matches!(self, TaskResultValue::Pending)
240 }
241
242 #[inline]
244 #[must_use]
245 pub const fn is_ready(&self) -> bool {
246 self.is_terminal()
247 }
248
249 #[inline]
251 #[must_use]
252 pub const fn is_successful(&self) -> bool {
253 matches!(self, TaskResultValue::Success(_))
254 }
255
256 #[inline]
258 #[must_use]
259 pub const fn is_failed(&self) -> bool {
260 matches!(
261 self,
262 TaskResultValue::Failure { .. } | TaskResultValue::Rejected { .. }
263 )
264 }
265
266 #[inline]
268 #[must_use]
269 pub fn success_value(&self) -> Option<&Value> {
270 match self {
271 TaskResultValue::Success(v) => Some(v),
272 _ => None,
273 }
274 }
275
276 #[inline]
278 #[must_use]
279 pub fn error_message(&self) -> Option<&str> {
280 match self {
281 TaskResultValue::Failure { error, .. } => Some(error),
282 TaskResultValue::Rejected { reason } => Some(reason),
283 _ => None,
284 }
285 }
286
287 #[inline]
289 #[must_use]
290 pub fn traceback(&self) -> Option<&str> {
291 match self {
292 TaskResultValue::Failure { traceback, .. } => traceback.as_deref(),
293 _ => None,
294 }
295 }
296}
297
298#[derive(Clone)]
300pub struct AsyncResult<S: ResultStore> {
301 task_id: TaskId,
303
304 store: S,
306
307 parent: Option<Box<AsyncResult<S>>>,
309
310 children: Vec<AsyncResult<S>>,
312}
313
314impl<S: ResultStore + Clone> AsyncResult<S> {
315 pub fn new(task_id: TaskId, store: S) -> Self {
317 Self {
318 task_id,
319 store,
320 parent: None,
321 children: Vec::new(),
322 }
323 }
324
325 pub fn with_parent(task_id: TaskId, store: S, parent: AsyncResult<S>) -> Self {
327 Self {
328 task_id,
329 store,
330 parent: Some(Box::new(parent)),
331 children: Vec::new(),
332 }
333 }
334
335 pub fn with_children(task_id: TaskId, store: S, children: Vec<AsyncResult<S>>) -> Self {
337 Self {
338 task_id,
339 store,
340 parent: None,
341 children,
342 }
343 }
344
345 #[inline]
347 #[must_use]
348 pub fn task_id(&self) -> TaskId {
349 self.task_id
350 }
351
352 #[inline]
354 #[must_use]
355 pub fn parent(&self) -> Option<&AsyncResult<S>> {
356 self.parent.as_deref()
357 }
358
359 #[inline]
361 #[must_use]
362 pub fn children(&self) -> &[AsyncResult<S>] {
363 &self.children
364 }
365
366 pub fn add_child(&mut self, child: AsyncResult<S>) {
368 self.children.push(child);
369 }
370
371 pub async fn children_ready(&self) -> crate::Result<bool> {
373 for child in &self.children {
374 if !child.ready().await? {
375 return Ok(false);
376 }
377 }
378 Ok(true)
379 }
380
381 pub async fn collect_children(
386 &self,
387 timeout: Option<Duration>,
388 ) -> crate::Result<Vec<Option<Value>>> {
389 let mut results = Vec::with_capacity(self.children.len());
390 for child in &self.children {
391 results.push(child.get(timeout).await?);
392 }
393 Ok(results)
394 }
395
396 pub async fn ready(&self) -> crate::Result<bool> {
398 let state = self.store.get_state(self.task_id).await?;
399 Ok(state.is_terminal())
400 }
401
402 pub async fn successful(&self) -> crate::Result<bool> {
404 match self.store.get_result(self.task_id).await? {
405 Some(result) => Ok(result.is_successful()),
406 None => Ok(false),
407 }
408 }
409
410 pub async fn failed(&self) -> crate::Result<bool> {
412 match self.store.get_result(self.task_id).await? {
413 Some(result) => Ok(result.is_failed()),
414 None => Ok(false),
415 }
416 }
417
418 pub async fn state(&self) -> crate::Result<TaskState> {
420 self.store.get_state(self.task_id).await
421 }
422
423 pub async fn info(&self) -> crate::Result<Option<TaskResultValue>> {
425 self.store.get_result(self.task_id).await
426 }
427
428 pub async fn get(&self, timeout: Option<Duration>) -> crate::Result<Option<Value>> {
438 let start = std::time::Instant::now();
439 let poll_interval = Duration::from_millis(100);
440
441 loop {
442 if let Some(timeout_duration) = timeout {
444 if start.elapsed() > timeout_duration {
445 return Err(crate::CelersError::Timeout(format!(
446 "Task {} did not complete within {:?}",
447 self.task_id, timeout_duration
448 )));
449 }
450 }
451
452 if let Some(result) = self.store.get_result(self.task_id).await? {
454 match result {
455 TaskResultValue::Success(value) => return Ok(Some(value)),
456 TaskResultValue::Failure { error, traceback } => {
457 let msg = if let Some(tb) = traceback {
458 format!("{error}\n{tb}")
459 } else {
460 error
461 };
462 return Err(crate::CelersError::TaskExecution(msg));
463 }
464 TaskResultValue::Revoked => {
465 return Err(crate::CelersError::TaskRevoked(self.task_id));
466 }
467 TaskResultValue::Rejected { reason } => {
468 return Err(crate::CelersError::TaskExecution(format!(
469 "Task rejected: {reason}"
470 )));
471 }
472 _ => {}
474 }
475 } else {
476 }
478
479 tokio::time::sleep(poll_interval).await;
481 }
482 }
483
484 pub async fn result(&self) -> crate::Result<Option<Value>> {
488 match self.store.get_result(self.task_id).await? {
489 Some(TaskResultValue::Success(value)) => Ok(Some(value)),
490 _ => Ok(None),
491 }
492 }
493
494 pub async fn traceback(&self) -> crate::Result<Option<String>> {
496 match self.store.get_result(self.task_id).await? {
497 Some(result) => Ok(result.traceback().map(String::from)),
498 None => Ok(None),
499 }
500 }
501
502 pub async fn revoke(&self) -> crate::Result<()> {
504 self.store
505 .store_result(self.task_id, TaskResultValue::Revoked)
506 .await
507 }
508
509 pub async fn forget(&self) -> crate::Result<()> {
511 self.store.forget(self.task_id).await
512 }
513
514 pub async fn wait(&self, timeout: Option<Duration>) -> crate::Result<Value> {
518 match self.get(timeout).await? {
519 Some(value) => Ok(value),
520 None => Err(crate::CelersError::TaskExecution(
521 "Task completed but returned no value".to_string(),
522 )),
523 }
524 }
525}
526
527impl<S: ResultStore + Clone> std::fmt::Debug for AsyncResult<S> {
528 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
529 f.debug_struct("AsyncResult")
530 .field("store", &"<ResultStore>")
531 .field("task_id", &self.task_id)
532 .field("has_parent", &self.parent.is_some())
533 .field("num_children", &self.children.len())
534 .finish()
535 }
536}
537
538impl<S: ResultStore + Clone> std::fmt::Display for AsyncResult<S> {
539 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
540 write!(f, "AsyncResult[{}]", &self.task_id.to_string()[..8])
541 }
542}
543
544use chrono::{DateTime, Utc};
549use serde::{Deserialize, Serialize};
550use std::collections::HashMap;
551
552#[derive(Debug, Clone, Serialize, Deserialize)]
554pub struct ResultMetadata {
555 pub tags: Vec<String>,
557
558 pub custom_fields: HashMap<String, Value>,
560
561 pub created_at: DateTime<Utc>,
563
564 #[serde(skip_serializing_if = "Option::is_none")]
566 pub expires_at: Option<DateTime<Utc>>,
567
568 pub compressed: bool,
570
571 #[serde(skip_serializing_if = "Option::is_none")]
573 pub compression_algorithm: Option<String>,
574
575 pub chunked: bool,
577
578 #[serde(skip_serializing_if = "Option::is_none")]
580 pub total_chunks: Option<usize>,
581
582 #[serde(skip_serializing_if = "Option::is_none")]
584 pub original_size: Option<usize>,
585
586 #[serde(skip_serializing_if = "Option::is_none")]
588 pub compressed_size: Option<usize>,
589}
590
591impl ResultMetadata {
592 #[must_use]
594 pub fn new() -> Self {
595 Self {
596 tags: Vec::new(),
597 custom_fields: HashMap::new(),
598 created_at: Utc::now(),
599 expires_at: None,
600 compressed: false,
601 compression_algorithm: None,
602 chunked: false,
603 total_chunks: None,
604 original_size: None,
605 compressed_size: None,
606 }
607 }
608
609 #[must_use]
611 pub fn with_tag(mut self, tag: impl Into<String>) -> Self {
612 self.tags.push(tag.into());
613 self
614 }
615
616 #[must_use]
618 pub fn with_tags(mut self, tags: Vec<String>) -> Self {
619 self.tags.extend(tags);
620 self
621 }
622
623 #[must_use]
625 pub fn with_field(mut self, key: impl Into<String>, value: Value) -> Self {
626 self.custom_fields.insert(key.into(), value);
627 self
628 }
629
630 #[must_use]
636 pub fn with_ttl(mut self, ttl: Duration) -> Self {
637 self.expires_at = Some(
638 Utc::now() + chrono::Duration::from_std(ttl).expect("TTL duration should be valid"),
639 );
640 self
641 }
642
643 #[must_use]
645 pub fn with_expires_at(mut self, expires_at: DateTime<Utc>) -> Self {
646 self.expires_at = Some(expires_at);
647 self
648 }
649
650 #[inline]
652 #[must_use]
653 pub fn is_expired(&self) -> bool {
654 self.expires_at.is_some_and(|exp| Utc::now() > exp)
655 }
656
657 #[inline]
659 #[must_use]
660 pub fn time_until_expiration(&self) -> Option<Duration> {
661 self.expires_at.and_then(|exp| {
662 let diff = exp - Utc::now();
663 diff.to_std().ok()
664 })
665 }
666
667 #[must_use]
669 pub fn with_compression(
670 mut self,
671 algorithm: impl Into<String>,
672 original_size: usize,
673 compressed_size: usize,
674 ) -> Self {
675 self.compressed = true;
676 self.compression_algorithm = Some(algorithm.into());
677 self.original_size = Some(original_size);
678 self.compressed_size = Some(compressed_size);
679 self
680 }
681
682 #[must_use]
684 pub fn with_chunking(mut self, total_chunks: usize) -> Self {
685 self.chunked = true;
686 self.total_chunks = Some(total_chunks);
687 self
688 }
689
690 #[allow(clippy::cast_precision_loss)]
692 #[must_use]
693 pub fn compression_ratio(&self) -> Option<f64> {
694 if let (Some(orig), Some(comp)) = (self.original_size, self.compressed_size) {
695 if orig > 0 {
696 return Some(comp as f64 / orig as f64);
697 }
698 }
699 None
700 }
701}
702
703impl Default for ResultMetadata {
704 fn default() -> Self {
705 Self::new()
706 }
707}
708
709#[derive(Debug, Clone, Serialize, Deserialize)]
711pub struct ResultChunk {
712 pub index: usize,
714
715 pub total: usize,
717
718 pub data: Vec<u8>,
720
721 #[serde(skip_serializing_if = "Option::is_none")]
723 pub checksum: Option<String>,
724}
725
726impl ResultChunk {
727 #[must_use]
729 pub fn new(index: usize, total: usize, data: Vec<u8>) -> Self {
730 Self {
731 index,
732 total,
733 data,
734 checksum: None,
735 }
736 }
737
738 #[must_use]
740 pub fn with_checksum(mut self, checksum: impl Into<String>) -> Self {
741 self.checksum = Some(checksum.into());
742 self
743 }
744
745 #[inline]
747 #[must_use]
748 pub const fn is_last(&self) -> bool {
749 self.index == self.total - 1
750 }
751}
752
753#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
755pub struct ResultTombstone {
756 pub task_id: TaskId,
758
759 pub deleted_at: DateTime<Utc>,
761
762 #[serde(skip_serializing_if = "Option::is_none")]
764 pub reason: Option<String>,
765
766 #[serde(skip_serializing_if = "Option::is_none")]
768 pub deleted_by: Option<String>,
769
770 #[serde(skip_serializing_if = "Option::is_none")]
772 pub tombstone_ttl: Option<Duration>,
773}
774
775impl ResultTombstone {
776 #[must_use]
778 pub fn new(task_id: TaskId) -> Self {
779 Self {
780 task_id,
781 deleted_at: Utc::now(),
782 reason: None,
783 deleted_by: None,
784 tombstone_ttl: None,
785 }
786 }
787
788 #[must_use]
790 pub fn with_reason(mut self, reason: impl Into<String>) -> Self {
791 self.reason = Some(reason.into());
792 self
793 }
794
795 #[must_use]
797 pub fn with_deleted_by(mut self, deleted_by: impl Into<String>) -> Self {
798 self.deleted_by = Some(deleted_by.into());
799 self
800 }
801
802 #[must_use]
804 pub fn with_ttl(mut self, ttl: Duration) -> Self {
805 self.tombstone_ttl = Some(ttl);
806 self
807 }
808}
809
810#[async_trait]
812pub trait ExtendedResultStore: ResultStore {
813 async fn store_result_with_metadata(
815 &self,
816 task_id: TaskId,
817 result: TaskResultValue,
818 metadata: ResultMetadata,
819 ) -> crate::Result<()>;
820
821 async fn get_metadata(&self, task_id: TaskId) -> crate::Result<Option<ResultMetadata>>;
823
824 async fn store_chunk(&self, task_id: TaskId, chunk: ResultChunk) -> crate::Result<()>;
826
827 async fn get_chunk(&self, task_id: TaskId, index: usize) -> crate::Result<Option<ResultChunk>>;
829
830 async fn get_all_chunks(&self, task_id: TaskId) -> crate::Result<Vec<ResultChunk>>;
832
833 async fn store_tombstone(&self, tombstone: ResultTombstone) -> crate::Result<()>;
835
836 async fn get_tombstone(&self, task_id: TaskId) -> crate::Result<Option<ResultTombstone>>;
838
839 async fn has_tombstone(&self, task_id: TaskId) -> crate::Result<bool> {
841 Ok(ExtendedResultStore::get_tombstone(self, task_id)
844 .await?
845 .is_some())
846 }
847
848 async fn cleanup_expired(&self) -> crate::Result<usize>;
850
851 async fn query_by_tags(&self, tags: &[String]) -> crate::Result<Vec<TaskId>>;
853}
854
855pub struct ResultCompressor {
857 threshold_bytes: usize,
858}
859
860impl ResultCompressor {
861 #[must_use]
863 pub fn new(threshold_bytes: usize) -> Self {
864 Self { threshold_bytes }
865 }
866
867 #[must_use]
869 pub const fn should_compress(&self, data: &[u8]) -> bool {
870 data.len() >= self.threshold_bytes
871 }
872
873 pub fn compress(&self, _data: &[u8], _algorithm: &str) -> crate::Result<Vec<u8>> {
878 Err(crate::CelersError::Other(
879 "Compression not available - use backend-specific implementation".to_string(),
880 ))
881 }
882
883 pub fn decompress(&self, _data: &[u8], _algorithm: &str) -> crate::Result<Vec<u8>> {
888 Err(crate::CelersError::Other(
889 "Decompression not available - use backend-specific implementation".to_string(),
890 ))
891 }
892}
893
894impl Default for ResultCompressor {
895 fn default() -> Self {
896 Self::new(1024 * 1024) }
898}
899
900pub struct ResultChunker {
902 chunk_size: usize,
903}
904
905impl ResultChunker {
906 #[must_use]
908 pub fn new(chunk_size: usize) -> Self {
909 Self { chunk_size }
910 }
911
912 #[must_use]
914 pub fn chunk(&self, data: &[u8]) -> Vec<ResultChunk> {
915 let total = data.len().div_ceil(self.chunk_size);
916
917 data.chunks(self.chunk_size)
918 .enumerate()
919 .map(|(index, chunk)| ResultChunk::new(index, total, chunk.to_vec()))
920 .collect()
921 }
922
923 pub fn reassemble(&self, chunks: &[ResultChunk]) -> crate::Result<Vec<u8>> {
925 if chunks.is_empty() {
926 return Ok(Vec::new());
927 }
928
929 let total = chunks[0].total;
931 if chunks.len() != total {
932 return Err(crate::CelersError::Other(format!(
933 "Incomplete chunks: expected {}, got {}",
934 total,
935 chunks.len()
936 )));
937 }
938
939 let mut result = Vec::new();
940 for (i, chunk) in chunks.iter().enumerate() {
941 if chunk.index != i {
942 return Err(crate::CelersError::Other(format!(
943 "Chunk out of order: expected index {}, got {}",
944 i, chunk.index
945 )));
946 }
947 result.extend_from_slice(&chunk.data);
948 }
949
950 Ok(result)
951 }
952}
953
954impl Default for ResultChunker {
955 fn default() -> Self {
956 Self::new(256 * 1024) }
958}
959
960#[cfg(test)]
961mod tests {
962 use super::*;
963 use std::collections::HashMap;
964 use std::sync::{Arc, Mutex};
965 use uuid::Uuid;
966
967 #[derive(Clone)]
969 struct MockBackend {
970 results: Arc<Mutex<HashMap<TaskId, TaskResultValue>>>,
971 states: Arc<Mutex<HashMap<TaskId, TaskState>>>,
972 }
973
974 impl MockBackend {
975 fn new() -> Self {
976 Self {
977 results: Arc::new(Mutex::new(HashMap::new())),
978 states: Arc::new(Mutex::new(HashMap::new())),
979 }
980 }
981
982 fn set_result(&self, task_id: TaskId, result: TaskResultValue, state: TaskState) {
983 self.results
984 .lock()
985 .unwrap_or_else(|e| e.into_inner())
986 .insert(task_id, result);
987 self.states
988 .lock()
989 .unwrap_or_else(|e| e.into_inner())
990 .insert(task_id, state);
991 }
992 }
993
994 #[async_trait]
995 impl ResultStore for MockBackend {
996 async fn store_result(
997 &self,
998 task_id: TaskId,
999 result: TaskResultValue,
1000 ) -> crate::Result<()> {
1001 self.results
1002 .lock()
1003 .unwrap_or_else(|e| e.into_inner())
1004 .insert(task_id, result);
1005 Ok(())
1006 }
1007
1008 async fn get_result(&self, task_id: TaskId) -> crate::Result<Option<TaskResultValue>> {
1009 Ok(self
1010 .results
1011 .lock()
1012 .unwrap_or_else(|e| e.into_inner())
1013 .get(&task_id)
1014 .cloned())
1015 }
1016
1017 async fn get_state(&self, task_id: TaskId) -> crate::Result<TaskState> {
1018 Ok(self
1019 .states
1020 .lock()
1021 .unwrap_or_else(|e| e.into_inner())
1022 .get(&task_id)
1023 .cloned()
1024 .unwrap_or(TaskState::Pending))
1025 }
1026
1027 async fn forget(&self, task_id: TaskId) -> crate::Result<()> {
1028 self.results
1029 .lock()
1030 .unwrap_or_else(|e| e.into_inner())
1031 .remove(&task_id);
1032 self.states
1033 .lock()
1034 .unwrap_or_else(|e| e.into_inner())
1035 .remove(&task_id);
1036 Ok(())
1037 }
1038
1039 async fn has_result(&self, task_id: TaskId) -> crate::Result<bool> {
1040 Ok(self
1041 .results
1042 .lock()
1043 .unwrap_or_else(|e| e.into_inner())
1044 .contains_key(&task_id))
1045 }
1046 }
1047
1048 #[tokio::test]
1049 async fn test_async_result_ready() {
1050 let backend = MockBackend::new();
1051 let task_id = Uuid::new_v4();
1052
1053 backend.set_result(
1054 task_id,
1055 TaskResultValue::Success(Value::String("test".to_string())),
1056 TaskState::Succeeded(vec![]),
1057 );
1058
1059 let result = AsyncResult::new(task_id, backend);
1060 assert!(result.ready().await.unwrap());
1061 }
1062
1063 #[tokio::test]
1064 async fn test_async_result_successful() {
1065 let backend = MockBackend::new();
1066 let task_id = Uuid::new_v4();
1067
1068 backend.set_result(
1069 task_id,
1070 TaskResultValue::Success(Value::String("test".to_string())),
1071 TaskState::Succeeded(vec![]),
1072 );
1073
1074 let result = AsyncResult::new(task_id, backend);
1075 assert!(result.successful().await.unwrap());
1076 assert!(!result.failed().await.unwrap());
1077 }
1078
1079 #[tokio::test]
1080 async fn test_async_result_failed() {
1081 let backend = MockBackend::new();
1082 let task_id = Uuid::new_v4();
1083
1084 backend.set_result(
1085 task_id,
1086 TaskResultValue::Failure {
1087 error: "Test error".to_string(),
1088 traceback: None,
1089 },
1090 TaskState::Failed(String::from("Test error")),
1091 );
1092
1093 let result = AsyncResult::new(task_id, backend);
1094 assert!(result.failed().await.unwrap());
1095 assert!(!result.successful().await.unwrap());
1096 }
1097
1098 #[tokio::test]
1099 async fn test_async_result_get_success() {
1100 let backend = MockBackend::new();
1101 let task_id = Uuid::new_v4();
1102
1103 backend.set_result(
1104 task_id,
1105 TaskResultValue::Success(Value::String("success".to_string())),
1106 TaskState::Succeeded(vec![]),
1107 );
1108
1109 let result = AsyncResult::new(task_id, backend);
1110 let value = result.get(Some(Duration::from_secs(1))).await.unwrap();
1111 assert_eq!(value, Some(Value::String("success".to_string())));
1112 }
1113
1114 #[tokio::test]
1115 async fn test_async_result_forget() {
1116 let backend = MockBackend::new();
1117 let task_id = Uuid::new_v4();
1118
1119 backend.set_result(
1120 task_id,
1121 TaskResultValue::Success(Value::String("test".to_string())),
1122 TaskState::Succeeded(vec![]),
1123 );
1124
1125 let result = AsyncResult::new(task_id, backend.clone());
1126 assert!(backend.has_result(task_id).await.unwrap());
1127
1128 result.forget().await.unwrap();
1129 assert!(!backend.has_result(task_id).await.unwrap());
1130 }
1131
1132 #[tokio::test]
1133 async fn test_async_result_children() {
1134 let backend = MockBackend::new();
1135
1136 let parent_id = Uuid::new_v4();
1138 backend.set_result(
1139 parent_id,
1140 TaskResultValue::Success(Value::String("parent".to_string())),
1141 TaskState::Succeeded(vec![]),
1142 );
1143
1144 let child1_id = Uuid::new_v4();
1146 let child2_id = Uuid::new_v4();
1147 backend.set_result(
1148 child1_id,
1149 TaskResultValue::Success(Value::Number(serde_json::Number::from(1))),
1150 TaskState::Succeeded(vec![]),
1151 );
1152 backend.set_result(
1153 child2_id,
1154 TaskResultValue::Success(Value::Number(serde_json::Number::from(2))),
1155 TaskState::Succeeded(vec![]),
1156 );
1157
1158 let child1 = AsyncResult::new(child1_id, backend.clone());
1160 let child2 = AsyncResult::new(child2_id, backend.clone());
1161
1162 let parent = AsyncResult::with_children(parent_id, backend, vec![child1, child2]);
1164
1165 assert_eq!(parent.children().len(), 2);
1167 assert_eq!(parent.children()[0].task_id(), child1_id);
1168 assert_eq!(parent.children()[1].task_id(), child2_id);
1169
1170 assert!(parent.children_ready().await.unwrap());
1172
1173 let results = parent
1175 .collect_children(Some(Duration::from_secs(1)))
1176 .await
1177 .unwrap();
1178 assert_eq!(results.len(), 2);
1179 assert_eq!(results[0], Some(Value::Number(serde_json::Number::from(1))));
1180 assert_eq!(results[1], Some(Value::Number(serde_json::Number::from(2))));
1181 }
1182
1183 #[tokio::test]
1184 async fn test_async_result_add_child() {
1185 let backend = MockBackend::new();
1186
1187 let parent_id = Uuid::new_v4();
1188 let child_id = Uuid::new_v4();
1189
1190 backend.set_result(
1191 child_id,
1192 TaskResultValue::Success(Value::String("child".to_string())),
1193 TaskState::Succeeded(vec![]),
1194 );
1195
1196 let mut parent = AsyncResult::new(parent_id, backend.clone());
1197 assert_eq!(parent.children().len(), 0);
1198
1199 let child = AsyncResult::new(child_id, backend);
1200 parent.add_child(child);
1201
1202 assert_eq!(parent.children().len(), 1);
1203 assert_eq!(parent.children()[0].task_id(), child_id);
1204 }
1205}