sz_rust_workflow/definition/
node.rs1use serde::{Deserialize, Serialize};
5
6use super::strategy::{ApprovalStrategyType, CandidateStrategy, FaultStrategy};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
10#[serde(rename_all = "snake_case")]
11pub enum NodeType {
12 Start,
13 Approval,
14 Condition,
15 Parallel,
16 Plugin,
17 End,
18}
19
20#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
22pub struct Node {
23 pub node_id: String,
25 pub node_type: NodeType,
27 #[serde(flatten)]
29 pub config: NodeConfig,
30}
31
32#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
34#[serde(tag = "kind", rename_all = "snake_case")]
35pub enum NodeConfig {
36 Start {
37 next: String,
38 },
39 Approval {
40 approval_strategy: ApprovalStrategyType,
41 candidate_strategy: CandidateStrategy,
42 next: String,
43 },
44 Condition {
45 branches: Vec<ConditionBranch>,
46 },
47 Parallel {
48 branches: Vec<String>,
49 join_node: String,
50 },
51 Plugin {
52 capability_name: String,
54 #[serde(default = "default_version_range")]
56 capability_version_range: String,
57 #[serde(default)]
59 args_mapping: serde_json::Value,
60 #[serde(default)]
62 fault_strategy: FaultStrategy,
63 #[serde(default, skip_serializing_if = "Option::is_none")]
65 output_schema: Option<serde_json::Value>,
66 next: String,
68 },
69 End,
70}
71
72fn default_version_range() -> String {
73 "*".to_string()
74}
75
76#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
78pub struct ConditionBranch {
79 pub condition: String,
81 pub next: String,
83}
84
85#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
87pub struct NodeEdge {
88 pub from: String,
89 pub to: String,
90 #[serde(default, skip_serializing_if = "Option::is_none")]
91 pub condition: Option<String>,
92}
93
94impl Node {
95 pub fn successors(&self) -> Vec<&str> {
97 match &self.config {
98 NodeConfig::Start { next } => vec![next.as_str()],
99 NodeConfig::Approval { next, .. } => vec![next.as_str()],
100 NodeConfig::Condition { branches } => {
101 branches.iter().map(|b| b.next.as_str()).collect()
102 }
103 NodeConfig::Parallel {
104 branches,
105 join_node,
106 } => {
107 let mut v: Vec<&str> = branches.iter().map(|s| s.as_str()).collect();
108 v.push(join_node.as_str());
109 v
110 }
111 NodeConfig::Plugin { next, .. } => vec![next.as_str()],
112 NodeConfig::End => vec![],
113 }
114 }
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120
121 #[test]
122 fn start_node_serde() {
123 let n = Node {
124 node_id: "start".into(),
125 node_type: NodeType::Start,
126 config: NodeConfig::Start { next: "n1".into() },
127 };
128 let json = serde_json::to_string(&n).unwrap();
129 let back: Node = serde_json::from_str(&json).unwrap();
130 assert_eq!(n, back);
131 }
132
133 #[test]
134 fn approval_node_serde() {
135 let n = Node {
136 node_id: "approve1".into(),
137 node_type: NodeType::Approval,
138 config: NodeConfig::Approval {
139 approval_strategy: ApprovalStrategyType::OrSign,
140 candidate_strategy: CandidateStrategy::Static {
141 users: vec!["u1".into(), "u2".into()],
142 roles: vec![],
143 },
144 next: "end".into(),
145 },
146 };
147 let json = serde_json::to_string(&n).unwrap();
148 let back: Node = serde_json::from_str(&json).unwrap();
149 assert_eq!(n, back);
150 }
151
152 #[test]
153 fn plugin_node_serde() {
154 let n = Node {
155 node_id: "p1".into(),
156 node_type: NodeType::Plugin,
157 config: NodeConfig::Plugin {
158 capability_name: "crm.search_customer".into(),
159 capability_version_range: "^1.0".into(),
160 args_mapping: serde_json::json!({"keyword": "$.keyword"}),
161 fault_strategy: FaultStrategy::Retry,
162 output_schema: None,
163 next: "end".into(),
164 },
165 };
166 let json = serde_json::to_string(&n).unwrap();
167 let back: Node = serde_json::from_str(&json).unwrap();
168 assert_eq!(n, back);
169 }
170
171 #[test]
172 fn condition_node_successors() {
173 let n = Node {
174 node_id: "c1".into(),
175 node_type: NodeType::Condition,
176 config: NodeConfig::Condition {
177 branches: vec![
178 ConditionBranch {
179 condition: "$.x > 0".into(),
180 next: "a".into(),
181 },
182 ConditionBranch {
183 condition: "$.x <= 0".into(),
184 next: "b".into(),
185 },
186 ],
187 },
188 };
189 assert_eq!(n.successors(), vec!["a", "b"]);
190 }
191
192 #[test]
193 fn parallel_node_successors() {
194 let n = Node {
195 node_id: "par1".into(),
196 node_type: NodeType::Parallel,
197 config: NodeConfig::Parallel {
198 branches: vec!["b1".into(), "b2".into()],
199 join_node: "join".into(),
200 },
201 };
202 assert_eq!(n.successors(), vec!["b1", "b2", "join"]);
203 }
204
205 #[test]
206 fn end_node_no_successors() {
207 let n = Node {
208 node_id: "end".into(),
209 node_type: NodeType::End,
210 config: NodeConfig::End,
211 };
212 assert!(n.successors().is_empty());
213 }
214}