1use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct EdgeDefinition {
34 pub from: String,
36
37 pub to: String,
39
40 #[serde(default)]
42 pub condition: Option<String>,
43
44 #[serde(default)]
46 pub loop_back: bool,
47
48 #[serde(default)]
50 pub description: Option<String>,
51}
52
53impl EdgeDefinition {
54 pub fn new(from: impl Into<String>, to: impl Into<String>) -> Self {
56 Self {
57 from: from.into(),
58 to: to.into(),
59 condition: None,
60 loop_back: false,
61 description: None,
62 }
63 }
64
65 pub fn with_condition(mut self, condition: impl Into<String>) -> Self {
67 self.condition = Some(condition.into());
68 self
69 }
70
71 pub fn as_loop_back(mut self) -> Self {
73 self.loop_back = true;
74 self
75 }
76
77 pub fn with_description(mut self, desc: impl Into<String>) -> Self {
79 self.description = Some(desc.into());
80 self
81 }
82
83 pub fn parse_from(&self) -> (&str, &str) {
87 parse_node_port(&self.from, "out")
88 }
89
90 pub fn parse_to(&self) -> (&str, &str) {
94 parse_node_port(&self.to, "in")
95 }
96
97 pub fn from_node(&self) -> &str {
99 self.parse_from().0
100 }
101
102 pub fn from_port(&self) -> &str {
104 self.parse_from().1
105 }
106
107 pub fn to_node(&self) -> &str {
109 self.parse_to().0
110 }
111
112 pub fn to_port(&self) -> &str {
114 self.parse_to().1
115 }
116}
117
118fn parse_node_port<'a>(s: &'a str, default_port: &'static str) -> (&'a str, &'a str) {
120 if let Some(dot_pos) = s.rfind('.') {
121 let after_dot = &s[dot_pos + 1..];
123 if !after_dot.is_empty()
126 && !after_dot.contains('$')
127 && !after_dot.contains('{')
128 && after_dot.len() < 20
129 && after_dot.chars().all(|c| c.is_alphanumeric() || c == '_')
130 {
131 return (&s[..dot_pos], after_dot);
132 }
133 }
134 (s, default_port)
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140
141 #[test]
142 fn deserialize_simple_edge() {
143 let yaml = r#"
144from: node_a
145to: node_b
146"#;
147 let edge: EdgeDefinition = serde_yaml::from_str(yaml).unwrap();
148 assert_eq!(edge.from, "node_a");
149 assert_eq!(edge.to, "node_b");
150 assert_eq!(edge.from_node(), "node_a");
151 assert_eq!(edge.from_port(), "out");
152 assert_eq!(edge.to_node(), "node_b");
153 assert_eq!(edge.to_port(), "in");
154 }
155
156 #[test]
157 fn deserialize_edge_with_ports() {
158 let yaml = r#"
159from: switch.true
160to: handler.input
161"#;
162 let edge: EdgeDefinition = serde_yaml::from_str(yaml).unwrap();
163 assert_eq!(edge.from_node(), "switch");
164 assert_eq!(edge.from_port(), "true");
165 assert_eq!(edge.to_node(), "handler");
166 assert_eq!(edge.to_port(), "input");
167 }
168
169 #[test]
170 fn deserialize_edge_with_condition() {
171 let yaml = r#"
172from: validator.out
173to: processor.in
174condition: "${validator.is_valid} == true"
175"#;
176 let edge: EdgeDefinition = serde_yaml::from_str(yaml).unwrap();
177 assert_eq!(
178 edge.condition,
179 Some("${validator.is_valid} == true".to_string())
180 );
181 }
182
183 #[test]
184 fn deserialize_loop_back_edge() {
185 let yaml = r#"
186from: processor.out
187to: loop_controller.in
188loop_back: true
189description: "Return to loop controller"
190"#;
191 let edge: EdgeDefinition = serde_yaml::from_str(yaml).unwrap();
192 assert!(edge.loop_back);
193 assert_eq!(
194 edge.description,
195 Some("Return to loop controller".to_string())
196 );
197 }
198
199 #[test]
200 fn edge_builder() {
201 let edge = EdgeDefinition::new("source.out", "target.in")
202 .with_condition("${source.success}")
203 .with_description("Main flow");
204
205 assert_eq!(edge.from_node(), "source");
206 assert_eq!(edge.from_port(), "out");
207 assert_eq!(edge.condition, Some("${source.success}".to_string()));
208 }
209
210 #[test]
211 fn parse_node_without_port() {
212 let edge = EdgeDefinition::new("simple_node", "another_node");
213 assert_eq!(edge.from_node(), "simple_node");
214 assert_eq!(edge.from_port(), "out");
215 assert_eq!(edge.to_node(), "another_node");
216 assert_eq!(edge.to_port(), "in");
217 }
218
219 #[test]
220 fn parse_special_ports() {
221 let edge1 = EdgeDefinition::new("switch.true", "handler");
223 assert_eq!(edge1.from_node(), "switch");
224 assert_eq!(edge1.from_port(), "true");
225
226 let edge2 = EdgeDefinition::new("switch.false", "error_handler");
227 assert_eq!(edge2.from_node(), "switch");
228 assert_eq!(edge2.from_port(), "false");
229
230 let edge3 = EdgeDefinition::new("processor.error", "error_log");
232 assert_eq!(edge3.from_node(), "processor");
233 assert_eq!(edge3.from_port(), "error");
234 }
235}