1use celers_core::{Broker, SerializedTask};
30use serde::{Deserialize, Serialize};
31use std::collections::HashMap;
32use uuid::Uuid;
33
34#[cfg(feature = "backend-redis")]
35use celers_backend_redis::{ChordState, ResultBackend};
36
37#[cfg(feature = "backend-redis")]
38use chrono::Utc;
39
40#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
42pub struct Signature {
43 pub task: String,
45
46 #[serde(default)]
48 pub args: Vec<serde_json::Value>,
49
50 #[serde(default)]
52 pub kwargs: HashMap<String, serde_json::Value>,
53
54 #[serde(default)]
56 pub options: TaskOptions,
57
58 #[serde(default)]
60 pub immutable: bool,
61}
62
63impl Signature {
64 pub fn new(task: String) -> Self {
65 Self {
66 task,
67 args: Vec::new(),
68 kwargs: HashMap::new(),
69 options: TaskOptions::default(),
70 immutable: false,
71 }
72 }
73
74 pub fn with_args(mut self, args: Vec<serde_json::Value>) -> Self {
75 self.args = args;
76 self
77 }
78
79 pub fn with_kwargs(mut self, kwargs: HashMap<String, serde_json::Value>) -> Self {
80 self.kwargs = kwargs;
81 self
82 }
83
84 pub fn with_priority(mut self, priority: u8) -> Self {
85 self.options.priority = Some(priority);
86 self
87 }
88
89 pub fn with_queue(mut self, queue: String) -> Self {
90 self.options.queue = Some(queue);
91 self
92 }
93
94 pub fn with_task_id(mut self, task_id: Uuid) -> Self {
95 self.options.task_id = Some(task_id);
96 self
97 }
98
99 pub fn with_link(mut self, link: Signature) -> Self {
100 self.options.link = Some(Box::new(link));
101 self
102 }
103
104 pub fn with_link_error(mut self, link_error: Signature) -> Self {
105 self.options.link_error = Some(Box::new(link_error));
106 self
107 }
108
109 pub fn add_link(mut self, link: Signature) -> Self {
111 self.options.links.push(link);
112 self
113 }
114
115 pub fn add_link_error(mut self, link_error: Signature) -> Self {
117 self.options.link_errors.push(link_error);
118 self
119 }
120
121 pub fn with_on_retry(mut self, callback: Signature) -> Self {
123 self.options.on_retry = Some(Box::new(callback));
124 self
125 }
126
127 pub fn with_soft_time_limit(mut self, seconds: u64) -> Self {
129 self.options.soft_time_limit = Some(seconds);
130 self
131 }
132
133 pub fn with_time_limit(mut self, seconds: u64) -> Self {
135 self.options.time_limit = Some(seconds);
136 self
137 }
138
139 pub fn with_retry_delay(mut self, seconds: u64) -> Self {
141 self.options.retry_delay = Some(seconds);
142 self
143 }
144
145 pub fn with_retry_backoff(mut self, factor: f64) -> Self {
147 self.options.retry_backoff = Some(factor);
148 self
149 }
150
151 pub fn with_retry_backoff_max(mut self, seconds: u64) -> Self {
153 self.options.retry_backoff_max = Some(seconds);
154 self
155 }
156
157 pub fn with_retry_jitter(mut self, jitter: bool) -> Self {
159 self.options.retry_jitter = Some(jitter);
160 self
161 }
162
163 pub fn immutable(mut self) -> Self {
164 self.immutable = true;
165 self
166 }
167
168 pub fn has_args(&self) -> bool {
170 !self.args.is_empty()
171 }
172
173 pub fn has_kwargs(&self) -> bool {
175 !self.kwargs.is_empty()
176 }
177
178 pub fn is_immutable(&self) -> bool {
180 self.immutable
181 }
182
183 pub fn has_kwarg(&self, key: &str) -> bool {
185 self.kwargs.contains_key(key)
186 }
187
188 pub fn get_kwarg(&self, key: &str) -> Option<&serde_json::Value> {
190 self.kwargs.get(key)
191 }
192
193 pub fn add_kwarg(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
195 self.kwargs.insert(key.into(), value);
196 self
197 }
198
199 pub fn add_arg(mut self, arg: serde_json::Value) -> Self {
201 self.args.push(arg);
202 self
203 }
204
205 pub fn clone_signature(&self) -> Self {
207 self.clone()
208 }
209
210 pub fn si(self) -> Self {
226 self.immutable()
227 }
228
229 pub fn partial(mut self, args: Vec<serde_json::Value>) -> Self {
247 self.args = args;
248 self
249 }
250
251 pub fn complete(mut self, additional_args: Vec<serde_json::Value>) -> Self {
256 if self.immutable {
257 return self;
258 }
259 self.args.extend(additional_args);
260 self
261 }
262
263 pub fn merge(mut self, other: Signature) -> Self {
268 for (key, value) in other.kwargs {
270 self.kwargs.insert(key, value);
271 }
272
273 if self.options.priority.is_none() {
275 self.options.priority = other.options.priority;
276 }
277 if self.options.queue.is_none() {
278 self.options.queue = other.options.queue;
279 }
280 if self.options.task_id.is_none() {
281 self.options.task_id = other.options.task_id;
282 }
283 if self.options.link.is_none() {
284 self.options.link = other.options.link;
285 }
286 if self.options.link_error.is_none() {
287 self.options.link_error = other.options.link_error;
288 }
289
290 self
291 }
292
293 pub fn replace_args(mut self, args: Vec<serde_json::Value>) -> Option<Self> {
298 if self.immutable {
299 return None;
300 }
301 self.args = args;
302 Some(self)
303 }
304
305 pub fn with_expires(mut self, expires: u64) -> Self {
307 self.options.expires = Some(expires);
308 self
309 }
310
311 pub fn with_countdown(mut self, countdown: u64) -> Self {
313 self.options.countdown = Some(countdown);
314 self
315 }
316
317 pub fn with_retries(mut self, max_retries: u32) -> Self {
319 self.options.max_retries = Some(max_retries);
320 self
321 }
322
323 pub fn with_routing_key(mut self, routing_key: String) -> Self {
325 self.options.routing_key = Some(routing_key);
326 self
327 }
328
329 pub fn with_callback_arg_mode(mut self, mode: CallbackArgMode) -> Self {
341 self.options.callback_arg_mode = mode;
342 self
343 }
344
345 pub fn with_callback_kwarg_key(mut self, key: impl Into<String>) -> Self {
350 self.options.callback_kwarg_key = Some(key.into());
351 self
352 }
353
354 pub fn with_result_as_kwarg(mut self, key: impl Into<String>) -> Self {
358 self.options.callback_arg_mode = CallbackArgMode::Kwarg;
359 self.options.callback_kwarg_key = Some(key.into());
360 self
361 }
362
363 pub fn to_json(&self) -> Result<String, serde_json::Error> {
365 serde_json::to_string(self)
366 }
367
368 pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
370 serde_json::from_str(json)
371 }
372
373 pub fn to_json_bytes(&self) -> Result<Vec<u8>, serde_json::Error> {
375 serde_json::to_vec(self)
376 }
377
378 pub fn from_json_bytes(bytes: &[u8]) -> Result<Self, serde_json::Error> {
380 serde_json::from_slice(bytes)
381 }
382
383 pub fn clear_args(mut self) -> Option<Self> {
398 if self.immutable {
399 return None;
400 }
401 self.args.clear();
402 Some(self)
403 }
404
405 pub fn clear_kwargs(mut self) -> Self {
420 self.kwargs.clear();
421 self
422 }
423
424 pub fn remove_kwarg(mut self, key: &str) -> Self {
439 self.kwargs.remove(key);
440 self
441 }
442
443 pub fn args_count(&self) -> usize {
455 self.args.len()
456 }
457
458 pub fn kwargs_count(&self) -> usize {
471 self.kwargs.len()
472 }
473
474 pub fn kwarg_keys(&self) -> Vec<&str> {
490 self.kwargs.keys().map(|k| k.as_str()).collect()
491 }
492
493 pub fn has_retry_config(&self) -> bool {
506 self.options.max_retries.is_some()
507 || self.options.retry_delay.is_some()
508 || self.options.retry_backoff.is_some()
509 }
510
511 pub fn has_time_limit_config(&self) -> bool {
524 self.options.time_limit.is_some() || self.options.soft_time_limit.is_some()
525 }
526
527 pub fn clone_without_args(&self) -> Self {
543 Self {
544 task: self.task.clone(),
545 args: Vec::new(),
546 kwargs: HashMap::new(),
547 options: self.options.clone(),
548 immutable: self.immutable,
549 }
550 }
551
552 pub fn estimated_size(&self) -> usize {
567 self.to_json().map(|s| s.len()).unwrap_or(0)
568 }
569}
570
571impl std::fmt::Display for Signature {
572 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
573 write!(f, "Signature[task={}]", self.task)?;
574 if !self.args.is_empty() {
575 write!(f, " args={}", self.args.len())?;
576 }
577 if !self.kwargs.is_empty() {
578 write!(f, " kwargs={}", self.kwargs.len())?;
579 }
580 if self.immutable {
581 write!(f, " (immutable)")?;
582 }
583 Ok(())
584 }
585}
586
587#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
589pub enum CallbackArgMode {
590 #[default]
592 Prepend,
593
594 Append,
596
597 Kwarg,
599
600 None,
602}
603
604impl CallbackArgMode {
605 pub fn kwarg() -> Self {
607 Self::Kwarg
608 }
609
610 pub fn passes_result(&self) -> bool {
612 !matches!(self, Self::None)
613 }
614}
615
616impl std::fmt::Display for CallbackArgMode {
617 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
618 match self {
619 Self::Prepend => write!(f, "prepend"),
620 Self::Append => write!(f, "append"),
621 Self::Kwarg => write!(f, "kwarg"),
622 Self::None => write!(f, "none"),
623 }
624 }
625}
626
627fn is_default_callback_arg_mode(mode: &CallbackArgMode) -> bool {
629 *mode == CallbackArgMode::Prepend
630}
631
632#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
634pub struct TaskOptions {
635 pub priority: Option<u8>,
637
638 pub queue: Option<String>,
640
641 pub task_id: Option<Uuid>,
643
644 pub link: Option<Box<Signature>>,
646
647 pub link_error: Option<Box<Signature>>,
649
650 #[serde(default, skip_serializing_if = "Vec::is_empty")]
652 pub links: Vec<Signature>,
653
654 #[serde(default, skip_serializing_if = "Vec::is_empty")]
656 pub link_errors: Vec<Signature>,
657
658 #[serde(skip_serializing_if = "Option::is_none")]
660 pub on_retry: Option<Box<Signature>>,
661
662 #[serde(default, skip_serializing_if = "is_default_callback_arg_mode")]
664 pub callback_arg_mode: CallbackArgMode,
665
666 #[serde(skip_serializing_if = "Option::is_none")]
668 pub callback_kwarg_key: Option<String>,
669
670 #[serde(skip_serializing_if = "Option::is_none")]
672 pub expires: Option<u64>,
673
674 #[serde(skip_serializing_if = "Option::is_none")]
676 pub countdown: Option<u64>,
677
678 #[serde(skip_serializing_if = "Option::is_none")]
680 pub max_retries: Option<u32>,
681
682 #[serde(skip_serializing_if = "Option::is_none")]
684 pub routing_key: Option<String>,
685
686 #[serde(skip_serializing_if = "Option::is_none")]
688 pub soft_time_limit: Option<u64>,
689
690 #[serde(skip_serializing_if = "Option::is_none")]
692 pub time_limit: Option<u64>,
693
694 #[serde(skip_serializing_if = "Option::is_none")]
696 pub retry_delay: Option<u64>,
697
698 #[serde(skip_serializing_if = "Option::is_none")]
700 pub retry_backoff: Option<f64>,
701
702 #[serde(skip_serializing_if = "Option::is_none")]
704 pub retry_backoff_max: Option<u64>,
705
706 #[serde(skip_serializing_if = "Option::is_none")]
708 pub retry_jitter: Option<bool>,
709}
710
711impl TaskOptions {
712 pub fn has_priority(&self) -> bool {
714 self.priority.is_some()
715 }
716
717 pub fn has_queue(&self) -> bool {
719 self.queue.is_some()
720 }
721
722 pub fn has_task_id(&self) -> bool {
724 self.task_id.is_some()
725 }
726
727 pub fn has_link(&self) -> bool {
729 self.link.is_some() || !self.links.is_empty()
730 }
731
732 pub fn has_link_error(&self) -> bool {
734 self.link_error.is_some() || !self.link_errors.is_empty()
735 }
736
737 pub fn has_on_retry(&self) -> bool {
739 self.on_retry.is_some()
740 }
741
742 pub fn has_expires(&self) -> bool {
744 self.expires.is_some()
745 }
746
747 pub fn has_countdown(&self) -> bool {
749 self.countdown.is_some()
750 }
751
752 pub fn has_max_retries(&self) -> bool {
754 self.max_retries.is_some()
755 }
756
757 pub fn has_routing_key(&self) -> bool {
759 self.routing_key.is_some()
760 }
761
762 pub fn has_soft_time_limit(&self) -> bool {
764 self.soft_time_limit.is_some()
765 }
766
767 pub fn has_time_limit(&self) -> bool {
769 self.time_limit.is_some()
770 }
771
772 pub fn all_links(&self) -> Vec<&Signature> {
774 let mut result = Vec::new();
775 if let Some(ref link) = self.link {
776 result.push(link.as_ref());
777 }
778 for link in &self.links {
779 result.push(link);
780 }
781 result
782 }
783
784 pub fn all_link_errors(&self) -> Vec<&Signature> {
786 let mut result = Vec::new();
787 if let Some(ref link_error) = self.link_error {
788 result.push(link_error.as_ref());
789 }
790 for link_error in &self.link_errors {
791 result.push(link_error);
792 }
793 result
794 }
795
796 pub fn calculate_retry_delay(&self, retry_count: u32) -> u64 {
798 let base_delay = self.retry_delay.unwrap_or(1);
799 let backoff = self.retry_backoff.unwrap_or(2.0);
800 let max_delay = self.retry_backoff_max.unwrap_or(3600);
801
802 let delay = (base_delay as f64 * backoff.powi(retry_count as i32)) as u64;
803 delay.min(max_delay)
804 }
805
806 pub fn callback_arg_mode(&self) -> CallbackArgMode {
808 self.callback_arg_mode
809 }
810
811 pub fn callback_kwarg_key(&self) -> &str {
813 self.callback_kwarg_key.as_deref().unwrap_or("result")
814 }
815
816 pub fn prepare_callback(
828 &self,
829 mut callback: Signature,
830 result: serde_json::Value,
831 ) -> Signature {
832 if callback.immutable {
834 return callback;
835 }
836
837 match self.callback_arg_mode {
838 CallbackArgMode::Prepend => {
839 callback.args.insert(0, result);
840 }
841 CallbackArgMode::Append => {
842 callback.args.push(result);
843 }
844 CallbackArgMode::Kwarg => {
845 let key = self.callback_kwarg_key().to_string();
846 callback.kwargs.insert(key, result);
847 }
848 CallbackArgMode::None => {
849 }
851 }
852
853 callback
854 }
855}
856
857impl std::fmt::Display for TaskOptions {
858 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
859 let mut parts = Vec::new();
860 if let Some(priority) = self.priority {
861 parts.push(format!("priority={}", priority));
862 }
863 if let Some(ref queue) = self.queue {
864 parts.push(format!("queue={}", queue));
865 }
866 if let Some(task_id) = self.task_id {
867 parts.push(format!("task_id={}", &task_id.to_string()[..8]));
868 }
869 if self.link.is_some() {
870 parts.push("link=yes".to_string());
871 }
872 if self.link_error.is_some() {
873 parts.push("link_error=yes".to_string());
874 }
875 if let Some(expires) = self.expires {
876 parts.push(format!("expires={}s", expires));
877 }
878 if let Some(countdown) = self.countdown {
879 parts.push(format!("countdown={}s", countdown));
880 }
881 if let Some(max_retries) = self.max_retries {
882 parts.push(format!("retries={}", max_retries));
883 }
884 if let Some(ref routing_key) = self.routing_key {
885 parts.push(format!("routing={}", routing_key));
886 }
887 if parts.is_empty() {
888 write!(f, "TaskOptions[default]")
889 } else {
890 write!(f, "TaskOptions[{}]", parts.join(", "))
891 }
892 }
893}
894
895#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
899pub struct Chain {
900 pub tasks: Vec<Signature>,
902}
903
904impl Chain {
905 pub fn new() -> Self {
906 Self { tasks: Vec::new() }
907 }
908
909 pub fn then(mut self, task: &str, args: Vec<serde_json::Value>) -> Self {
910 self.tasks
911 .push(Signature::new(task.to_string()).with_args(args));
912 self
913 }
914
915 pub fn then_signature(mut self, signature: Signature) -> Self {
916 self.tasks.push(signature);
917 self
918 }
919
920 pub async fn apply<B: Broker>(self, broker: &B) -> Result<Uuid, CanvasError> {
922 if self.tasks.is_empty() {
923 return Err(CanvasError::Invalid("Chain cannot be empty".to_string()));
924 }
925
926 let mut chain_iter = self.tasks.into_iter().rev();
928 let mut next_sig: Option<Signature> = None;
929
930 if let Some(last_task) = chain_iter.next() {
932 next_sig = Some(last_task);
934
935 for mut task in chain_iter {
937 task.options.link = next_sig.map(Box::new);
938 next_sig = Some(task);
939 }
940 }
941
942 if let Some(first_sig) = next_sig {
944 let task_id = Self::enqueue_signature(broker, &first_sig).await?;
945 Ok(task_id)
946 } else {
947 Err(CanvasError::Invalid("Failed to build chain".to_string()))
948 }
949 }
950
951 async fn enqueue_signature<B: Broker>(
952 broker: &B,
953 sig: &Signature,
954 ) -> Result<Uuid, CanvasError> {
955 let args_json = serde_json::json!({
956 "args": sig.args,
957 "kwargs": sig.kwargs
958 });
959 let args_bytes = serde_json::to_vec(&args_json)
960 .map_err(|e| CanvasError::Serialization(e.to_string()))?;
961
962 let mut task = SerializedTask::new(sig.task.clone(), args_bytes);
963
964 if let Some(priority) = sig.options.priority {
965 task = task.with_priority(priority.into());
966 }
967
968 let task_id = task.metadata.id;
969 broker
970 .enqueue(task)
971 .await
972 .map_err(|e| CanvasError::Broker(e.to_string()))?;
973
974 Ok(task_id)
975 }
976}
977
978impl Default for Chain {
979 fn default() -> Self {
980 Self::new()
981 }
982}
983
984impl Chain {
985 pub fn is_empty(&self) -> bool {
987 self.tasks.is_empty()
988 }
989
990 pub fn len(&self) -> usize {
992 self.tasks.len()
993 }
994
995 pub fn first(&self) -> Option<&Signature> {
997 self.tasks.first()
998 }
999
1000 pub fn last(&self) -> Option<&Signature> {
1002 self.tasks.last()
1003 }
1004
1005 pub fn iter(&self) -> std::slice::Iter<'_, Signature> {
1007 self.tasks.iter()
1008 }
1009
1010 pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, Signature> {
1012 self.tasks.iter_mut()
1013 }
1014
1015 pub fn get(&self, index: usize) -> Option<&Signature> {
1017 self.tasks.get(index)
1018 }
1019
1020 pub fn get_mut(&mut self, index: usize) -> Option<&mut Signature> {
1022 self.tasks.get_mut(index)
1023 }
1024
1025 pub fn with_capacity(capacity: usize) -> Self {
1027 Self {
1028 tasks: Vec::with_capacity(capacity),
1029 }
1030 }
1031
1032 pub fn extend(mut self, tasks: impl IntoIterator<Item = Signature>) -> Self {
1034 self.tasks.extend(tasks);
1035 self
1036 }
1037
1038 pub fn reverse(mut self) -> Self {
1040 self.tasks.reverse();
1041 self
1042 }
1043
1044 pub fn retain<F>(mut self, f: F) -> Self
1046 where
1047 F: FnMut(&Signature) -> bool,
1048 {
1049 self.tasks.retain(f);
1050 self
1051 }
1052
1053 pub async fn apply_with_countdown<B: Broker>(
1068 mut self,
1069 broker: &B,
1070 countdown: u64,
1071 ) -> Result<Uuid, CanvasError> {
1072 if self.tasks.is_empty() {
1073 return Err(CanvasError::Invalid("Chain cannot be empty".to_string()));
1074 }
1075
1076 if let Some(first) = self.tasks.first_mut() {
1078 first.options.countdown = Some(countdown);
1079 }
1080
1081 self.apply(broker).await
1083 }
1084
1085 pub async fn apply_with_eta<B: Broker>(
1104 mut self,
1105 broker: &B,
1106 eta: u64,
1107 ) -> Result<Uuid, CanvasError> {
1108 if self.tasks.is_empty() {
1109 return Err(CanvasError::Invalid("Chain cannot be empty".to_string()));
1110 }
1111
1112 let now = std::time::SystemTime::now()
1114 .duration_since(std::time::UNIX_EPOCH)
1115 .unwrap_or_default()
1116 .as_secs();
1117
1118 let countdown = eta.saturating_sub(now);
1119
1120 if let Some(first) = self.tasks.first_mut() {
1122 first.options.countdown = Some(countdown);
1123 }
1124
1125 self.apply(broker).await
1126 }
1127
1128 pub fn with_staggered_countdown(mut self, start: u64, step: u64) -> Self {
1136 let mut countdown = start;
1137 for task in &mut self.tasks {
1138 task.options.countdown = Some(countdown);
1139 countdown += step;
1140 }
1141 self
1142 }
1143
1144 pub fn append(mut self, other: Chain) -> Self {
1162 self.tasks.extend(other.tasks);
1163 self
1164 }
1165
1166 pub fn prepend(mut self, other: Chain) -> Self {
1185 let mut new_tasks = other.tasks;
1186 new_tasks.extend(self.tasks);
1187 self.tasks = new_tasks;
1188 self
1189 }
1190
1191 pub fn split_at(self, index: usize) -> (Chain, Chain) {
1211 let (before, after) = self.tasks.split_at(index.min(self.tasks.len()));
1212 (
1213 Chain {
1214 tasks: before.to_vec(),
1215 },
1216 Chain {
1217 tasks: after.to_vec(),
1218 },
1219 )
1220 }
1221
1222 pub fn concat<I>(chains: I) -> Self
1238 where
1239 I: IntoIterator<Item = Chain>,
1240 {
1241 let mut result = Chain::new();
1242 for chain in chains {
1243 result.tasks.extend(chain.tasks);
1244 }
1245 result
1246 }
1247
1248 pub fn with_task_prefix(mut self, prefix: &str) -> Self {
1264 for task in &mut self.tasks {
1265 task.task = format!("{}{}", prefix, task.task);
1266 }
1267 self
1268 }
1269
1270 pub fn with_task_suffix(mut self, suffix: &str) -> Self {
1284 for task in &mut self.tasks {
1285 task.task = format!("{}{}", task.task, suffix);
1286 }
1287 self
1288 }
1289
1290 pub fn is_valid(&self) -> bool {
1307 !self.tasks.is_empty() && self.tasks.iter().all(|t| !t.task.is_empty())
1308 }
1309
1310 pub fn count_matching<F>(&self, predicate: F) -> usize
1325 where
1326 F: Fn(&Signature) -> bool,
1327 {
1328 self.tasks.iter().filter(|t| predicate(t)).count()
1329 }
1330
1331 pub fn any<F>(&self, predicate: F) -> bool
1345 where
1346 F: Fn(&Signature) -> bool,
1347 {
1348 self.tasks.iter().any(predicate)
1349 }
1350
1351 pub fn all<F>(&self, predicate: F) -> bool
1364 where
1365 F: Fn(&Signature) -> bool,
1366 {
1367 self.tasks.iter().all(predicate)
1368 }
1369
1370 pub fn map_tasks<F>(mut self, f: F) -> Self
1387 where
1388 F: FnMut(Signature) -> Signature,
1389 {
1390 self.tasks = self.tasks.into_iter().map(f).collect();
1391 self
1392 }
1393
1394 pub fn filter_map<F>(mut self, f: F) -> Self
1416 where
1417 F: FnMut(Signature) -> Option<Signature>,
1418 {
1419 self.tasks = self.tasks.into_iter().filter_map(f).collect();
1420 self
1421 }
1422
1423 pub fn take(mut self, n: usize) -> Self {
1439 self.tasks.truncate(n);
1440 self
1441 }
1442
1443 pub fn skip(mut self, n: usize) -> Self {
1460 self.tasks = self.tasks.into_iter().skip(n).collect();
1461 self
1462 }
1463
1464 pub fn find_task(&self, task_name: &str) -> Option<usize> {
1480 self.tasks.iter().position(|t| t.task == task_name)
1481 }
1482
1483 pub fn find_all_tasks(&self, task_name: &str) -> Vec<usize> {
1498 self.tasks
1499 .iter()
1500 .enumerate()
1501 .filter(|(_, t)| t.task == task_name)
1502 .map(|(i, _)| i)
1503 .collect()
1504 }
1505
1506 pub fn contains_task(&self, task_name: &str) -> bool {
1520 self.tasks.iter().any(|t| t.task == task_name)
1521 }
1522
1523 pub fn estimated_duration(&self) -> Option<u64> {
1539 let mut total = 0u64;
1540 let mut found_any = false;
1541
1542 for task in &self.tasks {
1543 if let Some(limit) = task.options.time_limit.or(task.options.soft_time_limit) {
1544 total += limit;
1545 found_any = true;
1546 }
1547 }
1548
1549 if found_any {
1550 Some(total)
1551 } else {
1552 None
1553 }
1554 }
1555
1556 pub fn task_names(&self) -> Vec<&str> {
1570 self.tasks.iter().map(|t| t.task.as_str()).collect()
1571 }
1572
1573 pub fn unique_task_names(&self) -> std::collections::HashSet<&str> {
1590 self.tasks.iter().map(|t| t.task.as_str()).collect()
1591 }
1592
1593 pub fn clone_with_transform<F>(&self, mut transform: F) -> Self
1610 where
1611 F: FnMut(&Signature) -> Signature,
1612 {
1613 Self {
1614 tasks: self.tasks.iter().map(&mut transform).collect(),
1615 }
1616 }
1617}
1618
1619impl std::fmt::Display for Chain {
1620 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1621 write!(f, "Chain[{} tasks]", self.tasks.len())?;
1622 if !self.tasks.is_empty() {
1623 write!(
1624 f,
1625 " {} -> ... -> {}",
1626 self.tasks.first().unwrap().task,
1627 self.tasks.last().unwrap().task
1628 )?;
1629 }
1630 Ok(())
1631 }
1632}
1633
1634impl IntoIterator for Chain {
1635 type Item = Signature;
1636 type IntoIter = std::vec::IntoIter<Signature>;
1637
1638 fn into_iter(self) -> Self::IntoIter {
1639 self.tasks.into_iter()
1640 }
1641}
1642
1643impl<'a> IntoIterator for &'a Chain {
1644 type Item = &'a Signature;
1645 type IntoIter = std::slice::Iter<'a, Signature>;
1646
1647 fn into_iter(self) -> Self::IntoIter {
1648 self.tasks.iter()
1649 }
1650}
1651
1652impl From<Vec<Signature>> for Chain {
1653 fn from(tasks: Vec<Signature>) -> Self {
1654 Self { tasks }
1655 }
1656}
1657
1658impl FromIterator<Signature> for Chain {
1659 fn from_iter<T: IntoIterator<Item = Signature>>(iter: T) -> Self {
1660 Self {
1661 tasks: iter.into_iter().collect(),
1662 }
1663 }
1664}
1665
1666#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1670pub struct Group {
1671 pub tasks: Vec<Signature>,
1673
1674 pub group_id: Option<Uuid>,
1676}
1677
1678impl Group {
1679 pub fn new() -> Self {
1680 Self {
1681 tasks: Vec::new(),
1682 group_id: Some(Uuid::new_v4()),
1683 }
1684 }
1685
1686 pub fn add(mut self, task: &str, args: Vec<serde_json::Value>) -> Self {
1687 self.tasks
1688 .push(Signature::new(task.to_string()).with_args(args));
1689 self
1690 }
1691
1692 pub fn add_signature(mut self, signature: Signature) -> Self {
1693 self.tasks.push(signature);
1694 self
1695 }
1696
1697 pub async fn apply<B: Broker>(self, broker: &B) -> Result<Uuid, CanvasError> {
1699 if self.tasks.is_empty() {
1700 return Err(CanvasError::Invalid("Group cannot be empty".to_string()));
1701 }
1702
1703 let group_id = self.group_id.unwrap_or_else(Uuid::new_v4);
1704
1705 for sig in self.tasks {
1707 let args_json = serde_json::json!({
1709 "args": sig.args,
1710 "kwargs": sig.kwargs
1711 });
1712 let args_bytes = serde_json::to_vec(&args_json)
1713 .map_err(|e| CanvasError::Serialization(e.to_string()))?;
1714
1715 let mut task = SerializedTask::new(sig.task.clone(), args_bytes);
1716
1717 if let Some(priority) = sig.options.priority {
1719 task = task.with_priority(priority.into());
1720 }
1721
1722 task.metadata.group_id = Some(group_id);
1724
1725 broker
1727 .enqueue(task)
1728 .await
1729 .map_err(|e| CanvasError::Broker(e.to_string()))?;
1730 }
1731
1732 Ok(group_id)
1733 }
1734}
1735
1736impl Default for Group {
1737 fn default() -> Self {
1738 Self::new()
1739 }
1740}
1741
1742impl Group {
1743 pub fn is_empty(&self) -> bool {
1745 self.tasks.is_empty()
1746 }
1747
1748 pub fn first(&self) -> Option<&Signature> {
1750 self.tasks.first()
1751 }
1752
1753 pub fn last(&self) -> Option<&Signature> {
1755 self.tasks.last()
1756 }
1757
1758 pub fn iter(&self) -> std::slice::Iter<'_, Signature> {
1760 self.tasks.iter()
1761 }
1762
1763 pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, Signature> {
1765 self.tasks.iter_mut()
1766 }
1767
1768 pub fn get(&self, index: usize) -> Option<&Signature> {
1770 self.tasks.get(index)
1771 }
1772
1773 pub fn get_mut(&mut self, index: usize) -> Option<&mut Signature> {
1775 self.tasks.get_mut(index)
1776 }
1777
1778 pub fn with_capacity(capacity: usize) -> Self {
1780 Self {
1781 tasks: Vec::with_capacity(capacity),
1782 group_id: Some(Uuid::new_v4()),
1783 }
1784 }
1785
1786 pub fn extend(mut self, tasks: impl IntoIterator<Item = Signature>) -> Self {
1788 self.tasks.extend(tasks);
1789 self
1790 }
1791
1792 pub fn retain<F>(mut self, f: F) -> Self
1794 where
1795 F: FnMut(&Signature) -> bool,
1796 {
1797 self.tasks.retain(f);
1798 self
1799 }
1800
1801 pub fn find<F>(&self, predicate: F) -> Option<&Signature>
1803 where
1804 F: Fn(&Signature) -> bool,
1805 {
1806 self.tasks.iter().find(|sig| predicate(sig))
1807 }
1808
1809 pub fn filter<F>(mut self, predicate: F) -> Self
1811 where
1812 F: FnMut(&Signature) -> bool,
1813 {
1814 self.tasks.retain(predicate);
1815 self
1816 }
1817
1818 pub fn skew(mut self, start: f64, step: f64) -> Self {
1842 let mut countdown = start;
1843 for task in &mut self.tasks {
1844 task.options.countdown = Some(countdown as u64);
1845 countdown += step;
1846 }
1847 self
1848 }
1849
1850 pub fn jitter(mut self, max_delay: u64) -> Self {
1858 use std::collections::hash_map::DefaultHasher;
1859 use std::hash::{Hash, Hasher};
1860
1861 for (i, task) in self.tasks.iter_mut().enumerate() {
1862 let mut hasher = DefaultHasher::new();
1864 i.hash(&mut hasher);
1865 task.task.hash(&mut hasher);
1866 let hash = hasher.finish();
1867 let delay = hash % (max_delay + 1);
1868 task.options.countdown = Some(delay);
1869 }
1870 self
1871 }
1872
1873 pub fn len(&self) -> usize {
1875 self.tasks.len()
1876 }
1877
1878 pub fn has_group_id(&self) -> bool {
1880 self.group_id.is_some()
1881 }
1882
1883 pub fn merge(mut self, other: Group) -> Self {
1903 self.tasks.extend(other.tasks);
1904 self
1905 }
1906
1907 pub fn partition<F>(self, mut predicate: F) -> (Group, Group)
1926 where
1927 F: FnMut(&Signature) -> bool,
1928 {
1929 let (matching, non_matching): (Vec<_>, Vec<_>) =
1930 self.tasks.into_iter().partition(|sig| predicate(sig));
1931
1932 (
1933 Group {
1934 tasks: matching,
1935 group_id: self.group_id,
1936 },
1937 Group {
1938 tasks: non_matching,
1939 group_id: None, },
1941 )
1942 }
1943
1944 pub fn with_task_prefix(mut self, prefix: &str) -> Self {
1958 for task in &mut self.tasks {
1959 task.task = format!("{}{}", prefix, task.task);
1960 }
1961 self
1962 }
1963
1964 pub fn with_task_suffix(mut self, suffix: &str) -> Self {
1978 for task in &mut self.tasks {
1979 task.task = format!("{}{}", task.task, suffix);
1980 }
1981 self
1982 }
1983
1984 pub fn with_priority(mut self, priority: u8) -> Self {
1998 for task in &mut self.tasks {
1999 task.options.priority = Some(priority);
2000 }
2001 self
2002 }
2003
2004 pub fn with_queue(mut self, queue: String) -> Self {
2018 for task in &mut self.tasks {
2019 task.options.queue = Some(queue.clone());
2020 }
2021 self
2022 }
2023
2024 pub fn is_valid(&self) -> bool {
2041 !self.tasks.is_empty() && self.tasks.iter().all(|t| !t.task.is_empty())
2042 }
2043
2044 pub fn count_matching<F>(&self, predicate: F) -> usize
2059 where
2060 F: Fn(&Signature) -> bool,
2061 {
2062 self.tasks.iter().filter(|t| predicate(t)).count()
2063 }
2064
2065 pub fn any<F>(&self, predicate: F) -> bool
2079 where
2080 F: Fn(&Signature) -> bool,
2081 {
2082 self.tasks.iter().any(predicate)
2083 }
2084
2085 pub fn all<F>(&self, predicate: F) -> bool
2098 where
2099 F: Fn(&Signature) -> bool,
2100 {
2101 self.tasks.iter().all(predicate)
2102 }
2103
2104 pub fn map_tasks<F>(mut self, f: F) -> Self
2121 where
2122 F: FnMut(Signature) -> Signature,
2123 {
2124 self.tasks = self.tasks.into_iter().map(f).collect();
2125 self
2126 }
2127
2128 pub fn filter_map<F>(mut self, f: F) -> Self
2150 where
2151 F: FnMut(Signature) -> Option<Signature>,
2152 {
2153 self.tasks = self.tasks.into_iter().filter_map(f).collect();
2154 self
2155 }
2156
2157 pub fn take(mut self, n: usize) -> Self {
2173 self.tasks.truncate(n);
2174 self
2175 }
2176
2177 pub fn skip(mut self, n: usize) -> Self {
2194 self.tasks = self.tasks.into_iter().skip(n).collect();
2195 self
2196 }
2197
2198 pub fn find_task(&self, task_name: &str) -> Option<usize> {
2214 self.tasks.iter().position(|t| t.task == task_name)
2215 }
2216
2217 pub fn find_all_tasks(&self, task_name: &str) -> Vec<usize> {
2232 self.tasks
2233 .iter()
2234 .enumerate()
2235 .filter(|(_, t)| t.task == task_name)
2236 .map(|(i, _)| i)
2237 .collect()
2238 }
2239
2240 pub fn contains_task(&self, task_name: &str) -> bool {
2254 self.tasks.iter().any(|t| t.task == task_name)
2255 }
2256
2257 pub fn estimated_duration(&self) -> Option<u64> {
2273 self.tasks
2274 .iter()
2275 .filter_map(|t| t.options.time_limit.or(t.options.soft_time_limit))
2276 .max()
2277 }
2278
2279 pub fn task_names(&self) -> Vec<&str> {
2293 self.tasks.iter().map(|t| t.task.as_str()).collect()
2294 }
2295
2296 pub fn unique_task_names(&self) -> std::collections::HashSet<&str> {
2313 self.tasks.iter().map(|t| t.task.as_str()).collect()
2314 }
2315
2316 pub fn clone_with_transform<F>(&self, mut transform: F) -> Self
2333 where
2334 F: FnMut(&Signature) -> Signature,
2335 {
2336 Self {
2337 tasks: self.tasks.iter().map(&mut transform).collect(),
2338 group_id: self.group_id,
2339 }
2340 }
2341
2342 pub fn count_by_priority(&self) -> std::collections::HashMap<u8, usize> {
2360 let mut counts = std::collections::HashMap::new();
2361 for task in &self.tasks {
2362 if let Some(priority) = task.options.priority {
2363 *counts.entry(priority).or_insert(0) += 1;
2364 }
2365 }
2366 counts
2367 }
2368
2369 pub fn count_by_queue(&self) -> std::collections::HashMap<String, usize> {
2387 let mut counts = std::collections::HashMap::new();
2388 for task in &self.tasks {
2389 if let Some(ref queue) = task.options.queue {
2390 *counts.entry(queue.clone()).or_insert(0) += 1;
2391 }
2392 }
2393 counts
2394 }
2395}
2396
2397impl std::fmt::Display for Group {
2398 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2399 write!(f, "Group[{} tasks]", self.tasks.len())?;
2400 if let Some(group_id) = self.group_id {
2401 write!(f, " id={}", &group_id.to_string()[..8])?;
2402 }
2403 Ok(())
2404 }
2405}
2406
2407impl IntoIterator for Group {
2408 type Item = Signature;
2409 type IntoIter = std::vec::IntoIter<Signature>;
2410
2411 fn into_iter(self) -> Self::IntoIter {
2412 self.tasks.into_iter()
2413 }
2414}
2415
2416impl<'a> IntoIterator for &'a Group {
2417 type Item = &'a Signature;
2418 type IntoIter = std::slice::Iter<'a, Signature>;
2419
2420 fn into_iter(self) -> Self::IntoIter {
2421 self.tasks.iter()
2422 }
2423}
2424
2425impl From<Vec<Signature>> for Group {
2426 fn from(tasks: Vec<Signature>) -> Self {
2427 Self {
2428 tasks,
2429 group_id: Some(Uuid::new_v4()),
2430 }
2431 }
2432}
2433
2434impl FromIterator<Signature> for Group {
2435 fn from_iter<T: IntoIterator<Item = Signature>>(iter: T) -> Self {
2436 Self {
2437 tasks: iter.into_iter().collect(),
2438 group_id: Some(Uuid::new_v4()),
2439 }
2440 }
2441}
2442
2443#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2447pub struct Chord {
2448 pub header: Group,
2450
2451 pub body: Signature,
2453}
2454
2455impl Chord {
2456 pub fn new(header: Group, body: Signature) -> Self {
2457 Self { header, body }
2458 }
2459
2460 #[cfg(feature = "backend-redis")]
2462 pub async fn apply<B: Broker, R: ResultBackend>(
2463 mut self,
2464 broker: &B,
2465 backend: &mut R,
2466 ) -> Result<Uuid, CanvasError> {
2467 if self.header.tasks.is_empty() {
2468 return Err(CanvasError::Invalid(
2469 "Chord header cannot be empty".to_string(),
2470 ));
2471 }
2472
2473 let chord_id = Uuid::new_v4();
2474 let total = self.header.tasks.len();
2475
2476 let chord_state = ChordState {
2478 chord_id,
2479 total,
2480 completed: 0,
2481 callback: Some(self.body.task.clone()),
2482 task_ids: Vec::new(),
2483 created_at: Utc::now(),
2484 timeout: None,
2485 cancelled: false,
2486 cancellation_reason: None,
2487 retry_count: 0,
2488 max_retries: None,
2489 };
2490
2491 backend
2492 .chord_init(chord_state)
2493 .await
2494 .map_err(|e| CanvasError::Broker(format!("Failed to initialize chord: {}", e)))?;
2495
2496 for sig in &mut self.header.tasks {
2498 let args_json = serde_json::json!({
2499 "args": sig.args,
2500 "kwargs": sig.kwargs
2501 });
2502 let args_bytes = serde_json::to_vec(&args_json)
2503 .map_err(|e| CanvasError::Serialization(e.to_string()))?;
2504
2505 let mut task = SerializedTask::new(sig.task.clone(), args_bytes);
2506
2507 if let Some(priority) = sig.options.priority {
2508 task = task.with_priority(priority.into());
2509 }
2510
2511 task.metadata.chord_id = Some(chord_id);
2513
2514 broker
2515 .enqueue(task)
2516 .await
2517 .map_err(|e| CanvasError::Broker(e.to_string()))?;
2518 }
2519
2520 Ok(chord_id)
2521 }
2522
2523 #[cfg(not(feature = "backend-redis"))]
2525 pub async fn apply<B: Broker>(self, broker: &B) -> Result<Uuid, CanvasError> {
2526 self.header.apply(broker).await
2529 }
2530}
2531
2532impl std::fmt::Display for Chord {
2533 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2534 write!(
2535 f,
2536 "Chord[{} tasks] -> callback({})",
2537 self.header.tasks.len(),
2538 self.body.task
2539 )
2540 }
2541}
2542
2543#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2547pub struct Map {
2548 pub task: Signature,
2550
2551 pub argsets: Vec<Vec<serde_json::Value>>,
2553}
2554
2555impl Map {
2556 pub fn new(task: Signature, argsets: Vec<Vec<serde_json::Value>>) -> Self {
2557 Self { task, argsets }
2558 }
2559
2560 pub async fn apply<B: Broker>(self, broker: &B) -> Result<Uuid, CanvasError> {
2562 let mut group = Group::new();
2563
2564 for args in self.argsets {
2565 let mut sig = self.task.clone();
2566 sig.args = args;
2567 group = group.add_signature(sig);
2568 }
2569
2570 group.apply(broker).await
2571 }
2572
2573 pub fn is_empty(&self) -> bool {
2575 self.argsets.is_empty()
2576 }
2577
2578 pub fn len(&self) -> usize {
2580 self.argsets.len()
2581 }
2582}
2583
2584impl std::fmt::Display for Map {
2585 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2586 write!(
2587 f,
2588 "Map[task={}, {} argsets]",
2589 self.task.task,
2590 self.argsets.len()
2591 )
2592 }
2593}
2594
2595#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2599pub struct Starmap {
2600 pub task: Signature,
2602
2603 pub argsets: Vec<Vec<serde_json::Value>>,
2605}
2606
2607impl Starmap {
2608 pub fn new(task: Signature, argsets: Vec<Vec<serde_json::Value>>) -> Self {
2609 Self { task, argsets }
2610 }
2611
2612 pub async fn apply<B: Broker>(self, broker: &B) -> Result<Uuid, CanvasError> {
2614 let map = Map::new(self.task, self.argsets);
2616 map.apply(broker).await
2617 }
2618
2619 pub fn is_empty(&self) -> bool {
2621 self.argsets.is_empty()
2622 }
2623
2624 pub fn len(&self) -> usize {
2626 self.argsets.len()
2627 }
2628}
2629
2630impl std::fmt::Display for Starmap {
2631 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2632 write!(
2633 f,
2634 "Starmap[task={}, {} argsets]",
2635 self.task.task,
2636 self.argsets.len()
2637 )
2638 }
2639}
2640
2641#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2657pub struct Chunks {
2658 pub task: Signature,
2660
2661 pub items: Vec<serde_json::Value>,
2663
2664 pub chunk_size: usize,
2666}
2667
2668impl Chunks {
2669 pub fn new(task: Signature, items: Vec<serde_json::Value>, chunk_size: usize) -> Self {
2676 Self {
2677 task,
2678 items,
2679 chunk_size: chunk_size.max(1), }
2681 }
2682
2683 pub fn num_chunks(&self) -> usize {
2685 if self.items.is_empty() {
2686 0
2687 } else {
2688 self.items.len().div_ceil(self.chunk_size)
2689 }
2690 }
2691
2692 pub fn is_empty(&self) -> bool {
2694 self.items.is_empty()
2695 }
2696
2697 pub fn len(&self) -> usize {
2699 self.items.len()
2700 }
2701
2702 pub fn to_group(&self) -> Group {
2704 let mut group = Group::new();
2705
2706 for chunk in self.items.chunks(self.chunk_size) {
2707 let mut sig = self.task.clone();
2708 sig.args = vec![serde_json::json!(chunk)];
2709 group = group.add_signature(sig);
2710 }
2711
2712 group
2713 }
2714
2715 pub async fn apply<B: Broker>(self, broker: &B) -> Result<Uuid, CanvasError> {
2717 if self.items.is_empty() {
2718 return Err(CanvasError::Invalid("Chunks cannot be empty".to_string()));
2719 }
2720
2721 self.to_group().apply(broker).await
2722 }
2723}
2724
2725impl std::fmt::Display for Chunks {
2726 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2727 write!(
2728 f,
2729 "Chunks[task={}, {} items, chunk_size={}, {} chunks]",
2730 self.task.task,
2731 self.items.len(),
2732 self.chunk_size,
2733 self.num_chunks()
2734 )
2735 }
2736}
2737
2738#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2743pub struct XMap {
2744 pub task: Signature,
2746
2747 pub argsets: Vec<Vec<serde_json::Value>>,
2749
2750 pub fail_fast: bool,
2752}
2753
2754impl XMap {
2755 pub fn new(task: Signature, argsets: Vec<Vec<serde_json::Value>>) -> Self {
2757 Self {
2758 task,
2759 argsets,
2760 fail_fast: false,
2761 }
2762 }
2763
2764 pub fn fail_fast(mut self, fail_fast: bool) -> Self {
2766 self.fail_fast = fail_fast;
2767 self
2768 }
2769
2770 pub fn is_empty(&self) -> bool {
2772 self.argsets.is_empty()
2773 }
2774
2775 pub fn len(&self) -> usize {
2777 self.argsets.len()
2778 }
2779
2780 pub async fn apply<B: Broker>(self, broker: &B) -> Result<Uuid, CanvasError> {
2785 let map = Map::new(self.task, self.argsets);
2786 map.apply(broker).await
2787 }
2788}
2789
2790impl std::fmt::Display for XMap {
2791 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2792 write!(
2793 f,
2794 "XMap[task={}, {} argsets, fail_fast={}]",
2795 self.task.task,
2796 self.argsets.len(),
2797 self.fail_fast
2798 )
2799 }
2800}
2801
2802#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2806pub struct XStarmap {
2807 pub task: Signature,
2809
2810 pub argsets: Vec<Vec<serde_json::Value>>,
2812
2813 pub fail_fast: bool,
2815}
2816
2817impl XStarmap {
2818 pub fn new(task: Signature, argsets: Vec<Vec<serde_json::Value>>) -> Self {
2820 Self {
2821 task,
2822 argsets,
2823 fail_fast: false,
2824 }
2825 }
2826
2827 pub fn fail_fast(mut self, fail_fast: bool) -> Self {
2829 self.fail_fast = fail_fast;
2830 self
2831 }
2832
2833 pub fn is_empty(&self) -> bool {
2835 self.argsets.is_empty()
2836 }
2837
2838 pub fn len(&self) -> usize {
2840 self.argsets.len()
2841 }
2842
2843 pub async fn apply<B: Broker>(self, broker: &B) -> Result<Uuid, CanvasError> {
2845 let starmap = Starmap::new(self.task, self.argsets);
2846 starmap.apply(broker).await
2847 }
2848}
2849
2850impl std::fmt::Display for XStarmap {
2851 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2852 write!(
2853 f,
2854 "XStarmap[task={}, {} argsets, fail_fast={}]",
2855 self.task.task,
2856 self.argsets.len(),
2857 self.fail_fast
2858 )
2859 }
2860}
2861
2862#[derive(Debug, Clone, Serialize, Deserialize)]
2872pub enum Condition {
2873 Always,
2875
2876 Never,
2878
2879 Equals {
2881 field: Option<String>,
2883 value: serde_json::Value,
2885 },
2886
2887 NotEquals {
2889 field: Option<String>,
2891 value: serde_json::Value,
2893 },
2894
2895 GreaterThan {
2897 field: Option<String>,
2899 threshold: f64,
2901 },
2902
2903 LessThan {
2905 field: Option<String>,
2907 threshold: f64,
2909 },
2910
2911 Truthy {
2913 field: Option<String>,
2915 },
2916
2917 Falsy {
2919 field: Option<String>,
2921 },
2922
2923 Contains {
2925 field: Option<String>,
2927 value: serde_json::Value,
2929 },
2930
2931 Matches {
2933 field: Option<String>,
2935 pattern: String,
2937 },
2938
2939 And(Vec<Condition>),
2941
2942 Or(Vec<Condition>),
2944
2945 Not(Box<Condition>),
2947
2948 Custom {
2951 task: String,
2953 args: Vec<serde_json::Value>,
2955 },
2956}
2957
2958impl Condition {
2959 pub fn always() -> Self {
2961 Self::Always
2962 }
2963
2964 pub fn never() -> Self {
2966 Self::Never
2967 }
2968
2969 pub fn equals(value: serde_json::Value) -> Self {
2971 Self::Equals { field: None, value }
2972 }
2973
2974 pub fn field_equals(field: impl Into<String>, value: serde_json::Value) -> Self {
2976 Self::Equals {
2977 field: Some(field.into()),
2978 value,
2979 }
2980 }
2981
2982 pub fn not_equals(value: serde_json::Value) -> Self {
2984 Self::NotEquals { field: None, value }
2985 }
2986
2987 pub fn greater_than(threshold: f64) -> Self {
2989 Self::GreaterThan {
2990 field: None,
2991 threshold,
2992 }
2993 }
2994
2995 pub fn field_greater_than(field: impl Into<String>, threshold: f64) -> Self {
2997 Self::GreaterThan {
2998 field: Some(field.into()),
2999 threshold,
3000 }
3001 }
3002
3003 pub fn less_than(threshold: f64) -> Self {
3005 Self::LessThan {
3006 field: None,
3007 threshold,
3008 }
3009 }
3010
3011 pub fn truthy() -> Self {
3013 Self::Truthy { field: None }
3014 }
3015
3016 pub fn field_truthy(field: impl Into<String>) -> Self {
3018 Self::Truthy {
3019 field: Some(field.into()),
3020 }
3021 }
3022
3023 pub fn falsy() -> Self {
3025 Self::Falsy { field: None }
3026 }
3027
3028 pub fn contains(value: serde_json::Value) -> Self {
3030 Self::Contains { field: None, value }
3031 }
3032
3033 pub fn matches(pattern: impl Into<String>) -> Self {
3035 Self::Matches {
3036 field: None,
3037 pattern: pattern.into(),
3038 }
3039 }
3040
3041 pub fn custom(task: impl Into<String>, args: Vec<serde_json::Value>) -> Self {
3043 Self::Custom {
3044 task: task.into(),
3045 args,
3046 }
3047 }
3048
3049 pub fn and(self, other: Condition) -> Self {
3051 match self {
3052 Self::And(mut conditions) => {
3053 conditions.push(other);
3054 Self::And(conditions)
3055 }
3056 _ => Self::And(vec![self, other]),
3057 }
3058 }
3059
3060 pub fn or(self, other: Condition) -> Self {
3062 match self {
3063 Self::Or(mut conditions) => {
3064 conditions.push(other);
3065 Self::Or(conditions)
3066 }
3067 _ => Self::Or(vec![self, other]),
3068 }
3069 }
3070
3071 pub fn negate(self) -> Self {
3073 Self::Not(Box::new(self))
3074 }
3075
3076 pub fn evaluate(&self, value: &serde_json::Value) -> bool {
3078 match self {
3079 Self::Always => true,
3080 Self::Never => false,
3081 Self::Equals {
3082 field,
3083 value: expected,
3084 } => {
3085 let actual = extract_field(value, field.as_deref());
3086 actual == *expected
3087 }
3088 Self::NotEquals {
3089 field,
3090 value: expected,
3091 } => {
3092 let actual = extract_field(value, field.as_deref());
3093 actual != *expected
3094 }
3095 Self::GreaterThan { field, threshold } => {
3096 let actual = extract_field(value, field.as_deref());
3097 actual.as_f64().is_some_and(|v| v > *threshold)
3098 }
3099 Self::LessThan { field, threshold } => {
3100 let actual = extract_field(value, field.as_deref());
3101 actual.as_f64().is_some_and(|v| v < *threshold)
3102 }
3103 Self::Truthy { field } => {
3104 let actual = extract_field(value, field.as_deref());
3105 is_truthy(&actual)
3106 }
3107 Self::Falsy { field } => {
3108 let actual = extract_field(value, field.as_deref());
3109 !is_truthy(&actual)
3110 }
3111 Self::Contains {
3112 field,
3113 value: needle,
3114 } => {
3115 let actual = extract_field(value, field.as_deref());
3116 contains_value(&actual, needle)
3117 }
3118 Self::Matches { field, pattern } => {
3119 let actual = extract_field(value, field.as_deref());
3120 if let Some(s) = actual.as_str() {
3121 regex::Regex::new(pattern)
3122 .map(|re| re.is_match(s))
3123 .unwrap_or(false)
3124 } else {
3125 false
3126 }
3127 }
3128 Self::And(conditions) => conditions.iter().all(|c| c.evaluate(value)),
3129 Self::Or(conditions) => conditions.iter().any(|c| c.evaluate(value)),
3130 Self::Not(condition) => !condition.evaluate(value),
3131 Self::Custom { .. } => {
3132 false
3135 }
3136 }
3137 }
3138
3139 pub fn is_custom(&self) -> bool {
3141 match self {
3142 Self::Custom { .. } => true,
3143 Self::And(conditions) => conditions.iter().any(|c| c.is_custom()),
3144 Self::Or(conditions) => conditions.iter().any(|c| c.is_custom()),
3145 Self::Not(condition) => condition.is_custom(),
3146 _ => false,
3147 }
3148 }
3149}
3150
3151fn extract_field(value: &serde_json::Value, field: Option<&str>) -> serde_json::Value {
3153 match field {
3154 None => value.clone(),
3155 Some(path) => {
3156 let mut current = value;
3157 for part in path.split('.') {
3158 current = match current {
3159 serde_json::Value::Object(map) => {
3160 map.get(part).unwrap_or(&serde_json::Value::Null)
3161 }
3162 serde_json::Value::Array(arr) => {
3163 if let Ok(idx) = part.parse::<usize>() {
3164 arr.get(idx).unwrap_or(&serde_json::Value::Null)
3165 } else {
3166 &serde_json::Value::Null
3167 }
3168 }
3169 _ => &serde_json::Value::Null,
3170 };
3171 }
3172 current.clone()
3173 }
3174 }
3175}
3176
3177fn is_truthy(value: &serde_json::Value) -> bool {
3179 match value {
3180 serde_json::Value::Null => false,
3181 serde_json::Value::Bool(b) => *b,
3182 serde_json::Value::Number(n) => n.as_f64().is_some_and(|v| v != 0.0),
3183 serde_json::Value::String(s) => !s.is_empty(),
3184 serde_json::Value::Array(a) => !a.is_empty(),
3185 serde_json::Value::Object(o) => !o.is_empty(),
3186 }
3187}
3188
3189fn contains_value(haystack: &serde_json::Value, needle: &serde_json::Value) -> bool {
3191 match haystack {
3192 serde_json::Value::String(s) => {
3193 if let Some(needle_str) = needle.as_str() {
3194 s.contains(needle_str)
3195 } else {
3196 false
3197 }
3198 }
3199 serde_json::Value::Array(arr) => arr.contains(needle),
3200 serde_json::Value::Object(map) => {
3201 if let Some(key) = needle.as_str() {
3202 map.contains_key(key)
3203 } else {
3204 false
3205 }
3206 }
3207 _ => false,
3208 }
3209}
3210
3211impl std::fmt::Display for Condition {
3212 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3213 match self {
3214 Self::Always => write!(f, "always"),
3215 Self::Never => write!(f, "never"),
3216 Self::Equals { field, value } => {
3217 if let Some(field) = field {
3218 write!(f, "{} == {}", field, value)
3219 } else {
3220 write!(f, "result == {}", value)
3221 }
3222 }
3223 Self::NotEquals { field, value } => {
3224 if let Some(field) = field {
3225 write!(f, "{} != {}", field, value)
3226 } else {
3227 write!(f, "result != {}", value)
3228 }
3229 }
3230 Self::GreaterThan { field, threshold } => {
3231 if let Some(field) = field {
3232 write!(f, "{} > {}", field, threshold)
3233 } else {
3234 write!(f, "result > {}", threshold)
3235 }
3236 }
3237 Self::LessThan { field, threshold } => {
3238 if let Some(field) = field {
3239 write!(f, "{} < {}", field, threshold)
3240 } else {
3241 write!(f, "result < {}", threshold)
3242 }
3243 }
3244 Self::Truthy { field } => {
3245 if let Some(field) = field {
3246 write!(f, "truthy({})", field)
3247 } else {
3248 write!(f, "truthy(result)")
3249 }
3250 }
3251 Self::Falsy { field } => {
3252 if let Some(field) = field {
3253 write!(f, "falsy({})", field)
3254 } else {
3255 write!(f, "falsy(result)")
3256 }
3257 }
3258 Self::Contains { field, value } => {
3259 if let Some(field) = field {
3260 write!(f, "{} contains {}", field, value)
3261 } else {
3262 write!(f, "result contains {}", value)
3263 }
3264 }
3265 Self::Matches { field, pattern } => {
3266 if let Some(field) = field {
3267 write!(f, "{} matches /{}/", field, pattern)
3268 } else {
3269 write!(f, "result matches /{}/", pattern)
3270 }
3271 }
3272 Self::And(conditions) => {
3273 let parts: Vec<String> = conditions.iter().map(|c| format!("{}", c)).collect();
3274 write!(f, "({})", parts.join(" AND "))
3275 }
3276 Self::Or(conditions) => {
3277 let parts: Vec<String> = conditions.iter().map(|c| format!("{}", c)).collect();
3278 write!(f, "({})", parts.join(" OR "))
3279 }
3280 Self::Not(condition) => write!(f, "NOT ({})", condition),
3281 Self::Custom { task, .. } => write!(f, "custom({})", task),
3282 }
3283 }
3284}
3285
3286#[derive(Debug, Clone, Serialize, Deserialize)]
3303pub struct Branch {
3304 pub condition: Condition,
3306
3307 pub then_branch: Box<Signature>,
3309
3310 pub else_branch: Option<Box<Signature>>,
3312
3313 pub pass_result: bool,
3315}
3316
3317impl Branch {
3318 pub fn new(condition: Condition, then_sig: Signature) -> Self {
3324 Self {
3325 condition,
3326 then_branch: Box::new(then_sig),
3327 else_branch: None,
3328 pass_result: true,
3329 }
3330 }
3331
3332 pub fn otherwise(mut self, else_sig: Signature) -> Self {
3334 self.else_branch = Some(Box::new(else_sig));
3335 self
3336 }
3337
3338 pub fn else_do(self, else_sig: Signature) -> Self {
3340 self.otherwise(else_sig)
3341 }
3342
3343 pub fn with_pass_result(mut self, pass: bool) -> Self {
3345 self.pass_result = pass;
3346 self
3347 }
3348
3349 pub fn has_else(&self) -> bool {
3351 self.else_branch.is_some()
3352 }
3353
3354 pub fn evaluate(&self, result: &serde_json::Value) -> Option<Signature> {
3359 let should_then = self.condition.evaluate(result);
3360
3361 let sig = if should_then {
3362 Some((*self.then_branch).clone())
3363 } else {
3364 self.else_branch.as_ref().map(|s| (**s).clone())
3365 };
3366
3367 if let Some(mut sig) = sig {
3369 if self.pass_result && !sig.immutable {
3370 sig.args.insert(0, result.clone());
3371 }
3372 Some(sig)
3373 } else {
3374 None
3375 }
3376 }
3377}
3378
3379impl std::fmt::Display for Branch {
3380 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3381 if let Some(else_branch) = &self.else_branch {
3382 write!(
3383 f,
3384 "Branch[if {} then {} else {}]",
3385 self.condition, self.then_branch.task, else_branch.task
3386 )
3387 } else {
3388 write!(
3389 f,
3390 "Branch[if {} then {}]",
3391 self.condition, self.then_branch.task
3392 )
3393 }
3394 }
3395}
3396
3397#[derive(Debug, Clone, Serialize, Deserialize)]
3413pub struct Maybe {
3414 pub condition: Condition,
3416
3417 pub task: Signature,
3419
3420 pub pass_result: bool,
3422}
3423
3424impl Maybe {
3425 pub fn new(condition: Condition, task: Signature) -> Self {
3427 Self {
3428 condition,
3429 task,
3430 pass_result: true,
3431 }
3432 }
3433
3434 pub fn with_pass_result(mut self, pass: bool) -> Self {
3436 self.pass_result = pass;
3437 self
3438 }
3439
3440 pub fn evaluate(&self, result: &serde_json::Value) -> Option<Signature> {
3442 if self.condition.evaluate(result) {
3443 let mut task = self.task.clone();
3444 if self.pass_result && !task.immutable {
3445 task.args.insert(0, result.clone());
3446 }
3447 Some(task)
3448 } else {
3449 None
3450 }
3451 }
3452
3453 pub fn to_branch(self) -> Branch {
3455 Branch {
3456 condition: self.condition,
3457 then_branch: Box::new(self.task),
3458 else_branch: None,
3459 pass_result: self.pass_result,
3460 }
3461 }
3462}
3463
3464impl std::fmt::Display for Maybe {
3465 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3466 write!(f, "Maybe[if {} then {}]", self.condition, self.task.task)
3467 }
3468}
3469
3470#[derive(Debug, Clone, Serialize, Deserialize)]
3491pub struct Switch {
3492 pub cases: Vec<(Condition, Signature)>,
3494
3495 pub default: Option<Signature>,
3497
3498 pub pass_result: bool,
3500}
3501
3502impl Switch {
3503 pub fn new() -> Self {
3505 Self {
3506 cases: Vec::new(),
3507 default: None,
3508 pass_result: true,
3509 }
3510 }
3511
3512 pub fn case(mut self, condition: Condition, task: Signature) -> Self {
3514 self.cases.push((condition, task));
3515 self
3516 }
3517
3518 pub fn default(mut self, task: Signature) -> Self {
3520 self.default = Some(task);
3521 self
3522 }
3523
3524 pub fn with_pass_result(mut self, pass: bool) -> Self {
3526 self.pass_result = pass;
3527 self
3528 }
3529
3530 pub fn is_empty(&self) -> bool {
3532 self.cases.is_empty()
3533 }
3534
3535 pub fn len(&self) -> usize {
3537 self.cases.len()
3538 }
3539
3540 pub fn evaluate(&self, result: &serde_json::Value) -> Option<Signature> {
3542 for (condition, task) in &self.cases {
3544 if condition.evaluate(result) {
3545 let mut task = task.clone();
3546 if self.pass_result && !task.immutable {
3547 task.args.insert(0, result.clone());
3548 }
3549 return Some(task);
3550 }
3551 }
3552
3553 if let Some(default) = &self.default {
3555 let mut task = default.clone();
3556 if self.pass_result && !task.immutable {
3557 task.args.insert(0, result.clone());
3558 }
3559 Some(task)
3560 } else {
3561 None
3562 }
3563 }
3564}
3565
3566impl Default for Switch {
3567 fn default() -> Self {
3568 Self::new()
3569 }
3570}
3571
3572impl std::fmt::Display for Switch {
3573 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3574 let case_strs: Vec<String> = self
3575 .cases
3576 .iter()
3577 .map(|(c, t)| format!("{} => {}", c, t.task))
3578 .collect();
3579
3580 if let Some(default) = &self.default {
3581 write!(
3582 f,
3583 "Switch[{}, default => {}]",
3584 case_strs.join(", "),
3585 default.task
3586 )
3587 } else {
3588 write!(f, "Switch[{}]", case_strs.join(", "))
3589 }
3590 }
3591}
3592
3593#[derive(Debug, Clone, Serialize, Deserialize)]
3620#[serde(tag = "element_type")]
3621pub enum CanvasElement {
3622 Signature(Signature),
3624
3625 Chain(Chain),
3627
3628 Group(Group),
3630
3631 Chord {
3633 header: Group,
3635 body: Signature,
3637 },
3638
3639 Map {
3641 task: Signature,
3643 argsets: Vec<Vec<serde_json::Value>>,
3645 },
3646
3647 Branch(Branch),
3649
3650 Switch(Switch),
3652}
3653
3654impl CanvasElement {
3655 pub fn signature(sig: Signature) -> Self {
3657 Self::Signature(sig)
3658 }
3659
3660 pub fn task(name: impl Into<String>, args: Vec<serde_json::Value>) -> Self {
3662 Self::Signature(Signature::new(name.into()).with_args(args))
3663 }
3664
3665 pub fn chain(chain: Chain) -> Self {
3667 Self::Chain(chain)
3668 }
3669
3670 pub fn group(group: Group) -> Self {
3672 Self::Group(group)
3673 }
3674
3675 pub fn chord(header: Group, body: Signature) -> Self {
3677 Self::Chord { header, body }
3678 }
3679
3680 pub fn map(task: Signature, argsets: Vec<Vec<serde_json::Value>>) -> Self {
3682 Self::Map { task, argsets }
3683 }
3684
3685 pub fn branch(branch: Branch) -> Self {
3687 Self::Branch(branch)
3688 }
3689
3690 pub fn switch(switch: Switch) -> Self {
3692 Self::Switch(switch)
3693 }
3694
3695 pub fn is_signature(&self) -> bool {
3697 matches!(self, Self::Signature(_))
3698 }
3699
3700 pub fn is_chain(&self) -> bool {
3702 matches!(self, Self::Chain(_))
3703 }
3704
3705 pub fn is_group(&self) -> bool {
3707 matches!(self, Self::Group(_))
3708 }
3709
3710 pub fn is_chord(&self) -> bool {
3712 matches!(self, Self::Chord { .. })
3713 }
3714
3715 pub fn element_type(&self) -> &'static str {
3717 match self {
3718 Self::Signature(_) => "signature",
3719 Self::Chain(_) => "chain",
3720 Self::Group(_) => "group",
3721 Self::Chord { .. } => "chord",
3722 Self::Map { .. } => "map",
3723 Self::Branch(_) => "branch",
3724 Self::Switch(_) => "switch",
3725 }
3726 }
3727}
3728
3729impl std::fmt::Display for CanvasElement {
3730 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3731 match self {
3732 Self::Signature(sig) => write!(f, "Signature[{}]", sig.task),
3733 Self::Chain(chain) => write!(f, "{}", chain),
3734 Self::Group(group) => write!(f, "{}", group),
3735 Self::Chord { header, body } => {
3736 write!(f, "Chord[header={}, body={}]", header, body.task)
3737 }
3738 Self::Map { task, argsets } => {
3739 write!(f, "Map[task={}, {} argsets]", task.task, argsets.len())
3740 }
3741 Self::Branch(branch) => write!(f, "{}", branch),
3742 Self::Switch(switch) => write!(f, "{}", switch),
3743 }
3744 }
3745}
3746
3747impl From<Signature> for CanvasElement {
3748 fn from(sig: Signature) -> Self {
3749 Self::Signature(sig)
3750 }
3751}
3752
3753impl From<Chain> for CanvasElement {
3754 fn from(chain: Chain) -> Self {
3755 Self::Chain(chain)
3756 }
3757}
3758
3759impl From<Group> for CanvasElement {
3760 fn from(group: Group) -> Self {
3761 Self::Group(group)
3762 }
3763}
3764
3765impl From<Branch> for CanvasElement {
3766 fn from(branch: Branch) -> Self {
3767 Self::Branch(branch)
3768 }
3769}
3770
3771impl From<Switch> for CanvasElement {
3772 fn from(switch: Switch) -> Self {
3773 Self::Switch(switch)
3774 }
3775}
3776
3777#[derive(Debug, Clone, Serialize, Deserialize)]
3796pub struct NestedChain {
3797 pub elements: Vec<CanvasElement>,
3799}
3800
3801impl NestedChain {
3802 pub fn new() -> Self {
3804 Self {
3805 elements: Vec::new(),
3806 }
3807 }
3808
3809 pub fn then_element(mut self, element: CanvasElement) -> Self {
3811 self.elements.push(element);
3812 self
3813 }
3814
3815 pub fn then_signature(mut self, sig: Signature) -> Self {
3817 self.elements.push(CanvasElement::Signature(sig));
3818 self
3819 }
3820
3821 pub fn then(mut self, task: &str, args: Vec<serde_json::Value>) -> Self {
3823 self.elements.push(CanvasElement::task(task, args));
3824 self
3825 }
3826
3827 pub fn then_group(mut self, group: Group) -> Self {
3829 self.elements.push(CanvasElement::Group(group));
3830 self
3831 }
3832
3833 pub fn then_chord(mut self, header: Group, body: Signature) -> Self {
3835 self.elements.push(CanvasElement::Chord { header, body });
3836 self
3837 }
3838
3839 pub fn then_branch(mut self, branch: Branch) -> Self {
3841 self.elements.push(CanvasElement::Branch(branch));
3842 self
3843 }
3844
3845 pub fn then_chain(mut self, chain: Chain) -> Self {
3847 self.elements.push(CanvasElement::Chain(chain));
3848 self
3849 }
3850
3851 pub fn is_empty(&self) -> bool {
3853 self.elements.is_empty()
3854 }
3855
3856 pub fn len(&self) -> usize {
3858 self.elements.len()
3859 }
3860
3861 pub fn flatten_signatures(&self) -> Option<Vec<Signature>> {
3867 let mut result = Vec::new();
3868
3869 for element in &self.elements {
3870 match element {
3871 CanvasElement::Signature(sig) => result.push(sig.clone()),
3872 CanvasElement::Chain(chain) => {
3873 result.extend(chain.tasks.clone());
3874 }
3875 _ => return None, }
3877 }
3878
3879 Some(result)
3880 }
3881
3882 pub async fn apply<B: Broker>(&self, broker: &B) -> Result<Uuid, CanvasError> {
3889 if self.elements.is_empty() {
3890 return Err(CanvasError::Invalid(
3891 "NestedChain cannot be empty".to_string(),
3892 ));
3893 }
3894
3895 let mut last_id = None;
3897 for element in &self.elements {
3898 match element {
3899 CanvasElement::Signature(sig) => {
3900 let chain = Chain {
3902 tasks: vec![sig.clone()],
3903 };
3904 last_id = Some(chain.apply(broker).await?);
3905 }
3906 CanvasElement::Chain(chain) => {
3907 last_id = Some(chain.clone().apply(broker).await?);
3908 }
3909 CanvasElement::Group(group) => {
3910 last_id = Some(group.clone().apply(broker).await?);
3911 }
3912 CanvasElement::Chord { header, body } => {
3913 #[cfg(feature = "backend-redis")]
3914 {
3915 last_id = Some(header.clone().apply(broker).await?);
3918 let _ = body; }
3921 #[cfg(not(feature = "backend-redis"))]
3922 {
3923 last_id = Some(header.clone().apply(broker).await?);
3924 let _ = body; }
3926 }
3927 CanvasElement::Map { task, argsets } => {
3928 let map = Map::new(task.clone(), argsets.clone());
3929 last_id = Some(map.apply(broker).await?);
3930 }
3931 CanvasElement::Branch(_branch) => {
3932 return Err(CanvasError::Invalid(
3934 "Branch elements not supported in NestedChain.apply()".to_string(),
3935 ));
3936 }
3937 CanvasElement::Switch(_switch) => {
3938 return Err(CanvasError::Invalid(
3940 "Switch elements not supported in NestedChain.apply()".to_string(),
3941 ));
3942 }
3943 }
3944 }
3945
3946 last_id.ok_or_else(|| CanvasError::Invalid("No elements executed".to_string()))
3947 }
3948}
3949
3950impl Default for NestedChain {
3951 fn default() -> Self {
3952 Self::new()
3953 }
3954}
3955
3956impl std::fmt::Display for NestedChain {
3957 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3958 let element_strs: Vec<String> = self.elements.iter().map(|e| format!("{}", e)).collect();
3959 write!(f, "NestedChain[{}]", element_strs.join(" -> "))
3960 }
3961}
3962
3963#[derive(Debug, Clone, Serialize, Deserialize)]
3981pub struct NestedGroup {
3982 pub elements: Vec<CanvasElement>,
3984}
3985
3986impl NestedGroup {
3987 pub fn new() -> Self {
3989 Self {
3990 elements: Vec::new(),
3991 }
3992 }
3993
3994 pub fn add_element(mut self, element: CanvasElement) -> Self {
3996 self.elements.push(element);
3997 self
3998 }
3999
4000 pub fn add_signature(mut self, sig: Signature) -> Self {
4002 self.elements.push(CanvasElement::Signature(sig));
4003 self
4004 }
4005
4006 pub fn add(mut self, task: &str, args: Vec<serde_json::Value>) -> Self {
4008 self.elements.push(CanvasElement::task(task, args));
4009 self
4010 }
4011
4012 pub fn add_chain(mut self, chain: Chain) -> Self {
4014 self.elements.push(CanvasElement::Chain(chain));
4015 self
4016 }
4017
4018 pub fn is_empty(&self) -> bool {
4020 self.elements.is_empty()
4021 }
4022
4023 pub fn len(&self) -> usize {
4025 self.elements.len()
4026 }
4027
4028 pub fn flatten_signatures(&self) -> Option<Vec<Signature>> {
4030 let mut result = Vec::new();
4031
4032 for element in &self.elements {
4033 match element {
4034 CanvasElement::Signature(sig) => result.push(sig.clone()),
4035 _ => return None,
4036 }
4037 }
4038
4039 Some(result)
4040 }
4041
4042 pub async fn apply<B: Broker>(&self, broker: &B) -> Result<Uuid, CanvasError> {
4047 if self.elements.is_empty() {
4048 return Err(CanvasError::Invalid(
4049 "NestedGroup cannot be empty".to_string(),
4050 ));
4051 }
4052
4053 let group_id = Uuid::new_v4();
4055
4056 for element in &self.elements {
4058 match element {
4059 CanvasElement::Signature(sig) => {
4060 let chain = Chain {
4061 tasks: vec![sig.clone()],
4062 };
4063 chain.apply(broker).await?;
4064 }
4065 CanvasElement::Chain(chain) => {
4066 chain.clone().apply(broker).await?;
4067 }
4068 CanvasElement::Group(group) => {
4069 group.clone().apply(broker).await?;
4070 }
4071 CanvasElement::Chord { header, body } => {
4072 #[cfg(feature = "backend-redis")]
4073 {
4074 header.clone().apply(broker).await?;
4077 let _ = body; }
4079 #[cfg(not(feature = "backend-redis"))]
4080 {
4081 header.clone().apply(broker).await?;
4082 let _ = body; }
4084 }
4085 CanvasElement::Map { task, argsets } => {
4086 let map = Map::new(task.clone(), argsets.clone());
4087 map.apply(broker).await?;
4088 }
4089 CanvasElement::Branch(_branch) => {
4090 return Err(CanvasError::Invalid(
4092 "Branch elements not supported in NestedGroup.apply()".to_string(),
4093 ));
4094 }
4095 CanvasElement::Switch(_switch) => {
4096 return Err(CanvasError::Invalid(
4098 "Switch elements not supported in NestedGroup.apply()".to_string(),
4099 ));
4100 }
4101 }
4102 }
4103
4104 Ok(group_id)
4105 }
4106}
4107
4108impl Default for NestedGroup {
4109 fn default() -> Self {
4110 Self::new()
4111 }
4112}
4113
4114impl std::fmt::Display for NestedGroup {
4115 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4116 let element_strs: Vec<String> = self.elements.iter().map(|e| format!("{}", e)).collect();
4117 write!(f, "NestedGroup[{}]", element_strs.join(" | "))
4118 }
4119}
4120
4121#[derive(Debug, Clone, Serialize, Deserialize, Default)]
4125pub enum ErrorStrategy {
4126 #[default]
4128 StopOnError,
4129
4130 ContinueOnError,
4132
4133 RetryOnError {
4135 max_retries: u32,
4137 delay: Option<u64>,
4139 },
4140
4141 Fallback {
4143 fallback: Signature,
4145 },
4146
4147 ErrorHandler {
4149 handler: Signature,
4151 },
4152}
4153
4154impl ErrorStrategy {
4155 pub fn stop() -> Self {
4157 Self::StopOnError
4158 }
4159
4160 pub fn continue_on_error() -> Self {
4162 Self::ContinueOnError
4163 }
4164
4165 pub fn retry(max_retries: u32) -> Self {
4167 Self::RetryOnError {
4168 max_retries,
4169 delay: None,
4170 }
4171 }
4172
4173 pub fn retry_with_delay(max_retries: u32, delay: u64) -> Self {
4175 Self::RetryOnError {
4176 max_retries,
4177 delay: Some(delay),
4178 }
4179 }
4180
4181 pub fn fallback(task: Signature) -> Self {
4183 Self::Fallback { fallback: task }
4184 }
4185
4186 pub fn error_handler(handler: Signature) -> Self {
4188 Self::ErrorHandler { handler }
4189 }
4190
4191 pub fn allows_continue(&self) -> bool {
4193 !matches!(self, Self::StopOnError)
4194 }
4195}
4196
4197impl std::fmt::Display for ErrorStrategy {
4198 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4199 match self {
4200 Self::StopOnError => write!(f, "StopOnError"),
4201 Self::ContinueOnError => write!(f, "ContinueOnError"),
4202 Self::RetryOnError { max_retries, delay } => {
4203 if let Some(d) = delay {
4204 write!(f, "RetryOnError({} times, {}s delay)", max_retries, d)
4205 } else {
4206 write!(f, "RetryOnError({} times)", max_retries)
4207 }
4208 }
4209 Self::Fallback { fallback } => write!(f, "Fallback({})", fallback.task),
4210 Self::ErrorHandler { handler } => write!(f, "ErrorHandler({})", handler.task),
4211 }
4212 }
4213}
4214
4215#[derive(Debug, Clone, Serialize, Deserialize)]
4221pub struct CancellationToken {
4222 pub workflow_id: Uuid,
4224 pub reason: Option<String>,
4226 pub cancel_tree: bool,
4228 pub branch_id: Option<Uuid>,
4230}
4231
4232impl CancellationToken {
4233 pub fn new(workflow_id: Uuid) -> Self {
4235 Self {
4236 workflow_id,
4237 reason: None,
4238 cancel_tree: false,
4239 branch_id: None,
4240 }
4241 }
4242
4243 pub fn with_reason(mut self, reason: String) -> Self {
4245 self.reason = Some(reason);
4246 self
4247 }
4248
4249 pub fn cancel_tree(mut self) -> Self {
4251 self.cancel_tree = true;
4252 self
4253 }
4254
4255 pub fn cancel_branch(mut self, branch_id: Uuid) -> Self {
4257 self.branch_id = Some(branch_id);
4258 self
4259 }
4260}
4261
4262impl std::fmt::Display for CancellationToken {
4263 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4264 write!(f, "CancellationToken[workflow={}]", self.workflow_id)?;
4265 if let Some(ref reason) = self.reason {
4266 write!(f, " reason={}", reason)?;
4267 }
4268 if self.cancel_tree {
4269 write!(f, " (tree)")?;
4270 }
4271 if let Some(branch) = self.branch_id {
4272 write!(f, " branch={}", branch)?;
4273 }
4274 Ok(())
4275 }
4276}
4277
4278#[derive(Debug, Clone, Serialize, Deserialize)]
4284pub struct WorkflowRetryPolicy {
4285 pub max_retries: u32,
4287 pub retry_failed_only: bool,
4289 pub backoff_factor: Option<f64>,
4291 pub max_backoff: Option<u64>,
4293 pub initial_delay: Option<u64>,
4295}
4296
4297impl WorkflowRetryPolicy {
4298 pub fn new(max_retries: u32) -> Self {
4300 Self {
4301 max_retries,
4302 retry_failed_only: false,
4303 backoff_factor: None,
4304 max_backoff: None,
4305 initial_delay: None,
4306 }
4307 }
4308
4309 pub fn failed_only(mut self) -> Self {
4311 self.retry_failed_only = true;
4312 self
4313 }
4314
4315 pub fn with_backoff(mut self, factor: f64, max_delay: u64) -> Self {
4317 self.backoff_factor = Some(factor);
4318 self.max_backoff = Some(max_delay);
4319 self
4320 }
4321
4322 pub fn with_initial_delay(mut self, delay: u64) -> Self {
4324 self.initial_delay = Some(delay);
4325 self
4326 }
4327
4328 pub fn calculate_delay(&self, attempt: u32) -> u64 {
4330 let base_delay = self.initial_delay.unwrap_or(1);
4331
4332 if let Some(factor) = self.backoff_factor {
4333 let delay = (base_delay as f64) * factor.powi(attempt as i32);
4334 let max = self.max_backoff.unwrap_or(300);
4335 delay.min(max as f64) as u64
4336 } else {
4337 base_delay
4338 }
4339 }
4340}
4341
4342impl std::fmt::Display for WorkflowRetryPolicy {
4343 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4344 write!(f, "WorkflowRetryPolicy[max_retries={}]", self.max_retries)?;
4345 if self.retry_failed_only {
4346 write!(f, " (failed_only)")?;
4347 }
4348 if let Some(factor) = self.backoff_factor {
4349 write!(f, " backoff={}", factor)?;
4350 }
4351 Ok(())
4352 }
4353}
4354
4355#[derive(Debug, Clone, Serialize, Deserialize)]
4361pub struct WorkflowTimeout {
4362 pub total_timeout: Option<u64>,
4364 pub stage_timeout: Option<u64>,
4366 pub escalation: TimeoutEscalation,
4368}
4369
4370#[derive(Debug, Clone, Serialize, Deserialize)]
4372pub enum TimeoutEscalation {
4373 Cancel,
4375 Fail,
4377 ContinuePartial,
4379}
4380
4381impl WorkflowTimeout {
4382 pub fn new(total_timeout: u64) -> Self {
4384 Self {
4385 total_timeout: Some(total_timeout),
4386 stage_timeout: None,
4387 escalation: TimeoutEscalation::Cancel,
4388 }
4389 }
4390
4391 pub fn with_stage_timeout(mut self, timeout: u64) -> Self {
4393 self.stage_timeout = Some(timeout);
4394 self
4395 }
4396
4397 pub fn with_escalation(mut self, escalation: TimeoutEscalation) -> Self {
4399 self.escalation = escalation;
4400 self
4401 }
4402}
4403
4404impl std::fmt::Display for WorkflowTimeout {
4405 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4406 write!(f, "WorkflowTimeout[")?;
4407 if let Some(total) = self.total_timeout {
4408 write!(f, "total={}s", total)?;
4409 }
4410 if let Some(stage) = self.stage_timeout {
4411 write!(f, " stage={}s", stage)?;
4412 }
4413 write!(f, " escalation={:?}]", self.escalation)
4414 }
4415}
4416
4417#[derive(Debug, Clone, Serialize, Deserialize)]
4423pub struct ForEach {
4424 pub task: Signature,
4426 pub items: Vec<serde_json::Value>,
4428 pub concurrency: Option<usize>,
4430}
4431
4432impl ForEach {
4433 pub fn new(task: Signature, items: Vec<serde_json::Value>) -> Self {
4435 Self {
4436 task,
4437 items,
4438 concurrency: None,
4439 }
4440 }
4441
4442 pub fn with_concurrency(mut self, concurrency: usize) -> Self {
4444 self.concurrency = Some(concurrency);
4445 self
4446 }
4447
4448 pub fn is_empty(&self) -> bool {
4450 self.items.is_empty()
4451 }
4452
4453 pub fn len(&self) -> usize {
4455 self.items.len()
4456 }
4457}
4458
4459impl std::fmt::Display for ForEach {
4460 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4461 write!(f, "ForEach[task={}, {} items]", self.task.task, self.len())?;
4462 if let Some(conc) = self.concurrency {
4463 write!(f, " concurrency={}", conc)?;
4464 }
4465 Ok(())
4466 }
4467}
4468
4469#[derive(Debug, Clone, Serialize, Deserialize)]
4471pub struct WhileLoop {
4472 pub condition: Condition,
4474 pub body: Signature,
4476 pub max_iterations: Option<u32>,
4478}
4479
4480impl WhileLoop {
4481 pub fn new(condition: Condition, body: Signature) -> Self {
4483 Self {
4484 condition,
4485 body,
4486 max_iterations: Some(1000), }
4488 }
4489
4490 pub fn with_max_iterations(mut self, max: u32) -> Self {
4492 self.max_iterations = Some(max);
4493 self
4494 }
4495
4496 pub fn unlimited(mut self) -> Self {
4498 self.max_iterations = None;
4499 self
4500 }
4501}
4502
4503impl std::fmt::Display for WhileLoop {
4504 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4505 write!(f, "While[{} -> {}]", self.condition, self.body.task)?;
4506 if let Some(max) = self.max_iterations {
4507 write!(f, " max={}", max)?;
4508 }
4509 Ok(())
4510 }
4511}
4512
4513#[derive(Debug, Clone, Serialize, Deserialize)]
4519pub struct WorkflowState {
4520 pub workflow_id: Uuid,
4522 pub status: WorkflowStatus,
4524 pub total_tasks: usize,
4526 pub completed_tasks: usize,
4528 pub failed_tasks: usize,
4530 pub start_time: Option<u64>,
4532 pub end_time: Option<u64>,
4534 pub current_stage: Option<String>,
4536 pub intermediate_results: HashMap<String, serde_json::Value>,
4538}
4539
4540#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4542pub enum WorkflowStatus {
4543 Pending,
4545 Running,
4547 Success,
4549 Failed,
4551 Cancelled,
4553 Paused,
4555}
4556
4557impl WorkflowState {
4558 pub fn new(workflow_id: Uuid, total_tasks: usize) -> Self {
4560 Self {
4561 workflow_id,
4562 status: WorkflowStatus::Pending,
4563 total_tasks,
4564 completed_tasks: 0,
4565 failed_tasks: 0,
4566 start_time: None,
4567 end_time: None,
4568 current_stage: None,
4569 intermediate_results: HashMap::new(),
4570 }
4571 }
4572
4573 pub fn progress(&self) -> f64 {
4575 if self.total_tasks == 0 {
4576 return 100.0;
4577 }
4578 (self.completed_tasks as f64 / self.total_tasks as f64) * 100.0
4579 }
4580
4581 pub fn is_complete(&self) -> bool {
4583 matches!(
4584 self.status,
4585 WorkflowStatus::Success | WorkflowStatus::Failed | WorkflowStatus::Cancelled
4586 )
4587 }
4588
4589 pub fn mark_completed(&mut self) {
4591 self.completed_tasks += 1;
4592 }
4593
4594 pub fn mark_failed(&mut self) {
4596 self.failed_tasks += 1;
4597 }
4598
4599 pub fn set_result(&mut self, key: String, value: serde_json::Value) {
4601 self.intermediate_results.insert(key, value);
4602 }
4603
4604 pub fn get_result(&self, key: &str) -> Option<&serde_json::Value> {
4606 self.intermediate_results.get(key)
4607 }
4608}
4609
4610impl std::fmt::Display for WorkflowState {
4611 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4612 write!(
4613 f,
4614 "WorkflowState[id={}, status={:?}, progress={:.1}%]",
4615 self.workflow_id,
4616 self.status,
4617 self.progress()
4618 )?;
4619 if self.failed_tasks > 0 {
4620 write!(f, " failed={}", self.failed_tasks)?;
4621 }
4622 Ok(())
4623 }
4624}
4625
4626#[derive(Debug, Clone, Copy)]
4632pub enum DagFormat {
4633 Dot,
4635 Mermaid,
4637 Json,
4639 Svg,
4641 Png,
4643}
4644
4645fn render_dot_to_svg(dot: &str) -> Result<String, CanvasError> {
4653 use std::io::Write;
4654 use std::process::{Command, Stdio};
4655
4656 let mut child = Command::new("dot")
4657 .arg("-Tsvg")
4658 .stdin(Stdio::piped())
4659 .stdout(Stdio::piped())
4660 .stderr(Stdio::piped())
4661 .spawn()
4662 .map_err(|e| {
4663 CanvasError::Invalid(format!(
4664 "Failed to execute 'dot' command. Is GraphViz installed? Error: {}",
4665 e
4666 ))
4667 })?;
4668
4669 if let Some(mut stdin) = child.stdin.take() {
4670 stdin
4671 .write_all(dot.as_bytes())
4672 .map_err(|e| CanvasError::Invalid(format!("Failed to write DOT to stdin: {}", e)))?;
4673 }
4674
4675 let output = child
4676 .wait_with_output()
4677 .map_err(|e| CanvasError::Invalid(format!("Failed to wait for dot process: {}", e)))?;
4678
4679 if !output.status.success() {
4680 let stderr = String::from_utf8_lossy(&output.stderr);
4681 return Err(CanvasError::Invalid(format!(
4682 "dot command failed: {}",
4683 stderr
4684 )));
4685 }
4686
4687 String::from_utf8(output.stdout)
4688 .map_err(|e| CanvasError::Invalid(format!("Invalid UTF-8 in SVG output: {}", e)))
4689}
4690
4691fn render_dot_to_png(dot: &str) -> Result<Vec<u8>, CanvasError> {
4699 use std::io::Write;
4700 use std::process::{Command, Stdio};
4701
4702 let mut child = Command::new("dot")
4703 .arg("-Tpng")
4704 .stdin(Stdio::piped())
4705 .stdout(Stdio::piped())
4706 .stderr(Stdio::piped())
4707 .spawn()
4708 .map_err(|e| {
4709 CanvasError::Invalid(format!(
4710 "Failed to execute 'dot' command. Is GraphViz installed? Error: {}",
4711 e
4712 ))
4713 })?;
4714
4715 if let Some(mut stdin) = child.stdin.take() {
4716 stdin
4717 .write_all(dot.as_bytes())
4718 .map_err(|e| CanvasError::Invalid(format!("Failed to write DOT to stdin: {}", e)))?;
4719 }
4720
4721 let output = child
4722 .wait_with_output()
4723 .map_err(|e| CanvasError::Invalid(format!("Failed to wait for dot process: {}", e)))?;
4724
4725 if !output.status.success() {
4726 let stderr = String::from_utf8_lossy(&output.stderr);
4727 return Err(CanvasError::Invalid(format!(
4728 "dot command failed: {}",
4729 stderr
4730 )));
4731 }
4732
4733 Ok(output.stdout)
4734}
4735
4736#[allow(dead_code)]
4747pub fn is_graphviz_available() -> bool {
4748 use std::process::Command;
4749
4750 Command::new("dot")
4751 .arg("-V")
4752 .output()
4753 .map(|output| output.status.success())
4754 .unwrap_or(false)
4755}
4756
4757pub trait DagExport {
4759 fn to_dot(&self) -> String;
4761
4762 fn to_mermaid(&self) -> String;
4764
4765 fn to_json(&self) -> Result<String, serde_json::Error>;
4767
4768 fn to_svg(&self) -> Result<String, CanvasError> {
4776 let dot = self.to_dot();
4777 render_dot_to_svg(&dot)
4778 }
4779
4780 fn to_png(&self) -> Result<Vec<u8>, CanvasError> {
4788 let dot = self.to_dot();
4789 render_dot_to_png(&dot)
4790 }
4791
4792 fn svg_render_command(&self) -> String {
4797 "dot -Tsvg -o output.svg input.dot".to_string()
4798 }
4799
4800 fn png_render_command(&self) -> String {
4805 "dot -Tpng -o output.png input.dot".to_string()
4806 }
4807}
4808
4809impl DagExport for Chain {
4810 fn to_dot(&self) -> String {
4811 let mut dot = String::from("digraph Chain {\n");
4812 dot.push_str(" rankdir=LR;\n");
4813 dot.push_str(" node [shape=box];\n\n");
4814
4815 for (i, task) in self.tasks.iter().enumerate() {
4816 dot.push_str(&format!(" n{} [label=\"{}\"];\n", i, task.task));
4817 if i > 0 {
4818 dot.push_str(&format!(" n{} -> n{};\n", i - 1, i));
4819 }
4820 }
4821
4822 dot.push_str("}\n");
4823 dot
4824 }
4825
4826 fn to_mermaid(&self) -> String {
4827 let mut mmd = String::from("graph LR\n");
4828
4829 for (i, task) in self.tasks.iter().enumerate() {
4830 let node_id = format!("n{}", i);
4831 mmd.push_str(&format!(" {}[\"{}\"]\n", node_id, task.task));
4832 if i > 0 {
4833 mmd.push_str(&format!(" n{} --> n{}\n", i - 1, i));
4834 }
4835 }
4836
4837 mmd
4838 }
4839
4840 fn to_json(&self) -> Result<String, serde_json::Error> {
4841 serde_json::to_string_pretty(self)
4842 }
4843}
4844
4845impl DagExport for Group {
4846 fn to_dot(&self) -> String {
4847 let mut dot = String::from("digraph Group {\n");
4848 dot.push_str(" rankdir=TB;\n");
4849 dot.push_str(" node [shape=box];\n\n");
4850 dot.push_str(" start [shape=circle, label=\"start\"];\n");
4851
4852 for (i, task) in self.tasks.iter().enumerate() {
4853 dot.push_str(&format!(" n{} [label=\"{}\"];\n", i, task.task));
4854 dot.push_str(&format!(" start -> n{};\n", i));
4855 }
4856
4857 dot.push_str("}\n");
4858 dot
4859 }
4860
4861 fn to_mermaid(&self) -> String {
4862 let mut mmd = String::from("graph TB\n");
4863 mmd.push_str(" start((start))\n");
4864
4865 for (i, task) in self.tasks.iter().enumerate() {
4866 mmd.push_str(&format!(" n{}[\"{}\"]\n", i, task.task));
4867 mmd.push_str(&format!(" start --> n{}\n", i));
4868 }
4869
4870 mmd
4871 }
4872
4873 fn to_json(&self) -> Result<String, serde_json::Error> {
4874 serde_json::to_string_pretty(self)
4875 }
4876}
4877
4878impl DagExport for Chord {
4879 fn to_dot(&self) -> String {
4880 let mut dot = String::from("digraph Chord {\n");
4881 dot.push_str(" rankdir=TB;\n");
4882 dot.push_str(" node [shape=box];\n\n");
4883 dot.push_str(" start [shape=circle, label=\"start\"];\n");
4884 dot.push_str(&format!(
4885 " callback [label=\"{}\", style=filled, fillcolor=lightblue];\n",
4886 self.body.task
4887 ));
4888
4889 for (i, task) in self.header.tasks.iter().enumerate() {
4890 dot.push_str(&format!(" n{} [label=\"{}\"];\n", i, task.task));
4891 dot.push_str(&format!(" start -> n{};\n", i));
4892 dot.push_str(&format!(" n{} -> callback;\n", i));
4893 }
4894
4895 dot.push_str("}\n");
4896 dot
4897 }
4898
4899 fn to_mermaid(&self) -> String {
4900 let mut mmd = String::from("graph TB\n");
4901 mmd.push_str(" start((start))\n");
4902 mmd.push_str(&format!(" callback[\"{}\"]\n", self.body.task));
4903 mmd.push_str(" style callback fill:#add8e6\n");
4904
4905 for (i, task) in self.header.tasks.iter().enumerate() {
4906 mmd.push_str(&format!(" n{}[\"{}\"]\n", i, task.task));
4907 mmd.push_str(&format!(" start --> n{}\n", i));
4908 mmd.push_str(&format!(" n{} --> callback\n", i));
4909 }
4910
4911 mmd
4912 }
4913
4914 fn to_json(&self) -> Result<String, serde_json::Error> {
4915 serde_json::to_string_pretty(self)
4916 }
4917}
4918
4919#[derive(Debug, Clone, Serialize, Deserialize)]
4925pub struct NamedOutput {
4926 pub name: String,
4928 pub value: serde_json::Value,
4930 pub source: Option<String>,
4932}
4933
4934impl NamedOutput {
4935 pub fn new(name: impl Into<String>, value: serde_json::Value) -> Self {
4937 Self {
4938 name: name.into(),
4939 value,
4940 source: None,
4941 }
4942 }
4943
4944 pub fn with_source(mut self, source: impl Into<String>) -> Self {
4946 self.source = Some(source.into());
4947 self
4948 }
4949}
4950
4951#[derive(Debug, Clone, Serialize, Deserialize)]
4953pub enum ResultTransform {
4954 Extract { field: String },
4956 Map { task: Box<Signature> },
4958 Filter { condition: Condition },
4960 Aggregate { strategy: AggregationStrategy },
4962}
4963
4964#[derive(Debug, Clone, Serialize, Deserialize)]
4966pub enum AggregationStrategy {
4967 Sum,
4969 Average,
4971 Concat,
4973 Merge,
4975 Coalesce,
4977 Custom { task: Box<Signature> },
4979}
4980
4981impl std::fmt::Display for ResultTransform {
4982 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4983 match self {
4984 Self::Extract { field } => write!(f, "Extract[{}]", field),
4985 Self::Map { task } => write!(f, "Map[{}]", task.task),
4986 Self::Filter { condition } => write!(f, "Filter[{}]", condition),
4987 Self::Aggregate { strategy } => write!(f, "Aggregate[{:?}]", strategy),
4988 }
4989 }
4990}
4991
4992#[derive(Debug, Clone, Serialize, Deserialize)]
4998pub struct ResultCache {
4999 pub key: String,
5001 pub policy: CachePolicy,
5003 pub ttl: Option<u64>,
5005}
5006
5007#[derive(Debug, Clone, Serialize, Deserialize)]
5009pub enum CachePolicy {
5010 Always,
5012 OnSuccess,
5014 Conditional { condition: Condition },
5016 Never,
5018}
5019
5020impl ResultCache {
5021 pub fn new(key: impl Into<String>) -> Self {
5023 Self {
5024 key: key.into(),
5025 policy: CachePolicy::OnSuccess,
5026 ttl: None,
5027 }
5028 }
5029
5030 pub fn with_policy(mut self, policy: CachePolicy) -> Self {
5032 self.policy = policy;
5033 self
5034 }
5035
5036 pub fn with_ttl(mut self, ttl: u64) -> Self {
5038 self.ttl = Some(ttl);
5039 self
5040 }
5041}
5042
5043impl std::fmt::Display for ResultCache {
5044 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5045 write!(f, "Cache[key={}]", self.key)?;
5046 if let Some(ttl) = self.ttl {
5047 write!(f, " ttl={}s", ttl)?;
5048 }
5049 Ok(())
5050 }
5051}
5052
5053#[derive(Debug, Clone, Serialize, Deserialize)]
5059pub struct WorkflowErrorHandler {
5060 pub handler: Signature,
5062 pub error_types: Vec<String>,
5064 pub suppress: bool,
5066}
5067
5068impl WorkflowErrorHandler {
5069 pub fn new(handler: Signature) -> Self {
5071 Self {
5072 handler,
5073 error_types: Vec::new(),
5074 suppress: false,
5075 }
5076 }
5077
5078 pub fn for_errors(mut self, error_types: Vec<String>) -> Self {
5080 self.error_types = error_types;
5081 self
5082 }
5083
5084 pub fn suppress_error(mut self) -> Self {
5086 self.suppress = true;
5087 self
5088 }
5089}
5090
5091impl std::fmt::Display for WorkflowErrorHandler {
5092 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5093 write!(f, "ErrorHandler[{}]", self.handler.task)?;
5094 if self.suppress {
5095 write!(f, " (suppress)")?;
5096 }
5097 Ok(())
5098 }
5099}
5100
5101#[derive(Debug, Clone, Serialize, Deserialize)]
5107pub struct CompensationWorkflow {
5108 pub forward: Vec<Signature>,
5110 pub compensations: Vec<Signature>,
5112}
5113
5114impl CompensationWorkflow {
5115 pub fn new() -> Self {
5117 Self {
5118 forward: Vec::new(),
5119 compensations: Vec::new(),
5120 }
5121 }
5122
5123 pub fn step(mut self, forward: Signature, compensation: Signature) -> Self {
5125 self.forward.push(forward);
5126 self.compensations.push(compensation);
5127 self
5128 }
5129
5130 pub fn is_empty(&self) -> bool {
5132 self.forward.is_empty()
5133 }
5134
5135 pub fn len(&self) -> usize {
5137 self.forward.len()
5138 }
5139}
5140
5141impl Default for CompensationWorkflow {
5142 fn default() -> Self {
5143 Self::new()
5144 }
5145}
5146
5147impl std::fmt::Display for CompensationWorkflow {
5148 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5149 write!(
5150 f,
5151 "Compensation[{} steps, {} compensations]",
5152 self.forward.len(),
5153 self.compensations.len()
5154 )
5155 }
5156}
5157
5158#[derive(Debug, Clone, Serialize, Deserialize)]
5160pub struct Saga {
5161 pub workflow: CompensationWorkflow,
5163 pub isolation: SagaIsolation,
5165}
5166
5167#[derive(Debug, Clone, Serialize, Deserialize)]
5169pub enum SagaIsolation {
5170 ReadUncommitted,
5172 ReadCommitted,
5174 Serializable,
5176}
5177
5178impl Saga {
5179 pub fn new(workflow: CompensationWorkflow) -> Self {
5181 Self {
5182 workflow,
5183 isolation: SagaIsolation::ReadCommitted,
5184 }
5185 }
5186
5187 pub fn with_isolation(mut self, isolation: SagaIsolation) -> Self {
5189 self.isolation = isolation;
5190 self
5191 }
5192}
5193
5194impl std::fmt::Display for Saga {
5195 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5196 write!(
5197 f,
5198 "Saga[{} steps, isolation={:?}]",
5199 self.workflow.len(),
5200 self.isolation
5201 )
5202 }
5203}
5204
5205#[derive(Debug, Clone, Serialize, Deserialize)]
5211pub struct ScatterGather {
5212 pub scatter: Signature,
5214 pub workers: Vec<Signature>,
5216 pub gather: Signature,
5218 pub timeout: Option<u64>,
5220}
5221
5222impl ScatterGather {
5223 pub fn new(scatter: Signature, workers: Vec<Signature>, gather: Signature) -> Self {
5225 Self {
5226 scatter,
5227 workers,
5228 gather,
5229 timeout: None,
5230 }
5231 }
5232
5233 pub fn with_timeout(mut self, timeout: u64) -> Self {
5235 self.timeout = Some(timeout);
5236 self
5237 }
5238}
5239
5240impl std::fmt::Display for ScatterGather {
5241 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5242 write!(
5243 f,
5244 "ScatterGather[scatter={}, {} workers, gather={}]",
5245 self.scatter.task,
5246 self.workers.len(),
5247 self.gather.task
5248 )
5249 }
5250}
5251
5252#[derive(Debug, Clone, Serialize, Deserialize)]
5254pub struct Pipeline {
5255 pub stages: Vec<Signature>,
5257 pub buffer_size: Option<usize>,
5259}
5260
5261impl Pipeline {
5262 pub fn new() -> Self {
5264 Self {
5265 stages: Vec::new(),
5266 buffer_size: None,
5267 }
5268 }
5269
5270 pub fn stage(mut self, stage: Signature) -> Self {
5272 self.stages.push(stage);
5273 self
5274 }
5275
5276 pub fn with_buffer_size(mut self, size: usize) -> Self {
5278 self.buffer_size = Some(size);
5279 self
5280 }
5281
5282 pub fn is_empty(&self) -> bool {
5284 self.stages.is_empty()
5285 }
5286
5287 pub fn len(&self) -> usize {
5289 self.stages.len()
5290 }
5291}
5292
5293impl Default for Pipeline {
5294 fn default() -> Self {
5295 Self::new()
5296 }
5297}
5298
5299impl std::fmt::Display for Pipeline {
5300 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5301 write!(f, "Pipeline[{} stages]", self.stages.len())?;
5302 if let Some(buf) = self.buffer_size {
5303 write!(f, " buffer={}", buf)?;
5304 }
5305 Ok(())
5306 }
5307}
5308
5309#[derive(Debug, Clone, Serialize, Deserialize)]
5311pub struct FanOut {
5312 pub source: Signature,
5314 pub consumers: Vec<Signature>,
5316}
5317
5318impl FanOut {
5319 pub fn new(source: Signature) -> Self {
5321 Self {
5322 source,
5323 consumers: Vec::new(),
5324 }
5325 }
5326
5327 pub fn consumer(mut self, consumer: Signature) -> Self {
5329 self.consumers.push(consumer);
5330 self
5331 }
5332
5333 pub fn len(&self) -> usize {
5335 self.consumers.len()
5336 }
5337
5338 pub fn is_empty(&self) -> bool {
5340 self.consumers.is_empty()
5341 }
5342}
5343
5344impl std::fmt::Display for FanOut {
5345 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5346 write!(
5347 f,
5348 "FanOut[source={}, {} consumers]",
5349 self.source.task,
5350 self.consumers.len()
5351 )
5352 }
5353}
5354
5355#[derive(Debug, Clone, Serialize, Deserialize)]
5357pub struct FanIn {
5358 pub sources: Vec<Signature>,
5360 pub aggregator: Signature,
5362}
5363
5364impl FanIn {
5365 pub fn new(aggregator: Signature) -> Self {
5367 Self {
5368 sources: Vec::new(),
5369 aggregator,
5370 }
5371 }
5372
5373 pub fn source(mut self, source: Signature) -> Self {
5375 self.sources.push(source);
5376 self
5377 }
5378
5379 pub fn len(&self) -> usize {
5381 self.sources.len()
5382 }
5383
5384 pub fn is_empty(&self) -> bool {
5386 self.sources.is_empty()
5387 }
5388}
5389
5390impl std::fmt::Display for FanIn {
5391 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5392 write!(
5393 f,
5394 "FanIn[{} sources, aggregator={}]",
5395 self.sources.len(),
5396 self.aggregator.task
5397 )
5398 }
5399}
5400
5401#[derive(Debug, Clone)]
5407pub struct ValidationResult {
5408 pub valid: bool,
5410 pub errors: Vec<String>,
5412 pub warnings: Vec<String>,
5414}
5415
5416impl ValidationResult {
5417 pub fn valid() -> Self {
5419 Self {
5420 valid: true,
5421 errors: Vec::new(),
5422 warnings: Vec::new(),
5423 }
5424 }
5425
5426 pub fn invalid(error: impl Into<String>) -> Self {
5428 Self {
5429 valid: false,
5430 errors: vec![error.into()],
5431 warnings: Vec::new(),
5432 }
5433 }
5434
5435 pub fn add_error(&mut self, error: impl Into<String>) {
5437 self.errors.push(error.into());
5438 self.valid = false;
5439 }
5440
5441 pub fn add_warning(&mut self, warning: impl Into<String>) {
5443 self.warnings.push(warning.into());
5444 }
5445}
5446
5447impl std::fmt::Display for ValidationResult {
5448 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5449 if self.valid {
5450 write!(f, "Valid")?;
5451 if !self.warnings.is_empty() {
5452 write!(f, " ({} warnings)", self.warnings.len())?;
5453 }
5454 } else {
5455 write!(f, "Invalid ({} errors)", self.errors.len())?;
5456 }
5457 Ok(())
5458 }
5459}
5460
5461pub trait WorkflowValidator {
5463 fn validate(&self) -> ValidationResult;
5465}
5466
5467impl WorkflowValidator for Chain {
5468 fn validate(&self) -> ValidationResult {
5469 let mut result = ValidationResult::valid();
5470
5471 if self.is_empty() {
5472 result.add_error("Chain cannot be empty");
5473 }
5474
5475 if self.len() > 100 {
5476 result.add_warning(format!(
5477 "Chain has {} tasks, which may be inefficient",
5478 self.len()
5479 ));
5480 }
5481
5482 result
5483 }
5484}
5485
5486impl WorkflowValidator for Group {
5487 fn validate(&self) -> ValidationResult {
5488 let mut result = ValidationResult::valid();
5489
5490 if self.is_empty() {
5491 result.add_error("Group cannot be empty");
5492 }
5493
5494 if self.len() > 1000 {
5495 result.add_warning(format!(
5496 "Group has {} tasks, which may overwhelm workers",
5497 self.len()
5498 ));
5499 }
5500
5501 result
5502 }
5503}
5504
5505impl WorkflowValidator for Chord {
5506 fn validate(&self) -> ValidationResult {
5507 let mut result = ValidationResult::valid();
5508
5509 if self.header.is_empty() {
5510 result.add_error("Chord header cannot be empty");
5511 }
5512
5513 result
5514 }
5515}
5516
5517#[derive(Debug, Clone, Serialize, Deserialize)]
5523pub enum LoopControl {
5524 Continue,
5526 Break,
5528 BreakWith { value: serde_json::Value },
5530}
5531
5532impl LoopControl {
5533 pub fn continue_loop() -> Self {
5535 Self::Continue
5536 }
5537
5538 pub fn break_loop() -> Self {
5540 Self::Break
5541 }
5542
5543 pub fn break_with(value: serde_json::Value) -> Self {
5545 Self::BreakWith { value }
5546 }
5547}
5548
5549impl std::fmt::Display for LoopControl {
5550 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5551 match self {
5552 Self::Continue => write!(f, "Continue"),
5553 Self::Break => write!(f, "Break"),
5554 Self::BreakWith { .. } => write!(f, "BreakWith"),
5555 }
5556 }
5557}
5558
5559#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
5567pub enum ErrorPropagationMode {
5568 #[default]
5570 StopOnFirstError,
5571
5572 ContinueOnError,
5574
5575 PartialFailure {
5577 max_failures: usize,
5579 max_failure_rate: Option<f64>,
5581 },
5582}
5583
5584impl ErrorPropagationMode {
5585 pub fn partial_failure(max_failures: usize) -> Self {
5587 Self::PartialFailure {
5588 max_failures,
5589 max_failure_rate: None,
5590 }
5591 }
5592
5593 pub fn partial_failure_with_rate(max_failures: usize, max_rate: f64) -> Self {
5595 Self::PartialFailure {
5596 max_failures,
5597 max_failure_rate: Some(max_rate),
5598 }
5599 }
5600
5601 pub fn allows_continue(&self) -> bool {
5603 !matches!(self, Self::StopOnFirstError)
5604 }
5605}
5606
5607impl std::fmt::Display for ErrorPropagationMode {
5608 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5609 match self {
5610 Self::StopOnFirstError => write!(f, "StopOnFirstError"),
5611 Self::ContinueOnError => write!(f, "ContinueOnError"),
5612 Self::PartialFailure {
5613 max_failures,
5614 max_failure_rate,
5615 } => {
5616 write!(f, "PartialFailure(max={})", max_failures)?;
5617 if let Some(rate) = max_failure_rate {
5618 write!(f, " rate={:.1}%", rate * 100.0)?;
5619 }
5620 Ok(())
5621 }
5622 }
5623 }
5624}
5625
5626#[derive(Debug, Clone, Serialize, Deserialize)]
5628pub struct PartialFailureTracker {
5629 pub total_tasks: usize,
5631 pub successful_tasks: usize,
5633 pub failed_tasks: usize,
5635 pub successful_task_ids: Vec<Uuid>,
5637 pub failed_task_ids: Vec<(Uuid, String)>,
5639}
5640
5641impl PartialFailureTracker {
5642 pub fn new(total_tasks: usize) -> Self {
5644 Self {
5645 total_tasks,
5646 successful_tasks: 0,
5647 failed_tasks: 0,
5648 successful_task_ids: Vec::new(),
5649 failed_task_ids: Vec::new(),
5650 }
5651 }
5652
5653 pub fn record_success(&mut self, task_id: Uuid) {
5655 self.successful_tasks += 1;
5656 self.successful_task_ids.push(task_id);
5657 }
5658
5659 pub fn record_failure(&mut self, task_id: Uuid, error: String) {
5661 self.failed_tasks += 1;
5662 self.failed_task_ids.push((task_id, error));
5663 }
5664
5665 pub fn failure_rate(&self) -> f64 {
5667 if self.total_tasks == 0 {
5668 return 0.0;
5669 }
5670 self.failed_tasks as f64 / self.total_tasks as f64
5671 }
5672
5673 pub fn success_rate(&self) -> f64 {
5675 if self.total_tasks == 0 {
5676 return 1.0;
5677 }
5678 self.successful_tasks as f64 / self.total_tasks as f64
5679 }
5680
5681 pub fn exceeds_threshold(&self, mode: &ErrorPropagationMode) -> bool {
5683 match mode {
5684 ErrorPropagationMode::StopOnFirstError => self.failed_tasks > 0,
5685 ErrorPropagationMode::ContinueOnError => false,
5686 ErrorPropagationMode::PartialFailure {
5687 max_failures,
5688 max_failure_rate,
5689 } => {
5690 if self.failed_tasks >= *max_failures {
5691 return true;
5692 }
5693 if let Some(rate) = max_failure_rate {
5694 if self.failure_rate() > *rate {
5695 return true;
5696 }
5697 }
5698 false
5699 }
5700 }
5701 }
5702
5703 pub fn should_continue(&self, mode: &ErrorPropagationMode) -> bool {
5705 !self.exceeds_threshold(mode)
5706 }
5707}
5708
5709impl std::fmt::Display for PartialFailureTracker {
5710 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5711 write!(
5712 f,
5713 "PartialFailureTracker[success={}/{}, failed={}, rate={:.1}%]",
5714 self.successful_tasks,
5715 self.total_tasks,
5716 self.failed_tasks,
5717 self.failure_rate() * 100.0
5718 )
5719 }
5720}
5721
5722#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
5728pub enum IsolationLevel {
5729 #[default]
5731 None,
5732
5733 Resource {
5735 max_memory_mb: Option<u64>,
5737 max_cpu_percent: Option<u8>,
5739 },
5740
5741 Error,
5743
5744 Full {
5746 max_memory_mb: Option<u64>,
5748 max_cpu_percent: Option<u8>,
5750 },
5751}
5752
5753impl IsolationLevel {
5754 pub fn resource(max_memory_mb: u64) -> Self {
5756 Self::Resource {
5757 max_memory_mb: Some(max_memory_mb),
5758 max_cpu_percent: None,
5759 }
5760 }
5761
5762 pub fn full(max_memory_mb: u64) -> Self {
5764 Self::Full {
5765 max_memory_mb: Some(max_memory_mb),
5766 max_cpu_percent: None,
5767 }
5768 }
5769
5770 pub fn has_resource_limits(&self) -> bool {
5772 matches!(self, Self::Resource { .. } | Self::Full { .. })
5773 }
5774
5775 pub fn has_error_isolation(&self) -> bool {
5777 matches!(self, Self::Error | Self::Full { .. })
5778 }
5779}
5780
5781impl std::fmt::Display for IsolationLevel {
5782 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5783 match self {
5784 Self::None => write!(f, "None"),
5785 Self::Resource {
5786 max_memory_mb,
5787 max_cpu_percent,
5788 } => {
5789 write!(f, "Resource(")?;
5790 if let Some(mem) = max_memory_mb {
5791 write!(f, "mem={}MB", mem)?;
5792 }
5793 if let Some(cpu) = max_cpu_percent {
5794 write!(f, " cpu={}%", cpu)?;
5795 }
5796 write!(f, ")")
5797 }
5798 Self::Error => write!(f, "Error"),
5799 Self::Full {
5800 max_memory_mb,
5801 max_cpu_percent,
5802 } => {
5803 write!(f, "Full(")?;
5804 if let Some(mem) = max_memory_mb {
5805 write!(f, "mem={}MB", mem)?;
5806 }
5807 if let Some(cpu) = max_cpu_percent {
5808 write!(f, " cpu={}%", cpu)?;
5809 }
5810 write!(f, ")")
5811 }
5812 }
5813 }
5814}
5815
5816#[derive(Debug, Clone, Serialize, Deserialize)]
5818pub struct SubWorkflowIsolation {
5819 pub workflow_id: Uuid,
5821 pub parent_workflow_id: Option<Uuid>,
5823 pub isolation_level: IsolationLevel,
5825 pub propagate_errors: bool,
5827 pub propagate_cancellation: bool,
5829}
5830
5831impl SubWorkflowIsolation {
5832 pub fn new(workflow_id: Uuid, isolation_level: IsolationLevel) -> Self {
5834 Self {
5835 workflow_id,
5836 parent_workflow_id: None,
5837 isolation_level,
5838 propagate_errors: true,
5839 propagate_cancellation: true,
5840 }
5841 }
5842
5843 pub fn with_parent(mut self, parent_id: Uuid) -> Self {
5845 self.parent_workflow_id = Some(parent_id);
5846 self
5847 }
5848
5849 pub fn no_error_propagation(mut self) -> Self {
5851 self.propagate_errors = false;
5852 self
5853 }
5854
5855 pub fn no_cancellation_propagation(mut self) -> Self {
5857 self.propagate_cancellation = false;
5858 self
5859 }
5860}
5861
5862impl std::fmt::Display for SubWorkflowIsolation {
5863 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5864 write!(
5865 f,
5866 "SubWorkflowIsolation[id={}, level={}]",
5867 self.workflow_id, self.isolation_level
5868 )?;
5869 if let Some(parent) = self.parent_workflow_id {
5870 write!(f, " parent={}", parent)?;
5871 }
5872 Ok(())
5873 }
5874}
5875
5876#[derive(Debug, Clone, Serialize, Deserialize)]
5882pub struct WorkflowCheckpoint {
5883 pub workflow_id: Uuid,
5885 pub timestamp: u64,
5887 pub completed_tasks: Vec<Uuid>,
5889 pub failed_tasks: Vec<(Uuid, String)>,
5891 pub in_progress_tasks: Vec<Uuid>,
5893 pub state: WorkflowState,
5895 pub version: u32,
5897}
5898
5899impl WorkflowCheckpoint {
5900 pub fn new(workflow_id: Uuid, state: WorkflowState) -> Self {
5902 Self {
5903 workflow_id,
5904 timestamp: std::time::SystemTime::now()
5905 .duration_since(std::time::UNIX_EPOCH)
5906 .unwrap()
5907 .as_secs(),
5908 completed_tasks: Vec::new(),
5909 failed_tasks: Vec::new(),
5910 in_progress_tasks: Vec::new(),
5911 state,
5912 version: 1,
5913 }
5914 }
5915
5916 pub fn record_completed(&mut self, task_id: Uuid) {
5918 self.completed_tasks.push(task_id);
5919 self.in_progress_tasks.retain(|&id| id != task_id);
5921 }
5922
5923 pub fn record_failed(&mut self, task_id: Uuid, error: String) {
5925 self.failed_tasks.push((task_id, error));
5926 self.in_progress_tasks.retain(|&id| id != task_id);
5928 }
5929
5930 pub fn record_in_progress(&mut self, task_id: Uuid) {
5932 if !self.in_progress_tasks.contains(&task_id) {
5933 self.in_progress_tasks.push(task_id);
5934 }
5935 }
5936
5937 pub fn is_completed(&self, task_id: &Uuid) -> bool {
5939 self.completed_tasks.contains(task_id)
5940 }
5941
5942 pub fn is_failed(&self, task_id: &Uuid) -> bool {
5944 self.failed_tasks.iter().any(|(id, _)| id == task_id)
5945 }
5946
5947 pub fn tasks_to_retry(&self) -> &[Uuid] {
5949 &self.in_progress_tasks
5950 }
5951
5952 pub fn to_json(&self) -> Result<String, serde_json::Error> {
5954 serde_json::to_string(self)
5955 }
5956
5957 pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
5959 serde_json::from_str(json)
5960 }
5961}
5962
5963impl std::fmt::Display for WorkflowCheckpoint {
5964 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5965 write!(
5966 f,
5967 "WorkflowCheckpoint[id={}, completed={}, failed={}, in_progress={}]",
5968 self.workflow_id,
5969 self.completed_tasks.len(),
5970 self.failed_tasks.len(),
5971 self.in_progress_tasks.len()
5972 )
5973 }
5974}
5975
5976#[derive(Debug, Clone, Serialize, Deserialize)]
5978pub struct WorkflowRecoveryPolicy {
5979 pub auto_recovery: bool,
5981 pub resume_from_checkpoint: bool,
5983 pub replay_failed: bool,
5985 pub max_checkpoint_age: Option<u64>,
5987 pub retry_policy: Option<WorkflowRetryPolicy>,
5989}
5990
5991impl WorkflowRecoveryPolicy {
5992 pub fn auto_recover() -> Self {
5994 Self {
5995 auto_recovery: true,
5996 resume_from_checkpoint: true,
5997 replay_failed: true,
5998 max_checkpoint_age: Some(3600), retry_policy: None,
6000 }
6001 }
6002
6003 pub fn manual() -> Self {
6005 Self {
6006 auto_recovery: false,
6007 resume_from_checkpoint: true,
6008 replay_failed: false,
6009 max_checkpoint_age: None,
6010 retry_policy: None,
6011 }
6012 }
6013
6014 pub fn with_max_checkpoint_age(mut self, seconds: u64) -> Self {
6016 self.max_checkpoint_age = Some(seconds);
6017 self
6018 }
6019
6020 pub fn with_retry_policy(mut self, policy: WorkflowRetryPolicy) -> Self {
6022 self.retry_policy = Some(policy);
6023 self
6024 }
6025
6026 pub fn is_checkpoint_valid(&self, checkpoint: &WorkflowCheckpoint) -> bool {
6028 if let Some(max_age) = self.max_checkpoint_age {
6029 let now = std::time::SystemTime::now()
6030 .duration_since(std::time::UNIX_EPOCH)
6031 .unwrap()
6032 .as_secs();
6033 let age = now.saturating_sub(checkpoint.timestamp);
6034 age <= max_age
6035 } else {
6036 true
6037 }
6038 }
6039}
6040
6041impl std::fmt::Display for WorkflowRecoveryPolicy {
6042 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6043 write!(f, "WorkflowRecoveryPolicy[")?;
6044 if self.auto_recovery {
6045 write!(f, "auto")?;
6046 } else {
6047 write!(f, "manual")?;
6048 }
6049 if self.resume_from_checkpoint {
6050 write!(f, " resume")?;
6051 }
6052 if self.replay_failed {
6053 write!(f, " replay_failed")?;
6054 }
6055 write!(f, "]")
6056 }
6057}
6058
6059#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
6065pub struct StateVersion {
6066 pub major: u32,
6068 pub minor: u32,
6070 pub patch: u32,
6072}
6073
6074impl StateVersion {
6075 pub fn new(major: u32, minor: u32, patch: u32) -> Self {
6077 Self {
6078 major,
6079 minor,
6080 patch,
6081 }
6082 }
6083
6084 pub fn current() -> Self {
6086 Self {
6087 major: 1,
6088 minor: 0,
6089 patch: 0,
6090 }
6091 }
6092
6093 pub fn is_compatible(&self, other: &StateVersion) -> bool {
6096 self.major == other.major && self.minor <= other.minor
6097 }
6098}
6099
6100impl std::fmt::Display for StateVersion {
6101 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6102 write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
6103 }
6104}
6105
6106#[derive(Debug, Clone)]
6108pub enum StateMigrationError {
6109 IncompatibleVersion {
6111 from: StateVersion,
6112 to: StateVersion,
6113 },
6114 MigrationFailed(String),
6116 UnsupportedVersion(StateVersion),
6118}
6119
6120impl std::fmt::Display for StateMigrationError {
6121 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6122 match self {
6123 Self::IncompatibleVersion { from, to } => {
6124 write!(f, "Incompatible state version: {} -> {}", from, to)
6125 }
6126 Self::MigrationFailed(msg) => write!(f, "State migration failed: {}", msg),
6127 Self::UnsupportedVersion(version) => {
6128 write!(f, "Unsupported state version: {}", version)
6129 }
6130 }
6131 }
6132}
6133
6134impl std::error::Error for StateMigrationError {}
6135
6136pub trait StateMigration {
6138 fn migrate(&self, from: StateVersion, to: StateVersion) -> Result<(), StateMigrationError>;
6140}
6141
6142#[derive(Debug, Clone, Serialize, Deserialize)]
6144pub struct VersionedWorkflowState {
6145 pub version: StateVersion,
6147 pub state: WorkflowState,
6149 pub migration_history: Vec<(StateVersion, StateVersion, u64)>, }
6152
6153impl VersionedWorkflowState {
6154 pub fn new(state: WorkflowState) -> Self {
6156 Self {
6157 version: StateVersion::current(),
6158 state,
6159 migration_history: Vec::new(),
6160 }
6161 }
6162
6163 pub fn migrate_to(&mut self, target: StateVersion) -> Result<(), StateMigrationError> {
6165 if self.version == target {
6166 return Ok(());
6167 }
6168
6169 if !self.version.is_compatible(&target) {
6170 return Err(StateMigrationError::IncompatibleVersion {
6171 from: self.version,
6172 to: target,
6173 });
6174 }
6175
6176 let timestamp = std::time::SystemTime::now()
6178 .duration_since(std::time::UNIX_EPOCH)
6179 .unwrap()
6180 .as_secs();
6181 self.migration_history
6182 .push((self.version, target, timestamp));
6183 self.version = target;
6184
6185 Ok(())
6186 }
6187
6188 pub fn can_migrate_to(&self, target: &StateVersion) -> bool {
6190 self.version.is_compatible(target)
6191 }
6192
6193 pub fn get_migration_history(&self) -> &[(StateVersion, StateVersion, u64)] {
6195 &self.migration_history
6196 }
6197}
6198
6199#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6205pub enum OptimizationPass {
6206 CommonSubexpressionElimination,
6208 DeadCodeElimination,
6210 TaskFusion,
6212 ParallelScheduling,
6214 ResourceOptimization,
6216}
6217
6218impl std::fmt::Display for OptimizationPass {
6219 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6220 match self {
6221 Self::CommonSubexpressionElimination => write!(f, "CSE"),
6222 Self::DeadCodeElimination => write!(f, "DCE"),
6223 Self::TaskFusion => write!(f, "TaskFusion"),
6224 Self::ParallelScheduling => write!(f, "ParallelScheduling"),
6225 Self::ResourceOptimization => write!(f, "ResourceOptimization"),
6226 }
6227 }
6228}
6229
6230#[derive(Debug, Clone)]
6232pub struct WorkflowCompiler {
6233 pub passes: Vec<OptimizationPass>,
6235 pub aggressive: bool,
6237}
6238
6239impl WorkflowCompiler {
6240 pub fn new() -> Self {
6242 Self {
6243 passes: vec![
6244 OptimizationPass::DeadCodeElimination,
6245 OptimizationPass::CommonSubexpressionElimination,
6246 ],
6247 aggressive: false,
6248 }
6249 }
6250
6251 pub fn aggressive(mut self) -> Self {
6253 self.aggressive = true;
6254 self.passes.push(OptimizationPass::TaskFusion);
6255 self.passes.push(OptimizationPass::ParallelScheduling);
6256 self.passes.push(OptimizationPass::ResourceOptimization);
6257 self
6258 }
6259
6260 pub fn add_pass(mut self, pass: OptimizationPass) -> Self {
6262 if !self.passes.contains(&pass) {
6263 self.passes.push(pass);
6264 }
6265 self
6266 }
6267
6268 pub fn optimize_chain(&self, chain: &Chain) -> Chain {
6270 let mut optimized = chain.clone();
6271
6272 for pass in &self.passes {
6273 optimized = match pass {
6274 OptimizationPass::CommonSubexpressionElimination => {
6275 self.apply_cse_chain(&optimized)
6276 }
6277 OptimizationPass::DeadCodeElimination => self.apply_dce_chain(&optimized),
6278 OptimizationPass::TaskFusion => self.apply_task_fusion(&optimized),
6279 OptimizationPass::ParallelScheduling => {
6280 optimized
6282 }
6283 OptimizationPass::ResourceOptimization => {
6284 self.apply_resource_optimization_chain(&optimized)
6285 }
6286 };
6287 }
6288
6289 optimized
6290 }
6291
6292 pub fn optimize_group(&self, group: &Group) -> Group {
6294 let mut optimized = group.clone();
6295
6296 for pass in &self.passes {
6297 optimized = match pass {
6298 OptimizationPass::CommonSubexpressionElimination => {
6299 self.apply_cse_group(&optimized)
6300 }
6301 OptimizationPass::DeadCodeElimination => self.apply_dce_group(&optimized),
6302 OptimizationPass::TaskFusion => {
6303 optimized
6305 }
6306 OptimizationPass::ParallelScheduling => self.apply_parallel_scheduling(&optimized),
6307 OptimizationPass::ResourceOptimization => {
6308 self.apply_resource_optimization_group(&optimized)
6309 }
6310 };
6311 }
6312
6313 optimized
6314 }
6315
6316 pub fn optimize_chord(&self, chord: &Chord) -> Chord {
6318 let optimized_group = self.optimize_group(&chord.header);
6319 Chord {
6320 header: optimized_group,
6321 body: chord.body.clone(),
6322 }
6323 }
6324
6325 fn apply_cse_chain(&self, chain: &Chain) -> Chain {
6329 let mut seen = HashMap::new();
6330 let mut optimized_tasks = Vec::new();
6331
6332 for (idx, task) in chain.tasks.iter().enumerate() {
6333 let key = format!(
6335 "{}:{}:{}",
6336 task.task,
6337 serde_json::to_string(&task.args).unwrap_or_default(),
6338 serde_json::to_string(&task.kwargs).unwrap_or_default()
6339 );
6340
6341 if let Some(&prev_idx) = seen.get(&key) {
6342 if self.aggressive && prev_idx < idx {
6344 continue;
6345 }
6346 } else {
6347 seen.insert(key, idx);
6348 }
6349
6350 optimized_tasks.push(task.clone());
6351 }
6352
6353 Chain {
6354 tasks: optimized_tasks,
6355 }
6356 }
6357
6358 fn apply_cse_group(&self, group: &Group) -> Group {
6360 let mut seen = HashMap::new();
6361 let mut optimized_tasks = Vec::new();
6362
6363 for task in &group.tasks {
6364 let key = format!(
6366 "{}:{}:{}",
6367 task.task,
6368 serde_json::to_string(&task.args).unwrap_or_default(),
6369 serde_json::to_string(&task.kwargs).unwrap_or_default()
6370 );
6371
6372 if let std::collections::hash_map::Entry::Vacant(e) = seen.entry(key) {
6373 e.insert(true);
6374 optimized_tasks.push(task.clone());
6375 } else {
6376 if !self.aggressive {
6378 optimized_tasks.push(task.clone());
6379 }
6380 }
6381 }
6382
6383 Group {
6384 tasks: optimized_tasks,
6385 group_id: group.group_id,
6386 }
6387 }
6388
6389 fn apply_dce_chain(&self, chain: &Chain) -> Chain {
6391 let optimized_tasks: Vec<_> = chain
6393 .tasks
6394 .iter()
6395 .filter(|task| !task.task.is_empty())
6396 .cloned()
6397 .collect();
6398
6399 Chain {
6400 tasks: optimized_tasks,
6401 }
6402 }
6403
6404 fn apply_dce_group(&self, group: &Group) -> Group {
6406 let optimized_tasks: Vec<_> = group
6408 .tasks
6409 .iter()
6410 .filter(|task| !task.task.is_empty())
6411 .cloned()
6412 .collect();
6413
6414 Group {
6415 tasks: optimized_tasks,
6416 group_id: group.group_id,
6417 }
6418 }
6419
6420 fn apply_task_fusion(&self, chain: &Chain) -> Chain {
6422 if !self.aggressive || chain.tasks.len() < 2 {
6423 return chain.clone();
6424 }
6425
6426 let mut optimized_tasks = Vec::new();
6427 let mut i = 0;
6428
6429 while i < chain.tasks.len() {
6430 let current = &chain.tasks[i];
6431
6432 if i + 1 < chain.tasks.len() {
6434 let next = &chain.tasks[i + 1];
6435
6436 if current.task == next.task
6437 && current.immutable
6438 && next.immutable
6439 && current.options.priority == next.options.priority
6440 {
6441 let mut fused = current.clone();
6443 fused.args.extend(next.args.clone());
6444 optimized_tasks.push(fused);
6445 i += 2; continue;
6447 }
6448 }
6449
6450 optimized_tasks.push(current.clone());
6451 i += 1;
6452 }
6453
6454 Chain {
6455 tasks: optimized_tasks,
6456 }
6457 }
6458
6459 fn apply_parallel_scheduling(&self, group: &Group) -> Group {
6461 let mut optimized_tasks = group.tasks.clone();
6462
6463 optimized_tasks.sort_by(|a, b| {
6465 let a_priority = a.options.priority.unwrap_or(0);
6466 let b_priority = b.options.priority.unwrap_or(0);
6467 b_priority.cmp(&a_priority)
6468 });
6469
6470 Group {
6471 tasks: optimized_tasks,
6472 group_id: group.group_id,
6473 }
6474 }
6475
6476 fn apply_resource_optimization_chain(&self, chain: &Chain) -> Chain {
6478 let mut optimized_tasks = chain.tasks.clone();
6480
6481 if self.aggressive {
6482 optimized_tasks.sort_by(|a, b| {
6484 let a_queue = a.options.queue.as_deref().unwrap_or("");
6485 let b_queue = b.options.queue.as_deref().unwrap_or("");
6486 a_queue.cmp(b_queue)
6487 });
6488 }
6489
6490 Chain {
6491 tasks: optimized_tasks,
6492 }
6493 }
6494
6495 fn apply_resource_optimization_group(&self, group: &Group) -> Group {
6497 let mut optimized_tasks = group.tasks.clone();
6499
6500 optimized_tasks.sort_by(|a, b| {
6502 let a_queue = a.options.queue.as_deref().unwrap_or("");
6503 let b_queue = b.options.queue.as_deref().unwrap_or("");
6504 a_queue.cmp(b_queue)
6505 });
6506
6507 Group {
6508 tasks: optimized_tasks,
6509 group_id: group.group_id,
6510 }
6511 }
6512}
6513
6514impl Default for WorkflowCompiler {
6515 fn default() -> Self {
6516 Self::new()
6517 }
6518}
6519
6520impl std::fmt::Display for WorkflowCompiler {
6521 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6522 write!(f, "WorkflowCompiler[")?;
6523 for (i, pass) in self.passes.iter().enumerate() {
6524 if i > 0 {
6525 write!(f, ", ")?;
6526 }
6527 write!(f, "{}", pass)?;
6528 }
6529 if self.aggressive {
6530 write!(f, " aggressive")?;
6531 }
6532 write!(f, "]")
6533 }
6534}
6535
6536#[derive(Debug, Clone, Serialize, Deserialize)]
6542pub struct TypedResult<T> {
6543 pub value: T,
6545 pub type_name: String,
6547 #[serde(default)]
6549 pub metadata: HashMap<String, serde_json::Value>,
6550}
6551
6552impl<T: Serialize> TypedResult<T> {
6553 pub fn new(value: T) -> Self {
6555 Self {
6556 value,
6557 type_name: std::any::type_name::<T>().to_string(),
6558 metadata: HashMap::new(),
6559 }
6560 }
6561
6562 pub fn with_metadata(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
6564 self.metadata.insert(key.into(), value);
6565 self
6566 }
6567
6568 pub fn type_name(&self) -> &str {
6570 &self.type_name
6571 }
6572}
6573
6574impl<T: std::fmt::Display> std::fmt::Display for TypedResult<T> {
6575 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6576 write!(
6577 f,
6578 "TypedResult[type={}, value={}]",
6579 self.type_name, self.value
6580 )
6581 }
6582}
6583
6584#[derive(Debug, Clone)]
6586pub struct TypeValidator {
6587 pub expected_type: String,
6589 pub allow_compatible: bool,
6591}
6592
6593impl TypeValidator {
6594 pub fn new(expected_type: impl Into<String>) -> Self {
6596 Self {
6597 expected_type: expected_type.into(),
6598 allow_compatible: false,
6599 }
6600 }
6601
6602 pub fn allow_compatible(mut self) -> Self {
6604 self.allow_compatible = true;
6605 self
6606 }
6607
6608 pub fn validate(&self, actual_type: &str) -> bool {
6610 if actual_type == self.expected_type {
6611 return true;
6612 }
6613 if self.allow_compatible {
6614 self.is_compatible(actual_type)
6615 } else {
6616 false
6617 }
6618 }
6619
6620 fn is_compatible(&self, actual_type: &str) -> bool {
6622 if self.expected_type.contains("Option") && actual_type != "None" {
6624 return true;
6625 }
6626 if self.expected_type == "serde_json::Value" {
6627 return true;
6628 }
6629 false
6630 }
6631}
6632
6633impl std::fmt::Display for TypeValidator {
6634 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6635 write!(f, "TypeValidator[expected={}]", self.expected_type)?;
6636 if self.allow_compatible {
6637 write!(f, " (allow_compatible)")?;
6638 }
6639 Ok(())
6640 }
6641}
6642
6643#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
6649pub struct TaskDependency {
6650 pub task_id: Uuid,
6652 pub output_key: Option<String>,
6654 #[serde(default)]
6656 pub optional: bool,
6657}
6658
6659impl TaskDependency {
6660 pub fn new(task_id: Uuid) -> Self {
6662 Self {
6663 task_id,
6664 output_key: None,
6665 optional: false,
6666 }
6667 }
6668
6669 pub fn with_output_key(mut self, key: impl Into<String>) -> Self {
6671 self.output_key = Some(key.into());
6672 self
6673 }
6674
6675 pub fn optional(mut self) -> Self {
6677 self.optional = true;
6678 self
6679 }
6680}
6681
6682impl std::fmt::Display for TaskDependency {
6683 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6684 write!(f, "TaskDependency[{}]", self.task_id)?;
6685 if let Some(ref key) = self.output_key {
6686 write!(f, " output={}", key)?;
6687 }
6688 if self.optional {
6689 write!(f, " (optional)")?;
6690 }
6691 Ok(())
6692 }
6693}
6694
6695#[derive(Debug, Clone, Serialize, Deserialize)]
6697pub struct DependencyGraph {
6698 pub dependencies: HashMap<Uuid, Vec<TaskDependency>>,
6700 #[serde(skip)]
6702 pub dependents: HashMap<Uuid, Vec<Uuid>>,
6703}
6704
6705impl DependencyGraph {
6706 pub fn new() -> Self {
6708 Self {
6709 dependencies: HashMap::new(),
6710 dependents: HashMap::new(),
6711 }
6712 }
6713
6714 pub fn add_dependency(&mut self, task_id: Uuid, dependency: TaskDependency) {
6716 self.dependencies
6717 .entry(task_id)
6718 .or_default()
6719 .push(dependency.clone());
6720
6721 self.dependents
6723 .entry(dependency.task_id)
6724 .or_default()
6725 .push(task_id);
6726 }
6727
6728 pub fn get_dependencies(&self, task_id: &Uuid) -> Vec<&TaskDependency> {
6730 self.dependencies
6731 .get(task_id)
6732 .map(|deps| deps.iter().collect())
6733 .unwrap_or_default()
6734 }
6735
6736 pub fn get_dependents(&self, task_id: &Uuid) -> Vec<Uuid> {
6738 self.dependents.get(task_id).cloned().unwrap_or_default()
6739 }
6740
6741 pub fn has_circular_dependency(&self) -> bool {
6743 let mut visited = std::collections::HashSet::new();
6744 let mut rec_stack = std::collections::HashSet::new();
6745
6746 for task_id in self.dependencies.keys() {
6747 if self.is_cyclic(*task_id, &mut visited, &mut rec_stack) {
6748 return true;
6749 }
6750 }
6751 false
6752 }
6753
6754 fn is_cyclic(
6755 &self,
6756 task_id: Uuid,
6757 visited: &mut std::collections::HashSet<Uuid>,
6758 rec_stack: &mut std::collections::HashSet<Uuid>,
6759 ) -> bool {
6760 if rec_stack.contains(&task_id) {
6761 return true;
6762 }
6763 if visited.contains(&task_id) {
6764 return false;
6765 }
6766
6767 visited.insert(task_id);
6768 rec_stack.insert(task_id);
6769
6770 if let Some(deps) = self.dependencies.get(&task_id) {
6771 for dep in deps {
6772 if self.is_cyclic(dep.task_id, visited, rec_stack) {
6773 return true;
6774 }
6775 }
6776 }
6777
6778 rec_stack.remove(&task_id);
6779 false
6780 }
6781
6782 pub fn topological_sort(&self) -> Result<Vec<Uuid>, String> {
6784 if self.has_circular_dependency() {
6785 return Err("Circular dependency detected".to_string());
6786 }
6787
6788 let mut in_degree: HashMap<Uuid, usize> = HashMap::new();
6789 let mut queue: Vec<Uuid> = Vec::new();
6790 let mut result: Vec<Uuid> = Vec::new();
6791
6792 for (task_id, deps) in &self.dependencies {
6794 in_degree.entry(*task_id).or_insert(deps.len());
6795 for dep in deps {
6796 in_degree.entry(dep.task_id).or_insert(0);
6797 }
6798 }
6799
6800 for (task_id, °ree) in &in_degree {
6802 if degree == 0 {
6803 queue.push(*task_id);
6804 }
6805 }
6806
6807 while let Some(task_id) = queue.pop() {
6809 result.push(task_id);
6810
6811 if let Some(dependents) = self.dependents.get(&task_id) {
6812 for &dependent in dependents {
6813 if let Some(degree) = in_degree.get_mut(&dependent) {
6814 if *degree > 0 {
6815 *degree -= 1;
6816 if *degree == 0 {
6817 queue.push(dependent);
6818 }
6819 }
6820 }
6821 }
6822 }
6823 }
6824
6825 if result.len() == in_degree.len() {
6826 Ok(result)
6827 } else {
6828 Err("Failed to compute topological sort".to_string())
6829 }
6830 }
6831}
6832
6833impl Default for DependencyGraph {
6834 fn default() -> Self {
6835 Self::new()
6836 }
6837}
6838
6839impl std::fmt::Display for DependencyGraph {
6840 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6841 write!(f, "DependencyGraph[{} tasks]", self.dependencies.len())
6842 }
6843}
6844
6845#[derive(Debug, Clone, Serialize, Deserialize)]
6851pub struct ParallelReduce {
6852 pub map_task: Signature,
6854 pub reduce_task: Signature,
6856 pub inputs: Vec<serde_json::Value>,
6858 pub parallelism: usize,
6860 pub initial_value: Option<serde_json::Value>,
6862}
6863
6864impl ParallelReduce {
6865 pub fn new(
6867 map_task: Signature,
6868 reduce_task: Signature,
6869 inputs: Vec<serde_json::Value>,
6870 ) -> Self {
6871 Self {
6872 map_task,
6873 reduce_task,
6874 inputs,
6875 parallelism: 4,
6876 initial_value: None,
6877 }
6878 }
6879
6880 pub fn with_parallelism(mut self, parallelism: usize) -> Self {
6882 self.parallelism = parallelism;
6883 self
6884 }
6885
6886 pub fn with_initial_value(mut self, value: serde_json::Value) -> Self {
6888 self.initial_value = Some(value);
6889 self
6890 }
6891
6892 pub fn input_count(&self) -> usize {
6894 self.inputs.len()
6895 }
6896
6897 pub fn is_empty(&self) -> bool {
6899 self.inputs.is_empty()
6900 }
6901}
6902
6903impl std::fmt::Display for ParallelReduce {
6904 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6905 write!(
6906 f,
6907 "ParallelReduce[map={}, reduce={}, inputs={}, parallelism={}]",
6908 self.map_task.task,
6909 self.reduce_task.task,
6910 self.inputs.len(),
6911 self.parallelism
6912 )
6913 }
6914}
6915
6916#[derive(Debug, Clone, Serialize, Deserialize)]
6922pub struct TemplateParameter {
6923 pub name: String,
6925 pub param_type: String,
6927 pub default: Option<serde_json::Value>,
6929 #[serde(default = "default_true")]
6931 pub required: bool,
6932 pub description: Option<String>,
6934}
6935
6936fn default_true() -> bool {
6937 true
6938}
6939
6940impl TemplateParameter {
6941 pub fn new(name: impl Into<String>, param_type: impl Into<String>) -> Self {
6943 Self {
6944 name: name.into(),
6945 param_type: param_type.into(),
6946 default: None,
6947 required: true,
6948 description: None,
6949 }
6950 }
6951
6952 pub fn with_default(mut self, value: serde_json::Value) -> Self {
6954 self.default = Some(value);
6955 self.required = false;
6956 self
6957 }
6958
6959 pub fn with_description(mut self, description: impl Into<String>) -> Self {
6961 self.description = Some(description.into());
6962 self
6963 }
6964
6965 pub fn optional(mut self) -> Self {
6967 self.required = false;
6968 self
6969 }
6970}
6971
6972impl std::fmt::Display for TemplateParameter {
6973 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6974 write!(f, "{}:{}", self.name, self.param_type)?;
6975 if !self.required {
6976 write!(f, " (optional)")?;
6977 }
6978 Ok(())
6979 }
6980}
6981
6982#[derive(Debug, Clone, Serialize, Deserialize)]
6984pub struct WorkflowTemplate {
6985 pub name: String,
6987 pub version: String,
6989 pub parameters: Vec<TemplateParameter>,
6991 pub chain: Option<Chain>,
6993 pub group: Option<Group>,
6995 pub description: Option<String>,
6997}
6998
6999impl WorkflowTemplate {
7000 pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
7002 Self {
7003 name: name.into(),
7004 version: version.into(),
7005 parameters: Vec::new(),
7006 chain: None,
7007 group: None,
7008 description: None,
7009 }
7010 }
7011
7012 pub fn add_parameter(mut self, param: TemplateParameter) -> Self {
7014 self.parameters.push(param);
7015 self
7016 }
7017
7018 pub fn with_chain(mut self, chain: Chain) -> Self {
7020 self.chain = Some(chain);
7021 self
7022 }
7023
7024 pub fn with_group(mut self, group: Group) -> Self {
7026 self.group = Some(group);
7027 self
7028 }
7029
7030 pub fn with_description(mut self, description: impl Into<String>) -> Self {
7032 self.description = Some(description.into());
7033 self
7034 }
7035
7036 pub fn instantiate(&self, params: HashMap<String, serde_json::Value>) -> Result<Self, String> {
7038 for param in &self.parameters {
7040 if param.required && !params.contains_key(¶m.name) && param.default.is_none() {
7041 return Err(format!("Missing required parameter: {}", param.name));
7042 }
7043 }
7044
7045 Ok(self.clone())
7047 }
7048}
7049
7050impl std::fmt::Display for WorkflowTemplate {
7051 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
7052 write!(f, "WorkflowTemplate[{}@{}]", self.name, self.version)?;
7053 if !self.parameters.is_empty() {
7054 write!(f, " params={}", self.parameters.len())?;
7055 }
7056 Ok(())
7057 }
7058}
7059
7060#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
7066pub enum WorkflowEvent {
7067 TaskCompleted { task_id: Uuid },
7069 TaskFailed { task_id: Uuid, error: String },
7071 WorkflowStarted { workflow_id: Uuid },
7073 WorkflowCompleted { workflow_id: Uuid },
7075 WorkflowFailed { workflow_id: Uuid, error: String },
7077 Custom { event_type: String, data: String },
7079}
7080
7081impl std::fmt::Display for WorkflowEvent {
7082 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
7083 match self {
7084 Self::TaskCompleted { task_id } => write!(f, "TaskCompleted[{}]", task_id),
7085 Self::TaskFailed { task_id, .. } => write!(f, "TaskFailed[{}]", task_id),
7086 Self::WorkflowStarted { workflow_id } => write!(f, "WorkflowStarted[{}]", workflow_id),
7087 Self::WorkflowCompleted { workflow_id } => {
7088 write!(f, "WorkflowCompleted[{}]", workflow_id)
7089 }
7090 Self::WorkflowFailed { workflow_id, .. } => {
7091 write!(f, "WorkflowFailed[{}]", workflow_id)
7092 }
7093 Self::Custom { event_type, .. } => write!(f, "Custom[{}]", event_type),
7094 }
7095 }
7096}
7097
7098#[derive(Debug, Clone, Serialize, Deserialize)]
7100pub struct EventHandler {
7101 pub event_type: String,
7103 pub handler_task: Signature,
7105 pub filter: Option<String>,
7107}
7108
7109impl EventHandler {
7110 pub fn new(event_type: impl Into<String>, handler_task: Signature) -> Self {
7112 Self {
7113 event_type: event_type.into(),
7114 handler_task,
7115 filter: None,
7116 }
7117 }
7118
7119 pub fn with_filter(mut self, filter: impl Into<String>) -> Self {
7121 self.filter = Some(filter.into());
7122 self
7123 }
7124}
7125
7126impl std::fmt::Display for EventHandler {
7127 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
7128 write!(
7129 f,
7130 "EventHandler[event={}, handler={}]",
7131 self.event_type, self.handler_task.task
7132 )
7133 }
7134}
7135
7136#[derive(Debug, Clone, Serialize, Deserialize)]
7138pub struct EventDrivenWorkflow {
7139 pub workflow_id: Uuid,
7141 pub handlers: Vec<EventHandler>,
7143 pub active: bool,
7145}
7146
7147impl EventDrivenWorkflow {
7148 pub fn new() -> Self {
7150 Self {
7151 workflow_id: Uuid::new_v4(),
7152 handlers: Vec::new(),
7153 active: true,
7154 }
7155 }
7156
7157 pub fn on_event(mut self, handler: EventHandler) -> Self {
7159 self.handlers.push(handler);
7160 self
7161 }
7162
7163 pub fn on_task_completed(self, task: Signature) -> Self {
7165 self.on_event(EventHandler::new("TaskCompleted", task))
7166 }
7167
7168 pub fn on_task_failed(self, task: Signature) -> Self {
7170 self.on_event(EventHandler::new("TaskFailed", task))
7171 }
7172
7173 pub fn activate(mut self) -> Self {
7175 self.active = true;
7176 self
7177 }
7178
7179 pub fn deactivate(mut self) -> Self {
7181 self.active = false;
7182 self
7183 }
7184
7185 pub fn has_handlers(&self) -> bool {
7187 !self.handlers.is_empty()
7188 }
7189}
7190
7191impl Default for EventDrivenWorkflow {
7192 fn default() -> Self {
7193 Self::new()
7194 }
7195}
7196
7197impl std::fmt::Display for EventDrivenWorkflow {
7198 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
7199 write!(
7200 f,
7201 "EventDrivenWorkflow[id={}, handlers={}]",
7202 self.workflow_id,
7203 self.handlers.len()
7204 )?;
7205 if !self.active {
7206 write!(f, " (inactive)")?;
7207 }
7208 Ok(())
7209 }
7210}
7211
7212#[derive(Debug, thiserror::Error)]
7214pub enum CanvasError {
7215 #[error("Invalid workflow: {0}")]
7216 Invalid(String),
7217
7218 #[error("Broker error: {0}")]
7219 Broker(String),
7220
7221 #[error("Serialization error: {0}")]
7222 Serialization(String),
7223
7224 #[error("Workflow cancelled: {0}")]
7225 Cancelled(String),
7226
7227 #[error("Workflow timeout: {0}")]
7228 Timeout(String),
7229}
7230
7231impl CanvasError {
7232 pub fn is_invalid(&self) -> bool {
7234 matches!(self, CanvasError::Invalid(_))
7235 }
7236
7237 pub fn is_broker(&self) -> bool {
7239 matches!(self, CanvasError::Broker(_))
7240 }
7241
7242 pub fn is_serialization(&self) -> bool {
7244 matches!(self, CanvasError::Serialization(_))
7245 }
7246
7247 pub fn is_cancelled(&self) -> bool {
7249 matches!(self, CanvasError::Cancelled(_))
7250 }
7251
7252 pub fn is_timeout(&self) -> bool {
7254 matches!(self, CanvasError::Timeout(_))
7255 }
7256
7257 pub fn is_retryable(&self) -> bool {
7262 matches!(self, CanvasError::Broker(_))
7263 }
7264
7265 pub fn category(&self) -> &'static str {
7267 match self {
7268 CanvasError::Invalid(_) => "invalid",
7269 CanvasError::Broker(_) => "broker",
7270 CanvasError::Serialization(_) => "serialization",
7271 CanvasError::Cancelled(_) => "cancelled",
7272 CanvasError::Timeout(_) => "timeout",
7273 }
7274 }
7275}
7276
7277#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
7283pub enum TaskPriority {
7284 Low = 0,
7286 #[default]
7288 Normal = 5,
7289 High = 10,
7291 Critical = 15,
7293}
7294
7295impl std::fmt::Display for TaskPriority {
7296 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
7297 match self {
7298 Self::Low => write!(f, "Low"),
7299 Self::Normal => write!(f, "Normal"),
7300 Self::High => write!(f, "High"),
7301 Self::Critical => write!(f, "Critical"),
7302 }
7303 }
7304}
7305
7306#[derive(Debug, Clone, Serialize, Deserialize)]
7308pub struct WorkerCapacity {
7309 pub worker_id: String,
7311 pub cpu_cores: u32,
7313 pub memory_mb: u64,
7315 pub current_load: f64,
7317 pub active_tasks: usize,
7319}
7320
7321impl WorkerCapacity {
7322 pub fn new(worker_id: impl Into<String>, cpu_cores: u32, memory_mb: u64) -> Self {
7324 Self {
7325 worker_id: worker_id.into(),
7326 cpu_cores,
7327 memory_mb,
7328 current_load: 0.0,
7329 active_tasks: 0,
7330 }
7331 }
7332
7333 pub fn has_capacity(&self, required_load: f64) -> bool {
7335 self.current_load + required_load <= 1.0
7336 }
7337
7338 pub fn available_capacity(&self) -> f64 {
7340 (1.0 - self.current_load).max(0.0)
7341 }
7342}
7343
7344#[derive(Debug, Clone, Serialize, Deserialize)]
7346pub struct SchedulingDecision {
7347 pub task_id: Uuid,
7349 pub worker_id: String,
7351 pub priority: TaskPriority,
7353 pub estimated_time: Option<u64>,
7355}
7356
7357impl SchedulingDecision {
7358 pub fn new(task_id: Uuid, worker_id: impl Into<String>, priority: TaskPriority) -> Self {
7360 Self {
7361 task_id,
7362 worker_id: worker_id.into(),
7363 priority,
7364 estimated_time: None,
7365 }
7366 }
7367
7368 pub fn with_estimated_time(mut self, seconds: u64) -> Self {
7370 self.estimated_time = Some(seconds);
7371 self
7372 }
7373}
7374
7375#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
7377pub enum SchedulingStrategy {
7378 RoundRobin,
7380 #[default]
7382 LeastLoaded,
7383 PriorityBased,
7385 ResourceAware,
7387}
7388
7389impl std::fmt::Display for SchedulingStrategy {
7390 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
7391 match self {
7392 Self::RoundRobin => write!(f, "RoundRobin"),
7393 Self::LeastLoaded => write!(f, "LeastLoaded"),
7394 Self::PriorityBased => write!(f, "PriorityBased"),
7395 Self::ResourceAware => write!(f, "ResourceAware"),
7396 }
7397 }
7398}
7399
7400#[derive(Debug, Clone)]
7402pub struct ParallelScheduler {
7403 pub strategy: SchedulingStrategy,
7405 pub workers: Vec<WorkerCapacity>,
7407 pub load_balancing: bool,
7409 pub max_tasks_per_worker: Option<usize>,
7411}
7412
7413impl ParallelScheduler {
7414 pub fn new(strategy: SchedulingStrategy) -> Self {
7416 Self {
7417 strategy,
7418 workers: Vec::new(),
7419 load_balancing: true,
7420 max_tasks_per_worker: None,
7421 }
7422 }
7423
7424 pub fn add_worker(&mut self, worker: WorkerCapacity) {
7426 self.workers.push(worker);
7427 }
7428
7429 pub fn with_load_balancing(mut self, enabled: bool) -> Self {
7431 self.load_balancing = enabled;
7432 self
7433 }
7434
7435 pub fn with_max_tasks_per_worker(mut self, max: usize) -> Self {
7437 self.max_tasks_per_worker = Some(max);
7438 self
7439 }
7440
7441 pub fn schedule_task(
7443 &self,
7444 task_id: Uuid,
7445 priority: TaskPriority,
7446 ) -> Option<SchedulingDecision> {
7447 if self.workers.is_empty() {
7448 return None;
7449 }
7450
7451 let worker_id = match self.strategy {
7452 SchedulingStrategy::RoundRobin => {
7453 self.workers
7455 .iter()
7456 .min_by_key(|w| w.active_tasks)
7457 .map(|w| w.worker_id.clone())
7458 }
7459 SchedulingStrategy::LeastLoaded => {
7460 self.workers
7462 .iter()
7463 .filter(|w| {
7464 if let Some(max) = self.max_tasks_per_worker {
7465 w.active_tasks < max
7466 } else {
7467 true
7468 }
7469 })
7470 .min_by(|a, b| {
7471 a.current_load
7472 .partial_cmp(&b.current_load)
7473 .unwrap_or(std::cmp::Ordering::Equal)
7474 })
7475 .map(|w| w.worker_id.clone())
7476 }
7477 SchedulingStrategy::PriorityBased => {
7478 let priority_weight = priority as u8 as f64 / 15.0;
7480 self.workers
7481 .iter()
7482 .filter(|w| {
7483 if let Some(max) = self.max_tasks_per_worker {
7484 w.active_tasks < max
7485 } else {
7486 true
7487 }
7488 })
7489 .min_by(|a, b| {
7490 let a_score = a.current_load * (1.0 - priority_weight);
7491 let b_score = b.current_load * (1.0 - priority_weight);
7492 a_score
7493 .partial_cmp(&b_score)
7494 .unwrap_or(std::cmp::Ordering::Equal)
7495 })
7496 .map(|w| w.worker_id.clone())
7497 }
7498 SchedulingStrategy::ResourceAware => {
7499 self.workers
7501 .iter()
7502 .filter(|w| {
7503 if let Some(max) = self.max_tasks_per_worker {
7504 w.active_tasks < max
7505 } else {
7506 true
7507 }
7508 })
7509 .max_by(|a, b| {
7510 let a_score = a.available_capacity()
7511 * (a.cpu_cores as f64 / 100.0)
7512 * (a.memory_mb as f64 / 1_000_000.0);
7513 let b_score = b.available_capacity()
7514 * (b.cpu_cores as f64 / 100.0)
7515 * (b.memory_mb as f64 / 1_000_000.0);
7516 a_score
7517 .partial_cmp(&b_score)
7518 .unwrap_or(std::cmp::Ordering::Equal)
7519 })
7520 .map(|w| w.worker_id.clone())
7521 }
7522 };
7523
7524 worker_id.map(|id| SchedulingDecision::new(task_id, id, priority))
7525 }
7526
7527 pub fn worker_count(&self) -> usize {
7529 self.workers.len()
7530 }
7531
7532 pub fn total_capacity(&self) -> f64 {
7534 self.workers.iter().map(|w| w.available_capacity()).sum()
7535 }
7536
7537 pub fn average_load(&self) -> f64 {
7539 if self.workers.is_empty() {
7540 return 0.0;
7541 }
7542 let total_load: f64 = self.workers.iter().map(|w| w.current_load).sum();
7543 total_load / self.workers.len() as f64
7544 }
7545}
7546
7547impl Default for ParallelScheduler {
7548 fn default() -> Self {
7549 Self::new(SchedulingStrategy::default())
7550 }
7551}
7552
7553impl std::fmt::Display for ParallelScheduler {
7554 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
7555 write!(
7556 f,
7557 "ParallelScheduler[strategy={}, workers={}, avg_load={:.2}]",
7558 self.strategy,
7559 self.workers.len(),
7560 self.average_load()
7561 )
7562 }
7563}
7564
7565#[derive(Debug, Clone, Serialize, Deserialize)]
7571pub struct WorkflowBatch {
7572 pub batch_id: Uuid,
7574 pub workflow_ids: Vec<Uuid>,
7576 pub priority: TaskPriority,
7578 pub max_size: usize,
7580 pub timeout: Option<u64>,
7582 pub created_at: u64,
7584}
7585
7586impl WorkflowBatch {
7587 pub fn new(max_size: usize) -> Self {
7589 Self {
7590 batch_id: Uuid::new_v4(),
7591 workflow_ids: Vec::new(),
7592 priority: TaskPriority::Normal,
7593 max_size,
7594 timeout: None,
7595 created_at: std::time::SystemTime::now()
7596 .duration_since(std::time::UNIX_EPOCH)
7597 .unwrap()
7598 .as_secs(),
7599 }
7600 }
7601
7602 pub fn add_workflow(&mut self, workflow_id: Uuid) -> bool {
7604 if self.workflow_ids.len() < self.max_size {
7605 self.workflow_ids.push(workflow_id);
7606 true
7607 } else {
7608 false
7609 }
7610 }
7611
7612 pub fn is_full(&self) -> bool {
7614 self.workflow_ids.len() >= self.max_size
7615 }
7616
7617 pub fn is_empty(&self) -> bool {
7619 self.workflow_ids.is_empty()
7620 }
7621
7622 pub fn size(&self) -> usize {
7624 self.workflow_ids.len()
7625 }
7626
7627 pub fn is_timed_out(&self) -> bool {
7629 if let Some(timeout) = self.timeout {
7630 let now = std::time::SystemTime::now()
7631 .duration_since(std::time::UNIX_EPOCH)
7632 .unwrap()
7633 .as_secs();
7634 let age = now.saturating_sub(self.created_at);
7635 age >= timeout
7636 } else {
7637 false
7638 }
7639 }
7640
7641 pub fn with_priority(mut self, priority: TaskPriority) -> Self {
7643 self.priority = priority;
7644 self
7645 }
7646
7647 pub fn with_timeout(mut self, seconds: u64) -> Self {
7649 self.timeout = Some(seconds);
7650 self
7651 }
7652}
7653
7654impl std::fmt::Display for WorkflowBatch {
7655 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
7656 write!(
7657 f,
7658 "WorkflowBatch[id={}, size={}/{}, priority={}]",
7659 self.batch_id,
7660 self.size(),
7661 self.max_size,
7662 self.priority
7663 )
7664 }
7665}
7666
7667#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
7669pub enum BatchingStrategy {
7670 #[default]
7672 ByType,
7673 ByPriority,
7675 BySize,
7677 ByTimeWindow,
7679}
7680
7681impl std::fmt::Display for BatchingStrategy {
7682 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
7683 match self {
7684 Self::ByType => write!(f, "ByType"),
7685 Self::ByPriority => write!(f, "ByPriority"),
7686 Self::BySize => write!(f, "BySize"),
7687 Self::ByTimeWindow => write!(f, "ByTimeWindow"),
7688 }
7689 }
7690}
7691
7692#[derive(Debug, Clone)]
7694pub struct WorkflowBatcher {
7695 pub strategy: BatchingStrategy,
7697 pub batches: Vec<WorkflowBatch>,
7699 pub default_batch_size: usize,
7701 pub default_timeout: Option<u64>,
7703}
7704
7705impl WorkflowBatcher {
7706 pub fn new(strategy: BatchingStrategy) -> Self {
7708 Self {
7709 strategy,
7710 batches: Vec::new(),
7711 default_batch_size: 10,
7712 default_timeout: Some(60), }
7714 }
7715
7716 pub fn with_batch_size(mut self, size: usize) -> Self {
7718 self.default_batch_size = size;
7719 self
7720 }
7721
7722 pub fn with_timeout(mut self, seconds: u64) -> Self {
7724 self.default_timeout = Some(seconds);
7725 self
7726 }
7727
7728 pub fn add_workflow(&mut self, workflow_id: Uuid, priority: TaskPriority) -> Uuid {
7730 let batch_id = match self.strategy {
7732 BatchingStrategy::ByPriority => {
7733 let batch = self
7735 .batches
7736 .iter_mut()
7737 .find(|b| b.priority == priority && !b.is_full() && !b.is_timed_out());
7738
7739 if let Some(batch) = batch {
7740 batch.add_workflow(workflow_id);
7741 batch.batch_id
7742 } else {
7743 let mut new_batch =
7745 WorkflowBatch::new(self.default_batch_size).with_priority(priority);
7746 if let Some(timeout) = self.default_timeout {
7747 new_batch = new_batch.with_timeout(timeout);
7748 }
7749 new_batch.add_workflow(workflow_id);
7750 let batch_id = new_batch.batch_id;
7751 self.batches.push(new_batch);
7752 batch_id
7753 }
7754 }
7755 _ => {
7756 let batch = self
7758 .batches
7759 .iter_mut()
7760 .find(|b| !b.is_full() && !b.is_timed_out());
7761
7762 if let Some(batch) = batch {
7763 batch.add_workflow(workflow_id);
7764 batch.batch_id
7765 } else {
7766 let mut new_batch = WorkflowBatch::new(self.default_batch_size);
7768 if let Some(timeout) = self.default_timeout {
7769 new_batch = new_batch.with_timeout(timeout);
7770 }
7771 new_batch.add_workflow(workflow_id);
7772 let batch_id = new_batch.batch_id;
7773 self.batches.push(new_batch);
7774 batch_id
7775 }
7776 }
7777 };
7778
7779 batch_id
7780 }
7781
7782 pub fn get_ready_batches(&self) -> Vec<&WorkflowBatch> {
7784 self.batches
7785 .iter()
7786 .filter(|b| b.is_full() || b.is_timed_out())
7787 .collect()
7788 }
7789
7790 pub fn remove_ready_batches(&mut self) -> Vec<WorkflowBatch> {
7792 let (ready, pending): (Vec<_>, Vec<_>) = self
7793 .batches
7794 .drain(..)
7795 .partition(|b| b.is_full() || b.is_timed_out());
7796 self.batches = pending;
7797 ready
7798 }
7799
7800 pub fn batch_count(&self) -> usize {
7802 self.batches.len()
7803 }
7804
7805 pub fn total_workflow_count(&self) -> usize {
7807 self.batches.iter().map(|b| b.size()).sum()
7808 }
7809}
7810
7811impl Default for WorkflowBatcher {
7812 fn default() -> Self {
7813 Self::new(BatchingStrategy::default())
7814 }
7815}
7816
7817impl std::fmt::Display for WorkflowBatcher {
7818 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
7819 write!(
7820 f,
7821 "WorkflowBatcher[strategy={}, batches={}, workflows={}]",
7822 self.strategy,
7823 self.batch_count(),
7824 self.total_workflow_count()
7825 )
7826 }
7827}
7828
7829#[derive(Debug, Clone, Serialize, Deserialize)]
7835pub struct StreamingMapReduce {
7836 pub map_task: Signature,
7838 pub reduce_task: Signature,
7840 pub chunk_size: usize,
7842 pub buffer_size: usize,
7844 pub backpressure: bool,
7846}
7847
7848impl StreamingMapReduce {
7849 pub fn new(map_task: Signature, reduce_task: Signature) -> Self {
7851 Self {
7852 map_task,
7853 reduce_task,
7854 chunk_size: 100,
7855 buffer_size: 1000,
7856 backpressure: true,
7857 }
7858 }
7859
7860 pub fn with_chunk_size(mut self, size: usize) -> Self {
7862 self.chunk_size = size;
7863 self
7864 }
7865
7866 pub fn with_buffer_size(mut self, size: usize) -> Self {
7868 self.buffer_size = size;
7869 self
7870 }
7871
7872 pub fn with_backpressure(mut self, enabled: bool) -> Self {
7874 self.backpressure = enabled;
7875 self
7876 }
7877}
7878
7879impl std::fmt::Display for StreamingMapReduce {
7880 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
7881 write!(
7882 f,
7883 "StreamingMapReduce[map={}, reduce={}, chunk_size={}, buffer_size={}]",
7884 self.map_task.task, self.reduce_task.task, self.chunk_size, self.buffer_size
7885 )
7886 }
7887}
7888
7889#[derive(Debug, Clone, Serialize, Deserialize)]
7895pub struct Observable<T> {
7896 pub value: T,
7898 #[serde(skip)]
7900 pub subscribers: Vec<Uuid>,
7901 pub history: Vec<(T, u64)>, }
7904
7905impl<T: Clone> Observable<T> {
7906 pub fn new(value: T) -> Self {
7908 Self {
7909 value,
7910 subscribers: Vec::new(),
7911 history: Vec::new(),
7912 }
7913 }
7914
7915 pub fn subscribe(&mut self, workflow_id: Uuid) {
7917 if !self.subscribers.contains(&workflow_id) {
7918 self.subscribers.push(workflow_id);
7919 }
7920 }
7921
7922 pub fn unsubscribe(&mut self, workflow_id: &Uuid) {
7924 self.subscribers.retain(|id| id != workflow_id);
7925 }
7926
7927 pub fn set(&mut self, value: T) {
7929 let timestamp = std::time::SystemTime::now()
7930 .duration_since(std::time::UNIX_EPOCH)
7931 .unwrap()
7932 .as_secs();
7933 self.history.push((self.value.clone(), timestamp));
7934 self.value = value;
7935 }
7936
7937 pub fn get(&self) -> &T {
7939 &self.value
7940 }
7941
7942 pub fn subscriber_count(&self) -> usize {
7944 self.subscribers.len()
7945 }
7946}
7947
7948#[derive(Debug, Clone, Serialize, Deserialize)]
7950pub struct ReactiveWorkflow {
7951 pub workflow_id: Uuid,
7953 pub watched_observables: Vec<String>,
7955 pub reaction_task: Signature,
7957 pub debounce_ms: Option<u64>,
7959 pub throttle_ms: Option<u64>,
7961 pub filter: Option<String>,
7963}
7964
7965impl ReactiveWorkflow {
7966 pub fn new(reaction_task: Signature) -> Self {
7968 Self {
7969 workflow_id: Uuid::new_v4(),
7970 watched_observables: Vec::new(),
7971 reaction_task,
7972 debounce_ms: None,
7973 throttle_ms: None,
7974 filter: None,
7975 }
7976 }
7977
7978 pub fn watch(mut self, observable_id: impl Into<String>) -> Self {
7980 self.watched_observables.push(observable_id.into());
7981 self
7982 }
7983
7984 pub fn with_debounce(mut self, milliseconds: u64) -> Self {
7986 self.debounce_ms = Some(milliseconds);
7987 self
7988 }
7989
7990 pub fn with_throttle(mut self, milliseconds: u64) -> Self {
7992 self.throttle_ms = Some(milliseconds);
7993 self
7994 }
7995
7996 pub fn with_filter(mut self, condition: impl Into<String>) -> Self {
7998 self.filter = Some(condition.into());
7999 self
8000 }
8001}
8002
8003impl std::fmt::Display for ReactiveWorkflow {
8004 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
8005 write!(
8006 f,
8007 "ReactiveWorkflow[id={}, watching={}, reaction={}]",
8008 self.workflow_id,
8009 self.watched_observables.len(),
8010 self.reaction_task.task
8011 )
8012 }
8013}
8014
8015#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
8017pub enum StreamOperator {
8018 Map,
8020 Filter,
8022 Reduce,
8024 Scan,
8026 Take,
8028 Skip,
8030 Debounce,
8032 Throttle,
8034}
8035
8036impl std::fmt::Display for StreamOperator {
8037 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
8038 match self {
8039 Self::Map => write!(f, "Map"),
8040 Self::Filter => write!(f, "Filter"),
8041 Self::Reduce => write!(f, "Reduce"),
8042 Self::Scan => write!(f, "Scan"),
8043 Self::Take => write!(f, "Take"),
8044 Self::Skip => write!(f, "Skip"),
8045 Self::Debounce => write!(f, "Debounce"),
8046 Self::Throttle => write!(f, "Throttle"),
8047 }
8048 }
8049}
8050
8051#[derive(Debug, Clone, Serialize, Deserialize)]
8053pub struct ReactiveStream {
8054 pub stream_id: Uuid,
8056 pub source_id: String,
8058 pub operators: Vec<(StreamOperator, serde_json::Value)>,
8060 #[serde(skip)]
8062 pub subscribers: Vec<Uuid>,
8063}
8064
8065impl ReactiveStream {
8066 pub fn new(source_id: impl Into<String>) -> Self {
8068 Self {
8069 stream_id: Uuid::new_v4(),
8070 source_id: source_id.into(),
8071 operators: Vec::new(),
8072 subscribers: Vec::new(),
8073 }
8074 }
8075
8076 pub fn map(mut self, transform: serde_json::Value) -> Self {
8078 self.operators.push((StreamOperator::Map, transform));
8079 self
8080 }
8081
8082 pub fn filter(mut self, condition: serde_json::Value) -> Self {
8084 self.operators.push((StreamOperator::Filter, condition));
8085 self
8086 }
8087
8088 pub fn take(mut self, count: usize) -> Self {
8090 self.operators
8091 .push((StreamOperator::Take, serde_json::json!(count)));
8092 self
8093 }
8094
8095 pub fn skip(mut self, count: usize) -> Self {
8097 self.operators
8098 .push((StreamOperator::Skip, serde_json::json!(count)));
8099 self
8100 }
8101
8102 pub fn debounce(mut self, milliseconds: u64) -> Self {
8104 self.operators
8105 .push((StreamOperator::Debounce, serde_json::json!(milliseconds)));
8106 self
8107 }
8108
8109 pub fn throttle(mut self, milliseconds: u64) -> Self {
8111 self.operators
8112 .push((StreamOperator::Throttle, serde_json::json!(milliseconds)));
8113 self
8114 }
8115
8116 pub fn subscribe(&mut self, workflow_id: Uuid) {
8118 if !self.subscribers.contains(&workflow_id) {
8119 self.subscribers.push(workflow_id);
8120 }
8121 }
8122}
8123
8124impl std::fmt::Display for ReactiveStream {
8125 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
8126 write!(
8127 f,
8128 "ReactiveStream[id={}, source={}, operators={}]",
8129 self.stream_id,
8130 self.source_id,
8131 self.operators.len()
8132 )
8133 }
8134}
8135
8136#[derive(Debug, Clone, Serialize, Deserialize)]
8142pub struct ResourceUtilization {
8143 pub cpu: f64,
8145 pub memory: f64,
8147 pub disk_io: f64,
8149 pub network_io: f64,
8151 pub timestamp: u64,
8153}
8154
8155impl ResourceUtilization {
8156 pub fn new(cpu: f64, memory: f64, disk_io: f64, network_io: f64) -> Self {
8158 Self {
8159 cpu: cpu.clamp(0.0, 1.0),
8160 memory: memory.clamp(0.0, 1.0),
8161 disk_io: disk_io.clamp(0.0, 1.0),
8162 network_io: network_io.clamp(0.0, 1.0),
8163 timestamp: std::time::SystemTime::now()
8164 .duration_since(std::time::UNIX_EPOCH)
8165 .unwrap()
8166 .as_secs(),
8167 }
8168 }
8169
8170 pub fn overall(&self) -> f64 {
8172 (self.cpu + self.memory + self.disk_io + self.network_io) / 4.0
8173 }
8174
8175 pub fn is_overloaded(&self, threshold: f64) -> bool {
8177 self.cpu > threshold
8178 || self.memory > threshold
8179 || self.disk_io > threshold
8180 || self.network_io > threshold
8181 }
8182
8183 pub fn bottleneck(&self) -> &'static str {
8185 let max = self
8186 .cpu
8187 .max(self.memory)
8188 .max(self.disk_io)
8189 .max(self.network_io);
8190 if (max - self.cpu).abs() < f64::EPSILON {
8191 "cpu"
8192 } else if (max - self.memory).abs() < f64::EPSILON {
8193 "memory"
8194 } else if (max - self.disk_io).abs() < f64::EPSILON {
8195 "disk_io"
8196 } else {
8197 "network_io"
8198 }
8199 }
8200}
8201
8202impl std::fmt::Display for ResourceUtilization {
8203 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
8204 write!(
8205 f,
8206 "ResourceUtilization[cpu={:.2}, mem={:.2}, disk={:.2}, net={:.2}, overall={:.2}]",
8207 self.cpu,
8208 self.memory,
8209 self.disk_io,
8210 self.network_io,
8211 self.overall()
8212 )
8213 }
8214}
8215
8216#[derive(Debug, Clone)]
8218pub struct WorkflowResourceMonitor {
8219 pub workflow_id: Uuid,
8221 pub history: Vec<ResourceUtilization>,
8223 pub max_history: usize,
8225 pub sampling_interval: u64,
8227}
8228
8229impl WorkflowResourceMonitor {
8230 pub fn new(workflow_id: Uuid) -> Self {
8232 Self {
8233 workflow_id,
8234 history: Vec::new(),
8235 max_history: 1000,
8236 sampling_interval: 5,
8237 }
8238 }
8239
8240 pub fn with_max_history(mut self, max: usize) -> Self {
8242 self.max_history = max;
8243 self
8244 }
8245
8246 pub fn with_sampling_interval(mut self, seconds: u64) -> Self {
8248 self.sampling_interval = seconds;
8249 self
8250 }
8251
8252 pub fn record(&mut self, utilization: ResourceUtilization) {
8254 self.history.push(utilization);
8255 if self.history.len() > self.max_history {
8257 self.history
8258 .drain(0..(self.history.len() - self.max_history));
8259 }
8260 }
8261
8262 pub fn average_utilization(&self, window_seconds: u64) -> Option<ResourceUtilization> {
8264 if self.history.is_empty() {
8265 return None;
8266 }
8267
8268 let now = std::time::SystemTime::now()
8269 .duration_since(std::time::UNIX_EPOCH)
8270 .unwrap()
8271 .as_secs();
8272 let cutoff = now.saturating_sub(window_seconds);
8273
8274 let recent: Vec<_> = self
8275 .history
8276 .iter()
8277 .filter(|u| u.timestamp >= cutoff)
8278 .collect();
8279
8280 if recent.is_empty() {
8281 return None;
8282 }
8283
8284 let sum_cpu: f64 = recent.iter().map(|u| u.cpu).sum();
8285 let sum_memory: f64 = recent.iter().map(|u| u.memory).sum();
8286 let sum_disk: f64 = recent.iter().map(|u| u.disk_io).sum();
8287 let sum_network: f64 = recent.iter().map(|u| u.network_io).sum();
8288 let count = recent.len() as f64;
8289
8290 Some(ResourceUtilization::new(
8291 sum_cpu / count,
8292 sum_memory / count,
8293 sum_disk / count,
8294 sum_network / count,
8295 ))
8296 }
8297
8298 pub fn peak_utilization(&self) -> Option<&ResourceUtilization> {
8300 self.history.iter().max_by(|a, b| {
8301 a.overall()
8302 .partial_cmp(&b.overall())
8303 .unwrap_or(std::cmp::Ordering::Equal)
8304 })
8305 }
8306
8307 pub fn clear(&mut self) {
8309 self.history.clear();
8310 }
8311}
8312
8313impl std::fmt::Display for WorkflowResourceMonitor {
8314 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
8315 write!(
8316 f,
8317 "WorkflowResourceMonitor[workflow={}, samples={}]",
8318 self.workflow_id,
8319 self.history.len()
8320 )
8321 }
8322}
8323
8324#[derive(Debug, Clone, Serialize, Deserialize)]
8330pub struct MockTaskResult {
8331 pub task_name: String,
8333 pub result: serde_json::Value,
8335 pub delay_ms: u64,
8337 pub should_fail: bool,
8339 pub failure_message: Option<String>,
8341}
8342
8343impl MockTaskResult {
8344 pub fn success(task_name: impl Into<String>, result: serde_json::Value) -> Self {
8346 Self {
8347 task_name: task_name.into(),
8348 result,
8349 delay_ms: 0,
8350 should_fail: false,
8351 failure_message: None,
8352 }
8353 }
8354
8355 pub fn failure(task_name: impl Into<String>, message: impl Into<String>) -> Self {
8357 Self {
8358 task_name: task_name.into(),
8359 result: serde_json::Value::Null,
8360 delay_ms: 0,
8361 should_fail: true,
8362 failure_message: Some(message.into()),
8363 }
8364 }
8365
8366 pub fn with_delay(mut self, milliseconds: u64) -> Self {
8368 self.delay_ms = milliseconds;
8369 self
8370 }
8371}
8372
8373#[derive(Debug, Clone)]
8375pub struct MockTaskExecutor {
8376 pub mock_results: HashMap<String, MockTaskResult>,
8378 pub execution_history: Vec<(String, u64)>, }
8381
8382impl MockTaskExecutor {
8383 pub fn new() -> Self {
8385 Self {
8386 mock_results: HashMap::new(),
8387 execution_history: Vec::new(),
8388 }
8389 }
8390
8391 pub fn register(&mut self, result: MockTaskResult) {
8393 self.mock_results.insert(result.task_name.clone(), result);
8394 }
8395
8396 pub fn execute(&mut self, task_name: &str) -> Result<serde_json::Value, String> {
8398 let timestamp = std::time::SystemTime::now()
8399 .duration_since(std::time::UNIX_EPOCH)
8400 .unwrap()
8401 .as_secs();
8402 self.execution_history
8403 .push((task_name.to_string(), timestamp));
8404
8405 if let Some(mock_result) = self.mock_results.get(task_name) {
8406 if mock_result.delay_ms > 0 {
8408 std::thread::sleep(std::time::Duration::from_millis(mock_result.delay_ms));
8409 }
8410
8411 if mock_result.should_fail {
8412 Err(mock_result
8413 .failure_message
8414 .clone()
8415 .unwrap_or_else(|| "Mock task failed".to_string()))
8416 } else {
8417 Ok(mock_result.result.clone())
8418 }
8419 } else {
8420 Err(format!("No mock result registered for task: {}", task_name))
8421 }
8422 }
8423
8424 pub fn execution_count(&self, task_name: &str) -> usize {
8426 self.execution_history
8427 .iter()
8428 .filter(|(name, _)| name == task_name)
8429 .count()
8430 }
8431
8432 pub fn clear_history(&mut self) {
8434 self.execution_history.clear();
8435 }
8436}
8437
8438impl Default for MockTaskExecutor {
8439 fn default() -> Self {
8440 Self::new()
8441 }
8442}
8443
8444#[derive(Debug, Clone)]
8446pub struct TestDataInjector {
8447 pub data: HashMap<String, serde_json::Value>,
8449}
8450
8451impl TestDataInjector {
8452 pub fn new() -> Self {
8454 Self {
8455 data: HashMap::new(),
8456 }
8457 }
8458
8459 pub fn inject(&mut self, key: impl Into<String>, value: serde_json::Value) {
8461 self.data.insert(key.into(), value);
8462 }
8463
8464 pub fn get(&self, key: &str) -> Option<&serde_json::Value> {
8466 self.data.get(key)
8467 }
8468
8469 pub fn clear(&mut self) {
8471 self.data.clear();
8472 }
8473}
8474
8475impl Default for TestDataInjector {
8476 fn default() -> Self {
8477 Self::new()
8478 }
8479}
8480
8481#[derive(Debug, Clone, Serialize, Deserialize)]
8487pub struct WorkflowSnapshot {
8488 pub snapshot_id: Uuid,
8490 pub workflow_id: Uuid,
8492 pub timestamp: u64,
8494 pub state: WorkflowState,
8496 pub completed_tasks: Vec<Uuid>,
8498 pub task_results: HashMap<Uuid, serde_json::Value>,
8500 pub checkpoint: Option<WorkflowCheckpoint>,
8502}
8503
8504impl WorkflowSnapshot {
8505 pub fn new(workflow_id: Uuid, state: WorkflowState) -> Self {
8507 Self {
8508 snapshot_id: Uuid::new_v4(),
8509 workflow_id,
8510 timestamp: std::time::SystemTime::now()
8511 .duration_since(std::time::UNIX_EPOCH)
8512 .unwrap()
8513 .as_secs(),
8514 state,
8515 completed_tasks: Vec::new(),
8516 task_results: HashMap::new(),
8517 checkpoint: None,
8518 }
8519 }
8520
8521 pub fn record_task(&mut self, task_id: Uuid, result: serde_json::Value) {
8523 self.completed_tasks.push(task_id);
8524 self.task_results.insert(task_id, result);
8525 }
8526
8527 pub fn with_checkpoint(mut self, checkpoint: WorkflowCheckpoint) -> Self {
8529 self.checkpoint = Some(checkpoint);
8530 self
8531 }
8532}
8533
8534#[derive(Debug, Clone)]
8536pub struct TimeTravelDebugger {
8537 pub workflow_id: Uuid,
8539 pub snapshots: Vec<WorkflowSnapshot>,
8541 pub current_index: usize,
8543 pub step_mode: bool,
8545}
8546
8547impl TimeTravelDebugger {
8548 pub fn new(workflow_id: Uuid) -> Self {
8550 Self {
8551 workflow_id,
8552 snapshots: Vec::new(),
8553 current_index: 0,
8554 step_mode: false,
8555 }
8556 }
8557
8558 pub fn record_snapshot(&mut self, snapshot: WorkflowSnapshot) {
8560 self.snapshots.push(snapshot);
8561 self.current_index = self.snapshots.len() - 1;
8562 }
8563
8564 pub fn replay_from(&mut self, snapshot_index: usize) -> Option<&WorkflowSnapshot> {
8566 if snapshot_index < self.snapshots.len() {
8567 self.current_index = snapshot_index;
8568 self.snapshots.get(snapshot_index)
8569 } else {
8570 None
8571 }
8572 }
8573
8574 pub fn step_forward(&mut self) -> Option<&WorkflowSnapshot> {
8576 if self.current_index + 1 < self.snapshots.len() {
8577 self.current_index += 1;
8578 self.snapshots.get(self.current_index)
8579 } else {
8580 None
8581 }
8582 }
8583
8584 pub fn step_backward(&mut self) -> Option<&WorkflowSnapshot> {
8586 if self.current_index > 0 {
8587 self.current_index -= 1;
8588 self.snapshots.get(self.current_index)
8589 } else {
8590 None
8591 }
8592 }
8593
8594 pub fn current_snapshot(&self) -> Option<&WorkflowSnapshot> {
8596 self.snapshots.get(self.current_index)
8597 }
8598
8599 pub fn enable_step_mode(&mut self) {
8601 self.step_mode = true;
8602 }
8603
8604 pub fn disable_step_mode(&mut self) {
8606 self.step_mode = false;
8607 }
8608
8609 pub fn snapshot_count(&self) -> usize {
8611 self.snapshots.len()
8612 }
8613
8614 pub fn clear(&mut self) {
8616 self.snapshots.clear();
8617 self.current_index = 0;
8618 }
8619}
8620
8621impl std::fmt::Display for TimeTravelDebugger {
8622 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
8623 write!(
8624 f,
8625 "TimeTravelDebugger[workflow={}, snapshots={}, current={}]",
8626 self.workflow_id,
8627 self.snapshots.len(),
8628 self.current_index
8629 )
8630 }
8631}
8632
8633#[derive(Debug, Clone, Serialize, Deserialize)]
8639pub struct VisualTheme {
8640 pub name: String,
8642 pub colors: HashMap<String, String>,
8644 pub shapes: HashMap<String, String>,
8646 pub edge_styles: HashMap<String, String>,
8648 pub font_family: String,
8650 pub font_size: u8,
8651}
8652
8653impl VisualTheme {
8654 pub fn light() -> Self {
8656 let mut colors = HashMap::new();
8657 colors.insert("pending".to_string(), "#E0E0E0".to_string());
8658 colors.insert("running".to_string(), "#2196F3".to_string());
8659 colors.insert("completed".to_string(), "#4CAF50".to_string());
8660 colors.insert("failed".to_string(), "#F44336".to_string());
8661 colors.insert("cancelled".to_string(), "#FF9800".to_string());
8662
8663 let mut shapes = HashMap::new();
8664 shapes.insert("task".to_string(), "box".to_string());
8665 shapes.insert("group".to_string(), "ellipse".to_string());
8666 shapes.insert("chord".to_string(), "diamond".to_string());
8667
8668 let mut edge_styles = HashMap::new();
8669 edge_styles.insert("chain".to_string(), "solid".to_string());
8670 edge_styles.insert("callback".to_string(), "dashed".to_string());
8671 edge_styles.insert("error".to_string(), "dotted".to_string());
8672
8673 Self {
8674 name: "light".to_string(),
8675 colors,
8676 shapes,
8677 edge_styles,
8678 font_family: "Arial".to_string(),
8679 font_size: 12,
8680 }
8681 }
8682
8683 pub fn dark() -> Self {
8685 let mut colors = HashMap::new();
8686 colors.insert("pending".to_string(), "#424242".to_string());
8687 colors.insert("running".to_string(), "#1976D2".to_string());
8688 colors.insert("completed".to_string(), "#388E3C".to_string());
8689 colors.insert("failed".to_string(), "#D32F2F".to_string());
8690 colors.insert("cancelled".to_string(), "#F57C00".to_string());
8691
8692 let mut shapes = HashMap::new();
8693 shapes.insert("task".to_string(), "box".to_string());
8694 shapes.insert("group".to_string(), "ellipse".to_string());
8695 shapes.insert("chord".to_string(), "diamond".to_string());
8696
8697 let mut edge_styles = HashMap::new();
8698 edge_styles.insert("chain".to_string(), "solid".to_string());
8699 edge_styles.insert("callback".to_string(), "dashed".to_string());
8700 edge_styles.insert("error".to_string(), "dotted".to_string());
8701
8702 Self {
8703 name: "dark".to_string(),
8704 colors,
8705 shapes,
8706 edge_styles,
8707 font_family: "Arial".to_string(),
8708 font_size: 12,
8709 }
8710 }
8711
8712 pub fn color_for_state(&self, state: &str) -> Option<&str> {
8714 self.colors.get(state).map(|s| s.as_str())
8715 }
8716
8717 pub fn shape_for_type(&self, task_type: &str) -> Option<&str> {
8719 self.shapes.get(task_type).map(|s| s.as_str())
8720 }
8721}
8722
8723impl Default for VisualTheme {
8724 fn default() -> Self {
8725 Self::light()
8726 }
8727}
8728
8729#[derive(Debug, Clone, Serialize, Deserialize)]
8731pub struct TaskVisualMetadata {
8732 pub task_id: Uuid,
8734 pub task_name: String,
8736 pub state: String,
8738 pub progress: f64,
8740 pub position: Option<(f64, f64)>,
8742 pub color: String,
8744 pub shape: String,
8746 pub css_classes: Vec<String>,
8748 pub metadata: HashMap<String, serde_json::Value>,
8750}
8751
8752impl TaskVisualMetadata {
8753 pub fn new(task_id: Uuid, task_name: String, state: String) -> Self {
8755 Self {
8756 task_id,
8757 task_name,
8758 state: state.clone(),
8759 progress: 0.0,
8760 position: None,
8761 color: Self::default_color_for_state(&state),
8762 shape: "box".to_string(),
8763 css_classes: vec![format!("task-{}", state)],
8764 metadata: HashMap::new(),
8765 }
8766 }
8767
8768 fn default_color_for_state(state: &str) -> String {
8770 match state {
8771 "pending" => "#E0E0E0",
8772 "running" => "#2196F3",
8773 "completed" => "#4CAF50",
8774 "failed" => "#F44336",
8775 "cancelled" => "#FF9800",
8776 _ => "#9E9E9E",
8777 }
8778 .to_string()
8779 }
8780
8781 pub fn with_progress(mut self, progress: f64) -> Self {
8783 self.progress = progress.clamp(0.0, 100.0);
8784 self
8785 }
8786
8787 pub fn with_position(mut self, x: f64, y: f64) -> Self {
8789 self.position = Some((x, y));
8790 self
8791 }
8792
8793 pub fn with_color(mut self, color: String) -> Self {
8795 self.color = color;
8796 self
8797 }
8798
8799 pub fn add_css_class(&mut self, class: String) {
8801 if !self.css_classes.contains(&class) {
8802 self.css_classes.push(class);
8803 }
8804 }
8805
8806 pub fn add_metadata(&mut self, key: String, value: serde_json::Value) {
8808 self.metadata.insert(key, value);
8809 }
8810}
8811
8812#[derive(Debug, Clone, Serialize, Deserialize)]
8814pub struct WorkflowVisualizationData {
8815 pub workflow_id: Uuid,
8817 pub workflow_name: String,
8819 pub state: WorkflowState,
8821 pub tasks: Vec<TaskVisualMetadata>,
8823 pub edges: Vec<(Uuid, Uuid, String)>,
8825 pub theme: VisualTheme,
8827 pub layout_hint: String,
8829 pub viewport: (f64, f64),
8831}
8832
8833impl WorkflowVisualizationData {
8834 pub fn new(workflow_id: Uuid, workflow_name: String, state: WorkflowState) -> Self {
8836 Self {
8837 workflow_id,
8838 workflow_name,
8839 state,
8840 tasks: Vec::new(),
8841 edges: Vec::new(),
8842 theme: VisualTheme::default(),
8843 layout_hint: "hierarchical".to_string(),
8844 viewport: (1000.0, 600.0),
8845 }
8846 }
8847
8848 pub fn add_task(&mut self, task: TaskVisualMetadata) {
8850 self.tasks.push(task);
8851 }
8852
8853 pub fn add_edge(&mut self, from: Uuid, to: Uuid, edge_type: String) {
8855 self.edges.push((from, to, edge_type));
8856 }
8857
8858 pub fn with_theme(mut self, theme: VisualTheme) -> Self {
8860 self.theme = theme;
8861 self
8862 }
8863
8864 pub fn with_layout(mut self, layout_hint: String) -> Self {
8866 self.layout_hint = layout_hint;
8867 self
8868 }
8869
8870 pub fn to_json(&self) -> Result<String, serde_json::Error> {
8872 serde_json::to_string_pretty(self)
8873 }
8874
8875 pub fn to_visjs_format(&self) -> serde_json::Value {
8877 let nodes: Vec<serde_json::Value> = self
8878 .tasks
8879 .iter()
8880 .map(|task| {
8881 serde_json::json!({
8882 "id": task.task_id.to_string(),
8883 "label": task.task_name,
8884 "color": task.color,
8885 "shape": task.shape,
8886 "title": format!("{} ({})", task.task_name, task.state),
8887 "value": task.progress,
8888 })
8889 })
8890 .collect();
8891
8892 let edges: Vec<serde_json::Value> = self
8893 .edges
8894 .iter()
8895 .map(|(from, to, edge_type)| {
8896 serde_json::json!({
8897 "from": from.to_string(),
8898 "to": to.to_string(),
8899 "arrows": "to",
8900 "dashes": edge_type == "callback",
8901 })
8902 })
8903 .collect();
8904
8905 serde_json::json!({
8906 "nodes": nodes,
8907 "edges": edges,
8908 })
8909 }
8910}
8911
8912#[derive(Debug, Clone, Serialize, Deserialize)]
8914pub struct TimelineEntry {
8915 pub task_id: Uuid,
8917 pub task_name: String,
8919 pub start_time: u64,
8921 pub end_time: Option<u64>,
8923 pub duration: Option<u64>,
8925 pub state: String,
8927 pub worker_id: Option<String>,
8929 pub parent_id: Option<Uuid>,
8931 pub color: String,
8933}
8934
8935impl TimelineEntry {
8936 pub fn new(task_id: Uuid, task_name: String, start_time: u64) -> Self {
8938 Self {
8939 task_id,
8940 task_name,
8941 start_time,
8942 end_time: None,
8943 duration: None,
8944 state: "running".to_string(),
8945 worker_id: None,
8946 parent_id: None,
8947 color: "#2196F3".to_string(),
8948 }
8949 }
8950
8951 pub fn complete(&mut self, end_time: u64) {
8953 self.end_time = Some(end_time);
8954 self.duration = Some(end_time.saturating_sub(self.start_time));
8955 self.state = "completed".to_string();
8956 self.color = "#4CAF50".to_string();
8957 }
8958
8959 pub fn fail(&mut self, end_time: u64) {
8961 self.end_time = Some(end_time);
8962 self.duration = Some(end_time.saturating_sub(self.start_time));
8963 self.state = "failed".to_string();
8964 self.color = "#F44336".to_string();
8965 }
8966
8967 pub fn with_worker(mut self, worker_id: String) -> Self {
8969 self.worker_id = Some(worker_id);
8970 self
8971 }
8972
8973 pub fn with_parent(mut self, parent_id: Uuid) -> Self {
8975 self.parent_id = Some(parent_id);
8976 self
8977 }
8978}
8979
8980#[derive(Debug, Clone, Serialize, Deserialize)]
8982pub struct ExecutionTimeline {
8983 pub workflow_id: Uuid,
8985 pub entries: Vec<TimelineEntry>,
8987 pub workflow_start: u64,
8989 pub workflow_end: Option<u64>,
8991}
8992
8993impl ExecutionTimeline {
8994 pub fn new(workflow_id: Uuid) -> Self {
8996 Self {
8997 workflow_id,
8998 entries: Vec::new(),
8999 workflow_start: std::time::SystemTime::now()
9000 .duration_since(std::time::UNIX_EPOCH)
9001 .unwrap()
9002 .as_millis() as u64,
9003 workflow_end: None,
9004 }
9005 }
9006
9007 pub fn add_entry(&mut self, entry: TimelineEntry) {
9009 self.entries.push(entry);
9010 }
9011
9012 pub fn start_task(&mut self, task_id: Uuid, task_name: String) -> usize {
9014 let now = std::time::SystemTime::now()
9015 .duration_since(std::time::UNIX_EPOCH)
9016 .unwrap()
9017 .as_millis() as u64;
9018 let entry = TimelineEntry::new(task_id, task_name, now);
9019 self.entries.push(entry);
9020 self.entries.len() - 1
9021 }
9022
9023 pub fn complete_task(&mut self, index: usize) {
9025 if let Some(entry) = self.entries.get_mut(index) {
9026 let now = std::time::SystemTime::now()
9027 .duration_since(std::time::UNIX_EPOCH)
9028 .unwrap()
9029 .as_millis() as u64;
9030 entry.complete(now);
9031 }
9032 }
9033
9034 pub fn fail_task(&mut self, index: usize) {
9036 if let Some(entry) = self.entries.get_mut(index) {
9037 let now = std::time::SystemTime::now()
9038 .duration_since(std::time::UNIX_EPOCH)
9039 .unwrap()
9040 .as_millis() as u64;
9041 entry.fail(now);
9042 }
9043 }
9044
9045 pub fn complete_workflow(&mut self) {
9047 self.workflow_end = Some(
9048 std::time::SystemTime::now()
9049 .duration_since(std::time::UNIX_EPOCH)
9050 .unwrap()
9051 .as_millis() as u64,
9052 );
9053 }
9054
9055 pub fn to_json(&self) -> Result<String, serde_json::Error> {
9057 serde_json::to_string_pretty(self)
9058 }
9059
9060 pub fn to_google_charts_format(&self) -> serde_json::Value {
9062 let rows: Vec<serde_json::Value> = self
9063 .entries
9064 .iter()
9065 .map(|entry| {
9066 serde_json::json!([
9067 entry.task_name,
9068 entry.task_name,
9069 entry.start_time,
9070 entry.end_time.unwrap_or(entry.start_time),
9071 ])
9072 })
9073 .collect();
9074
9075 serde_json::json!({
9076 "cols": [
9077 {"id": "", "label": "Task ID", "type": "string"},
9078 {"id": "", "label": "Task Name", "type": "string"},
9079 {"id": "", "label": "Start", "type": "number"},
9080 {"id": "", "label": "End", "type": "number"}
9081 ],
9082 "rows": rows.iter().map(|row| serde_json::json!({"c": row})).collect::<Vec<_>>()
9083 })
9084 }
9085}
9086
9087#[derive(Debug, Clone, Serialize, Deserialize)]
9089pub struct AnimationFrame {
9090 pub frame_number: usize,
9092 pub timestamp: u64,
9094 pub workflow_state: WorkflowState,
9096 pub task_states: HashMap<Uuid, String>,
9098 pub active_tasks: Vec<Uuid>,
9100 pub completed_tasks: Vec<Uuid>,
9102 pub events: Vec<WorkflowEvent>,
9104}
9105
9106impl AnimationFrame {
9107 pub fn new(frame_number: usize, workflow_state: WorkflowState) -> Self {
9109 Self {
9110 frame_number,
9111 timestamp: std::time::SystemTime::now()
9112 .duration_since(std::time::UNIX_EPOCH)
9113 .unwrap()
9114 .as_millis() as u64,
9115 workflow_state,
9116 task_states: HashMap::new(),
9117 active_tasks: Vec::new(),
9118 completed_tasks: Vec::new(),
9119 events: Vec::new(),
9120 }
9121 }
9122
9123 pub fn set_task_state(&mut self, task_id: Uuid, state: String) {
9125 self.task_states.insert(task_id, state);
9126 }
9127
9128 pub fn add_active_task(&mut self, task_id: Uuid) {
9130 if !self.active_tasks.contains(&task_id) {
9131 self.active_tasks.push(task_id);
9132 }
9133 }
9134
9135 pub fn add_completed_task(&mut self, task_id: Uuid) {
9137 if !self.completed_tasks.contains(&task_id) {
9138 self.completed_tasks.push(task_id);
9139 }
9140 self.active_tasks.retain(|id| id != &task_id);
9142 }
9143
9144 pub fn add_event(&mut self, event: WorkflowEvent) {
9146 self.events.push(event);
9147 }
9148}
9149
9150#[derive(Debug, Clone, Serialize, Deserialize)]
9152pub struct WorkflowAnimation {
9153 pub workflow_id: Uuid,
9155 pub frames: Vec<AnimationFrame>,
9157 pub frame_duration: u64,
9159 pub total_duration: u64,
9161}
9162
9163impl WorkflowAnimation {
9164 pub fn new(workflow_id: Uuid, frame_duration: u64) -> Self {
9166 Self {
9167 workflow_id,
9168 frames: Vec::new(),
9169 frame_duration,
9170 total_duration: 0,
9171 }
9172 }
9173
9174 pub fn add_frame(&mut self, frame: AnimationFrame) {
9176 self.frames.push(frame);
9177 self.total_duration = self.frames.len() as u64 * self.frame_duration;
9178 }
9179
9180 pub fn get_frame(&self, index: usize) -> Option<&AnimationFrame> {
9182 self.frames.get(index)
9183 }
9184
9185 pub fn frame_count(&self) -> usize {
9187 self.frames.len()
9188 }
9189
9190 pub fn to_json(&self) -> Result<String, serde_json::Error> {
9192 serde_json::to_string_pretty(self)
9193 }
9194}
9195
9196pub trait DagExportWithState {
9198 fn to_dot_with_state(
9200 &self,
9201 state: &WorkflowState,
9202 task_states: &HashMap<Uuid, String>,
9203 ) -> String;
9204
9205 fn to_mermaid_with_state(
9207 &self,
9208 state: &WorkflowState,
9209 task_states: &HashMap<Uuid, String>,
9210 ) -> String;
9211
9212 fn to_json_with_state(
9214 &self,
9215 state: &WorkflowState,
9216 task_states: &HashMap<Uuid, String>,
9217 ) -> Result<String, serde_json::Error>;
9218}
9219
9220impl DagExportWithState for Chain {
9221 fn to_dot_with_state(
9222 &self,
9223 _state: &WorkflowState,
9224 task_states: &HashMap<Uuid, String>,
9225 ) -> String {
9226 let mut dot = String::from("digraph Chain {\n");
9227 dot.push_str(" rankdir=LR;\n");
9228
9229 for (i, sig) in self.tasks.iter().enumerate() {
9230 let task_id = sig.options.task_id.unwrap_or_else(Uuid::new_v4);
9231 let state = task_states
9232 .get(&task_id)
9233 .map(|s| s.as_str())
9234 .unwrap_or("pending");
9235 let color = match state {
9236 "completed" => "#4CAF50",
9237 "running" => "#2196F3",
9238 "failed" => "#F44336",
9239 _ => "#E0E0E0",
9240 };
9241
9242 dot.push_str(&format!(
9243 " task{} [label=\"{}\" style=filled fillcolor=\"{}\"];\n",
9244 i, sig.task, color
9245 ));
9246
9247 if i > 0 {
9248 dot.push_str(&format!(" task{} -> task{};\n", i - 1, i));
9249 }
9250 }
9251
9252 dot.push('}');
9253 dot
9254 }
9255
9256 fn to_mermaid_with_state(
9257 &self,
9258 _state: &WorkflowState,
9259 task_states: &HashMap<Uuid, String>,
9260 ) -> String {
9261 let mut mmd = String::from("graph LR\n");
9262
9263 for (i, sig) in self.tasks.iter().enumerate() {
9264 let task_id = sig.options.task_id.unwrap_or_else(Uuid::new_v4);
9265 let state = task_states
9266 .get(&task_id)
9267 .map(|s| s.as_str())
9268 .unwrap_or("pending");
9269 let style_class = match state {
9270 "completed" => "completed",
9271 "running" => "running",
9272 "failed" => "failed",
9273 _ => "pending",
9274 };
9275
9276 mmd.push_str(&format!(
9277 " task{}[\"{}\"]:::{}\n",
9278 i, sig.task, style_class
9279 ));
9280
9281 if i > 0 {
9282 mmd.push_str(&format!(" task{} --> task{}\n", i - 1, i));
9283 }
9284 }
9285
9286 mmd.push_str("\n classDef completed fill:#4CAF50,stroke:#333,stroke-width:2px\n");
9288 mmd.push_str(" classDef running fill:#2196F3,stroke:#333,stroke-width:2px\n");
9289 mmd.push_str(" classDef failed fill:#F44336,stroke:#333,stroke-width:2px\n");
9290 mmd.push_str(" classDef pending fill:#E0E0E0,stroke:#333,stroke-width:2px\n");
9291
9292 mmd
9293 }
9294
9295 fn to_json_with_state(
9296 &self,
9297 state: &WorkflowState,
9298 task_states: &HashMap<Uuid, String>,
9299 ) -> Result<String, serde_json::Error> {
9300 let mut nodes = Vec::new();
9301 let mut edges = Vec::new();
9302
9303 for (i, sig) in self.tasks.iter().enumerate() {
9304 let task_id = sig.options.task_id.unwrap_or_else(Uuid::new_v4);
9305 let task_state = task_states
9306 .get(&task_id)
9307 .map(|s| s.as_str())
9308 .unwrap_or("pending");
9309
9310 nodes.push(serde_json::json!({
9311 "id": format!("task{}", i),
9312 "label": sig.task,
9313 "state": task_state,
9314 "task_id": task_id,
9315 }));
9316
9317 if i > 0 {
9318 edges.push(serde_json::json!({
9319 "from": format!("task{}", i - 1),
9320 "to": format!("task{}", i),
9321 }));
9322 }
9323 }
9324
9325 let result = serde_json::json!({
9326 "type": "chain",
9327 "workflow_state": state,
9328 "nodes": nodes,
9329 "edges": edges,
9330 });
9331
9332 serde_json::to_string_pretty(&result)
9333 }
9334}
9335
9336impl DagExportWithState for Group {
9337 fn to_dot_with_state(
9338 &self,
9339 _state: &WorkflowState,
9340 task_states: &HashMap<Uuid, String>,
9341 ) -> String {
9342 let mut dot = String::from("digraph Group {\n");
9343
9344 for (i, sig) in self.tasks.iter().enumerate() {
9345 let task_id = sig.options.task_id.unwrap_or_else(Uuid::new_v4);
9346 let state = task_states
9347 .get(&task_id)
9348 .map(|s| s.as_str())
9349 .unwrap_or("pending");
9350 let color = match state {
9351 "completed" => "#4CAF50",
9352 "running" => "#2196F3",
9353 "failed" => "#F44336",
9354 _ => "#E0E0E0",
9355 };
9356
9357 dot.push_str(&format!(
9358 " task{} [label=\"{}\" style=filled fillcolor=\"{}\"];\n",
9359 i, sig.task, color
9360 ));
9361 }
9362
9363 dot.push('}');
9364 dot
9365 }
9366
9367 fn to_mermaid_with_state(
9368 &self,
9369 _state: &WorkflowState,
9370 task_states: &HashMap<Uuid, String>,
9371 ) -> String {
9372 let mut mmd = String::from("graph TB\n");
9373
9374 for (i, sig) in self.tasks.iter().enumerate() {
9375 let task_id = sig.options.task_id.unwrap_or_else(Uuid::new_v4);
9376 let state = task_states
9377 .get(&task_id)
9378 .map(|s| s.as_str())
9379 .unwrap_or("pending");
9380 let style_class = match state {
9381 "completed" => "completed",
9382 "running" => "running",
9383 "failed" => "failed",
9384 _ => "pending",
9385 };
9386
9387 mmd.push_str(&format!(
9388 " task{}[\"{}\"]:::{}\n",
9389 i, sig.task, style_class
9390 ));
9391 }
9392
9393 mmd.push_str("\n classDef completed fill:#4CAF50,stroke:#333,stroke-width:2px\n");
9395 mmd.push_str(" classDef running fill:#2196F3,stroke:#333,stroke-width:2px\n");
9396 mmd.push_str(" classDef failed fill:#F44336,stroke:#333,stroke-width:2px\n");
9397 mmd.push_str(" classDef pending fill:#E0E0E0,stroke:#333,stroke-width:2px\n");
9398
9399 mmd
9400 }
9401
9402 fn to_json_with_state(
9403 &self,
9404 state: &WorkflowState,
9405 task_states: &HashMap<Uuid, String>,
9406 ) -> Result<String, serde_json::Error> {
9407 let mut nodes = Vec::new();
9408
9409 for (i, sig) in self.tasks.iter().enumerate() {
9410 let task_id = sig.options.task_id.unwrap_or_else(Uuid::new_v4);
9411 let task_state = task_states
9412 .get(&task_id)
9413 .map(|s| s.as_str())
9414 .unwrap_or("pending");
9415
9416 nodes.push(serde_json::json!({
9417 "id": format!("task{}", i),
9418 "label": sig.task,
9419 "state": task_state,
9420 "task_id": task_id,
9421 }));
9422 }
9423
9424 let result = serde_json::json!({
9425 "type": "group",
9426 "workflow_state": state,
9427 "nodes": nodes,
9428 "edges": [],
9429 });
9430
9431 serde_json::to_string_pretty(&result)
9432 }
9433}
9434
9435impl DagExportWithState for Chord {
9436 fn to_dot_with_state(
9437 &self,
9438 _state: &WorkflowState,
9439 task_states: &HashMap<Uuid, String>,
9440 ) -> String {
9441 let mut dot = String::from("digraph Chord {\n");
9442 dot.push_str(" rankdir=LR;\n");
9443
9444 for (i, sig) in self.header.tasks.iter().enumerate() {
9446 let task_id = sig.options.task_id.unwrap_or_else(Uuid::new_v4);
9447 let state = task_states
9448 .get(&task_id)
9449 .map(|s| s.as_str())
9450 .unwrap_or("pending");
9451 let color = match state {
9452 "completed" => "#4CAF50",
9453 "running" => "#2196F3",
9454 "failed" => "#F44336",
9455 _ => "#E0E0E0",
9456 };
9457
9458 dot.push_str(&format!(
9459 " task{} [label=\"{}\" style=filled fillcolor=\"{}\"];\n",
9460 i, sig.task, color
9461 ));
9462 }
9463
9464 let task_id = self.body.options.task_id.unwrap_or_else(Uuid::new_v4);
9466 let state = task_states
9467 .get(&task_id)
9468 .map(|s| s.as_str())
9469 .unwrap_or("pending");
9470 let color = match state {
9471 "completed" => "#4CAF50",
9472 "running" => "#2196F3",
9473 "failed" => "#F44336",
9474 _ => "#E0E0E0",
9475 };
9476
9477 dot.push_str(&format!(
9478 " callback [label=\"{}\" shape=diamond style=filled fillcolor=\"{}\"];\n",
9479 self.body.task, color
9480 ));
9481
9482 for i in 0..self.header.tasks.len() {
9483 dot.push_str(&format!(" task{} -> callback;\n", i));
9484 }
9485
9486 dot.push('}');
9487 dot
9488 }
9489
9490 fn to_mermaid_with_state(
9491 &self,
9492 _state: &WorkflowState,
9493 task_states: &HashMap<Uuid, String>,
9494 ) -> String {
9495 let mut mmd = String::from("graph TB\n");
9496
9497 for (i, sig) in self.header.tasks.iter().enumerate() {
9499 let task_id = sig.options.task_id.unwrap_or_else(Uuid::new_v4);
9500 let state = task_states
9501 .get(&task_id)
9502 .map(|s| s.as_str())
9503 .unwrap_or("pending");
9504 let style_class = match state {
9505 "completed" => "completed",
9506 "running" => "running",
9507 "failed" => "failed",
9508 _ => "pending",
9509 };
9510
9511 mmd.push_str(&format!(
9512 " task{}[\"{}\"]:::{}\n",
9513 i, sig.task, style_class
9514 ));
9515 }
9516
9517 let task_id = self.body.options.task_id.unwrap_or_else(Uuid::new_v4);
9519 let state = task_states
9520 .get(&task_id)
9521 .map(|s| s.as_str())
9522 .unwrap_or("pending");
9523 let style_class = match state {
9524 "completed" => "completed",
9525 "running" => "running",
9526 "failed" => "failed",
9527 _ => "pending",
9528 };
9529
9530 mmd.push_str(&format!(
9531 " callback{{\"{}\"}}:::{}\n",
9532 self.body.task, style_class
9533 ));
9534
9535 for i in 0..self.header.tasks.len() {
9536 mmd.push_str(&format!(" task{} --> callback\n", i));
9537 }
9538
9539 mmd.push_str("\n classDef completed fill:#4CAF50,stroke:#333,stroke-width:2px\n");
9541 mmd.push_str(" classDef running fill:#2196F3,stroke:#333,stroke-width:2px\n");
9542 mmd.push_str(" classDef failed fill:#F44336,stroke:#333,stroke-width:2px\n");
9543 mmd.push_str(" classDef pending fill:#E0E0E0,stroke:#333,stroke-width:2px\n");
9544
9545 mmd
9546 }
9547
9548 fn to_json_with_state(
9549 &self,
9550 state: &WorkflowState,
9551 task_states: &HashMap<Uuid, String>,
9552 ) -> Result<String, serde_json::Error> {
9553 let mut nodes = Vec::new();
9554 let mut edges = Vec::new();
9555
9556 for (i, sig) in self.header.tasks.iter().enumerate() {
9558 let task_id = sig.options.task_id.unwrap_or_else(Uuid::new_v4);
9559 let task_state = task_states
9560 .get(&task_id)
9561 .map(|s| s.as_str())
9562 .unwrap_or("pending");
9563
9564 nodes.push(serde_json::json!({
9565 "id": format!("task{}", i),
9566 "label": sig.task,
9567 "state": task_state,
9568 "task_id": task_id,
9569 }));
9570 }
9571
9572 let task_id = self.body.options.task_id.unwrap_or_else(Uuid::new_v4);
9574 let task_state = task_states
9575 .get(&task_id)
9576 .map(|s| s.as_str())
9577 .unwrap_or("pending");
9578
9579 nodes.push(serde_json::json!({
9580 "id": "callback",
9581 "label": self.body.task,
9582 "state": task_state,
9583 "task_id": task_id,
9584 "shape": "diamond",
9585 }));
9586
9587 for i in 0..self.header.tasks.len() {
9588 edges.push(serde_json::json!({
9589 "from": format!("task{}", i),
9590 "to": "callback",
9591 }));
9592 }
9593
9594 let result = serde_json::json!({
9595 "type": "chord",
9596 "workflow_state": state,
9597 "nodes": nodes,
9598 "edges": edges,
9599 });
9600
9601 serde_json::to_string_pretty(&result)
9602 }
9603}
9604
9605#[derive(Debug, Clone)]
9607pub struct WorkflowEventStream {
9608 pub workflow_id: Uuid,
9610 pub events: Vec<(u64, WorkflowEvent)>,
9612 pub max_buffer_size: usize,
9614}
9615
9616impl WorkflowEventStream {
9617 pub fn new(workflow_id: Uuid) -> Self {
9619 Self {
9620 workflow_id,
9621 events: Vec::new(),
9622 max_buffer_size: 1000,
9623 }
9624 }
9625
9626 pub fn with_max_buffer_size(mut self, size: usize) -> Self {
9628 self.max_buffer_size = size;
9629 self
9630 }
9631
9632 pub fn push(&mut self, event: WorkflowEvent) {
9634 let timestamp = std::time::SystemTime::now()
9635 .duration_since(std::time::UNIX_EPOCH)
9636 .unwrap()
9637 .as_millis() as u64;
9638
9639 self.events.push((timestamp, event));
9640
9641 if self.events.len() > self.max_buffer_size {
9643 self.events.remove(0);
9644 }
9645 }
9646
9647 pub fn events_since(&self, timestamp: u64) -> Vec<&(u64, WorkflowEvent)> {
9649 self.events
9650 .iter()
9651 .filter(|(ts, _)| *ts > timestamp)
9652 .collect()
9653 }
9654
9655 pub fn all_events(&self) -> &[(u64, WorkflowEvent)] {
9657 &self.events
9658 }
9659
9660 pub fn clear(&mut self) {
9662 self.events.clear();
9663 }
9664
9665 pub fn to_sse_format(&self) -> Vec<String> {
9667 self.events
9668 .iter()
9669 .map(|(ts, event)| {
9670 format!(
9671 "event: workflow\ndata: {{\"timestamp\": {}, \"event\": \"{}\"}}\n\n",
9672 ts, event
9673 )
9674 })
9675 .collect()
9676 }
9677}
9678
9679#[derive(Debug, Clone, Serialize, Deserialize)]
9685pub struct WorkflowMetricsCollector {
9686 pub workflow_id: Uuid,
9688 pub start_time: u64,
9690 pub end_time: Option<u64>,
9692 pub total_tasks: usize,
9694 pub completed_tasks: usize,
9696 pub failed_tasks: usize,
9698 pub task_durations: HashMap<Uuid, u64>,
9700 pub task_retries: HashMap<Uuid, usize>,
9702 pub total_duration: Option<u64>,
9704 pub avg_task_duration: Option<f64>,
9706 pub success_rate: Option<f64>,
9708}
9709
9710impl WorkflowMetricsCollector {
9711 pub fn new(workflow_id: Uuid) -> Self {
9713 Self {
9714 workflow_id,
9715 start_time: std::time::SystemTime::now()
9716 .duration_since(std::time::UNIX_EPOCH)
9717 .unwrap()
9718 .as_millis() as u64,
9719 end_time: None,
9720 total_tasks: 0,
9721 completed_tasks: 0,
9722 failed_tasks: 0,
9723 task_durations: HashMap::new(),
9724 task_retries: HashMap::new(),
9725 total_duration: None,
9726 avg_task_duration: None,
9727 success_rate: None,
9728 }
9729 }
9730
9731 pub fn record_task_start(&mut self, task_id: Uuid) {
9733 self.total_tasks += 1;
9734 self.task_durations.insert(task_id, 0);
9735 }
9736
9737 pub fn record_task_complete(&mut self, task_id: Uuid, duration_ms: u64) {
9739 self.completed_tasks += 1;
9740 self.task_durations.insert(task_id, duration_ms);
9741 }
9742
9743 pub fn record_task_failure(&mut self, task_id: Uuid, duration_ms: u64) {
9745 self.failed_tasks += 1;
9746 self.task_durations.insert(task_id, duration_ms);
9747 }
9748
9749 pub fn record_task_retry(&mut self, task_id: Uuid) {
9751 *self.task_retries.entry(task_id).or_insert(0) += 1;
9752 }
9753
9754 pub fn finalize(&mut self) {
9756 let now = std::time::SystemTime::now()
9757 .duration_since(std::time::UNIX_EPOCH)
9758 .unwrap()
9759 .as_millis() as u64;
9760
9761 self.end_time = Some(now);
9762 self.total_duration = Some(now.saturating_sub(self.start_time));
9763
9764 if !self.task_durations.is_empty() {
9766 let sum: u64 = self.task_durations.values().sum();
9767 self.avg_task_duration = Some(sum as f64 / self.task_durations.len() as f64);
9768 }
9769
9770 if self.total_tasks > 0 {
9772 self.success_rate = Some(self.completed_tasks as f64 / self.total_tasks as f64);
9773 }
9774 }
9775
9776 pub fn summary(&self) -> String {
9778 format!(
9779 "WorkflowMetrics[id={}, total={}, completed={}, failed={}, success_rate={:.2}%, avg_duration={:.2}ms]",
9780 self.workflow_id,
9781 self.total_tasks,
9782 self.completed_tasks,
9783 self.failed_tasks,
9784 self.success_rate.unwrap_or(0.0) * 100.0,
9785 self.avg_task_duration.unwrap_or(0.0)
9786 )
9787 }
9788}
9789
9790impl std::fmt::Display for WorkflowMetricsCollector {
9791 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
9792 write!(f, "{}", self.summary())
9793 }
9794}
9795
9796#[derive(Debug, Clone)]
9798pub struct WorkflowRateLimiter {
9799 pub max_workflows: usize,
9801 pub window_ms: u64,
9803 pub workflow_timestamps: Vec<u64>,
9805 pub total_workflows: usize,
9807 pub rejected_workflows: usize,
9809}
9810
9811impl WorkflowRateLimiter {
9812 pub fn new(max_workflows: usize, window_ms: u64) -> Self {
9814 Self {
9815 max_workflows,
9816 window_ms,
9817 workflow_timestamps: Vec::new(),
9818 total_workflows: 0,
9819 rejected_workflows: 0,
9820 }
9821 }
9822
9823 pub fn allow_workflow(&mut self) -> bool {
9825 let now = std::time::SystemTime::now()
9826 .duration_since(std::time::UNIX_EPOCH)
9827 .unwrap()
9828 .as_millis() as u64;
9829
9830 self.workflow_timestamps
9832 .retain(|&ts| now.saturating_sub(ts) < self.window_ms);
9833
9834 if self.workflow_timestamps.len() < self.max_workflows {
9836 self.workflow_timestamps.push(now);
9837 self.total_workflows += 1;
9838 true
9839 } else {
9840 self.rejected_workflows += 1;
9841 false
9842 }
9843 }
9844
9845 pub fn current_rate(&self) -> f64 {
9847 if self.workflow_timestamps.is_empty() {
9848 return 0.0;
9849 }
9850
9851 let now = std::time::SystemTime::now()
9852 .duration_since(std::time::UNIX_EPOCH)
9853 .unwrap()
9854 .as_millis() as u64;
9855
9856 let active_timestamps: Vec<_> = self
9857 .workflow_timestamps
9858 .iter()
9859 .filter(|&&ts| now.saturating_sub(ts) < self.window_ms)
9860 .collect();
9861
9862 if active_timestamps.is_empty() {
9863 return 0.0;
9864 }
9865
9866 active_timestamps.len() as f64 / (self.window_ms as f64 / 1000.0)
9867 }
9868
9869 pub fn reset(&mut self) {
9871 self.workflow_timestamps.clear();
9872 }
9873
9874 pub fn rejection_rate(&self) -> f64 {
9876 if self.total_workflows == 0 {
9877 0.0
9878 } else {
9879 self.rejected_workflows as f64 / self.total_workflows as f64
9880 }
9881 }
9882}
9883
9884impl std::fmt::Display for WorkflowRateLimiter {
9885 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
9886 write!(
9887 f,
9888 "RateLimiter[max={}/{}ms, current_rate={:.2}/s, rejected={}]",
9889 self.max_workflows,
9890 self.window_ms,
9891 self.current_rate(),
9892 self.rejected_workflows
9893 )
9894 }
9895}
9896
9897#[derive(Debug, Clone)]
9899pub struct WorkflowConcurrencyControl {
9900 pub max_concurrent: usize,
9902 pub active_workflows: HashMap<Uuid, u64>,
9904 pub total_started: usize,
9906 pub total_completed: usize,
9908 pub peak_concurrency: usize,
9910}
9911
9912impl WorkflowConcurrencyControl {
9913 pub fn new(max_concurrent: usize) -> Self {
9915 Self {
9916 max_concurrent,
9917 active_workflows: HashMap::new(),
9918 total_started: 0,
9919 total_completed: 0,
9920 peak_concurrency: 0,
9921 }
9922 }
9923
9924 pub fn try_start(&mut self, workflow_id: Uuid) -> bool {
9926 if self.active_workflows.len() >= self.max_concurrent {
9927 return false;
9928 }
9929
9930 let now = std::time::SystemTime::now()
9931 .duration_since(std::time::UNIX_EPOCH)
9932 .unwrap()
9933 .as_millis() as u64;
9934
9935 self.active_workflows.insert(workflow_id, now);
9936 self.total_started += 1;
9937
9938 if self.active_workflows.len() > self.peak_concurrency {
9940 self.peak_concurrency = self.active_workflows.len();
9941 }
9942
9943 true
9944 }
9945
9946 pub fn complete(&mut self, workflow_id: Uuid) -> bool {
9948 if self.active_workflows.remove(&workflow_id).is_some() {
9949 self.total_completed += 1;
9950 true
9951 } else {
9952 false
9953 }
9954 }
9955
9956 pub fn current_concurrency(&self) -> usize {
9958 self.active_workflows.len()
9959 }
9960
9961 pub fn available_slots(&self) -> usize {
9963 self.max_concurrent
9964 .saturating_sub(self.active_workflows.len())
9965 }
9966
9967 pub fn is_at_capacity(&self) -> bool {
9969 self.active_workflows.len() >= self.max_concurrent
9970 }
9971
9972 pub fn avg_workflow_duration(&self) -> Option<f64> {
9974 if self.total_completed == 0 {
9975 return None;
9976 }
9977
9978 let now = std::time::SystemTime::now()
9979 .duration_since(std::time::UNIX_EPOCH)
9980 .unwrap()
9981 .as_millis() as u64;
9982
9983 let total_duration: u64 = self
9984 .active_workflows
9985 .values()
9986 .map(|&start_time| now.saturating_sub(start_time))
9987 .sum();
9988
9989 Some(total_duration as f64 / self.total_completed as f64)
9990 }
9991}
9992
9993impl std::fmt::Display for WorkflowConcurrencyControl {
9994 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
9995 write!(
9996 f,
9997 "ConcurrencyControl[max={}, active={}, peak={}, available={}]",
9998 self.max_concurrent,
9999 self.current_concurrency(),
10000 self.peak_concurrency,
10001 self.available_slots()
10002 )
10003 }
10004}
10005
10006#[derive(Debug, Clone)]
10008pub struct WorkflowBuilder {
10009 pub name: String,
10011 pub description: Option<String>,
10013 pub tags: Vec<String>,
10015 pub metadata: HashMap<String, serde_json::Value>,
10017}
10018
10019impl WorkflowBuilder {
10020 pub fn new(name: impl Into<String>) -> Self {
10022 Self {
10023 name: name.into(),
10024 description: None,
10025 tags: Vec::new(),
10026 metadata: HashMap::new(),
10027 }
10028 }
10029
10030 pub fn with_description(mut self, description: impl Into<String>) -> Self {
10032 self.description = Some(description.into());
10033 self
10034 }
10035
10036 pub fn add_tag(mut self, tag: impl Into<String>) -> Self {
10038 self.tags.push(tag.into());
10039 self
10040 }
10041
10042 pub fn add_metadata(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
10044 self.metadata.insert(key.into(), value);
10045 self
10046 }
10047
10048 pub fn chain(self) -> Chain {
10050 Chain::new()
10051 }
10052
10053 pub fn group(self) -> Group {
10055 Group::new()
10056 }
10057
10058 pub fn map(self, task: Signature, argsets: Vec<Vec<serde_json::Value>>) -> Map {
10060 Map::new(task, argsets)
10061 }
10062}
10063
10064#[derive(Debug, Clone)]
10066pub struct WorkflowRegistry {
10067 pub workflows: HashMap<Uuid, String>,
10069 pub metadata: HashMap<Uuid, HashMap<String, serde_json::Value>>,
10071 pub states: HashMap<Uuid, WorkflowStatus>,
10073 pub start_times: HashMap<Uuid, u64>,
10075 pub tags: HashMap<String, Vec<Uuid>>,
10077}
10078
10079impl WorkflowRegistry {
10080 pub fn new() -> Self {
10082 Self {
10083 workflows: HashMap::new(),
10084 metadata: HashMap::new(),
10085 states: HashMap::new(),
10086 start_times: HashMap::new(),
10087 tags: HashMap::new(),
10088 }
10089 }
10090
10091 pub fn register(
10093 &mut self,
10094 workflow_id: Uuid,
10095 name: String,
10096 metadata: HashMap<String, serde_json::Value>,
10097 ) {
10098 self.workflows.insert(workflow_id, name);
10099 self.metadata.insert(workflow_id, metadata);
10100 self.states.insert(workflow_id, WorkflowStatus::Pending);
10101
10102 let now = std::time::SystemTime::now()
10103 .duration_since(std::time::UNIX_EPOCH)
10104 .unwrap()
10105 .as_millis() as u64;
10106 self.start_times.insert(workflow_id, now);
10107 }
10108
10109 pub fn update_state(&mut self, workflow_id: Uuid, state: WorkflowStatus) {
10111 self.states.insert(workflow_id, state);
10112 }
10113
10114 pub fn add_tag(&mut self, workflow_id: Uuid, tag: String) {
10116 self.tags.entry(tag).or_default().push(workflow_id);
10117 }
10118
10119 pub fn get_by_tag(&self, tag: &str) -> Vec<Uuid> {
10121 self.tags.get(tag).cloned().unwrap_or_default()
10122 }
10123
10124 pub fn get_state(&self, workflow_id: &Uuid) -> Option<&WorkflowStatus> {
10126 self.states.get(workflow_id)
10127 }
10128
10129 pub fn get_name(&self, workflow_id: &Uuid) -> Option<&str> {
10131 self.workflows.get(workflow_id).map(|s| s.as_str())
10132 }
10133
10134 pub fn get_metadata(&self, workflow_id: &Uuid) -> Option<&HashMap<String, serde_json::Value>> {
10136 self.metadata.get(workflow_id)
10137 }
10138
10139 pub fn remove(&mut self, workflow_id: &Uuid) -> bool {
10141 let removed = self.workflows.remove(workflow_id).is_some();
10142 self.metadata.remove(workflow_id);
10143 self.states.remove(workflow_id);
10144 self.start_times.remove(workflow_id);
10145
10146 for workflows in self.tags.values_mut() {
10148 workflows.retain(|id| id != workflow_id);
10149 }
10150
10151 removed
10152 }
10153
10154 pub fn count(&self) -> usize {
10156 self.workflows.len()
10157 }
10158
10159 pub fn get_by_state(&self, state: &WorkflowStatus) -> Vec<Uuid> {
10161 self.states
10162 .iter()
10163 .filter(|(_, s)| *s == state)
10164 .map(|(id, _)| *id)
10165 .collect()
10166 }
10167
10168 pub fn clear(&mut self) {
10170 self.workflows.clear();
10171 self.metadata.clear();
10172 self.states.clear();
10173 self.start_times.clear();
10174 self.tags.clear();
10175 }
10176
10177 pub fn all_workflow_ids(&self) -> Vec<Uuid> {
10179 self.workflows.keys().copied().collect()
10180 }
10181
10182 pub fn find_by_name(&self, pattern: &str) -> Vec<Uuid> {
10184 self.workflows
10185 .iter()
10186 .filter(|(_, name)| name.contains(pattern))
10187 .map(|(id, _)| *id)
10188 .collect()
10189 }
10190
10191 pub fn get_older_than(&self, duration_ms: u64) -> Vec<Uuid> {
10193 let now = std::time::SystemTime::now()
10194 .duration_since(std::time::UNIX_EPOCH)
10195 .unwrap()
10196 .as_millis() as u64;
10197
10198 self.start_times
10199 .iter()
10200 .filter(|(_, &start_time)| now.saturating_sub(start_time) > duration_ms)
10201 .map(|(id, _)| *id)
10202 .collect()
10203 }
10204
10205 pub fn get_age(&self, workflow_id: &Uuid) -> Option<u64> {
10207 self.start_times.get(workflow_id).map(|&start_time| {
10208 let now = std::time::SystemTime::now()
10209 .duration_since(std::time::UNIX_EPOCH)
10210 .unwrap()
10211 .as_millis() as u64;
10212 now.saturating_sub(start_time)
10213 })
10214 }
10215
10216 pub fn contains(&self, workflow_id: &Uuid) -> bool {
10218 self.workflows.contains_key(workflow_id)
10219 }
10220
10221 pub fn all_tags(&self) -> Vec<String> {
10223 self.tags.keys().cloned().collect()
10224 }
10225
10226 pub fn get_by_tags_all(&self, tags: &[&str]) -> Vec<Uuid> {
10228 if tags.is_empty() {
10229 return Vec::new();
10230 }
10231
10232 let mut result: Option<Vec<Uuid>> = None;
10233
10234 for tag in tags {
10235 let tagged = self.get_by_tag(tag);
10236 result = match result {
10237 None => Some(tagged),
10238 Some(current) => {
10239 Some(
10241 current
10242 .into_iter()
10243 .filter(|id| tagged.contains(id))
10244 .collect(),
10245 )
10246 }
10247 };
10248 }
10249
10250 result.unwrap_or_default()
10251 }
10252
10253 pub fn get_by_tags_any(&self, tags: &[&str]) -> Vec<Uuid> {
10255 let mut result = Vec::new();
10256 for tag in tags {
10257 result.extend(self.get_by_tag(tag));
10258 }
10259 result.sort();
10261 result.dedup();
10262 result
10263 }
10264
10265 pub fn count_by_state(&self, state: &WorkflowStatus) -> usize {
10267 self.states.iter().filter(|(_, s)| *s == state).count()
10268 }
10269
10270 pub fn running_count(&self) -> usize {
10272 self.count_by_state(&WorkflowStatus::Running)
10273 }
10274
10275 pub fn pending_count(&self) -> usize {
10277 self.count_by_state(&WorkflowStatus::Pending)
10278 }
10279
10280 pub fn completed_count(&self) -> usize {
10282 self.count_by_state(&WorkflowStatus::Success) + self.count_by_state(&WorkflowStatus::Failed)
10283 }
10284}
10285
10286impl Default for WorkflowRegistry {
10287 fn default() -> Self {
10288 Self::new()
10289 }
10290}
10291
10292impl std::fmt::Display for WorkflowRegistry {
10293 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
10294 write!(
10295 f,
10296 "WorkflowRegistry[total={}, pending={}, running={}, success={}, failed={}]",
10297 self.count(),
10298 self.get_by_state(&WorkflowStatus::Pending).len(),
10299 self.get_by_state(&WorkflowStatus::Running).len(),
10300 self.get_by_state(&WorkflowStatus::Success).len(),
10301 self.get_by_state(&WorkflowStatus::Failed).len()
10302 )
10303 }
10304}
10305
10306#[cfg(test)]
10307mod tests {
10308 use super::*;
10309
10310 #[test]
10311 fn test_signature_creation() {
10312 let sig = Signature::new("test_task".to_string())
10313 .with_args(vec![serde_json::json!(1), serde_json::json!(2)])
10314 .with_priority(9);
10315
10316 assert_eq!(sig.task, "test_task");
10317 assert_eq!(sig.args.len(), 2);
10318 assert_eq!(sig.options.priority, Some(9));
10319 }
10320
10321 #[test]
10322 fn test_signature_predicates() {
10323 let sig = Signature::new("task".to_string())
10324 .with_args(vec![serde_json::json!(1)])
10325 .with_kwargs({
10326 let mut map = HashMap::new();
10327 map.insert("key".to_string(), serde_json::json!("value"));
10328 map
10329 })
10330 .immutable();
10331
10332 assert!(sig.has_args());
10333 assert!(sig.has_kwargs());
10334 assert!(sig.is_immutable());
10335 }
10336
10337 #[test]
10338 fn test_signature_display() {
10339 let sig = Signature::new("my_task".to_string())
10340 .with_args(vec![serde_json::json!(1), serde_json::json!(2)])
10341 .immutable();
10342
10343 let display = format!("{}", sig);
10344 assert!(display.contains("Signature[task=my_task]"));
10345 assert!(display.contains("args=2"));
10346 assert!(display.contains("(immutable)"));
10347 }
10348
10349 #[test]
10350 fn test_task_options_predicates() {
10351 let mut opts = TaskOptions::default();
10352 assert!(!opts.has_priority());
10353 assert!(!opts.has_queue());
10354 assert!(!opts.has_task_id());
10355 assert!(!opts.has_link());
10356 assert!(!opts.has_link_error());
10357
10358 opts.priority = Some(5);
10359 opts.queue = Some("celery".to_string());
10360 opts.task_id = Some(Uuid::new_v4());
10361 opts.link = Some(Box::new(Signature::new("link_task".to_string())));
10362 opts.link_error = Some(Box::new(Signature::new("error_task".to_string())));
10363
10364 assert!(opts.has_priority());
10365 assert!(opts.has_queue());
10366 assert!(opts.has_task_id());
10367 assert!(opts.has_link());
10368 assert!(opts.has_link_error());
10369 }
10370
10371 #[test]
10372 fn test_task_options_display() {
10373 let task_id = Uuid::new_v4();
10374 let opts = TaskOptions {
10375 priority: Some(9),
10376 queue: Some("high_priority".to_string()),
10377 task_id: Some(task_id),
10378 link: Some(Box::new(Signature::new("success".to_string()))),
10379 link_error: Some(Box::new(Signature::new("failure".to_string()))),
10380 ..Default::default()
10381 };
10382
10383 let display = format!("{}", opts);
10384 assert!(display.contains("TaskOptions"));
10385 assert!(display.contains("priority=9"));
10386 assert!(display.contains("queue=high_priority"));
10387 assert!(display.contains("task_id="));
10388 assert!(display.contains("link=yes"));
10389 assert!(display.contains("link_error=yes"));
10390 }
10391
10392 #[test]
10393 fn test_task_options_display_default() {
10394 let opts = TaskOptions::default();
10395 let display = format!("{}", opts);
10396 assert_eq!(display, "TaskOptions[default]");
10397 }
10398
10399 #[test]
10400 fn test_chain_builder() {
10401 let chain = Chain::new()
10402 .then("task1", vec![serde_json::json!(1)])
10403 .then("task2", vec![serde_json::json!(2)])
10404 .then("task3", vec![serde_json::json!(3)]);
10405
10406 assert_eq!(chain.tasks.len(), 3);
10407 assert_eq!(chain.tasks[0].task, "task1");
10408 assert_eq!(chain.tasks[2].task, "task3");
10409 }
10410
10411 #[test]
10412 fn test_chain_predicates() {
10413 let chain = Chain::new();
10414 assert!(chain.is_empty());
10415 assert_eq!(chain.len(), 0);
10416
10417 let chain = chain.then("task1", vec![]).then("task2", vec![]);
10418 assert!(!chain.is_empty());
10419 assert_eq!(chain.len(), 2);
10420 }
10421
10422 #[test]
10423 fn test_chain_display() {
10424 let chain = Chain::new()
10425 .then("first", vec![])
10426 .then("middle", vec![])
10427 .then("last", vec![]);
10428
10429 let display = format!("{}", chain);
10430 assert!(display.contains("Chain[3 tasks]"));
10431 assert!(display.contains("first -> ... -> last"));
10432 }
10433
10434 #[test]
10435 fn test_chain_display_empty() {
10436 let chain = Chain::new();
10437 let display = format!("{}", chain);
10438 assert_eq!(display, "Chain[0 tasks]");
10439 }
10440
10441 #[test]
10442 fn test_group_builder() {
10443 let group = Group::new()
10444 .add("task1", vec![])
10445 .add("task2", vec![])
10446 .add("task3", vec![]);
10447
10448 assert_eq!(group.tasks.len(), 3);
10449 }
10450
10451 #[test]
10452 fn test_group_predicates() {
10453 let group = Group::new();
10454 assert!(group.is_empty());
10455 assert_eq!(group.len(), 0);
10456 assert!(group.has_group_id());
10457
10458 let group = group.add("task1", vec![]).add("task2", vec![]);
10459 assert!(!group.is_empty());
10460 assert_eq!(group.len(), 2);
10461 }
10462
10463 #[test]
10464 fn test_group_display() {
10465 let group = Group::new()
10466 .add("task1", vec![])
10467 .add("task2", vec![])
10468 .add("task3", vec![]);
10469
10470 let display = format!("{}", group);
10471 assert!(display.contains("Group[3 tasks]"));
10472 assert!(display.contains("id="));
10473 }
10474
10475 #[test]
10476 fn test_chord_creation() {
10477 let header = Group::new().add("task1", vec![]).add("task2", vec![]);
10478
10479 let body = Signature::new("callback".to_string());
10480
10481 let chord = Chord::new(header, body);
10482
10483 assert_eq!(chord.header.tasks.len(), 2);
10484 assert_eq!(chord.body.task, "callback");
10485 }
10486
10487 #[test]
10488 fn test_chord_display() {
10489 let header = Group::new()
10490 .add("task1", vec![])
10491 .add("task2", vec![])
10492 .add("task3", vec![]);
10493 let body = Signature::new("aggregate".to_string());
10494 let chord = Chord::new(header, body);
10495
10496 let display = format!("{}", chord);
10497 assert!(display.contains("Chord[3 tasks] -> callback(aggregate)"));
10498 }
10499
10500 #[test]
10501 fn test_map_creation() {
10502 let task = Signature::new("process".to_string());
10503 let argsets = vec![
10504 vec![serde_json::json!(1)],
10505 vec![serde_json::json!(2)],
10506 vec![serde_json::json!(3)],
10507 ];
10508
10509 let map = Map::new(task, argsets);
10510
10511 assert_eq!(map.argsets.len(), 3);
10512 }
10513
10514 #[test]
10515 fn test_map_predicates() {
10516 let task = Signature::new("process".to_string());
10517 let empty_map = Map::new(task.clone(), vec![]);
10518 assert!(empty_map.is_empty());
10519 assert_eq!(empty_map.len(), 0);
10520
10521 let map = Map::new(
10522 task,
10523 vec![vec![serde_json::json!(1)], vec![serde_json::json!(2)]],
10524 );
10525 assert!(!map.is_empty());
10526 assert_eq!(map.len(), 2);
10527 }
10528
10529 #[test]
10530 fn test_map_display() {
10531 let task = Signature::new("process".to_string());
10532 let argsets = vec![
10533 vec![serde_json::json!(1)],
10534 vec![serde_json::json!(2)],
10535 vec![serde_json::json!(3)],
10536 ];
10537 let map = Map::new(task, argsets);
10538
10539 let display = format!("{}", map);
10540 assert!(display.contains("Map[task=process, 3 argsets]"));
10541 }
10542
10543 #[test]
10544 fn test_starmap_predicates() {
10545 let task = Signature::new("add".to_string());
10546 let empty_starmap = Starmap::new(task.clone(), vec![]);
10547 assert!(empty_starmap.is_empty());
10548 assert_eq!(empty_starmap.len(), 0);
10549
10550 let starmap = Starmap::new(
10551 task,
10552 vec![
10553 vec![serde_json::json!(1), serde_json::json!(2)],
10554 vec![serde_json::json!(3), serde_json::json!(4)],
10555 ],
10556 );
10557 assert!(!starmap.is_empty());
10558 assert_eq!(starmap.len(), 2);
10559 }
10560
10561 #[test]
10562 fn test_starmap_display() {
10563 let task = Signature::new("multiply".to_string());
10564 let argsets = vec![
10565 vec![serde_json::json!(2), serde_json::json!(3)],
10566 vec![serde_json::json!(4), serde_json::json!(5)],
10567 ];
10568 let starmap = Starmap::new(task, argsets);
10569
10570 let display = format!("{}", starmap);
10571 assert!(display.contains("Starmap[task=multiply, 2 argsets]"));
10572 }
10573
10574 #[test]
10575 fn test_canvas_error_predicates() {
10576 let invalid_err = CanvasError::Invalid("bad workflow".to_string());
10577 assert!(invalid_err.is_invalid());
10578 assert!(!invalid_err.is_broker());
10579 assert!(!invalid_err.is_serialization());
10580 assert!(!invalid_err.is_retryable());
10581
10582 let broker_err = CanvasError::Broker("connection failed".to_string());
10583 assert!(!broker_err.is_invalid());
10584 assert!(broker_err.is_broker());
10585 assert!(!broker_err.is_serialization());
10586 assert!(broker_err.is_retryable());
10587
10588 let ser_err = CanvasError::Serialization("bad json".to_string());
10589 assert!(!ser_err.is_invalid());
10590 assert!(!ser_err.is_broker());
10591 assert!(ser_err.is_serialization());
10592 assert!(!ser_err.is_retryable());
10593 }
10594
10595 #[test]
10596 fn test_canvas_error_category() {
10597 let invalid_err = CanvasError::Invalid("test".to_string());
10598 assert_eq!(invalid_err.category(), "invalid");
10599
10600 let broker_err = CanvasError::Broker("test".to_string());
10601 assert_eq!(broker_err.category(), "broker");
10602
10603 let ser_err = CanvasError::Serialization("test".to_string());
10604 assert_eq!(ser_err.category(), "serialization");
10605
10606 let cancelled_err = CanvasError::Cancelled("test".to_string());
10607 assert_eq!(cancelled_err.category(), "cancelled");
10608
10609 let timeout_err = CanvasError::Timeout("test".to_string());
10610 assert_eq!(timeout_err.category(), "timeout");
10611 }
10612
10613 #[test]
10614 fn test_canvas_error_display() {
10615 let err = CanvasError::Invalid("empty chain".to_string());
10616 assert_eq!(err.to_string(), "Invalid workflow: empty chain");
10617
10618 let err = CanvasError::Broker("timeout".to_string());
10619 assert_eq!(err.to_string(), "Broker error: timeout");
10620
10621 let err = CanvasError::Serialization("malformed json".to_string());
10622 assert_eq!(err.to_string(), "Serialization error: malformed json");
10623
10624 let err = CanvasError::Cancelled("user requested".to_string());
10625 assert_eq!(err.to_string(), "Workflow cancelled: user requested");
10626
10627 let err = CanvasError::Timeout("exceeded 5s".to_string());
10628 assert_eq!(err.to_string(), "Workflow timeout: exceeded 5s");
10629 }
10630
10631 #[test]
10632 fn test_cancellation_token() {
10633 let workflow_id = Uuid::new_v4();
10634 let token = CancellationToken::new(workflow_id)
10635 .with_reason("user requested".to_string())
10636 .cancel_tree();
10637
10638 assert_eq!(token.workflow_id, workflow_id);
10639 assert_eq!(token.reason, Some("user requested".to_string()));
10640 assert!(token.cancel_tree);
10641 assert!(token.branch_id.is_none());
10642
10643 let display = format!("{}", token);
10644 assert!(display.contains("CancellationToken"));
10645 assert!(display.contains("reason=user requested"));
10646 assert!(display.contains("(tree)"));
10647 }
10648
10649 #[test]
10650 fn test_workflow_retry_policy() {
10651 let policy = WorkflowRetryPolicy::new(3)
10652 .failed_only()
10653 .with_backoff(2.0, 60)
10654 .with_initial_delay(1);
10655
10656 assert_eq!(policy.max_retries, 3);
10657 assert!(policy.retry_failed_only);
10658 assert_eq!(policy.backoff_factor, Some(2.0));
10659 assert_eq!(policy.max_backoff, Some(60));
10660 assert_eq!(policy.initial_delay, Some(1));
10661
10662 assert_eq!(policy.calculate_delay(0), 1); assert_eq!(policy.calculate_delay(1), 2); assert_eq!(policy.calculate_delay(2), 4); assert_eq!(policy.calculate_delay(10), 60); let display = format!("{}", policy);
10669 assert!(display.contains("WorkflowRetryPolicy"));
10670 assert!(display.contains("max_retries=3"));
10671 assert!(display.contains("(failed_only)"));
10672 }
10673
10674 #[test]
10675 fn test_workflow_timeout() {
10676 let timeout = WorkflowTimeout::new(300)
10677 .with_stage_timeout(60)
10678 .with_escalation(TimeoutEscalation::ContinuePartial);
10679
10680 assert_eq!(timeout.total_timeout, Some(300));
10681 assert_eq!(timeout.stage_timeout, Some(60));
10682 assert!(matches!(
10683 timeout.escalation,
10684 TimeoutEscalation::ContinuePartial
10685 ));
10686
10687 let display = format!("{}", timeout);
10688 assert!(display.contains("WorkflowTimeout"));
10689 assert!(display.contains("total=300s"));
10690 assert!(display.contains("stage=60s"));
10691 }
10692
10693 #[test]
10694 fn test_foreach_loop() {
10695 let task = Signature::new("process".to_string());
10696 let items = vec![
10697 serde_json::json!(1),
10698 serde_json::json!(2),
10699 serde_json::json!(3),
10700 ];
10701 let foreach = ForEach::new(task, items).with_concurrency(2);
10702
10703 assert_eq!(foreach.len(), 3);
10704 assert!(!foreach.is_empty());
10705 assert_eq!(foreach.concurrency, Some(2));
10706
10707 let display = format!("{}", foreach);
10708 assert!(display.contains("ForEach"));
10709 assert!(display.contains("process"));
10710 assert!(display.contains("3 items"));
10711 assert!(display.contains("concurrency=2"));
10712
10713 let empty = ForEach::new(Signature::new("task".to_string()), vec![]);
10714 assert!(empty.is_empty());
10715 assert_eq!(empty.len(), 0);
10716 }
10717
10718 #[test]
10719 fn test_while_loop() {
10720 let condition = Condition::field_equals("status", serde_json::json!("pending"));
10721 let body = Signature::new("check".to_string());
10722 let while_loop = WhileLoop::new(condition, body).with_max_iterations(100);
10723
10724 assert_eq!(while_loop.max_iterations, Some(100));
10725
10726 let display = format!("{}", while_loop);
10727 assert!(display.contains("While"));
10728 assert!(display.contains("check"));
10729 assert!(display.contains("max=100"));
10730
10731 let unlimited = WhileLoop::new(
10732 Condition::field_equals("x", serde_json::json!(0)),
10733 Signature::new("task".to_string()),
10734 )
10735 .unlimited();
10736 assert!(unlimited.max_iterations.is_none());
10737 }
10738
10739 #[test]
10740 fn test_workflow_state() {
10741 let workflow_id = Uuid::new_v4();
10742 let mut state = WorkflowState::new(workflow_id, 10);
10743
10744 assert_eq!(state.workflow_id, workflow_id);
10745 assert_eq!(state.status, WorkflowStatus::Pending);
10746 assert_eq!(state.total_tasks, 10);
10747 assert_eq!(state.completed_tasks, 0);
10748 assert_eq!(state.failed_tasks, 0);
10749 assert_eq!(state.progress(), 0.0);
10750 assert!(!state.is_complete());
10751
10752 state.mark_completed();
10754 state.mark_completed();
10755 state.mark_completed();
10756 assert_eq!(state.completed_tasks, 3);
10757 assert_eq!(state.progress(), 30.0);
10758
10759 state.mark_failed();
10761 assert_eq!(state.failed_tasks, 1);
10762
10763 state.set_result("step1".to_string(), serde_json::json!({"result": 42}));
10765 assert_eq!(
10766 state.get_result("step1"),
10767 Some(&serde_json::json!({"result": 42}))
10768 );
10769 assert_eq!(state.get_result("nonexistent"), None);
10770
10771 state.status = WorkflowStatus::Success;
10773 assert!(state.is_complete());
10774
10775 state.status = WorkflowStatus::Failed;
10776 assert!(state.is_complete());
10777
10778 state.status = WorkflowStatus::Cancelled;
10779 assert!(state.is_complete());
10780
10781 state.status = WorkflowStatus::Running;
10782 assert!(!state.is_complete());
10783
10784 let display = format!("{}", state);
10785 assert!(display.contains("WorkflowState"));
10786 assert!(display.contains("progress=30.0%"));
10787 assert!(display.contains("failed=1"));
10788 }
10789
10790 #[test]
10791 fn test_dag_export_chain() {
10792 let chain = Chain::new()
10793 .then("task1", vec![])
10794 .then("task2", vec![])
10795 .then("task3", vec![]);
10796
10797 let dot = chain.to_dot();
10799 assert!(dot.contains("digraph Chain"));
10800 assert!(dot.contains("rankdir=LR"));
10801 assert!(dot.contains("task1"));
10802 assert!(dot.contains("task2"));
10803 assert!(dot.contains("task3"));
10804 assert!(dot.contains("n0 -> n1"));
10805 assert!(dot.contains("n1 -> n2"));
10806
10807 let mmd = chain.to_mermaid();
10809 assert!(mmd.contains("graph LR"));
10810 assert!(mmd.contains("task1"));
10811 assert!(mmd.contains("task2"));
10812 assert!(mmd.contains("task3"));
10813 assert!(mmd.contains("n0 --> n1"));
10814 assert!(mmd.contains("n1 --> n2"));
10815
10816 let json = chain.to_json().unwrap();
10818 assert!(json.contains("task1"));
10819 assert!(json.contains("task2"));
10820 assert!(json.contains("task3"));
10821 }
10822
10823 #[test]
10824 fn test_dag_export_group() {
10825 let group = Group::new()
10826 .add("task1", vec![])
10827 .add("task2", vec![])
10828 .add("task3", vec![]);
10829
10830 let dot = group.to_dot();
10832 assert!(dot.contains("digraph Group"));
10833 assert!(dot.contains("rankdir=TB"));
10834 assert!(dot.contains("start"));
10835 assert!(dot.contains("task1"));
10836 assert!(dot.contains("task2"));
10837 assert!(dot.contains("task3"));
10838 assert!(dot.contains("start -> n0"));
10839 assert!(dot.contains("start -> n1"));
10840 assert!(dot.contains("start -> n2"));
10841
10842 let mmd = group.to_mermaid();
10844 assert!(mmd.contains("graph TB"));
10845 assert!(mmd.contains("start"));
10846 assert!(mmd.contains("task1"));
10847 assert!(mmd.contains("start --> n0"));
10848
10849 let json = group.to_json().unwrap();
10851 assert!(json.contains("task1"));
10852 }
10853
10854 #[test]
10855 fn test_dag_export_chord() {
10856 let header = Group::new().add("task1", vec![]).add("task2", vec![]);
10857 let body = Signature::new("callback".to_string());
10858 let chord = Chord::new(header, body);
10859
10860 let dot = chord.to_dot();
10862 assert!(dot.contains("digraph Chord"));
10863 assert!(dot.contains("callback"));
10864 assert!(dot.contains("task1"));
10865 assert!(dot.contains("task2"));
10866 assert!(dot.contains("n0 -> callback"));
10867 assert!(dot.contains("n1 -> callback"));
10868
10869 let mmd = chord.to_mermaid();
10871 assert!(mmd.contains("graph TB"));
10872 assert!(mmd.contains("callback"));
10873 assert!(mmd.contains("task1"));
10874 assert!(mmd.contains("n0 --> callback"));
10875
10876 let json = chord.to_json().unwrap();
10878 assert!(json.contains("callback"));
10879 assert!(json.contains("task1"));
10880 }
10881
10882 #[test]
10883 fn test_canvas_error_new_variants() {
10884 let cancelled = CanvasError::Cancelled("user cancelled".to_string());
10885 assert!(cancelled.is_cancelled());
10886 assert!(!cancelled.is_timeout());
10887 assert!(!cancelled.is_retryable());
10888 assert_eq!(cancelled.category(), "cancelled");
10889
10890 let timeout = CanvasError::Timeout("exceeded limit".to_string());
10891 assert!(timeout.is_timeout());
10892 assert!(!timeout.is_cancelled());
10893 assert!(!timeout.is_retryable());
10894 assert_eq!(timeout.category(), "timeout");
10895 }
10896
10897 #[test]
10898 fn test_named_output() {
10899 let output = NamedOutput::new("result", serde_json::json!(42)).with_source("task1");
10900
10901 assert_eq!(output.name, "result");
10902 assert_eq!(output.value, serde_json::json!(42));
10903 assert_eq!(output.source, Some("task1".to_string()));
10904 }
10905
10906 #[test]
10907 fn test_result_transform() {
10908 let extract = ResultTransform::Extract {
10909 field: "data".to_string(),
10910 };
10911 assert!(format!("{}", extract).contains("Extract[data]"));
10912
10913 let map = ResultTransform::Map {
10914 task: Box::new(Signature::new("transform".to_string())),
10915 };
10916 assert!(format!("{}", map).contains("Map[transform]"));
10917 }
10918
10919 #[test]
10920 fn test_result_cache() {
10921 let cache = ResultCache::new("task:123")
10922 .with_policy(CachePolicy::OnSuccess)
10923 .with_ttl(3600);
10924
10925 assert_eq!(cache.key, "task:123");
10926 assert_eq!(cache.ttl, Some(3600));
10927
10928 let display = format!("{}", cache);
10929 assert!(display.contains("Cache[key=task:123]"));
10930 assert!(display.contains("ttl=3600s"));
10931 }
10932
10933 #[test]
10934 fn test_workflow_error_handler() {
10935 let handler = WorkflowErrorHandler::new(Signature::new("handle_error".to_string()))
10936 .for_errors(vec!["NetworkError".to_string(), "TimeoutError".to_string()])
10937 .suppress_error();
10938
10939 assert_eq!(handler.handler.task, "handle_error");
10940 assert_eq!(handler.error_types.len(), 2);
10941 assert!(handler.suppress);
10942
10943 let display = format!("{}", handler);
10944 assert!(display.contains("ErrorHandler[handle_error]"));
10945 assert!(display.contains("(suppress)"));
10946 }
10947
10948 #[test]
10949 fn test_compensation_workflow() {
10950 let mut workflow = CompensationWorkflow::new();
10951 assert!(workflow.is_empty());
10952 assert_eq!(workflow.len(), 0);
10953
10954 workflow = workflow
10955 .step(
10956 Signature::new("create".to_string()),
10957 Signature::new("delete".to_string()),
10958 )
10959 .step(
10960 Signature::new("update".to_string()),
10961 Signature::new("rollback".to_string()),
10962 );
10963
10964 assert!(!workflow.is_empty());
10965 assert_eq!(workflow.len(), 2);
10966 assert_eq!(workflow.forward.len(), 2);
10967 assert_eq!(workflow.compensations.len(), 2);
10968
10969 let display = format!("{}", workflow);
10970 assert!(display.contains("Compensation[2 steps, 2 compensations]"));
10971 }
10972
10973 #[test]
10974 fn test_saga() {
10975 let workflow = CompensationWorkflow::new()
10976 .step(
10977 Signature::new("reserve".to_string()),
10978 Signature::new("cancel_reservation".to_string()),
10979 )
10980 .step(
10981 Signature::new("charge".to_string()),
10982 Signature::new("refund".to_string()),
10983 );
10984
10985 let saga = Saga::new(workflow).with_isolation(SagaIsolation::Serializable);
10986
10987 assert_eq!(saga.workflow.len(), 2);
10988 assert!(matches!(saga.isolation, SagaIsolation::Serializable));
10989
10990 let display = format!("{}", saga);
10991 assert!(display.contains("Saga[2 steps"));
10992 assert!(display.contains("Serializable"));
10993 }
10994
10995 #[test]
10996 fn test_scatter_gather() {
10997 let scatter = Signature::new("distribute".to_string());
10998 let workers = vec![
10999 Signature::new("worker1".to_string()),
11000 Signature::new("worker2".to_string()),
11001 Signature::new("worker3".to_string()),
11002 ];
11003 let gather = Signature::new("collect".to_string());
11004
11005 let sg = ScatterGather::new(scatter, workers, gather).with_timeout(30);
11006
11007 assert_eq!(sg.workers.len(), 3);
11008 assert_eq!(sg.timeout, Some(30));
11009
11010 let display = format!("{}", sg);
11011 assert!(display.contains("ScatterGather"));
11012 assert!(display.contains("distribute"));
11013 assert!(display.contains("3 workers"));
11014 assert!(display.contains("collect"));
11015 }
11016
11017 #[test]
11018 fn test_pipeline() {
11019 let mut pipeline = Pipeline::new();
11020 assert!(pipeline.is_empty());
11021 assert_eq!(pipeline.len(), 0);
11022
11023 pipeline = pipeline
11024 .stage(Signature::new("fetch".to_string()))
11025 .stage(Signature::new("transform".to_string()))
11026 .stage(Signature::new("load".to_string()))
11027 .with_buffer_size(100);
11028
11029 assert!(!pipeline.is_empty());
11030 assert_eq!(pipeline.len(), 3);
11031 assert_eq!(pipeline.buffer_size, Some(100));
11032
11033 let display = format!("{}", pipeline);
11034 assert!(display.contains("Pipeline[3 stages]"));
11035 assert!(display.contains("buffer=100"));
11036 }
11037
11038 #[test]
11039 fn test_fanout() {
11040 let source = Signature::new("broadcast".to_string());
11041 let fanout = FanOut::new(source)
11042 .consumer(Signature::new("consumer1".to_string()))
11043 .consumer(Signature::new("consumer2".to_string()))
11044 .consumer(Signature::new("consumer3".to_string()));
11045
11046 assert!(!fanout.is_empty());
11047 assert_eq!(fanout.len(), 3);
11048
11049 let display = format!("{}", fanout);
11050 assert!(display.contains("FanOut"));
11051 assert!(display.contains("broadcast"));
11052 assert!(display.contains("3 consumers"));
11053 }
11054
11055 #[test]
11056 fn test_fanin() {
11057 let aggregator = Signature::new("aggregate".to_string());
11058 let fanin = FanIn::new(aggregator)
11059 .source(Signature::new("source1".to_string()))
11060 .source(Signature::new("source2".to_string()));
11061
11062 assert!(!fanin.is_empty());
11063 assert_eq!(fanin.len(), 2);
11064
11065 let display = format!("{}", fanin);
11066 assert!(display.contains("FanIn"));
11067 assert!(display.contains("2 sources"));
11068 assert!(display.contains("aggregate"));
11069 }
11070
11071 #[test]
11072 fn test_validation_result() {
11073 let mut result = ValidationResult::valid();
11074 assert!(result.valid);
11075 assert!(result.errors.is_empty());
11076 assert!(result.warnings.is_empty());
11077
11078 result.add_warning("This is a warning");
11079 assert!(result.valid);
11080 assert_eq!(result.warnings.len(), 1);
11081
11082 result.add_error("This is an error");
11083 assert!(!result.valid);
11084 assert_eq!(result.errors.len(), 1);
11085
11086 let display = format!("{}", result);
11087 assert!(display.contains("Invalid"));
11088 assert!(display.contains("1 errors"));
11089 }
11090
11091 #[test]
11092 fn test_workflow_validator_chain() {
11093 let empty_chain = Chain::new();
11094 let result = empty_chain.validate();
11095 assert!(!result.valid);
11096 assert!(result.errors.iter().any(|e| e.contains("cannot be empty")));
11097
11098 let valid_chain = Chain::new()
11099 .then("task1", vec![])
11100 .then("task2", vec![])
11101 .then("task3", vec![]);
11102 let result = valid_chain.validate();
11103 assert!(result.valid);
11104
11105 let mut large_chain = Chain::new();
11107 for i in 0..150 {
11108 large_chain = large_chain.then(&format!("task{}", i), vec![]);
11109 }
11110 let result = large_chain.validate();
11111 assert!(result.valid);
11112 assert!(!result.warnings.is_empty());
11113 }
11114
11115 #[test]
11116 fn test_workflow_validator_group() {
11117 let empty_group = Group::new();
11118 let result = empty_group.validate();
11119 assert!(!result.valid);
11120 assert!(result.errors.iter().any(|e| e.contains("cannot be empty")));
11121
11122 let valid_group = Group::new().add("task1", vec![]).add("task2", vec![]);
11123 let result = valid_group.validate();
11124 assert!(result.valid);
11125 }
11126
11127 #[test]
11128 fn test_workflow_validator_chord() {
11129 let empty_header = Group::new();
11130 let body = Signature::new("callback".to_string());
11131 let chord = Chord::new(empty_header, body);
11132
11133 let result = chord.validate();
11134 assert!(!result.valid);
11135 assert!(result.errors.iter().any(|e| e.contains("cannot be empty")));
11136
11137 let valid_header = Group::new().add("task1", vec![]).add("task2", vec![]);
11138 let body = Signature::new("callback".to_string());
11139 let chord = Chord::new(valid_header, body);
11140
11141 let result = chord.validate();
11142 assert!(result.valid);
11143 }
11144
11145 #[test]
11146 fn test_loop_control() {
11147 let continue_ctrl = LoopControl::continue_loop();
11148 assert!(matches!(continue_ctrl, LoopControl::Continue));
11149 assert_eq!(format!("{}", continue_ctrl), "Continue");
11150
11151 let break_ctrl = LoopControl::break_loop();
11152 assert!(matches!(break_ctrl, LoopControl::Break));
11153 assert_eq!(format!("{}", break_ctrl), "Break");
11154
11155 let break_with = LoopControl::break_with(serde_json::json!({"result": 42}));
11156 assert!(matches!(break_with, LoopControl::BreakWith { .. }));
11157 assert_eq!(format!("{}", break_with), "BreakWith");
11158 }
11159
11160 #[test]
11161 fn test_error_propagation_mode() {
11162 let stop = ErrorPropagationMode::StopOnFirstError;
11163 assert!(!stop.allows_continue());
11164 assert_eq!(format!("{}", stop), "StopOnFirstError");
11165
11166 let continue_mode = ErrorPropagationMode::ContinueOnError;
11167 assert!(continue_mode.allows_continue());
11168 assert_eq!(format!("{}", continue_mode), "ContinueOnError");
11169
11170 let partial = ErrorPropagationMode::partial_failure(3);
11171 assert!(partial.allows_continue());
11172 assert!(format!("{}", partial).contains("PartialFailure"));
11173
11174 let partial_rate = ErrorPropagationMode::partial_failure_with_rate(5, 0.5);
11175 assert!(partial_rate.allows_continue());
11176 let display = format!("{}", partial_rate);
11177 assert!(display.contains("PartialFailure"));
11178 assert!(display.contains("50.0%"));
11179 }
11180
11181 #[test]
11182 fn test_partial_failure_tracker() {
11183 let mut tracker = PartialFailureTracker::new(10);
11184 assert_eq!(tracker.total_tasks, 10);
11185 assert_eq!(tracker.successful_tasks, 0);
11186 assert_eq!(tracker.failed_tasks, 0);
11187
11188 tracker.record_success(Uuid::new_v4());
11190 tracker.record_success(Uuid::new_v4());
11191 assert_eq!(tracker.successful_tasks, 2);
11192 assert_eq!(tracker.success_rate(), 0.2);
11193
11194 tracker.record_failure(Uuid::new_v4(), "error1".to_string());
11196 tracker.record_failure(Uuid::new_v4(), "error2".to_string());
11197 assert_eq!(tracker.failed_tasks, 2);
11198 assert_eq!(tracker.failure_rate(), 0.2);
11199
11200 let stop_mode = ErrorPropagationMode::StopOnFirstError;
11202 assert!(tracker.exceeds_threshold(&stop_mode));
11203 assert!(!tracker.should_continue(&stop_mode));
11204
11205 let continue_mode = ErrorPropagationMode::ContinueOnError;
11206 assert!(!tracker.exceeds_threshold(&continue_mode));
11207 assert!(tracker.should_continue(&continue_mode));
11208
11209 let partial_mode = ErrorPropagationMode::partial_failure(3);
11210 assert!(!tracker.exceeds_threshold(&partial_mode));
11211 assert!(tracker.should_continue(&partial_mode));
11212
11213 tracker.record_failure(Uuid::new_v4(), "error3".to_string());
11215 assert!(tracker.exceeds_threshold(&partial_mode));
11216 assert!(!tracker.should_continue(&partial_mode));
11217
11218 let display = format!("{}", tracker);
11219 assert!(display.contains("PartialFailureTracker"));
11220 assert!(display.contains("2/10"));
11221 }
11222
11223 #[test]
11224 fn test_isolation_level() {
11225 let none = IsolationLevel::None;
11226 assert!(!none.has_resource_limits());
11227 assert!(!none.has_error_isolation());
11228 assert_eq!(format!("{}", none), "None");
11229
11230 let resource = IsolationLevel::resource(512);
11231 assert!(resource.has_resource_limits());
11232 assert!(!resource.has_error_isolation());
11233 let display = format!("{}", resource);
11234 assert!(display.contains("Resource"));
11235 assert!(display.contains("512MB"));
11236
11237 let error = IsolationLevel::Error;
11238 assert!(!error.has_resource_limits());
11239 assert!(error.has_error_isolation());
11240 assert_eq!(format!("{}", error), "Error");
11241
11242 let full = IsolationLevel::full(1024);
11243 assert!(full.has_resource_limits());
11244 assert!(full.has_error_isolation());
11245 let display = format!("{}", full);
11246 assert!(display.contains("Full"));
11247 assert!(display.contains("1024MB"));
11248 }
11249
11250 #[test]
11251 fn test_sub_workflow_isolation() {
11252 let workflow_id = Uuid::new_v4();
11253 let parent_id = Uuid::new_v4();
11254
11255 let isolation = SubWorkflowIsolation::new(workflow_id, IsolationLevel::Error)
11256 .with_parent(parent_id)
11257 .no_error_propagation()
11258 .no_cancellation_propagation();
11259
11260 assert_eq!(isolation.workflow_id, workflow_id);
11261 assert_eq!(isolation.parent_workflow_id, Some(parent_id));
11262 assert_eq!(isolation.isolation_level, IsolationLevel::Error);
11263 assert!(!isolation.propagate_errors);
11264 assert!(!isolation.propagate_cancellation);
11265
11266 let display = format!("{}", isolation);
11267 assert!(display.contains("SubWorkflowIsolation"));
11268 assert!(display.contains(&workflow_id.to_string()));
11269 }
11270
11271 #[test]
11272 fn test_workflow_checkpoint() {
11273 let workflow_id = Uuid::new_v4();
11274 let state = WorkflowState::new(workflow_id, 10);
11275 let mut checkpoint = WorkflowCheckpoint::new(workflow_id, state);
11276
11277 assert_eq!(checkpoint.workflow_id, workflow_id);
11278 assert_eq!(checkpoint.version, 1);
11279 assert_eq!(checkpoint.completed_tasks.len(), 0);
11280
11281 let task1 = Uuid::new_v4();
11283 let task2 = Uuid::new_v4();
11284 let task3 = Uuid::new_v4();
11285
11286 checkpoint.record_in_progress(task1);
11287 checkpoint.record_completed(task2);
11288 checkpoint.record_failed(task3, "test error".to_string());
11289
11290 assert!(checkpoint.is_completed(&task2));
11291 assert!(checkpoint.is_failed(&task3));
11292 assert!(!checkpoint.is_completed(&task1));
11293 assert_eq!(checkpoint.tasks_to_retry().len(), 1);
11294
11295 let json = checkpoint.to_json().unwrap();
11297 let deserialized = WorkflowCheckpoint::from_json(&json).unwrap();
11298 assert_eq!(deserialized.workflow_id, workflow_id);
11299 assert_eq!(deserialized.completed_tasks.len(), 1);
11300 assert_eq!(deserialized.failed_tasks.len(), 1);
11301
11302 let display = format!("{}", checkpoint);
11303 assert!(display.contains("WorkflowCheckpoint"));
11304 assert!(display.contains("completed=1"));
11305 assert!(display.contains("failed=1"));
11306 }
11307
11308 #[test]
11309 fn test_workflow_recovery_policy() {
11310 let auto = WorkflowRecoveryPolicy::auto_recover();
11311 assert!(auto.auto_recovery);
11312 assert!(auto.resume_from_checkpoint);
11313 assert!(auto.replay_failed);
11314 assert_eq!(auto.max_checkpoint_age, Some(3600));
11315
11316 let manual = WorkflowRecoveryPolicy::manual();
11317 assert!(!manual.auto_recovery);
11318 assert!(manual.resume_from_checkpoint);
11319 assert!(!manual.replay_failed);
11320 assert_eq!(manual.max_checkpoint_age, None);
11321
11322 let custom = WorkflowRecoveryPolicy::auto_recover()
11323 .with_max_checkpoint_age(7200)
11324 .with_retry_policy(WorkflowRetryPolicy::new(3));
11325 assert_eq!(custom.max_checkpoint_age, Some(7200));
11326 assert!(custom.retry_policy.is_some());
11327
11328 let workflow_id = Uuid::new_v4();
11330 let state = WorkflowState::new(workflow_id, 10);
11331 let checkpoint = WorkflowCheckpoint::new(workflow_id, state);
11332 assert!(auto.is_checkpoint_valid(&checkpoint));
11333
11334 let display = format!("{}", auto);
11335 assert!(display.contains("WorkflowRecoveryPolicy"));
11336 assert!(display.contains("auto"));
11337 }
11338
11339 #[test]
11340 fn test_optimization_pass() {
11341 let cse = OptimizationPass::CommonSubexpressionElimination;
11342 assert_eq!(format!("{}", cse), "CSE");
11343
11344 let dce = OptimizationPass::DeadCodeElimination;
11345 assert_eq!(format!("{}", dce), "DCE");
11346
11347 let fusion = OptimizationPass::TaskFusion;
11348 assert_eq!(format!("{}", fusion), "TaskFusion");
11349
11350 let scheduling = OptimizationPass::ParallelScheduling;
11351 assert_eq!(format!("{}", scheduling), "ParallelScheduling");
11352
11353 let resource = OptimizationPass::ResourceOptimization;
11354 assert_eq!(format!("{}", resource), "ResourceOptimization");
11355 }
11356
11357 #[test]
11358 fn test_workflow_compiler() {
11359 let compiler = WorkflowCompiler::new();
11360 assert!(!compiler.aggressive);
11361 assert_eq!(compiler.passes.len(), 2);
11362
11363 let aggressive = WorkflowCompiler::new().aggressive();
11364 assert!(aggressive.aggressive);
11365 assert!(aggressive.passes.len() > 2);
11366
11367 let custom = WorkflowCompiler::new()
11368 .add_pass(OptimizationPass::TaskFusion)
11369 .add_pass(OptimizationPass::ParallelScheduling);
11370 assert_eq!(custom.passes.len(), 4);
11371
11372 let chain = Chain::new().then("task1", vec![]).then("task2", vec![]);
11374 let optimized = compiler.optimize_chain(&chain);
11375 assert_eq!(optimized.tasks.len(), chain.tasks.len());
11376
11377 let group = Group::new().add("task1", vec![]).add("task2", vec![]);
11378 let optimized_group = compiler.optimize_group(&group);
11379 assert_eq!(optimized_group.tasks.len(), group.tasks.len());
11380
11381 let display = format!("{}", compiler);
11382 assert!(display.contains("WorkflowCompiler"));
11383 assert!(display.contains("DCE"));
11384 assert!(display.contains("CSE"));
11385 }
11386
11387 #[test]
11388 fn test_workflow_compiler_cse_chain() {
11389 use serde_json::json;
11390
11391 let compiler = WorkflowCompiler::new().aggressive();
11393 let chain = Chain::new()
11394 .then_signature(Signature::new("task1".to_string()).with_args(vec![json!(1)]))
11395 .then_signature(Signature::new("task2".to_string()).with_args(vec![json!(2)]))
11396 .then_signature(Signature::new("task1".to_string()).with_args(vec![json!(1)])); let optimized = compiler.optimize_chain(&chain);
11399 assert_eq!(optimized.tasks.len(), 2);
11401 assert_eq!(optimized.tasks[0].task, "task1");
11402 assert_eq!(optimized.tasks[1].task, "task2");
11403 }
11404
11405 #[test]
11406 fn test_workflow_compiler_cse_group() {
11407 use serde_json::json;
11408
11409 let compiler = WorkflowCompiler::new().aggressive();
11411 let group = Group::new()
11412 .add_signature(Signature::new("task1".to_string()).with_args(vec![json!(1)]))
11413 .add_signature(Signature::new("task2".to_string()).with_args(vec![json!(2)]))
11414 .add_signature(Signature::new("task1".to_string()).with_args(vec![json!(1)])); let optimized = compiler.optimize_group(&group);
11417 assert_eq!(optimized.tasks.len(), 2);
11419 assert_eq!(optimized.tasks[0].task, "task1");
11420 assert_eq!(optimized.tasks[1].task, "task2");
11421 }
11422
11423 #[test]
11424 fn test_workflow_compiler_dce_chain() {
11425 use serde_json::json;
11426
11427 let compiler = WorkflowCompiler::new();
11429 let chain = Chain::new()
11430 .then_signature(Signature::new("task1".to_string()).with_args(vec![json!(1)]))
11431 .then_signature(Signature::new("".to_string())) .then_signature(Signature::new("task2".to_string()).with_args(vec![json!(2)]));
11433
11434 let optimized = compiler.optimize_chain(&chain);
11435 assert_eq!(optimized.tasks.len(), 2);
11437 assert_eq!(optimized.tasks[0].task, "task1");
11438 assert_eq!(optimized.tasks[1].task, "task2");
11439 }
11440
11441 #[test]
11442 fn test_workflow_compiler_dce_group() {
11443 use serde_json::json;
11444
11445 let compiler = WorkflowCompiler::new();
11447 let group = Group::new()
11448 .add_signature(Signature::new("task1".to_string()).with_args(vec![json!(1)]))
11449 .add_signature(Signature::new("".to_string())) .add_signature(Signature::new("task2".to_string()).with_args(vec![json!(2)]));
11451
11452 let optimized = compiler.optimize_group(&group);
11453 assert_eq!(optimized.tasks.len(), 2);
11455 assert_eq!(optimized.tasks[0].task, "task1");
11456 assert_eq!(optimized.tasks[1].task, "task2");
11457 }
11458
11459 #[test]
11460 fn test_workflow_compiler_task_fusion() {
11461 use serde_json::json;
11462
11463 let compiler = WorkflowCompiler::new().aggressive();
11465 let chain = Chain::new()
11466 .then_signature(
11467 Signature::new("process".to_string())
11468 .with_args(vec![json!(1)])
11469 .immutable(),
11470 )
11471 .then_signature(
11472 Signature::new("process".to_string())
11473 .with_args(vec![json!(2)])
11474 .immutable(),
11475 )
11476 .then_signature(Signature::new("finalize".to_string()));
11477
11478 let optimized = compiler.optimize_chain(&chain);
11479 assert_eq!(optimized.tasks.len(), 2);
11481 assert_eq!(optimized.tasks[0].task, "process");
11482 assert_eq!(optimized.tasks[0].args.len(), 2); assert_eq!(optimized.tasks[1].task, "finalize");
11484 }
11485
11486 #[test]
11487 fn test_workflow_compiler_parallel_scheduling() {
11488 let compiler = WorkflowCompiler::new().add_pass(OptimizationPass::ParallelScheduling);
11490
11491 let group = Group::new()
11492 .add_signature(Signature::new("task1".to_string()).with_priority(1))
11493 .add_signature(Signature::new("task2".to_string()).with_priority(5))
11494 .add_signature(Signature::new("task3".to_string()).with_priority(3));
11495
11496 let optimized = compiler.optimize_group(&group);
11497 assert_eq!(optimized.tasks.len(), 3);
11499 assert_eq!(optimized.tasks[0].options.priority, Some(5));
11500 assert_eq!(optimized.tasks[1].options.priority, Some(3));
11501 assert_eq!(optimized.tasks[2].options.priority, Some(1));
11502 }
11503
11504 #[test]
11505 fn test_workflow_compiler_resource_optimization() {
11506 let compiler = WorkflowCompiler::new().add_pass(OptimizationPass::ResourceOptimization);
11508
11509 let group = Group::new()
11510 .add_signature(Signature::new("task1".to_string()).with_queue("queue_b".to_string()))
11511 .add_signature(Signature::new("task2".to_string()).with_queue("queue_a".to_string()))
11512 .add_signature(Signature::new("task3".to_string()).with_queue("queue_a".to_string()));
11513
11514 let optimized = compiler.optimize_group(&group);
11515 assert_eq!(optimized.tasks.len(), 3);
11517 assert_eq!(
11518 optimized.tasks[0].options.queue.as_ref().unwrap(),
11519 "queue_a"
11520 );
11521 assert_eq!(
11522 optimized.tasks[1].options.queue.as_ref().unwrap(),
11523 "queue_a"
11524 );
11525 assert_eq!(
11526 optimized.tasks[2].options.queue.as_ref().unwrap(),
11527 "queue_b"
11528 );
11529 }
11530
11531 #[test]
11532 fn test_workflow_compiler_optimize_chord() {
11533 use serde_json::json;
11534
11535 let compiler = WorkflowCompiler::new().aggressive();
11537
11538 let group = Group::new()
11539 .add_signature(Signature::new("task1".to_string()).with_args(vec![json!(1)]))
11540 .add_signature(Signature::new("task1".to_string()).with_args(vec![json!(1)])) .add_signature(Signature::new("task2".to_string()).with_args(vec![json!(2)]));
11542
11543 let chord = Chord::new(group, Signature::new("callback".to_string()));
11544 let optimized = compiler.optimize_chord(&chord);
11545
11546 assert_eq!(optimized.header.tasks.len(), 2);
11548 assert_eq!(optimized.body.task, "callback");
11549 }
11550
11551 #[test]
11552 fn test_workflow_compiler_combined_passes() {
11553 use serde_json::json;
11554
11555 let compiler = WorkflowCompiler::new()
11557 .aggressive()
11558 .add_pass(OptimizationPass::ParallelScheduling);
11559
11560 let group = Group::new()
11561 .add_signature(
11562 Signature::new("task1".to_string())
11563 .with_priority(1)
11564 .with_args(vec![json!(1)]),
11565 )
11566 .add_signature(Signature::new("".to_string())) .add_signature(
11568 Signature::new("task2".to_string())
11569 .with_priority(5)
11570 .with_args(vec![json!(2)]),
11571 )
11572 .add_signature(
11573 Signature::new("task1".to_string())
11574 .with_priority(1)
11575 .with_args(vec![json!(1)]),
11576 ); let optimized = compiler.optimize_group(&group);
11579 assert_eq!(optimized.tasks.len(), 2);
11581 assert_eq!(optimized.tasks[0].options.priority, Some(5));
11582 assert_eq!(optimized.tasks[0].task, "task2");
11583 assert_eq!(optimized.tasks[1].options.priority, Some(1));
11584 assert_eq!(optimized.tasks[1].task, "task1");
11585 }
11586
11587 #[test]
11588 fn test_typed_result() {
11589 let result = TypedResult::new(42i32).with_metadata("source", serde_json::json!("test"));
11590
11591 assert_eq!(result.value, 42);
11592 assert_eq!(result.type_name(), "i32");
11593 assert!(result.metadata.contains_key("source"));
11594
11595 let display = format!("{}", result);
11596 assert!(display.contains("TypedResult"));
11597 assert!(display.contains("i32"));
11598 }
11599
11600 #[test]
11601 fn test_type_validator() {
11602 let validator = TypeValidator::new("i32");
11603 assert!(validator.validate("i32"));
11604 assert!(!validator.validate("String"));
11605
11606 let compatible = TypeValidator::new("serde_json::Value").allow_compatible();
11607 assert!(compatible.validate("i32"));
11608 assert!(compatible.validate("String"));
11609 assert!(compatible.allow_compatible);
11610
11611 let display = format!("{}", validator);
11612 assert!(display.contains("TypeValidator"));
11613 assert!(display.contains("i32"));
11614 }
11615
11616 #[test]
11617 fn test_task_dependency() {
11618 let task_id = Uuid::new_v4();
11619 let dep = TaskDependency::new(task_id)
11620 .with_output_key("result")
11621 .optional();
11622
11623 assert_eq!(dep.task_id, task_id);
11624 assert_eq!(dep.output_key, Some("result".to_string()));
11625 assert!(dep.optional);
11626
11627 let display = format!("{}", dep);
11628 assert!(display.contains("TaskDependency"));
11629 assert!(display.contains(&task_id.to_string()));
11630 assert!(display.contains("result"));
11631 }
11632
11633 #[test]
11634 fn test_dependency_graph() {
11635 let mut graph = DependencyGraph::new();
11636 let task1 = Uuid::new_v4();
11637 let task2 = Uuid::new_v4();
11638 let task3 = Uuid::new_v4();
11639
11640 graph.add_dependency(task2, TaskDependency::new(task1));
11641 graph.add_dependency(task3, TaskDependency::new(task2));
11642
11643 assert_eq!(graph.get_dependencies(&task2).len(), 1);
11644 assert_eq!(graph.get_dependents(&task1).len(), 1);
11645 assert!(!graph.has_circular_dependency());
11646
11647 let sorted = graph.topological_sort().unwrap();
11648 assert_eq!(sorted.len(), 3);
11649
11650 let display = format!("{}", graph);
11651 assert!(display.contains("DependencyGraph"));
11652 assert!(display.contains("2 tasks")); }
11654
11655 #[test]
11656 fn test_circular_dependency() {
11657 let mut graph = DependencyGraph::new();
11658 let task1 = Uuid::new_v4();
11659 let task2 = Uuid::new_v4();
11660
11661 graph.add_dependency(task1, TaskDependency::new(task2));
11662 graph.add_dependency(task2, TaskDependency::new(task1));
11663
11664 assert!(graph.has_circular_dependency());
11665 assert!(graph.topological_sort().is_err());
11666 }
11667
11668 #[test]
11669 fn test_parallel_reduce() {
11670 let map_task = Signature::new("map".to_string());
11671 let reduce_task = Signature::new("reduce".to_string());
11672 let inputs = vec![
11673 serde_json::json!(1),
11674 serde_json::json!(2),
11675 serde_json::json!(3),
11676 ];
11677
11678 let pr = ParallelReduce::new(map_task, reduce_task, inputs)
11679 .with_parallelism(8)
11680 .with_initial_value(serde_json::json!(0));
11681
11682 assert_eq!(pr.parallelism, 8);
11683 assert_eq!(pr.input_count(), 3);
11684 assert!(!pr.is_empty());
11685 assert!(pr.initial_value.is_some());
11686
11687 let display = format!("{}", pr);
11688 assert!(display.contains("ParallelReduce"));
11689 assert!(display.contains("map"));
11690 assert!(display.contains("reduce"));
11691 assert!(display.contains("parallelism=8"));
11692 }
11693
11694 #[test]
11695 fn test_template_parameter() {
11696 let param = TemplateParameter::new("count", "usize")
11697 .with_default(serde_json::json!(10))
11698 .with_description("Number of items");
11699
11700 assert_eq!(param.name, "count");
11701 assert_eq!(param.param_type, "usize");
11702 assert!(!param.required);
11703 assert!(param.default.is_some());
11704 assert!(param.description.is_some());
11705
11706 let display = format!("{}", param);
11707 assert!(display.contains("count:usize"));
11708 assert!(display.contains("optional"));
11709 }
11710
11711 #[test]
11712 fn test_workflow_template() {
11713 let param =
11714 TemplateParameter::new("queue", "String").with_default(serde_json::json!("default"));
11715
11716 let template = WorkflowTemplate::new("etl_pipeline", "1.0")
11717 .add_parameter(param)
11718 .with_description("ETL workflow template")
11719 .with_chain(Chain::new().then("extract", vec![]));
11720
11721 assert_eq!(template.name, "etl_pipeline");
11722 assert_eq!(template.version, "1.0");
11723 assert_eq!(template.parameters.len(), 1);
11724 assert!(template.chain.is_some());
11725 assert!(template.description.is_some());
11726
11727 let mut params = HashMap::new();
11729 params.insert("queue".to_string(), serde_json::json!("custom"));
11730 let instance = template.instantiate(params).unwrap();
11731 assert_eq!(instance.name, "etl_pipeline");
11732
11733 let display = format!("{}", template);
11734 assert!(display.contains("WorkflowTemplate"));
11735 assert!(display.contains("etl_pipeline@1.0"));
11736 }
11737
11738 #[test]
11739 fn test_workflow_template_validation() {
11740 let required_param = TemplateParameter::new("api_key", "String");
11741 let template = WorkflowTemplate::new("api_workflow", "1.0").add_parameter(required_param);
11742
11743 let result = template.instantiate(HashMap::new());
11745 assert!(result.is_err());
11746 }
11747
11748 #[test]
11749 fn test_workflow_event() {
11750 let task_id = Uuid::new_v4();
11751 let workflow_id = Uuid::new_v4();
11752
11753 let task_completed = WorkflowEvent::TaskCompleted { task_id };
11754 assert_eq!(
11755 format!("{}", task_completed),
11756 format!("TaskCompleted[{}]", task_id)
11757 );
11758
11759 let task_failed = WorkflowEvent::TaskFailed {
11760 task_id,
11761 error: "test error".to_string(),
11762 };
11763 assert!(format!("{}", task_failed).contains("TaskFailed"));
11764
11765 let workflow_started = WorkflowEvent::WorkflowStarted { workflow_id };
11766 assert!(format!("{}", workflow_started).contains("WorkflowStarted"));
11767
11768 let custom = WorkflowEvent::Custom {
11769 event_type: "data_updated".to_string(),
11770 data: "{}".to_string(),
11771 };
11772 assert!(format!("{}", custom).contains("Custom"));
11773 assert!(format!("{}", custom).contains("data_updated"));
11774 }
11775
11776 #[test]
11777 fn test_event_handler() {
11778 let handler_task = Signature::new("handle_completion".to_string());
11779 let handler = EventHandler::new("TaskCompleted", handler_task)
11780 .with_filter("task_type == 'important'");
11781
11782 assert_eq!(handler.event_type, "TaskCompleted");
11783 assert_eq!(handler.handler_task.task, "handle_completion");
11784 assert!(handler.filter.is_some());
11785
11786 let display = format!("{}", handler);
11787 assert!(display.contains("EventHandler"));
11788 assert!(display.contains("TaskCompleted"));
11789 assert!(display.contains("handle_completion"));
11790 }
11791
11792 #[test]
11793 fn test_event_driven_workflow() {
11794 let handler1 =
11795 EventHandler::new("TaskCompleted", Signature::new("on_complete".to_string()));
11796 let handler2 = EventHandler::new("TaskFailed", Signature::new("on_fail".to_string()));
11797
11798 let workflow = EventDrivenWorkflow::new()
11799 .on_event(handler1)
11800 .on_event(handler2)
11801 .on_task_completed(Signature::new("notify".to_string()));
11802
11803 assert!(workflow.active);
11804 assert_eq!(workflow.handlers.len(), 3);
11805 assert!(workflow.has_handlers());
11806
11807 let deactivated = workflow.clone().deactivate();
11808 assert!(!deactivated.active);
11809
11810 let display = format!("{}", workflow);
11811 assert!(display.contains("EventDrivenWorkflow"));
11812 assert!(display.contains("handlers=3"));
11813 }
11814
11815 #[test]
11816 fn test_state_version() {
11817 let v1 = StateVersion::new(1, 0, 0);
11818 let v2 = StateVersion::new(1, 1, 0);
11819 let v3 = StateVersion::new(2, 0, 0);
11820
11821 assert!(v1.is_compatible(&v2));
11823 assert!(!v1.is_compatible(&v3));
11825
11826 assert_eq!(format!("{}", v1), "1.0.0");
11828 assert_eq!(format!("{}", v2), "1.1.0");
11829
11830 let current = StateVersion::current();
11832 assert_eq!(current.major, 1);
11833 assert_eq!(current.minor, 0);
11834 assert_eq!(current.patch, 0);
11835 }
11836
11837 #[test]
11838 fn test_state_migration_error() {
11839 let v1 = StateVersion::new(1, 0, 0);
11840 let v2 = StateVersion::new(2, 0, 0);
11841
11842 let err = StateMigrationError::IncompatibleVersion { from: v1, to: v2 };
11843 let display = format!("{}", err);
11844 assert!(display.contains("Incompatible"));
11845 assert!(display.contains("1.0.0"));
11846 assert!(display.contains("2.0.0"));
11847
11848 let err2 = StateMigrationError::MigrationFailed("test error".to_string());
11849 assert!(format!("{}", err2).contains("migration failed"));
11850
11851 let err3 = StateMigrationError::UnsupportedVersion(v1);
11852 assert!(format!("{}", err3).contains("Unsupported"));
11853 }
11854
11855 #[test]
11856 fn test_versioned_workflow_state() {
11857 let workflow_id = Uuid::new_v4();
11858 let state = WorkflowState::new(workflow_id, 5);
11859 let mut versioned = VersionedWorkflowState::new(state);
11860
11861 assert_eq!(versioned.version, StateVersion::current());
11863 assert_eq!(versioned.migration_history.len(), 0);
11864
11865 let target = StateVersion::new(1, 1, 0);
11867 assert!(versioned.can_migrate_to(&target));
11868 assert!(versioned.migrate_to(target).is_ok());
11869 assert_eq!(versioned.version, target);
11870 assert_eq!(versioned.migration_history.len(), 1);
11871
11872 assert!(versioned.migrate_to(target).is_ok());
11874 assert_eq!(versioned.migration_history.len(), 1);
11875
11876 let incompatible = StateVersion::new(2, 0, 0);
11878 assert!(!versioned.can_migrate_to(&incompatible));
11879 assert!(versioned.migrate_to(incompatible).is_err());
11880 }
11881
11882 #[test]
11883 fn test_task_priority() {
11884 let low = TaskPriority::Low;
11885 let normal = TaskPriority::Normal;
11886 let high = TaskPriority::High;
11887 let critical = TaskPriority::Critical;
11888
11889 assert!(low < normal);
11891 assert!(normal < high);
11892 assert!(high < critical);
11893
11894 assert_eq!(format!("{}", low), "Low");
11896 assert_eq!(format!("{}", normal), "Normal");
11897 assert_eq!(format!("{}", high), "High");
11898 assert_eq!(format!("{}", critical), "Critical");
11899
11900 assert_eq!(TaskPriority::default(), TaskPriority::Normal);
11902 }
11903
11904 #[test]
11905 fn test_worker_capacity() {
11906 let mut worker = WorkerCapacity::new("worker1", 4, 8192);
11907
11908 assert_eq!(worker.worker_id, "worker1");
11909 assert_eq!(worker.cpu_cores, 4);
11910 assert_eq!(worker.memory_mb, 8192);
11911 assert_eq!(worker.current_load, 0.0);
11912 assert_eq!(worker.active_tasks, 0);
11913
11914 assert!(worker.has_capacity(0.5));
11916 assert!(worker.has_capacity(1.0));
11917 assert!(!worker.has_capacity(1.1));
11918
11919 assert_eq!(worker.available_capacity(), 1.0);
11921 worker.current_load = 0.3;
11922 assert_eq!(worker.available_capacity(), 0.7);
11923 }
11924
11925 #[test]
11926 fn test_scheduling_decision() {
11927 let task_id = Uuid::new_v4();
11928 let decision = SchedulingDecision::new(task_id, "worker1", TaskPriority::High)
11929 .with_estimated_time(120);
11930
11931 assert_eq!(decision.task_id, task_id);
11932 assert_eq!(decision.worker_id, "worker1");
11933 assert_eq!(decision.priority, TaskPriority::High);
11934 assert_eq!(decision.estimated_time, Some(120));
11935 }
11936
11937 #[test]
11938 fn test_scheduling_strategy() {
11939 let rr = SchedulingStrategy::RoundRobin;
11940 let ll = SchedulingStrategy::LeastLoaded;
11941 let pb = SchedulingStrategy::PriorityBased;
11942 let ra = SchedulingStrategy::ResourceAware;
11943
11944 assert_eq!(format!("{}", rr), "RoundRobin");
11945 assert_eq!(format!("{}", ll), "LeastLoaded");
11946 assert_eq!(format!("{}", pb), "PriorityBased");
11947 assert_eq!(format!("{}", ra), "ResourceAware");
11948
11949 assert_eq!(
11950 SchedulingStrategy::default(),
11951 SchedulingStrategy::LeastLoaded
11952 );
11953 }
11954
11955 #[test]
11956 fn test_parallel_scheduler_round_robin() {
11957 let mut scheduler = ParallelScheduler::new(SchedulingStrategy::RoundRobin);
11958
11959 scheduler.add_worker(WorkerCapacity::new("worker1", 4, 8192));
11961 scheduler.add_worker(WorkerCapacity::new("worker2", 4, 8192));
11962
11963 assert_eq!(scheduler.worker_count(), 2);
11964
11965 let task1 = Uuid::new_v4();
11967 let decision = scheduler.schedule_task(task1, TaskPriority::Normal);
11968 assert!(decision.is_some());
11969 }
11970
11971 #[test]
11972 fn test_parallel_scheduler_least_loaded() {
11973 let mut scheduler = ParallelScheduler::new(SchedulingStrategy::LeastLoaded);
11974
11975 let mut worker1 = WorkerCapacity::new("worker1", 4, 8192);
11976 worker1.current_load = 0.8;
11977 let mut worker2 = WorkerCapacity::new("worker2", 4, 8192);
11978 worker2.current_load = 0.3;
11979
11980 scheduler.add_worker(worker1);
11981 scheduler.add_worker(worker2);
11982
11983 let task = Uuid::new_v4();
11985 let decision = scheduler.schedule_task(task, TaskPriority::Normal);
11986 assert!(decision.is_some());
11987 assert_eq!(decision.unwrap().worker_id, "worker2");
11988 }
11989
11990 #[test]
11991 fn test_parallel_scheduler_metrics() {
11992 let mut scheduler = ParallelScheduler::new(SchedulingStrategy::LeastLoaded);
11993
11994 let mut worker1 = WorkerCapacity::new("worker1", 4, 8192);
11995 worker1.current_load = 0.5;
11996 let mut worker2 = WorkerCapacity::new("worker2", 4, 8192);
11997 worker2.current_load = 0.3;
11998
11999 scheduler.add_worker(worker1);
12000 scheduler.add_worker(worker2);
12001
12002 assert_eq!(scheduler.worker_count(), 2);
12004 assert_eq!(scheduler.average_load(), 0.4);
12005 assert_eq!(scheduler.total_capacity(), 1.2);
12006
12007 let display = format!("{}", scheduler);
12008 assert!(display.contains("ParallelScheduler"));
12009 assert!(display.contains("workers=2"));
12010 }
12011
12012 #[test]
12013 fn test_parallel_scheduler_max_tasks() {
12014 let mut scheduler =
12015 ParallelScheduler::new(SchedulingStrategy::LeastLoaded).with_max_tasks_per_worker(2);
12016
12017 let mut worker = WorkerCapacity::new("worker1", 4, 8192);
12018 worker.active_tasks = 2;
12019 scheduler.add_worker(worker);
12020
12021 let task = Uuid::new_v4();
12023 let decision = scheduler.schedule_task(task, TaskPriority::Normal);
12024 assert!(decision.is_none());
12025 }
12026
12027 #[test]
12028 fn test_workflow_batch() {
12029 let mut batch = WorkflowBatch::new(5);
12030
12031 assert!(batch.is_empty());
12032 assert!(!batch.is_full());
12033 assert_eq!(batch.size(), 0);
12034 assert_eq!(batch.max_size, 5);
12035
12036 let wf1 = Uuid::new_v4();
12038 let wf2 = Uuid::new_v4();
12039 assert!(batch.add_workflow(wf1));
12040 assert!(batch.add_workflow(wf2));
12041 assert_eq!(batch.size(), 2);
12042 assert!(!batch.is_empty());
12043 assert!(!batch.is_full());
12044
12045 for _ in 0..3 {
12047 batch.add_workflow(Uuid::new_v4());
12048 }
12049 assert!(batch.is_full());
12050
12051 assert!(!batch.add_workflow(Uuid::new_v4()));
12053
12054 let display = format!("{}", batch);
12056 assert!(display.contains("WorkflowBatch"));
12057 assert!(display.contains("5/5"));
12058 }
12059
12060 #[test]
12061 fn test_workflow_batch_timeout() {
12062 let mut batch = WorkflowBatch::new(10).with_timeout(0);
12063
12064 std::thread::sleep(std::time::Duration::from_millis(10));
12066 assert!(batch.is_timed_out());
12067
12068 batch.timeout = None;
12070 assert!(!batch.is_timed_out());
12071 }
12072
12073 #[test]
12074 fn test_batching_strategy() {
12075 let by_type = BatchingStrategy::ByType;
12076 let by_priority = BatchingStrategy::ByPriority;
12077 let by_size = BatchingStrategy::BySize;
12078 let by_time = BatchingStrategy::ByTimeWindow;
12079
12080 assert_eq!(format!("{}", by_type), "ByType");
12081 assert_eq!(format!("{}", by_priority), "ByPriority");
12082 assert_eq!(format!("{}", by_size), "BySize");
12083 assert_eq!(format!("{}", by_time), "ByTimeWindow");
12084
12085 assert_eq!(BatchingStrategy::default(), BatchingStrategy::ByType);
12086 }
12087
12088 #[test]
12089 fn test_workflow_batcher() {
12090 let mut batcher = WorkflowBatcher::new(BatchingStrategy::ByType)
12091 .with_batch_size(3)
12092 .with_timeout(60);
12093
12094 assert_eq!(batcher.batch_count(), 0);
12095 assert_eq!(batcher.total_workflow_count(), 0);
12096
12097 let wf1 = Uuid::new_v4();
12099 let wf2 = Uuid::new_v4();
12100 let wf3 = Uuid::new_v4();
12101
12102 batcher.add_workflow(wf1, TaskPriority::Normal);
12103 assert_eq!(batcher.batch_count(), 1);
12104 assert_eq!(batcher.total_workflow_count(), 1);
12105
12106 batcher.add_workflow(wf2, TaskPriority::Normal);
12107 batcher.add_workflow(wf3, TaskPriority::Normal);
12108 assert_eq!(batcher.batch_count(), 1);
12109 assert_eq!(batcher.total_workflow_count(), 3);
12110
12111 let ready = batcher.get_ready_batches();
12113 assert_eq!(ready.len(), 1);
12114 assert!(ready[0].is_full());
12115 }
12116
12117 #[test]
12118 fn test_workflow_batcher_by_priority() {
12119 let mut batcher = WorkflowBatcher::new(BatchingStrategy::ByPriority).with_batch_size(5);
12120
12121 let wf1 = Uuid::new_v4();
12123 let wf2 = Uuid::new_v4();
12124 let wf3 = Uuid::new_v4();
12125
12126 batcher.add_workflow(wf1, TaskPriority::High);
12127 batcher.add_workflow(wf2, TaskPriority::Low);
12128 batcher.add_workflow(wf3, TaskPriority::High);
12129
12130 assert_eq!(batcher.batch_count(), 2);
12132 assert_eq!(batcher.total_workflow_count(), 3);
12133 }
12134
12135 #[test]
12136 fn test_workflow_batcher_remove_ready() {
12137 let mut batcher = WorkflowBatcher::new(BatchingStrategy::ByType).with_batch_size(2);
12138
12139 batcher.add_workflow(Uuid::new_v4(), TaskPriority::Normal);
12141 batcher.add_workflow(Uuid::new_v4(), TaskPriority::Normal);
12142
12143 batcher.add_workflow(Uuid::new_v4(), TaskPriority::Normal);
12145
12146 assert_eq!(batcher.batch_count(), 2);
12147
12148 let ready = batcher.remove_ready_batches();
12150 assert_eq!(ready.len(), 1);
12151 assert_eq!(batcher.batch_count(), 1);
12152
12153 let display = format!("{}", batcher);
12154 assert!(display.contains("WorkflowBatcher"));
12155 assert!(display.contains("batches=1"));
12156 }
12157
12158 #[test]
12159 fn test_streaming_map_reduce() {
12160 let map_task = Signature::new("map_task".to_string());
12161 let reduce_task = Signature::new("reduce_task".to_string());
12162
12163 let stream = StreamingMapReduce::new(map_task, reduce_task)
12164 .with_chunk_size(50)
12165 .with_buffer_size(500)
12166 .with_backpressure(true);
12167
12168 assert_eq!(stream.chunk_size, 50);
12169 assert_eq!(stream.buffer_size, 500);
12170 assert!(stream.backpressure);
12171
12172 let display = format!("{}", stream);
12173 assert!(display.contains("StreamingMapReduce"));
12174 assert!(display.contains("map_task"));
12175 assert!(display.contains("reduce_task"));
12176 assert!(display.contains("chunk_size=50"));
12177 assert!(display.contains("buffer_size=500"));
12178 }
12179
12180 #[test]
12181 fn test_resource_utilization() {
12182 let util = ResourceUtilization::new(0.8, 0.6, 0.4, 0.2);
12183
12184 assert_eq!(util.cpu, 0.8);
12185 assert_eq!(util.memory, 0.6);
12186 assert_eq!(util.disk_io, 0.4);
12187 assert_eq!(util.network_io, 0.2);
12188
12189 assert!((util.overall() - 0.5).abs() < 0.01);
12191
12192 assert!(util.is_overloaded(0.7));
12194 assert!(!util.is_overloaded(0.9));
12195
12196 assert_eq!(util.bottleneck(), "cpu");
12198
12199 let util2 = ResourceUtilization::new(1.5, -0.5, 0.5, 0.5);
12201 assert_eq!(util2.cpu, 1.0);
12202 assert_eq!(util2.memory, 0.0);
12203 }
12204
12205 #[test]
12206 fn test_resource_utilization_display() {
12207 let util = ResourceUtilization::new(0.5, 0.6, 0.7, 0.8);
12208 let display = format!("{}", util);
12209 assert!(display.contains("ResourceUtilization"));
12210 assert!(display.contains("cpu=0.50"));
12211 assert!(display.contains("mem=0.60"));
12212 }
12213
12214 #[test]
12215 fn test_workflow_resource_monitor() {
12216 let workflow_id = Uuid::new_v4();
12217 let mut monitor = WorkflowResourceMonitor::new(workflow_id)
12218 .with_max_history(100)
12219 .with_sampling_interval(10);
12220
12221 assert_eq!(monitor.workflow_id, workflow_id);
12222 assert_eq!(monitor.max_history, 100);
12223 assert_eq!(monitor.sampling_interval, 10);
12224 assert_eq!(monitor.history.len(), 0);
12225
12226 monitor.record(ResourceUtilization::new(0.5, 0.6, 0.4, 0.3));
12228 monitor.record(ResourceUtilization::new(0.7, 0.8, 0.6, 0.5));
12229
12230 assert_eq!(monitor.history.len(), 2);
12231
12232 let peak = monitor.peak_utilization().unwrap();
12234 assert!(peak.overall() > 0.6);
12235
12236 let avg = monitor.average_utilization(3600).unwrap();
12238 assert!(avg.cpu > 0.5 && avg.cpu < 0.7);
12239
12240 monitor.clear();
12242 assert_eq!(monitor.history.len(), 0);
12243 }
12244
12245 #[test]
12246 fn test_workflow_resource_monitor_max_history() {
12247 let workflow_id = Uuid::new_v4();
12248 let mut monitor = WorkflowResourceMonitor::new(workflow_id).with_max_history(3);
12249
12250 for i in 0..5 {
12252 monitor.record(ResourceUtilization::new(i as f64 * 0.1, 0.5, 0.5, 0.5));
12253 }
12254
12255 assert_eq!(monitor.history.len(), 3);
12257
12258 let display = format!("{}", monitor);
12259 assert!(display.contains("WorkflowResourceMonitor"));
12260 assert!(display.contains("samples=3"));
12261 }
12262
12263 #[test]
12264 fn test_batching_strategy_display() {
12265 let strategy = BatchingStrategy::ByPriority;
12266 assert_eq!(format!("{}", strategy), "ByPriority");
12267 }
12268
12269 #[test]
12270 fn test_observable() {
12271 let mut obs = Observable::new(42);
12272 assert_eq!(*obs.get(), 42);
12273 assert_eq!(obs.subscriber_count(), 0);
12274
12275 let wf1 = Uuid::new_v4();
12277 let wf2 = Uuid::new_v4();
12278 obs.subscribe(wf1);
12279 obs.subscribe(wf2);
12280 assert_eq!(obs.subscriber_count(), 2);
12281
12282 obs.set(100);
12284 assert_eq!(*obs.get(), 100);
12285 assert_eq!(obs.history.len(), 1);
12286
12287 obs.unsubscribe(&wf1);
12289 assert_eq!(obs.subscriber_count(), 1);
12290 }
12291
12292 #[test]
12293 fn test_reactive_workflow() {
12294 let reaction = Signature::new("on_change".to_string());
12295 let workflow = ReactiveWorkflow::new(reaction)
12296 .watch("observable1")
12297 .watch("observable2")
12298 .with_debounce(100)
12299 .with_throttle(500)
12300 .with_filter("value > 10");
12301
12302 assert_eq!(workflow.watched_observables.len(), 2);
12303 assert_eq!(workflow.debounce_ms, Some(100));
12304 assert_eq!(workflow.throttle_ms, Some(500));
12305 assert!(workflow.filter.is_some());
12306
12307 let display = format!("{}", workflow);
12308 assert!(display.contains("ReactiveWorkflow"));
12309 assert!(display.contains("watching=2"));
12310 assert!(display.contains("on_change"));
12311 }
12312
12313 #[test]
12314 fn test_stream_operator() {
12315 let map_op = StreamOperator::Map;
12316 let filter_op = StreamOperator::Filter;
12317 let debounce_op = StreamOperator::Debounce;
12318
12319 assert_eq!(format!("{}", map_op), "Map");
12320 assert_eq!(format!("{}", filter_op), "Filter");
12321 assert_eq!(format!("{}", debounce_op), "Debounce");
12322 }
12323
12324 #[test]
12325 fn test_reactive_stream() {
12326 let mut stream = ReactiveStream::new("source1")
12327 .map(serde_json::json!({"transform": "uppercase"}))
12328 .filter(serde_json::json!({"condition": "length > 5"}))
12329 .take(10)
12330 .skip(2)
12331 .debounce(100)
12332 .throttle(500);
12333
12334 assert_eq!(stream.source_id, "source1");
12335 assert_eq!(stream.operators.len(), 6);
12336
12337 let wf = Uuid::new_v4();
12339 stream.subscribe(wf);
12340 assert_eq!(stream.subscribers.len(), 1);
12341
12342 let display = format!("{}", stream);
12343 assert!(display.contains("ReactiveStream"));
12344 assert!(display.contains("source=source1"));
12345 assert!(display.contains("operators=6"));
12346 }
12347
12348 #[test]
12349 fn test_mock_task_result() {
12350 let success =
12351 MockTaskResult::success("task1", serde_json::json!({"result": "ok"})).with_delay(50);
12352 assert!(!success.should_fail);
12353 assert_eq!(success.delay_ms, 50);
12354
12355 let failure = MockTaskResult::failure("task2", "Task failed");
12356 assert!(failure.should_fail);
12357 assert_eq!(failure.failure_message, Some("Task failed".to_string()));
12358 }
12359
12360 #[test]
12361 fn test_mock_task_executor() {
12362 let mut executor = MockTaskExecutor::new();
12363
12364 executor.register(MockTaskResult::success("task1", serde_json::json!(42)));
12366 executor.register(MockTaskResult::failure("task2", "Error"));
12367
12368 let result1 = executor.execute("task1");
12370 assert!(result1.is_ok());
12371 assert_eq!(result1.unwrap(), serde_json::json!(42));
12372
12373 let result2 = executor.execute("task2");
12375 assert!(result2.is_err());
12376
12377 let result3 = executor.execute("task3");
12379 assert!(result3.is_err());
12380
12381 assert_eq!(executor.execution_count("task1"), 1);
12383 assert_eq!(executor.execution_count("task2"), 1);
12384
12385 executor.clear_history();
12387 assert_eq!(executor.execution_count("task1"), 0);
12388 }
12389
12390 #[test]
12391 fn test_test_data_injector() {
12392 let mut injector = TestDataInjector::new();
12393
12394 injector.inject("key1", serde_json::json!({"value": 123}));
12396 injector.inject("key2", serde_json::json!("test"));
12397
12398 assert!(injector.get("key1").is_some());
12400 assert_eq!(injector.get("key2"), Some(&serde_json::json!("test")));
12401 assert!(injector.get("key3").is_none());
12402
12403 injector.clear();
12405 assert!(injector.get("key1").is_none());
12406 }
12407
12408 #[test]
12409 fn test_workflow_snapshot() {
12410 let workflow_id = Uuid::new_v4();
12411 let state = WorkflowState::new(workflow_id, 5);
12412 let mut snapshot = WorkflowSnapshot::new(workflow_id, state);
12413
12414 assert_eq!(snapshot.workflow_id, workflow_id);
12415 assert_eq!(snapshot.completed_tasks.len(), 0);
12416
12417 let task_id = Uuid::new_v4();
12419 snapshot.record_task(task_id, serde_json::json!({"result": "ok"}));
12420
12421 assert_eq!(snapshot.completed_tasks.len(), 1);
12422 assert!(snapshot.task_results.contains_key(&task_id));
12423
12424 let checkpoint = WorkflowCheckpoint::new(workflow_id, WorkflowState::new(workflow_id, 5));
12426 snapshot = snapshot.with_checkpoint(checkpoint);
12427 assert!(snapshot.checkpoint.is_some());
12428 }
12429
12430 #[test]
12431 fn test_time_travel_debugger() {
12432 let workflow_id = Uuid::new_v4();
12433 let mut debugger = TimeTravelDebugger::new(workflow_id);
12434
12435 assert_eq!(debugger.snapshot_count(), 0);
12436 assert!(!debugger.step_mode);
12437
12438 let snapshot1 = WorkflowSnapshot::new(workflow_id, WorkflowState::new(workflow_id, 5));
12440 let snapshot2 = WorkflowSnapshot::new(workflow_id, WorkflowState::new(workflow_id, 5));
12441 debugger.record_snapshot(snapshot1);
12442 debugger.record_snapshot(snapshot2);
12443
12444 assert_eq!(debugger.snapshot_count(), 2);
12445 assert_eq!(debugger.current_index, 1);
12446
12447 let snapshot = debugger.step_backward();
12449 assert!(snapshot.is_some());
12450 assert_eq!(debugger.current_index, 0);
12451
12452 let snapshot = debugger.step_forward();
12454 assert!(snapshot.is_some());
12455 assert_eq!(debugger.current_index, 1);
12456
12457 let snapshot = debugger.replay_from(0);
12459 assert!(snapshot.is_some());
12460 assert_eq!(debugger.current_index, 0);
12461
12462 debugger.enable_step_mode();
12464 assert!(debugger.step_mode);
12465
12466 let display = format!("{}", debugger);
12468 assert!(display.contains("TimeTravelDebugger"));
12469 assert!(display.contains("snapshots=2"));
12470
12471 debugger.clear();
12473 assert_eq!(debugger.snapshot_count(), 0);
12474 }
12475
12476 mod integration {
12480 use super::*;
12481 use std::sync::atomic::{AtomicUsize, Ordering};
12482 use std::sync::Arc;
12483 use std::time::{Duration, Instant};
12484
12485 #[derive(Clone)]
12487 struct MockBroker {
12488 tasks: Arc<std::sync::Mutex<Vec<String>>>,
12489 }
12490
12491 impl MockBroker {
12492 fn new() -> Self {
12493 Self {
12494 tasks: Arc::new(std::sync::Mutex::new(Vec::new())),
12495 }
12496 }
12497
12498 fn enqueued_tasks(&self) -> Vec<String> {
12499 self.tasks.lock().unwrap().clone()
12500 }
12501
12502 fn task_count(&self) -> usize {
12503 self.tasks.lock().unwrap().len()
12504 }
12505 }
12506
12507 #[async_trait::async_trait]
12508 impl celers_core::Broker for MockBroker {
12509 async fn enqueue(
12510 &self,
12511 task: celers_core::SerializedTask,
12512 ) -> celers_core::Result<celers_core::TaskId> {
12513 let task_name = task.metadata.name.clone();
12514 let task_id = task.metadata.id;
12515 self.tasks.lock().unwrap().push(task_name);
12516 Ok(task_id)
12517 }
12518
12519 async fn dequeue(&self) -> celers_core::Result<Option<celers_core::BrokerMessage>> {
12520 Ok(None)
12521 }
12522
12523 async fn ack(
12524 &self,
12525 _task_id: &celers_core::TaskId,
12526 _receipt_handle: Option<&str>,
12527 ) -> celers_core::Result<()> {
12528 Ok(())
12529 }
12530
12531 async fn reject(
12532 &self,
12533 _task_id: &celers_core::TaskId,
12534 _receipt_handle: Option<&str>,
12535 _requeue: bool,
12536 ) -> celers_core::Result<()> {
12537 Ok(())
12538 }
12539
12540 async fn queue_size(&self) -> celers_core::Result<usize> {
12541 Ok(self.tasks.lock().unwrap().len())
12542 }
12543
12544 async fn cancel(&self, _task_id: &celers_core::TaskId) -> celers_core::Result<bool> {
12545 Ok(true)
12546 }
12547 }
12548
12549 #[tokio::test]
12552 async fn test_chain_broker_integration() {
12553 let broker = MockBroker::new();
12554
12555 let chain = Chain::new()
12556 .then("task1", vec![serde_json::json!(1)])
12557 .then("task2", vec![serde_json::json!(2)])
12558 .then("task3", vec![serde_json::json!(3)]);
12559
12560 let result = chain.apply(&broker).await;
12562 assert!(result.is_ok(), "Chain apply should succeed");
12563
12564 let tasks = broker.enqueued_tasks();
12567 assert_eq!(tasks.len(), 1, "Chain should publish only first task");
12568 assert!(tasks.contains(&"task1".to_string()));
12569 }
12570
12571 #[tokio::test]
12572 async fn test_group_broker_integration() {
12573 let broker = MockBroker::new();
12574
12575 let group = Group::new()
12576 .add("task1", vec![serde_json::json!(1)])
12577 .add("task2", vec![serde_json::json!(2)])
12578 .add("task3", vec![serde_json::json!(3)]);
12579
12580 let result = group.apply(&broker).await;
12582 assert!(result.is_ok(), "Group apply should succeed");
12583
12584 let tasks = broker.enqueued_tasks();
12586 assert_eq!(tasks.len(), 3, "Should publish 3 tasks");
12587 }
12588
12589 #[tokio::test]
12590 async fn test_map_broker_integration() {
12591 let broker = MockBroker::new();
12592
12593 let map = Map::new(
12594 Signature::new("process".to_string()),
12595 vec![
12596 vec![serde_json::json!(1)],
12597 vec![serde_json::json!(2)],
12598 vec![serde_json::json!(3)],
12599 ],
12600 );
12601
12602 let result = map.apply(&broker).await;
12603 assert!(result.is_ok(), "Map apply should succeed");
12604
12605 let tasks = broker.enqueued_tasks();
12607 assert_eq!(tasks.len(), 3, "Should publish 3 task instances");
12608 assert_eq!(tasks.iter().filter(|t| *t == "process").count(), 3);
12609 }
12610
12611 #[tokio::test]
12612 async fn test_nested_workflow_broker_integration() {
12613 let broker = MockBroker::new();
12614
12615 let inner_group1 = Group::new()
12617 .add("task1", vec![serde_json::json!(1)])
12618 .add("task2", vec![serde_json::json!(2)]);
12619
12620 let inner_group2 = Group::new()
12621 .add("task3", vec![serde_json::json!(3)])
12622 .add("task4", vec![serde_json::json!(4)]);
12623
12624 let _ = inner_group1.apply(&broker).await;
12625 let _ = inner_group2.apply(&broker).await;
12626
12627 assert_eq!(broker.task_count(), 4, "Should publish all nested tasks");
12628 }
12629
12630 #[cfg(feature = "backend-redis")]
12633 #[tokio::test]
12634 async fn test_chord_backend_integration() {
12635 let group = Group::new()
12636 .add("task1", vec![serde_json::json!(1)])
12637 .add("task2", vec![serde_json::json!(2)]);
12638 let callback = Signature::new("aggregate".to_string());
12639 let chord = Chord::new(group, callback);
12640
12641 assert_eq!(chord.header.tasks.len(), 2);
12642 assert_eq!(chord.body.task, "aggregate");
12643 }
12644
12645 #[cfg(feature = "backend-redis")]
12646 #[tokio::test]
12647 async fn test_chord_state_tracking() {
12648 let chord_id = Uuid::new_v4();
12649 let mut group = Group::new();
12650 group.group_id = Some(chord_id);
12651 let group = group
12652 .add("task1", vec![serde_json::json!(1)])
12653 .add("task2", vec![serde_json::json!(2)]);
12654 let callback = Signature::new("aggregate".to_string());
12655 let chord = Chord::new(group, callback);
12656
12657 assert_eq!(chord.header.group_id, Some(chord_id));
12658 assert_eq!(chord.header.tasks.len(), 2);
12659 }
12660
12661 #[tokio::test]
12664 async fn test_chord_concurrent_completion() {
12665 let counter = Arc::new(AtomicUsize::new(0));
12666 let barrier = Arc::new(tokio::sync::Barrier::new(10));
12667
12668 let mut handles = vec![];
12669
12670 for _ in 0..10 {
12672 let counter = counter.clone();
12673 let barrier = barrier.clone();
12674
12675 let handle = tokio::spawn(async move {
12676 barrier.wait().await;
12678
12679 let old = counter.fetch_add(1, Ordering::SeqCst);
12681 old + 1
12682 });
12683
12684 handles.push(handle);
12685 }
12686
12687 let mut results = vec![];
12689 for handle in handles {
12690 results.push(handle.await.unwrap());
12691 }
12692
12693 results.sort();
12695 assert_eq!(results, vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
12696 assert_eq!(counter.load(Ordering::SeqCst), 10);
12697 }
12698
12699 #[tokio::test]
12700 async fn test_chord_barrier_idempotency() {
12701 let callback_count = Arc::new(AtomicUsize::new(0));
12703 let completed_count = Arc::new(AtomicUsize::new(0));
12704 let total_tasks = 5;
12705
12706 let mut handles = vec![];
12707
12708 for _ in 0..total_tasks {
12709 let callback_count = callback_count.clone();
12710 let completed_count = completed_count.clone();
12711
12712 let handle = tokio::spawn(async move {
12713 let count = completed_count.fetch_add(1, Ordering::SeqCst) + 1;
12715
12716 if count == total_tasks {
12718 callback_count.fetch_add(1, Ordering::SeqCst);
12719 }
12720 });
12721
12722 handles.push(handle);
12723 }
12724
12725 for handle in handles {
12726 handle.await.unwrap();
12727 }
12728
12729 assert_eq!(callback_count.load(Ordering::SeqCst), 1);
12731 assert_eq!(completed_count.load(Ordering::SeqCst), total_tasks);
12732 }
12733
12734 #[tokio::test]
12735 async fn test_chord_partial_failure_handling() {
12736 let success_count = Arc::new(AtomicUsize::new(0));
12738 let failure_count = Arc::new(AtomicUsize::new(0));
12739
12740 let mut handles = vec![];
12741
12742 for i in 0..10 {
12743 let success_count = success_count.clone();
12744 let failure_count = failure_count.clone();
12745
12746 let handle = tokio::spawn(async move {
12747 if i % 3 == 0 {
12748 failure_count.fetch_add(1, Ordering::SeqCst);
12750 Err::<(), &str>("Task failed")
12751 } else {
12752 success_count.fetch_add(1, Ordering::SeqCst);
12754 Ok(())
12755 }
12756 });
12757
12758 handles.push(handle);
12759 }
12760
12761 for handle in handles {
12762 let _ = handle.await.unwrap();
12763 }
12764
12765 let success = success_count.load(Ordering::SeqCst);
12766 let failure = failure_count.load(Ordering::SeqCst);
12767
12768 assert_eq!(success + failure, 10);
12769 assert!(failure > 0, "Should have some failures");
12770 }
12771
12772 #[test]
12775 fn test_chain_creation_performance() {
12776 let start = Instant::now();
12777
12778 for _ in 0..1000 {
12779 let _ = Chain::new()
12780 .then("task1", vec![serde_json::json!(1)])
12781 .then("task2", vec![serde_json::json!(2)])
12782 .then("task3", vec![serde_json::json!(3)]);
12783 }
12784
12785 let duration = start.elapsed();
12786 assert!(
12787 duration < Duration::from_millis(100),
12788 "Creating 1000 chains should take less than 100ms, took {:?}",
12789 duration
12790 );
12791 }
12792
12793 #[test]
12794 fn test_group_creation_performance() {
12795 let start = Instant::now();
12796
12797 for _ in 0..1000 {
12798 let _ = Group::new()
12799 .add("task1", vec![serde_json::json!(1)])
12800 .add("task2", vec![serde_json::json!(2)])
12801 .add("task3", vec![serde_json::json!(3)]);
12802 }
12803
12804 let duration = start.elapsed();
12805 assert!(
12806 duration < Duration::from_millis(100),
12807 "Creating 1000 groups should take less than 100ms, took {:?}",
12808 duration
12809 );
12810 }
12811
12812 #[test]
12813 fn test_large_workflow_creation() {
12814 let start = Instant::now();
12815
12816 let mut chain = Chain::new();
12817 for i in 0..1000 {
12818 let task_name = format!("task{}", i);
12819 chain = chain.then(&task_name, vec![serde_json::json!(i)]);
12820 }
12821
12822 let duration = start.elapsed();
12823 assert!(
12824 duration < Duration::from_millis(500),
12825 "Creating chain with 1000 tasks should take less than 500ms, took {:?}",
12826 duration
12827 );
12828 assert_eq!(chain.len(), 1000);
12829 }
12830
12831 #[test]
12832 fn test_map_with_large_dataset() {
12833 let start = Instant::now();
12834
12835 let args: Vec<Vec<serde_json::Value>> =
12836 (0..1000).map(|i| vec![serde_json::json!(i)]).collect();
12837
12838 let map = Map::new(Signature::new("process".to_string()), args);
12839
12840 let duration = start.elapsed();
12841 assert!(
12842 duration < Duration::from_millis(100),
12843 "Creating map with 1000 items should take less than 100ms, took {:?}",
12844 duration
12845 );
12846 assert_eq!(map.len(), 1000);
12847 }
12848
12849 #[test]
12850 fn test_workflow_serialization_performance() {
12851 let chain = Chain::new()
12852 .then("task1", vec![serde_json::json!(1)])
12853 .then("task2", vec![serde_json::json!(2)])
12854 .then("task3", vec![serde_json::json!(3)]);
12855
12856 let start = Instant::now();
12857
12858 for _ in 0..1000 {
12859 let _ = serde_json::to_string(&chain).unwrap();
12860 }
12861
12862 let duration = start.elapsed();
12863 assert!(
12864 duration < Duration::from_millis(100),
12865 "Serializing chain 1000 times should take less than 100ms, took {:?}",
12866 duration
12867 );
12868 }
12869
12870 #[tokio::test]
12871 async fn test_broker_publish_performance() {
12872 let broker = MockBroker::new();
12873 let start = Instant::now();
12874
12875 for i in 0..100 {
12876 let task_name = format!("task{}", i);
12877 let chain = Chain::new().then(&task_name, vec![serde_json::json!(i)]);
12878 let _ = chain.apply(&broker).await;
12879 }
12880
12881 let duration = start.elapsed();
12882 assert!(
12883 duration < Duration::from_secs(1),
12884 "Publishing 100 chains should take less than 1s, took {:?}",
12885 duration
12886 );
12887 assert_eq!(broker.task_count(), 100);
12888 }
12889
12890 #[tokio::test]
12891 async fn test_concurrent_workflow_enqueue() {
12892 let broker = Arc::new(MockBroker::new());
12893 let mut handles = vec![];
12894
12895 let start = Instant::now();
12896
12897 for i in 0..100 {
12898 let broker = broker.clone();
12899 let handle = tokio::spawn(async move {
12900 let task_name = format!("task{}", i);
12901 let chain = Chain::new().then(&task_name, vec![serde_json::json!(i)]);
12902 chain.apply(&*broker).await
12903 });
12904 handles.push(handle);
12905 }
12906
12907 for handle in handles {
12908 handle.await.unwrap().unwrap();
12909 }
12910
12911 let duration = start.elapsed();
12912 assert!(
12913 duration < Duration::from_secs(2),
12914 "Concurrent publishing of 100 chains should take less than 2s, took {:?}",
12915 duration
12916 );
12917 assert_eq!(broker.task_count(), 100);
12918 }
12919
12920 #[test]
12921 fn test_memory_efficiency_large_group() {
12922 let mut group = Group::new();
12924
12925 for i in 0..10000 {
12926 let task_name = format!("task{}", i);
12927 group = group.add(&task_name, vec![serde_json::json!(i)]);
12928 }
12929
12930 assert_eq!(group.len(), 10000);
12931 assert!(!group.is_empty());
12932 }
12933
12934 #[test]
12935 fn test_workflow_clone_performance() {
12936 let chain = Chain::new()
12937 .then("task1", vec![serde_json::json!(1)])
12938 .then("task2", vec![serde_json::json!(2)])
12939 .then("task3", vec![serde_json::json!(3)]);
12940
12941 let start = Instant::now();
12942
12943 for _ in 0..1000 {
12944 let _ = chain.clone();
12945 }
12946
12947 let duration = start.elapsed();
12948 assert!(
12949 duration < Duration::from_millis(50),
12950 "Cloning chain 1000 times should take less than 50ms, took {:?}",
12951 duration
12952 );
12953 }
12954
12955 #[test]
12958 fn test_deeply_nested_workflows() {
12959 let mut current = Chain::new().then("task0", vec![serde_json::json!(0)]);
12961
12962 for i in 1..100 {
12963 let task_name = format!("task{}", i);
12964 current = current.then(&task_name, vec![serde_json::json!(i)]);
12965 }
12966
12967 assert_eq!(current.len(), 100);
12968 }
12969
12970 #[test]
12971 fn test_workflow_with_large_payloads() {
12972 let large_data = vec![serde_json::json!({ "data": "x".repeat(10000) })];
12974
12975 let chain = Chain::new()
12976 .then("process_large", large_data.clone())
12977 .then("process_large2", large_data);
12978
12979 let serialized = serde_json::to_string(&chain).unwrap();
12980 assert!(
12981 serialized.len() > 20000,
12982 "Serialized chain should contain large data"
12983 );
12984 }
12985
12986 #[test]
12989 fn test_dag_export_dot_format() {
12990 let chain = Chain::new()
12991 .then("task1", vec![serde_json::json!(1)])
12992 .then("task2", vec![serde_json::json!(2)])
12993 .then("task3", vec![serde_json::json!(3)]);
12994
12995 let dot = chain.to_dot();
12996 assert!(dot.contains("digraph Chain"));
12997 assert!(dot.contains("task1"));
12998 assert!(dot.contains("task2"));
12999 assert!(dot.contains("task3"));
13000 assert!(dot.contains("->"));
13001 }
13002
13003 #[test]
13004 fn test_dag_export_mermaid_format() {
13005 let group = Group::new()
13006 .add("task1", vec![serde_json::json!(1)])
13007 .add("task2", vec![serde_json::json!(2)]);
13008
13009 let mermaid = group.to_mermaid();
13010 assert!(mermaid.contains("graph"));
13011 assert!(mermaid.contains("task1"));
13012 assert!(mermaid.contains("task2"));
13013 }
13014
13015 #[test]
13016 fn test_dag_export_json_format() {
13017 let chain = Chain::new().then("task1", vec![serde_json::json!(1)]);
13018
13019 let json = chain.to_json().unwrap();
13020 assert!(json.contains("task1"));
13021 assert!(json.contains("tasks"));
13022 }
13023
13024 #[test]
13025 fn test_dag_export_render_commands() {
13026 let chain = Chain::new().then("task1", vec![serde_json::json!(1)]);
13027
13028 let svg_cmd = chain.svg_render_command();
13029 assert!(svg_cmd.contains("dot"));
13030 assert!(svg_cmd.contains("-Tsvg"));
13031
13032 let png_cmd = chain.png_render_command();
13033 assert!(png_cmd.contains("dot"));
13034 assert!(png_cmd.contains("-Tpng"));
13035 }
13036
13037 #[test]
13038 #[ignore] fn test_dag_export_to_svg() {
13040 let chain = Chain::new()
13041 .then("task1", vec![serde_json::json!(1)])
13042 .then("task2", vec![serde_json::json!(2)]);
13043
13044 if is_graphviz_available() {
13045 let svg = chain.to_svg().unwrap();
13046 assert!(svg.contains("<svg"));
13047 assert!(svg.contains("</svg>"));
13048 assert!(svg.contains("task1"));
13049 } else {
13050 println!("Skipping: GraphViz not installed");
13051 }
13052 }
13053
13054 #[test]
13055 #[ignore] fn test_dag_export_to_png() {
13057 let chain = Chain::new()
13058 .then("task1", vec![serde_json::json!(1)])
13059 .then("task2", vec![serde_json::json!(2)]);
13060
13061 if is_graphviz_available() {
13062 let png = chain.to_png().unwrap();
13063 assert!(!png.is_empty());
13064 assert_eq!(&png[0..4], &[0x89, 0x50, 0x4E, 0x47]);
13066 } else {
13067 println!("Skipping: GraphViz not installed");
13068 }
13069 }
13070
13071 #[test]
13072 fn test_graphviz_availability_check() {
13073 let _available = is_graphviz_available();
13075 }
13077
13078 #[test]
13079 fn test_dag_format_enum() {
13080 let _dot = DagFormat::Dot;
13081 let _mermaid = DagFormat::Mermaid;
13082 let _json = DagFormat::Json;
13083 let _svg = DagFormat::Svg;
13084 let _png = DagFormat::Png;
13085 }
13086
13087 #[test]
13092 fn test_visual_theme_light() {
13093 let theme = VisualTheme::light();
13094 assert_eq!(theme.name, "light");
13095 assert_eq!(theme.color_for_state("completed"), Some("#4CAF50"));
13096 assert_eq!(theme.color_for_state("running"), Some("#2196F3"));
13097 assert_eq!(theme.color_for_state("failed"), Some("#F44336"));
13098 assert_eq!(theme.shape_for_type("task"), Some("box"));
13099 }
13100
13101 #[test]
13102 fn test_visual_theme_dark() {
13103 let theme = VisualTheme::dark();
13104 assert_eq!(theme.name, "dark");
13105 assert_eq!(theme.color_for_state("completed"), Some("#388E3C"));
13106 assert_eq!(theme.color_for_state("running"), Some("#1976D2"));
13107 assert_eq!(theme.color_for_state("failed"), Some("#D32F2F"));
13108 }
13109
13110 #[test]
13111 fn test_visual_theme_default() {
13112 let theme = VisualTheme::default();
13113 assert_eq!(theme.name, "light");
13114 }
13115
13116 #[test]
13117 fn test_task_visual_metadata() {
13118 let task_id = Uuid::new_v4();
13119 let mut metadata =
13120 TaskVisualMetadata::new(task_id, "test_task".to_string(), "running".to_string());
13121
13122 assert_eq!(metadata.task_name, "test_task");
13123 assert_eq!(metadata.state, "running");
13124 assert_eq!(metadata.progress, 0.0);
13125 assert_eq!(metadata.color, "#2196F3");
13126
13127 metadata = metadata.with_progress(50.0);
13128 assert_eq!(metadata.progress, 50.0);
13129
13130 metadata = metadata.with_position(100.0, 200.0);
13131 assert_eq!(metadata.position, Some((100.0, 200.0)));
13132
13133 metadata.add_css_class("highlight".to_string());
13134 assert!(metadata.css_classes.contains(&"highlight".to_string()));
13135
13136 metadata.add_metadata("custom".to_string(), serde_json::json!("value"));
13137 assert_eq!(
13138 metadata.metadata.get("custom"),
13139 Some(&serde_json::json!("value"))
13140 );
13141 }
13142
13143 #[test]
13144 fn test_workflow_visualization_data() {
13145 let workflow_id = Uuid::new_v4();
13146 let state = WorkflowState {
13147 workflow_id,
13148 status: WorkflowStatus::Running,
13149 total_tasks: 3,
13150 completed_tasks: 1,
13151 failed_tasks: 0,
13152 start_time: Some(12345),
13153 end_time: None,
13154 current_stage: Some("stage1".to_string()),
13155 intermediate_results: HashMap::new(),
13156 };
13157
13158 let mut viz_data =
13159 WorkflowVisualizationData::new(workflow_id, "test_workflow".to_string(), state);
13160
13161 let task1_id = Uuid::new_v4();
13162 let task1 =
13163 TaskVisualMetadata::new(task1_id, "task1".to_string(), "completed".to_string());
13164 viz_data.add_task(task1);
13165
13166 let task2_id = Uuid::new_v4();
13167 let task2 =
13168 TaskVisualMetadata::new(task2_id, "task2".to_string(), "running".to_string());
13169 viz_data.add_task(task2);
13170
13171 viz_data.add_edge(task1_id, task2_id, "chain".to_string());
13172
13173 assert_eq!(viz_data.tasks.len(), 2);
13174 assert_eq!(viz_data.edges.len(), 1);
13175
13176 let json = viz_data.to_json();
13178 assert!(json.is_ok());
13179
13180 let visjs = viz_data.to_visjs_format();
13182 assert!(visjs.is_object());
13183 }
13184
13185 #[test]
13186 fn test_workflow_visualization_data_with_theme() {
13187 let workflow_id = Uuid::new_v4();
13188 let state = WorkflowState {
13189 workflow_id,
13190 status: WorkflowStatus::Pending,
13191 total_tasks: 1,
13192 completed_tasks: 0,
13193 failed_tasks: 0,
13194 start_time: None,
13195 end_time: None,
13196 current_stage: None,
13197 intermediate_results: HashMap::new(),
13198 };
13199
13200 let viz_data = WorkflowVisualizationData::new(workflow_id, "test".to_string(), state)
13201 .with_theme(VisualTheme::dark())
13202 .with_layout("force".to_string());
13203
13204 assert_eq!(viz_data.theme.name, "dark");
13205 assert_eq!(viz_data.layout_hint, "force");
13206 }
13207
13208 #[test]
13209 fn test_timeline_entry() {
13210 let task_id = Uuid::new_v4();
13211 let mut entry = TimelineEntry::new(task_id, "test_task".to_string(), 1000);
13212
13213 assert_eq!(entry.task_name, "test_task");
13214 assert_eq!(entry.start_time, 1000);
13215 assert_eq!(entry.state, "running");
13216 assert_eq!(entry.end_time, None);
13217
13218 entry.complete(2000);
13219 assert_eq!(entry.end_time, Some(2000));
13220 assert_eq!(entry.duration, Some(1000));
13221 assert_eq!(entry.state, "completed");
13222 assert_eq!(entry.color, "#4CAF50");
13223 }
13224
13225 #[test]
13226 fn test_timeline_entry_fail() {
13227 let task_id = Uuid::new_v4();
13228 let mut entry = TimelineEntry::new(task_id, "test_task".to_string(), 1000);
13229
13230 entry.fail(2500);
13231 assert_eq!(entry.end_time, Some(2500));
13232 assert_eq!(entry.duration, Some(1500));
13233 assert_eq!(entry.state, "failed");
13234 assert_eq!(entry.color, "#F44336");
13235 }
13236
13237 #[test]
13238 fn test_timeline_entry_with_worker() {
13239 let task_id = Uuid::new_v4();
13240 let entry = TimelineEntry::new(task_id, "test".to_string(), 1000)
13241 .with_worker("worker-1".to_string())
13242 .with_parent(Uuid::new_v4());
13243
13244 assert_eq!(entry.worker_id, Some("worker-1".to_string()));
13245 assert!(entry.parent_id.is_some());
13246 }
13247
13248 #[test]
13249 fn test_execution_timeline() {
13250 let workflow_id = Uuid::new_v4();
13251 let mut timeline = ExecutionTimeline::new(workflow_id);
13252
13253 let task1_id = Uuid::new_v4();
13254 let index = timeline.start_task(task1_id, "task1".to_string());
13255 assert_eq!(timeline.entries.len(), 1);
13256
13257 timeline.complete_task(index);
13258 assert_eq!(timeline.entries[index].state, "completed");
13259
13260 timeline.complete_workflow();
13261 assert!(timeline.workflow_end.is_some());
13262 }
13263
13264 #[test]
13265 fn test_execution_timeline_fail_task() {
13266 let workflow_id = Uuid::new_v4();
13267 let mut timeline = ExecutionTimeline::new(workflow_id);
13268
13269 let task_id = Uuid::new_v4();
13270 let index = timeline.start_task(task_id, "failing_task".to_string());
13271
13272 timeline.fail_task(index);
13273 assert_eq!(timeline.entries[index].state, "failed");
13274 }
13275
13276 #[test]
13277 fn test_execution_timeline_json_export() {
13278 let workflow_id = Uuid::new_v4();
13279 let timeline = ExecutionTimeline::new(workflow_id);
13280
13281 let json = timeline.to_json();
13282 assert!(json.is_ok());
13283 }
13284
13285 #[test]
13286 fn test_execution_timeline_google_charts() {
13287 let workflow_id = Uuid::new_v4();
13288 let mut timeline = ExecutionTimeline::new(workflow_id);
13289
13290 timeline.add_entry(TimelineEntry::new(
13291 Uuid::new_v4(),
13292 "task1".to_string(),
13293 1000,
13294 ));
13295
13296 let chart_data = timeline.to_google_charts_format();
13297 assert!(chart_data.is_object());
13298 assert!(chart_data["cols"].is_array());
13299 assert!(chart_data["rows"].is_array());
13300 }
13301
13302 #[test]
13303 fn test_animation_frame() {
13304 let workflow_state = WorkflowState {
13305 workflow_id: Uuid::new_v4(),
13306 status: WorkflowStatus::Running,
13307 total_tasks: 5,
13308 completed_tasks: 2,
13309 failed_tasks: 0,
13310 start_time: Some(1000),
13311 end_time: None,
13312 current_stage: Some("processing".to_string()),
13313 intermediate_results: HashMap::new(),
13314 };
13315
13316 let mut frame = AnimationFrame::new(0, workflow_state);
13317
13318 let task1_id = Uuid::new_v4();
13319 let task2_id = Uuid::new_v4();
13320
13321 frame.set_task_state(task1_id, "completed".to_string());
13322 frame.add_active_task(task2_id);
13323 frame.add_completed_task(task1_id);
13324
13325 assert_eq!(
13326 frame.task_states.get(&task1_id),
13327 Some(&"completed".to_string())
13328 );
13329 assert!(frame.active_tasks.contains(&task2_id));
13330 assert!(frame.completed_tasks.contains(&task1_id));
13331 assert!(!frame.active_tasks.contains(&task1_id));
13332 }
13333
13334 #[test]
13335 fn test_animation_frame_with_events() {
13336 let workflow_state = WorkflowState {
13337 workflow_id: Uuid::new_v4(),
13338 status: WorkflowStatus::Running,
13339 total_tasks: 1,
13340 completed_tasks: 0,
13341 failed_tasks: 0,
13342 start_time: Some(1000),
13343 end_time: None,
13344 current_stage: None,
13345 intermediate_results: HashMap::new(),
13346 };
13347
13348 let mut frame = AnimationFrame::new(0, workflow_state);
13349
13350 let task_id = Uuid::new_v4();
13351 frame.add_event(WorkflowEvent::TaskCompleted { task_id });
13352
13353 assert_eq!(frame.events.len(), 1);
13354 }
13355
13356 #[test]
13357 fn test_workflow_animation() {
13358 let workflow_id = Uuid::new_v4();
13359 let mut animation = WorkflowAnimation::new(workflow_id, 100);
13360
13361 let state1 = WorkflowState {
13362 workflow_id,
13363 status: WorkflowStatus::Pending,
13364 total_tasks: 1,
13365 completed_tasks: 0,
13366 failed_tasks: 0,
13367 start_time: None,
13368 end_time: None,
13369 current_stage: None,
13370 intermediate_results: HashMap::new(),
13371 };
13372
13373 let frame1 = AnimationFrame::new(0, state1);
13374 animation.add_frame(frame1);
13375
13376 assert_eq!(animation.frame_count(), 1);
13377 assert_eq!(animation.total_duration, 100);
13378
13379 let retrieved_frame = animation.get_frame(0);
13380 assert!(retrieved_frame.is_some());
13381 }
13382
13383 #[test]
13384 fn test_workflow_animation_json_export() {
13385 let workflow_id = Uuid::new_v4();
13386 let animation = WorkflowAnimation::new(workflow_id, 100);
13387
13388 let json = animation.to_json();
13389 assert!(json.is_ok());
13390 }
13391
13392 #[test]
13393 fn test_dag_export_with_state_chain() {
13394 let mut chain = Chain::new();
13395 chain.tasks.push(Signature::new("task1".to_string()));
13396 chain.tasks.push(Signature::new("task2".to_string()));
13397
13398 let workflow_state = WorkflowState {
13399 workflow_id: Uuid::new_v4(),
13400 status: WorkflowStatus::Running,
13401 total_tasks: 2,
13402 completed_tasks: 1,
13403 failed_tasks: 0,
13404 start_time: Some(1000),
13405 end_time: None,
13406 current_stage: Some("task2".to_string()),
13407 intermediate_results: HashMap::new(),
13408 };
13409
13410 let mut task_states = HashMap::new();
13411 let task1_id = Uuid::new_v4();
13412 let task2_id = Uuid::new_v4();
13413 task_states.insert(task1_id, "completed".to_string());
13414 task_states.insert(task2_id, "running".to_string());
13415
13416 let dot = chain.to_dot_with_state(&workflow_state, &task_states);
13417 assert!(dot.contains("digraph Chain"));
13418 assert!(dot.contains("task1"));
13419 assert!(dot.contains("task2"));
13420
13421 let mermaid = chain.to_mermaid_with_state(&workflow_state, &task_states);
13422 assert!(mermaid.contains("graph LR"));
13423 assert!(mermaid.contains("completed"));
13424 assert!(mermaid.contains("running"));
13425
13426 let json = chain.to_json_with_state(&workflow_state, &task_states);
13427 assert!(json.is_ok());
13428 }
13429
13430 #[test]
13431 fn test_dag_export_with_state_group() {
13432 let mut group = Group::new();
13433 group.tasks.push(Signature::new("task1".to_string()));
13434 group.tasks.push(Signature::new("task2".to_string()));
13435
13436 let workflow_state = WorkflowState {
13437 workflow_id: Uuid::new_v4(),
13438 status: WorkflowStatus::Running,
13439 total_tasks: 2,
13440 completed_tasks: 0,
13441 failed_tasks: 0,
13442 start_time: Some(1000),
13443 end_time: None,
13444 current_stage: None,
13445 intermediate_results: HashMap::new(),
13446 };
13447
13448 let task_states = HashMap::new();
13449
13450 let dot = group.to_dot_with_state(&workflow_state, &task_states);
13451 assert!(dot.contains("digraph Group"));
13452
13453 let mermaid = group.to_mermaid_with_state(&workflow_state, &task_states);
13454 assert!(mermaid.contains("graph TB"));
13455
13456 let json = group.to_json_with_state(&workflow_state, &task_states);
13457 assert!(json.is_ok());
13458 }
13459
13460 #[test]
13461 fn test_dag_export_with_state_chord() {
13462 let mut header = Group::new();
13463 header.tasks.push(Signature::new("task1".to_string()));
13464 header.tasks.push(Signature::new("task2".to_string()));
13465 let body = Signature::new("callback".to_string());
13466 let chord = Chord::new(header, body);
13467
13468 let workflow_state = WorkflowState {
13469 workflow_id: Uuid::new_v4(),
13470 status: WorkflowStatus::Running,
13471 total_tasks: 3,
13472 completed_tasks: 2,
13473 failed_tasks: 0,
13474 start_time: Some(1000),
13475 end_time: None,
13476 current_stage: Some("callback".to_string()),
13477 intermediate_results: HashMap::new(),
13478 };
13479
13480 let task_states = HashMap::new();
13481
13482 let dot = chord.to_dot_with_state(&workflow_state, &task_states);
13483 assert!(dot.contains("digraph Chord"));
13484 assert!(dot.contains("callback"));
13485
13486 let mermaid = chord.to_mermaid_with_state(&workflow_state, &task_states);
13487 assert!(mermaid.contains("graph TB"));
13488 assert!(mermaid.contains("callback"));
13489
13490 let json = chord.to_json_with_state(&workflow_state, &task_states);
13491 assert!(json.is_ok());
13492 }
13493
13494 #[test]
13495 fn test_workflow_event_stream() {
13496 let workflow_id = Uuid::new_v4();
13497 let mut stream = WorkflowEventStream::new(workflow_id);
13498
13499 assert_eq!(stream.workflow_id, workflow_id);
13500 assert_eq!(stream.events.len(), 0);
13501
13502 let task_id = Uuid::new_v4();
13503 stream.push(WorkflowEvent::TaskCompleted { task_id });
13504 stream.push(WorkflowEvent::WorkflowStarted { workflow_id });
13505
13506 assert_eq!(stream.events.len(), 2);
13507
13508 let all_events = stream.all_events();
13509 assert_eq!(all_events.len(), 2);
13510 }
13511
13512 #[test]
13513 fn test_workflow_event_stream_buffer_limit() {
13514 let workflow_id = Uuid::new_v4();
13515 let mut stream = WorkflowEventStream::new(workflow_id).with_max_buffer_size(2);
13516
13517 let task1 = Uuid::new_v4();
13518 let task2 = Uuid::new_v4();
13519 let task3 = Uuid::new_v4();
13520
13521 stream.push(WorkflowEvent::TaskCompleted { task_id: task1 });
13522 stream.push(WorkflowEvent::TaskCompleted { task_id: task2 });
13523 stream.push(WorkflowEvent::TaskCompleted { task_id: task3 });
13524
13525 assert_eq!(stream.events.len(), 2);
13527 }
13528
13529 #[test]
13530 fn test_workflow_event_stream_since() {
13531 let workflow_id = Uuid::new_v4();
13532 let mut stream = WorkflowEventStream::new(workflow_id);
13533
13534 stream.push(WorkflowEvent::WorkflowStarted { workflow_id });
13535 std::thread::sleep(std::time::Duration::from_millis(10));
13536
13537 let timestamp = std::time::SystemTime::now()
13538 .duration_since(std::time::UNIX_EPOCH)
13539 .unwrap()
13540 .as_millis() as u64;
13541
13542 std::thread::sleep(std::time::Duration::from_millis(10));
13543 let task_id = Uuid::new_v4();
13544 stream.push(WorkflowEvent::TaskCompleted { task_id });
13545
13546 let recent_events = stream.events_since(timestamp);
13547 assert_eq!(recent_events.len(), 1);
13548 }
13549
13550 #[test]
13551 fn test_workflow_event_stream_clear() {
13552 let workflow_id = Uuid::new_v4();
13553 let mut stream = WorkflowEventStream::new(workflow_id);
13554
13555 stream.push(WorkflowEvent::WorkflowStarted { workflow_id });
13556 assert_eq!(stream.events.len(), 1);
13557
13558 stream.clear();
13559 assert_eq!(stream.events.len(), 0);
13560 }
13561
13562 #[test]
13563 fn test_workflow_event_stream_sse_format() {
13564 let workflow_id = Uuid::new_v4();
13565 let mut stream = WorkflowEventStream::new(workflow_id);
13566
13567 stream.push(WorkflowEvent::WorkflowStarted { workflow_id });
13568
13569 let sse_messages = stream.to_sse_format();
13570 assert_eq!(sse_messages.len(), 1);
13571 assert!(sse_messages[0].starts_with("event: workflow"));
13572 }
13573
13574 #[test]
13579 fn test_workflow_metrics_collector() {
13580 let workflow_id = Uuid::new_v4();
13581 let mut collector = WorkflowMetricsCollector::new(workflow_id);
13582
13583 assert_eq!(collector.workflow_id, workflow_id);
13584 assert_eq!(collector.total_tasks, 0);
13585 assert_eq!(collector.completed_tasks, 0);
13586 assert_eq!(collector.failed_tasks, 0);
13587
13588 let task1 = Uuid::new_v4();
13589 let task2 = Uuid::new_v4();
13590
13591 collector.record_task_start(task1);
13592 collector.record_task_complete(task1, 100);
13593
13594 assert_eq!(collector.total_tasks, 1);
13595 assert_eq!(collector.completed_tasks, 1);
13596
13597 collector.record_task_start(task2);
13598 collector.record_task_failure(task2, 50);
13599
13600 assert_eq!(collector.total_tasks, 2);
13601 assert_eq!(collector.failed_tasks, 1);
13602
13603 collector.record_task_retry(task2);
13604 assert_eq!(*collector.task_retries.get(&task2).unwrap(), 1);
13605
13606 collector.finalize();
13607 assert!(collector.end_time.is_some());
13608 assert!(collector.total_duration.is_some());
13609 assert!(collector.avg_task_duration.is_some());
13610 assert!(collector.success_rate.is_some());
13611
13612 let summary = collector.summary();
13613 assert!(summary.contains("WorkflowMetrics"));
13614 }
13615
13616 #[test]
13617 fn test_workflow_metrics_collector_display() {
13618 let workflow_id = Uuid::new_v4();
13619 let collector = WorkflowMetricsCollector::new(workflow_id);
13620
13621 let display = format!("{}", collector);
13622 assert!(display.contains("WorkflowMetrics"));
13623 }
13624
13625 #[test]
13626 fn test_workflow_rate_limiter() {
13627 let mut limiter = WorkflowRateLimiter::new(2, 1000);
13628
13629 assert_eq!(limiter.max_workflows, 2);
13630 assert_eq!(limiter.window_ms, 1000);
13631
13632 assert!(limiter.allow_workflow());
13633 assert!(limiter.allow_workflow());
13634 assert!(!limiter.allow_workflow()); assert_eq!(limiter.total_workflows, 2);
13637 assert_eq!(limiter.rejected_workflows, 1);
13638
13639 let rate = limiter.current_rate();
13640 assert!(rate > 0.0);
13641
13642 let rejection_rate = limiter.rejection_rate();
13643 assert!(rejection_rate > 0.0);
13644
13645 limiter.reset();
13646 assert_eq!(limiter.workflow_timestamps.len(), 0);
13647 }
13648
13649 #[test]
13650 fn test_workflow_rate_limiter_display() {
13651 let limiter = WorkflowRateLimiter::new(10, 1000);
13652
13653 let display = format!("{}", limiter);
13654 assert!(display.contains("RateLimiter"));
13655 }
13656
13657 #[test]
13658 fn test_workflow_concurrency_control() {
13659 let mut control = WorkflowConcurrencyControl::new(2);
13660
13661 assert_eq!(control.max_concurrent, 2);
13662 assert_eq!(control.current_concurrency(), 0);
13663 assert_eq!(control.available_slots(), 2);
13664 assert!(!control.is_at_capacity());
13665
13666 let wf1 = Uuid::new_v4();
13667 let wf2 = Uuid::new_v4();
13668 let wf3 = Uuid::new_v4();
13669
13670 assert!(control.try_start(wf1));
13671 assert!(control.try_start(wf2));
13672 assert!(!control.try_start(wf3)); assert_eq!(control.current_concurrency(), 2);
13675 assert!(control.is_at_capacity());
13676 assert_eq!(control.peak_concurrency, 2);
13677
13678 assert!(control.complete(wf1));
13679 assert_eq!(control.current_concurrency(), 1);
13680 assert_eq!(control.total_completed, 1);
13681
13682 assert!(control.try_start(wf3));
13683 assert_eq!(control.current_concurrency(), 2);
13684 }
13685
13686 #[test]
13687 fn test_workflow_concurrency_control_display() {
13688 let control = WorkflowConcurrencyControl::new(5);
13689
13690 let display = format!("{}", control);
13691 assert!(display.contains("ConcurrencyControl"));
13692 }
13693
13694 #[test]
13695 fn test_workflow_builder() {
13696 let builder = WorkflowBuilder::new("test_workflow")
13697 .with_description("Test workflow description")
13698 .add_tag("test")
13699 .add_tag("production")
13700 .add_metadata("version", serde_json::json!("1.0"));
13701
13702 assert_eq!(builder.name, "test_workflow");
13703 assert_eq!(
13704 builder.description,
13705 Some("Test workflow description".to_string())
13706 );
13707 assert_eq!(builder.tags.len(), 2);
13708 assert_eq!(builder.metadata.len(), 1);
13709
13710 let chain = builder.clone().chain();
13711 assert!(chain.is_empty());
13712
13713 let group = builder.clone().group();
13714 assert!(group.is_empty());
13715
13716 let task = Signature::new("map_task".to_string());
13717 let argsets = vec![vec![serde_json::json!(1)], vec![serde_json::json!(2)]];
13718 let map = builder.map(task, argsets);
13719 assert_eq!(map.task.task, "map_task");
13720 }
13721
13722 #[test]
13723 fn test_workflow_registry() {
13724 let mut registry = WorkflowRegistry::new();
13725
13726 assert_eq!(registry.count(), 0);
13727
13728 let wf1 = Uuid::new_v4();
13729 let wf2 = Uuid::new_v4();
13730
13731 let mut metadata1 = HashMap::new();
13732 metadata1.insert("version".to_string(), serde_json::json!("1.0"));
13733
13734 registry.register(wf1, "workflow_1".to_string(), metadata1.clone());
13735 registry.register(wf2, "workflow_2".to_string(), HashMap::new());
13736
13737 assert_eq!(registry.count(), 2);
13738 assert_eq!(registry.get_name(&wf1), Some("workflow_1"));
13739 assert_eq!(registry.get_state(&wf1), Some(&WorkflowStatus::Pending));
13740
13741 registry.update_state(wf1, WorkflowStatus::Running);
13742 assert_eq!(registry.get_state(&wf1), Some(&WorkflowStatus::Running));
13743
13744 registry.add_tag(wf1, "production".to_string());
13745 registry.add_tag(wf2, "production".to_string());
13746
13747 let production_workflows = registry.get_by_tag("production");
13748 assert_eq!(production_workflows.len(), 2);
13749
13750 let running = registry.get_by_state(&WorkflowStatus::Running);
13751 assert_eq!(running.len(), 1);
13752
13753 assert!(registry.remove(&wf1));
13754 assert_eq!(registry.count(), 1);
13755
13756 registry.clear();
13757 assert_eq!(registry.count(), 0);
13758 }
13759
13760 #[test]
13761 fn test_workflow_registry_default() {
13762 let registry = WorkflowRegistry::default();
13763 assert_eq!(registry.count(), 0);
13764 }
13765
13766 #[test]
13767 fn test_workflow_registry_display() {
13768 let mut registry = WorkflowRegistry::new();
13769
13770 let wf1 = Uuid::new_v4();
13771 registry.register(wf1, "test".to_string(), HashMap::new());
13772
13773 let display = format!("{}", registry);
13774 assert!(display.contains("WorkflowRegistry"));
13775 assert!(display.contains("total=1"));
13776 }
13777
13778 #[tokio::test]
13781 async fn test_nested_chain_apply() {
13782 let broker = MockBroker::new();
13783
13784 let nested_chain = NestedChain::new()
13785 .then("task1", vec![serde_json::json!(1)])
13786 .then_group(
13787 Group::new()
13788 .add("task2a", vec![serde_json::json!(2)])
13789 .add("task2b", vec![serde_json::json!(3)]),
13790 )
13791 .then("task3", vec![serde_json::json!(4)]);
13792
13793 let result = nested_chain.apply(&broker).await;
13794 assert!(result.is_ok(), "NestedChain apply should succeed");
13795
13796 assert!(
13798 broker.task_count() >= 4,
13799 "Should publish at least 4 tasks (task1, task2a, task2b, task3)"
13800 );
13801 }
13802
13803 #[tokio::test]
13804 async fn test_nested_chain_with_chains() {
13805 let broker = MockBroker::new();
13806
13807 let nested_chain = NestedChain::new()
13808 .then_chain(Chain::new().then("step1", vec![]).then("step2", vec![]))
13809 .then_chain(Chain::new().then("step3", vec![]).then("step4", vec![]));
13810
13811 let result = nested_chain.apply(&broker).await;
13812 assert!(result.is_ok(), "NestedChain with chains should succeed");
13813
13814 assert_eq!(
13817 broker.task_count(),
13818 2,
13819 "Should publish 2 first tasks from two chains"
13820 );
13821 }
13822
13823 #[tokio::test]
13824 async fn test_nested_chain_empty_error() {
13825 let broker = MockBroker::new();
13826 let nested_chain = NestedChain::new();
13827
13828 let result = nested_chain.apply(&broker).await;
13829 assert!(result.is_err(), "Empty NestedChain should return error");
13830 match result {
13831 Err(CanvasError::Invalid(msg)) => {
13832 assert!(msg.contains("empty"));
13833 }
13834 _ => panic!("Expected Invalid error for empty NestedChain"),
13835 }
13836 }
13837
13838 #[test]
13839 fn test_nested_chain_builder_methods() {
13840 let chain = NestedChain::new()
13841 .then("task1", vec![])
13842 .then_signature(Signature::new("task2".to_string()))
13843 .then_group(Group::new().add("task3", vec![]));
13844
13845 assert_eq!(chain.len(), 3);
13846 assert!(!chain.is_empty());
13847 }
13848
13849 #[test]
13850 fn test_nested_chain_display() {
13851 let chain = NestedChain::new()
13852 .then("task1", vec![])
13853 .then("task2", vec![]);
13854
13855 let display = format!("{}", chain);
13856 assert!(display.contains("NestedChain"));
13857 assert!(display.contains("->"));
13858 }
13859
13860 #[tokio::test]
13863 async fn test_nested_group_apply() {
13864 let broker = MockBroker::new();
13865
13866 let nested_group = NestedGroup::new()
13867 .add("task1", vec![serde_json::json!(1)])
13868 .add_chain(Chain::new().then("task2a", vec![]).then("task2b", vec![]))
13869 .add("task3", vec![serde_json::json!(3)]);
13870
13871 let result = nested_group.apply(&broker).await;
13872 assert!(result.is_ok(), "NestedGroup apply should succeed");
13873
13874 assert_eq!(
13877 broker.task_count(),
13878 3,
13879 "Should publish 3 tasks (task1, task2a from chain, task3)"
13880 );
13881 }
13882
13883 #[tokio::test]
13884 async fn test_nested_group_with_multiple_chains() {
13885 let broker = MockBroker::new();
13886
13887 let nested_group = NestedGroup::new()
13888 .add_chain(Chain::new().then("a1", vec![]).then("a2", vec![]))
13889 .add_chain(Chain::new().then("b1", vec![]).then("b2", vec![]))
13890 .add_chain(Chain::new().then("c1", vec![]).then("c2", vec![]));
13891
13892 let result = nested_group.apply(&broker).await;
13893 assert!(
13894 result.is_ok(),
13895 "NestedGroup with multiple chains should succeed"
13896 );
13897
13898 assert_eq!(
13901 broker.task_count(),
13902 3,
13903 "Should publish 3 first tasks from three chains"
13904 );
13905 }
13906
13907 #[tokio::test]
13908 async fn test_nested_group_empty_error() {
13909 let broker = MockBroker::new();
13910 let nested_group = NestedGroup::new();
13911
13912 let result = nested_group.apply(&broker).await;
13913 assert!(result.is_err(), "Empty NestedGroup should return error");
13914 match result {
13915 Err(CanvasError::Invalid(msg)) => {
13916 assert!(msg.contains("empty"));
13917 }
13918 _ => panic!("Expected Invalid error for empty NestedGroup"),
13919 }
13920 }
13921
13922 #[test]
13923 fn test_nested_group_builder_methods() {
13924 let group = NestedGroup::new()
13925 .add("task1", vec![])
13926 .add_signature(Signature::new("task2".to_string()))
13927 .add_chain(Chain::new().then("task3", vec![]));
13928
13929 assert_eq!(group.len(), 3);
13930 assert!(!group.is_empty());
13931 }
13932
13933 #[test]
13934 fn test_nested_group_display() {
13935 let group = NestedGroup::new().add("task1", vec![]).add("task2", vec![]);
13936
13937 let display = format!("{}", group);
13938 assert!(display.contains("NestedGroup"));
13939 assert!(display.contains("|"));
13940 }
13941
13942 #[tokio::test]
13943 async fn test_nested_workflows_complex_composition() {
13944 let broker = MockBroker::new();
13945
13946 let nested = NestedChain::new()
13949 .then_group(
13950 Group::new()
13951 .add("parallel1", vec![])
13952 .add("parallel2", vec![])
13953 .add("parallel3", vec![]),
13954 )
13955 .then_chain(Chain::new().then("seq1", vec![]).then("seq2", vec![]))
13956 .then_element(CanvasElement::Group(
13957 Group::new().add("final1", vec![]).add("final2", vec![]),
13958 ));
13959
13960 let result = nested.apply(&broker).await;
13961 assert!(result.is_ok(), "Complex nested workflow should succeed");
13962
13963 assert_eq!(broker.task_count(), 6, "Should publish 6 initial tasks");
13966 }
13967
13968 #[test]
13969 fn test_chain_iterator_methods() {
13970 let chain = Chain::new()
13971 .then("task1", vec![])
13972 .then("task2", vec![])
13973 .then("task3", vec![]);
13974
13975 assert_eq!(chain.first().unwrap().task, "task1");
13977 assert_eq!(chain.last().unwrap().task, "task3");
13978
13979 assert_eq!(chain.get(1).unwrap().task, "task2");
13981 assert!(chain.get(10).is_none());
13982
13983 let names: Vec<String> = chain.iter().map(|s| s.task.clone()).collect();
13985 assert_eq!(names, vec!["task1", "task2", "task3"]);
13986
13987 assert_eq!(chain.len(), 3);
13988 assert!(!chain.is_empty());
13989 }
13990
13991 #[test]
13992 fn test_chain_into_iterator() {
13993 let chain = Chain::new().then("task1", vec![]).then("task2", vec![]);
13994
13995 let mut count = 0;
13996 for sig in &chain {
13997 assert!(sig.task.starts_with("task"));
13998 count += 1;
13999 }
14000 assert_eq!(count, 2);
14001
14002 let tasks: Vec<Signature> = chain.into_iter().collect();
14004 assert_eq!(tasks.len(), 2);
14005 }
14006
14007 #[test]
14008 fn test_chain_from_vec() {
14009 let sigs = vec![
14010 Signature::new("task1".to_string()),
14011 Signature::new("task2".to_string()),
14012 ];
14013
14014 let chain = Chain::from(sigs);
14015 assert_eq!(chain.len(), 2);
14016 assert_eq!(chain.first().unwrap().task, "task1");
14017 }
14018
14019 #[test]
14020 fn test_chain_from_iter() {
14021 let chain: Chain = vec![
14022 Signature::new("task1".to_string()),
14023 Signature::new("task2".to_string()),
14024 ]
14025 .into_iter()
14026 .collect();
14027
14028 assert_eq!(chain.len(), 2);
14029 }
14030
14031 #[test]
14032 fn test_chain_with_capacity() {
14033 let chain = Chain::with_capacity(10);
14034 assert_eq!(chain.len(), 0);
14035 assert!(chain.is_empty());
14036 }
14037
14038 #[test]
14039 fn test_chain_extend() {
14040 let chain = Chain::new().then("task1", vec![]).extend(vec![
14041 Signature::new("task2".to_string()),
14042 Signature::new("task3".to_string()),
14043 ]);
14044
14045 assert_eq!(chain.len(), 3);
14046 }
14047
14048 #[test]
14049 fn test_chain_reverse() {
14050 let chain = Chain::new()
14051 .then("task1", vec![])
14052 .then("task2", vec![])
14053 .then("task3", vec![])
14054 .reverse();
14055
14056 assert_eq!(chain.first().unwrap().task, "task3");
14057 assert_eq!(chain.last().unwrap().task, "task1");
14058 }
14059
14060 #[test]
14061 fn test_chain_retain() {
14062 let chain = Chain::new()
14063 .then("keep1", vec![])
14064 .then("remove", vec![])
14065 .then("keep2", vec![])
14066 .retain(|sig| sig.task.starts_with("keep"));
14067
14068 assert_eq!(chain.len(), 2);
14069 assert_eq!(chain.first().unwrap().task, "keep1");
14070 assert_eq!(chain.last().unwrap().task, "keep2");
14071 }
14072
14073 #[test]
14074 fn test_group_iterator_methods() {
14075 let group = Group::new()
14076 .add("task1", vec![])
14077 .add("task2", vec![])
14078 .add("task3", vec![]);
14079
14080 assert_eq!(group.first().unwrap().task, "task1");
14082 assert_eq!(group.last().unwrap().task, "task3");
14083
14084 assert_eq!(group.get(1).unwrap().task, "task2");
14086 assert!(group.get(10).is_none());
14087
14088 let names: Vec<String> = group.iter().map(|s| s.task.clone()).collect();
14090 assert_eq!(names, vec!["task1", "task2", "task3"]);
14091
14092 assert_eq!(group.len(), 3);
14093 assert!(!group.is_empty());
14094 }
14095
14096 #[test]
14097 fn test_group_find_filter() {
14098 let group = Group::new()
14099 .add("task_a", vec![])
14100 .add("task_b", vec![])
14101 .add("other", vec![]);
14102
14103 let found = group.find(|sig| sig.task == "task_b");
14105 assert!(found.is_some());
14106 assert_eq!(found.unwrap().task, "task_b");
14107
14108 let filtered = group.clone().filter(|sig| sig.task.starts_with("task_"));
14110 assert_eq!(filtered.len(), 2);
14111 }
14112
14113 #[test]
14114 fn test_group_from_vec() {
14115 let sigs = vec![
14116 Signature::new("task1".to_string()),
14117 Signature::new("task2".to_string()),
14118 ];
14119
14120 let group = Group::from(sigs);
14121 assert_eq!(group.len(), 2);
14122 assert!(group.has_group_id());
14123 }
14124
14125 #[test]
14126 fn test_group_with_capacity() {
14127 let group = Group::with_capacity(10);
14128 assert_eq!(group.len(), 0);
14129 assert!(group.is_empty());
14130 assert!(group.has_group_id());
14131 }
14132
14133 #[test]
14134 fn test_signature_convenience_methods() {
14135 let sig = Signature::new("task".to_string())
14136 .add_kwarg("key1", serde_json::json!("value1"))
14137 .add_kwarg("key2", serde_json::json!(42))
14138 .add_arg(serde_json::json!(1))
14139 .add_arg(serde_json::json!(2));
14140
14141 assert!(sig.has_kwarg("key1"));
14142 assert!(sig.has_kwarg("key2"));
14143 assert!(!sig.has_kwarg("key3"));
14144
14145 assert_eq!(sig.get_kwarg("key1"), Some(&serde_json::json!("value1")));
14146 assert_eq!(sig.get_kwarg("key2"), Some(&serde_json::json!(42)));
14147
14148 assert_eq!(sig.args.len(), 2);
14149 assert_eq!(sig.args[0], serde_json::json!(1));
14150 }
14151
14152 #[test]
14153 fn test_workflow_registry_query_methods() {
14154 let mut registry = WorkflowRegistry::new();
14155
14156 let id1 = Uuid::new_v4();
14157 let id2 = Uuid::new_v4();
14158 let id3 = Uuid::new_v4();
14159
14160 registry.register(id1, "workflow_test_1".to_string(), HashMap::new());
14161 registry.register(id2, "workflow_test_2".to_string(), HashMap::new());
14162 registry.register(id3, "other_workflow".to_string(), HashMap::new());
14163
14164 let found = registry.find_by_name("test");
14166 assert_eq!(found.len(), 2);
14167
14168 let all_ids = registry.all_workflow_ids();
14170 assert_eq!(all_ids.len(), 3);
14171
14172 assert!(registry.contains(&id1));
14174 assert!(!registry.contains(&Uuid::new_v4()));
14175
14176 registry.add_tag(id1, "tag1".to_string());
14178 registry.add_tag(id2, "tag1".to_string());
14179 registry.add_tag(id2, "tag2".to_string());
14180
14181 let all_tags = registry.all_tags();
14182 assert!(all_tags.contains(&"tag1".to_string()));
14183
14184 let with_both = registry.get_by_tags_all(&["tag1", "tag2"]);
14186 assert_eq!(with_both.len(), 1);
14187 assert!(with_both.contains(&id2));
14188
14189 let with_any = registry.get_by_tags_any(&["tag1", "tag2"]);
14191 assert!(with_any.len() >= 2);
14192
14193 registry.update_state(id1, WorkflowStatus::Running);
14195 registry.update_state(id2, WorkflowStatus::Success);
14196 registry.update_state(id3, WorkflowStatus::Pending);
14197
14198 assert_eq!(registry.running_count(), 1);
14199 assert_eq!(registry.pending_count(), 1);
14200 assert_eq!(registry.completed_count(), 1);
14201 assert_eq!(registry.count_by_state(&WorkflowStatus::Success), 1);
14202 }
14203
14204 #[test]
14205 fn test_workflow_registry_age_queries() {
14206 let mut registry = WorkflowRegistry::new();
14207 let id = Uuid::new_v4();
14208
14209 registry.register(id, "test".to_string(), HashMap::new());
14210
14211 let age = registry.get_age(&id);
14213 assert!(age.is_some());
14214 assert!(age.unwrap() < 1000); let old = registry.get_older_than(1_000_000); assert_eq!(old.len(), 0); std::thread::sleep(std::time::Duration::from_millis(1));
14222
14223 let recent = registry.get_older_than(0); assert_eq!(recent.len(), 1); }
14226 }
14227}