1use serde::{Deserialize, Serialize};
5use serde_json::Value;
6
7pub mod compensation_policy {
10 pub const NONE: &str = "none";
11 pub const ON_FAILURE: &str = "on_failure";
12 pub const ALWAYS: &str = "always";
13}
14
15pub mod aggregate_strategy {
18 pub const MERGE: &str = "merge";
19 pub const WEIGHTED_FIRST_K: &str = "weighted_first_k";
20 pub const MERGE_ALL: &str = "merge_all";
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct DagNode {
27 pub id: String,
28 pub action: String,
29 #[serde(skip_serializing_if = "Option::is_none")]
30 pub target_nid: Option<String>,
31 #[serde(skip_serializing_if = "Option::is_none")]
32 pub inputs: Option<serde_json::Map<String, Value>>,
33 #[serde(skip_serializing_if = "Option::is_none")]
34 pub depends_on: Option<Vec<String>>,
35 #[serde(skip_serializing_if = "Option::is_none")]
36 pub compensate_action: Option<String>,
37 #[serde(skip_serializing_if = "Option::is_none")]
38 pub compensate_params_mapping: Option<serde_json::Map<String, Value>>,
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub enum BackoffStrategy {
45 Fixed,
46 Linear,
47 Exponential,
48}
49
50impl BackoffStrategy {
51 pub fn compute_delay_ms(self, base_ms: u64, max_ms: u64, attempt: u32) -> u64 {
53 let raw = match self {
54 BackoffStrategy::Fixed => base_ms,
55 BackoffStrategy::Linear => base_ms * (attempt as u64 + 1),
56 BackoffStrategy::Exponential => base_ms * (1u64 << attempt),
57 };
58 raw.min(max_ms)
59 }
60}
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65pub enum TaskState {
66 Pending,
67 Running,
68 Completed,
69 Failed,
70 Cancelled,
71 Compensating,
72 Compensated,
73}
74
75impl TaskState {
76 pub fn from_str(s: &str) -> Option<Self> {
77 match s {
78 "pending" => Some(TaskState::Pending),
79 "running" => Some(TaskState::Running),
80 "completed" => Some(TaskState::Completed),
81 "failed" => Some(TaskState::Failed),
82 "cancelled" => Some(TaskState::Cancelled),
83 "compensating" => Some(TaskState::Compensating),
84 "compensated" => Some(TaskState::Compensated),
85 _ => None,
86 }
87 }
88
89 pub fn is_terminal(self) -> bool {
90 matches!(self, TaskState::Completed | TaskState::Failed | TaskState::Cancelled | TaskState::Compensated)
91 }
92}
93
94#[derive(Debug, Clone)]
97pub struct NopTaskStatus {
98 raw: serde_json::Map<String, Value>,
99}
100
101impl NopTaskStatus {
102 pub fn from_dict(raw: serde_json::Map<String, Value>) -> Self {
103 NopTaskStatus { raw }
104 }
105
106 pub fn task_id(&self) -> &str {
107 self.raw
108 .get("task_id")
109 .and_then(Value::as_str)
110 .unwrap_or("")
111 }
112
113 pub fn state(&self) -> Option<TaskState> {
114 self.raw
115 .get("state")
116 .and_then(Value::as_str)
117 .and_then(TaskState::from_str)
118 }
119
120 pub fn is_terminal(&self) -> bool {
121 self.state().map(TaskState::is_terminal).unwrap_or(false)
122 }
123
124 pub fn error_code(&self) -> Option<&str> {
125 self.raw.get("error_code").and_then(Value::as_str)
126 }
127
128 pub fn error_message(&self) -> Option<&str> {
129 self.raw.get("error_message").and_then(Value::as_str)
130 }
131
132 pub fn node_results(&self) -> Option<&serde_json::Map<String, Value>> {
133 self.raw.get("node_results").and_then(Value::as_object)
134 }
135
136 pub fn raw(&self) -> &serde_json::Map<String, Value> {
137 &self.raw
138 }
139}
140
141impl std::fmt::Display for NopTaskStatus {
142 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
143 write!(
144 f,
145 "NopTaskStatus(task_id={}, state={:?})",
146 self.task_id(),
147 self.state()
148 )
149 }
150}