1use super::trigger::{AuthConfig, TriggerConfig};
2use serde::{Deserialize, Serialize};
3use ts_rs::TS;
4
5#[derive(Debug, Clone, Serialize, Deserialize, TS)]
6#[ts(export)]
7pub struct Node {
8 pub id: String,
9 pub node_type: NodeType,
10 #[ts(type = "any")]
11 pub config: serde_json::Value,
12 #[serde(skip_serializing_if = "Option::is_none")]
13 pub position: Option<Position>,
14}
15
16impl Node {
17 pub fn is_trigger(&self) -> bool {
19 matches!(
20 self.node_type,
21 NodeType::ManualTrigger | NodeType::WebhookTrigger | NodeType::ScheduleTrigger
22 )
23 }
24
25 pub fn extract_trigger_config(&self) -> Option<TriggerConfig> {
27 match self.node_type {
28 NodeType::ManualTrigger => {
29 Some(TriggerConfig::Webhook {
31 path: format!("/manual/{}", self.id),
32 method: "POST".to_string(),
33 auth: None,
34 })
35 }
36 NodeType::WebhookTrigger => {
37 let data = self.config.get("data").unwrap_or(&self.config);
40
41 let path = data
42 .get("path")
43 .and_then(|v| v.as_str())
44 .unwrap_or(&format!("/webhook/{}", self.id))
45 .to_string();
46
47 let method = data
48 .get("method")
49 .and_then(|v| v.as_str())
50 .unwrap_or("POST")
51 .to_string();
52
53 let auth = data.get("auth").and_then(|auth| {
55 let auth_type = auth.get("type")?.as_str()?;
56 match auth_type {
57 "api_key" => {
58 let key = auth.get("key")?.as_str()?.to_string();
59 let header_name = auth
60 .get("header_name")
61 .and_then(|v| v.as_str())
62 .map(|s| s.to_string());
63 Some(AuthConfig::ApiKey { key, header_name })
64 }
65 "basic" => {
66 let username = auth.get("username")?.as_str()?.to_string();
67 let password = auth.get("password")?.as_str()?.to_string();
68 Some(AuthConfig::Basic { username, password })
69 }
70 _ => None,
71 }
72 });
73
74 Some(TriggerConfig::Webhook { path, method, auth })
75 }
76 NodeType::ScheduleTrigger => {
77 let data = self.config.get("data").unwrap_or(&self.config);
80
81 let cron = data
82 .get("cron")
83 .and_then(|v| v.as_str())
84 .unwrap_or("0 * * * *")
85 .to_string();
86
87 let timezone = data
88 .get("timezone")
89 .and_then(|v| v.as_str())
90 .map(|s| s.to_string());
91
92 let payload = data.get("payload").cloned();
93
94 Some(TriggerConfig::Schedule {
95 cron,
96 timezone,
97 payload,
98 })
99 }
100 _ => None,
101 }
102 }
103}
104
105#[derive(Debug, Clone, Serialize, Deserialize, TS)]
106#[ts(export)]
107pub struct Position {
108 pub x: f64,
109 pub y: f64,
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, TS)]
113#[ts(export)]
114pub enum NodeType {
115 ManualTrigger,
116 WebhookTrigger,
117 ScheduleTrigger,
118 Agent,
119 HttpRequest,
120 Print,
121 DataTransform,
122 Python,
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128 use crate::models::trigger::{AuthConfig, TriggerConfig};
129 use serde_json::json;
130
131 #[test]
132 fn test_extract_webhook_trigger_config() {
133 let node = Node {
135 id: "webhook-1".to_string(),
136 node_type: NodeType::WebhookTrigger,
137 config: json!({
138 "type": "WebhookTrigger",
139 "data": {
140 "path": "/webhook/test",
141 "method": "POST"
142 }
143 }),
144 position: None,
145 };
146
147 let config = node.extract_trigger_config();
148 assert!(config.is_some());
149
150 if let Some(TriggerConfig::Webhook { path, method, auth }) = config {
151 assert_eq!(path, "/webhook/test");
152 assert_eq!(method, "POST");
153 assert!(auth.is_none());
154 } else {
155 panic!("Expected Webhook trigger config");
156 }
157 }
158
159 #[test]
160 fn test_extract_webhook_trigger_config_with_auth() {
161 let node = Node {
162 id: "webhook-2".to_string(),
163 node_type: NodeType::WebhookTrigger,
164 config: json!({
165 "type": "WebhookTrigger",
166 "data": {
167 "path": "/api/webhook",
168 "method": "PUT",
169 "auth": {
170 "type": "api_key",
171 "key": "secret123",
172 "header_name": "X-API-Key"
173 }
174 }
175 }),
176 position: None,
177 };
178
179 let config = node.extract_trigger_config();
180 assert!(config.is_some());
181
182 if let Some(TriggerConfig::Webhook { path, method, auth }) = config {
183 assert_eq!(path, "/api/webhook");
184 assert_eq!(method, "PUT");
185 assert!(auth.is_some());
186
187 if let Some(AuthConfig::ApiKey { key, header_name }) = auth {
188 assert_eq!(key, "secret123");
189 assert_eq!(header_name, Some("X-API-Key".to_string()));
190 } else {
191 panic!("Expected ApiKey auth config");
192 }
193 } else {
194 panic!("Expected Webhook trigger config");
195 }
196 }
197
198 #[test]
199 fn test_extract_schedule_trigger_config() {
200 let node = Node {
201 id: "schedule-1".to_string(),
202 node_type: NodeType::ScheduleTrigger,
203 config: json!({
204 "type": "ScheduleTrigger",
205 "data": {
206 "cron": "0 10 * * *",
207 "timezone": "America/New_York",
208 "payload": {"key": "value"}
209 }
210 }),
211 position: None,
212 };
213
214 let config = node.extract_trigger_config();
215 assert!(config.is_some());
216
217 if let Some(TriggerConfig::Schedule {
218 cron,
219 timezone,
220 payload,
221 }) = config
222 {
223 assert_eq!(cron, "0 10 * * *");
224 assert_eq!(timezone, Some("America/New_York".to_string()));
225 assert_eq!(payload, Some(json!({"key": "value"})));
226 } else {
227 panic!("Expected Schedule trigger config");
228 }
229 }
230
231 #[test]
232 fn test_extract_manual_trigger_config() {
233 let node = Node {
234 id: "manual-1".to_string(),
235 node_type: NodeType::ManualTrigger,
236 config: json!({
237 "type": "ManualTrigger",
238 "data": {
239 "payload": null
240 }
241 }),
242 position: None,
243 };
244
245 let config = node.extract_trigger_config();
246 assert!(config.is_some());
247
248 if let Some(TriggerConfig::Webhook { path, method, auth }) = config {
249 assert_eq!(path, "/manual/manual-1");
250 assert_eq!(method, "POST");
251 assert!(auth.is_none());
252 } else {
253 panic!("Expected Webhook trigger config for ManualTrigger");
254 }
255 }
256
257 #[test]
258 fn test_backward_compatibility_webhook() {
259 let node = Node {
261 id: "webhook-old".to_string(),
262 node_type: NodeType::WebhookTrigger,
263 config: json!({
264 "path": "/old/webhook",
265 "method": "GET"
266 }),
267 position: None,
268 };
269
270 let config = node.extract_trigger_config();
271 assert!(config.is_some());
272
273 if let Some(TriggerConfig::Webhook { path, method, .. }) = config {
274 assert_eq!(path, "/old/webhook");
275 assert_eq!(method, "GET");
276 } else {
277 panic!("Expected Webhook trigger config");
278 }
279 }
280
281 #[test]
282 fn test_backward_compatibility_schedule() {
283 let node = Node {
285 id: "schedule-old".to_string(),
286 node_type: NodeType::ScheduleTrigger,
287 config: json!({
288 "cron": "0 0 * * *",
289 "timezone": "UTC"
290 }),
291 position: None,
292 };
293
294 let config = node.extract_trigger_config();
295 assert!(config.is_some());
296
297 if let Some(TriggerConfig::Schedule { cron, timezone, .. }) = config {
298 assert_eq!(cron, "0 0 * * *");
299 assert_eq!(timezone, Some("UTC".to_string()));
300 } else {
301 panic!("Expected Schedule trigger config");
302 }
303 }
304}