1use crate::{
6 models::{
7 NotificationMessage, ScriptLanguage, SecretString, SecretValue, Trigger, TriggerType,
8 TriggerTypeConfig,
9 },
10 utils::HttpRetryConfig,
11};
12use email_address::EmailAddress;
13
14pub struct TriggerBuilder {
16 name: String,
17 trigger_type: TriggerType,
18 config: TriggerTypeConfig,
19}
20
21impl Default for TriggerBuilder {
22 fn default() -> Self {
23 Self {
24 name: "test_trigger".to_string(),
25 trigger_type: TriggerType::Webhook,
26 config: TriggerTypeConfig::Webhook {
27 url: SecretValue::Plain(SecretString::new(
28 "https://api.example.com/webhook".to_string(),
29 )),
30 secret: None,
31 method: Some("POST".to_string()),
32 headers: None,
33 message: NotificationMessage {
34 title: "Alert".to_string(),
35 body: "Test message".to_string(),
36 },
37 retry_policy: HttpRetryConfig::default(),
38 },
39 }
40 }
41}
42
43impl TriggerBuilder {
44 pub fn new() -> Self {
45 Self::default()
46 }
47
48 pub fn name(mut self, name: &str) -> Self {
49 self.name = name.to_string();
50 self
51 }
52
53 pub fn config(mut self, config: TriggerTypeConfig) -> Self {
54 self.config = config;
55 self
56 }
57
58 pub fn webhook(mut self, url: &str) -> Self {
59 self.trigger_type = TriggerType::Webhook;
60 self.config = TriggerTypeConfig::Webhook {
61 url: SecretValue::Plain(SecretString::new(url.to_string())),
62 secret: None,
63 method: Some("POST".to_string()),
64 headers: None,
65 message: NotificationMessage {
66 title: "Alert".to_string(),
67 body: "Test message".to_string(),
68 },
69 retry_policy: HttpRetryConfig::default(),
70 };
71 self
72 }
73
74 pub fn slack(mut self, webhook_url: &str) -> Self {
75 self.trigger_type = TriggerType::Slack;
76 self.config = TriggerTypeConfig::Slack {
77 slack_url: SecretValue::Plain(SecretString::new(webhook_url.to_string())),
78 message: NotificationMessage {
79 title: "Alert".to_string(),
80 body: "Test message".to_string(),
81 },
82 retry_policy: HttpRetryConfig::default(),
83 };
84 self
85 }
86
87 pub fn discord(mut self, webhook_url: &str) -> Self {
88 self.trigger_type = TriggerType::Discord;
89 self.config = TriggerTypeConfig::Discord {
90 discord_url: SecretValue::Plain(SecretString::new(webhook_url.to_string())),
91 message: NotificationMessage {
92 title: "Alert".to_string(),
93 body: "Test message".to_string(),
94 },
95 retry_policy: HttpRetryConfig::default(),
96 };
97 self
98 }
99
100 pub fn telegram(mut self, token: &str, chat_id: &str, disable_web_preview: bool) -> Self {
101 self.trigger_type = TriggerType::Telegram;
102 self.config = TriggerTypeConfig::Telegram {
103 token: SecretValue::Plain(SecretString::new(token.to_string())),
104 chat_id: chat_id.to_string(),
105 disable_web_preview: Some(disable_web_preview),
106 message: NotificationMessage {
107 title: "Test title".to_string(),
108 body: "Test message".to_string(),
109 },
110 retry_policy: HttpRetryConfig::default(),
111 };
112 self
113 }
114
115 pub fn telegram_token(mut self, token: SecretValue) -> Self {
116 if let TriggerTypeConfig::Telegram { token: t, .. } = &mut self.config {
117 *t = token;
118 }
119 self
120 }
121
122 pub fn script(mut self, script_path: &str, language: ScriptLanguage) -> Self {
123 self.trigger_type = TriggerType::Script;
124 self.config = TriggerTypeConfig::Script {
125 script_path: script_path.to_string(),
126 arguments: None,
127 language,
128 timeout_ms: 1000,
129 };
130 self
131 }
132
133 pub fn script_arguments(mut self, arguments: Vec<String>) -> Self {
134 if let TriggerTypeConfig::Script { arguments: a, .. } = &mut self.config {
135 *a = Some(arguments);
136 }
137 self
138 }
139
140 pub fn script_timeout_ms(mut self, timeout_ms: u32) -> Self {
141 if let TriggerTypeConfig::Script { timeout_ms: t, .. } = &mut self.config {
142 *t = timeout_ms;
143 }
144 self
145 }
146
147 pub fn message(mut self, title: &str, body: &str) -> Self {
148 match &mut self.config {
149 TriggerTypeConfig::Webhook { message, .. }
150 | TriggerTypeConfig::Slack { message, .. }
151 | TriggerTypeConfig::Discord { message, .. }
152 | TriggerTypeConfig::Telegram { message, .. }
153 | TriggerTypeConfig::Email { message, .. } => {
154 message.title = title.to_string();
155 message.body = body.to_string();
156 }
157 _ => {}
158 }
159 self
160 }
161
162 pub fn trigger_type(mut self, trigger_type: TriggerType) -> Self {
163 self.trigger_type = trigger_type;
164 self
165 }
166
167 pub fn email(
168 mut self,
169 host: &str,
170 username: &str,
171 password: &str,
172 sender: &str,
173 recipients: Vec<&str>,
174 ) -> Self {
175 self.trigger_type = TriggerType::Email;
176 self.config = TriggerTypeConfig::Email {
177 host: host.to_string(),
178 port: Some(587),
179 username: SecretValue::Plain(SecretString::new(username.to_string())),
180 password: SecretValue::Plain(SecretString::new(password.to_string())),
181 message: NotificationMessage {
182 title: "Test Subject".to_string(),
183 body: "Test Body".to_string(),
184 },
185 sender: EmailAddress::new_unchecked(sender),
186 recipients: recipients
187 .into_iter()
188 .map(EmailAddress::new_unchecked)
189 .collect(),
190 };
191 self
192 }
193
194 pub fn email_port(mut self, port: u16) -> Self {
195 if let TriggerTypeConfig::Email { port: p, .. } = &mut self.config {
196 *p = Some(port);
197 }
198 self
199 }
200
201 pub fn email_subject(mut self, subject: &str) -> Self {
202 if let TriggerTypeConfig::Email { message, .. } = &mut self.config {
203 message.title = subject.to_string();
204 }
205 self
206 }
207
208 pub fn email_username(mut self, username: SecretValue) -> Self {
209 if let TriggerTypeConfig::Email { username: u, .. } = &mut self.config {
210 *u = username;
211 }
212 self
213 }
214
215 pub fn email_password(mut self, password: SecretValue) -> Self {
216 if let TriggerTypeConfig::Email { password: p, .. } = &mut self.config {
217 *p = password;
218 }
219 self
220 }
221
222 pub fn webhook_method(mut self, method: &str) -> Self {
223 if let TriggerTypeConfig::Webhook { method: m, .. } = &mut self.config {
224 *m = Some(method.to_string());
225 }
226 self
227 }
228
229 pub fn webhook_secret(mut self, secret: SecretValue) -> Self {
230 if let TriggerTypeConfig::Webhook { secret: s, .. } = &mut self.config {
231 *s = Some(secret);
232 }
233 self
234 }
235
236 pub fn webhook_headers(mut self, headers: std::collections::HashMap<String, String>) -> Self {
237 if let TriggerTypeConfig::Webhook { headers: h, .. } = &mut self.config {
238 *h = Some(headers);
239 }
240 self
241 }
242
243 pub fn url(mut self, url: SecretValue) -> Self {
244 self.config = match self.config {
245 TriggerTypeConfig::Webhook {
246 url: _,
247 method,
248 headers,
249 secret,
250 message,
251 retry_policy,
252 } => TriggerTypeConfig::Webhook {
253 url,
254 method,
255 headers,
256 secret,
257 message,
258 retry_policy,
259 },
260 TriggerTypeConfig::Discord {
261 discord_url: _,
262 message,
263 retry_policy,
264 } => TriggerTypeConfig::Discord {
265 discord_url: url,
266 message,
267 retry_policy,
268 },
269 TriggerTypeConfig::Slack {
270 slack_url: _,
271 message,
272 retry_policy,
273 } => TriggerTypeConfig::Slack {
274 slack_url: url,
275 message,
276 retry_policy,
277 },
278 config => config,
279 };
280 self
281 }
282
283 pub fn build(self) -> Trigger {
284 Trigger {
285 name: self.name,
286 trigger_type: self.trigger_type,
287 config: self.config,
288 }
289 }
290}
291
292#[cfg(test)]
293mod tests {
294 use super::*;
295
296 #[test]
297 fn test_default_trigger() {
298 let trigger = TriggerBuilder::new().build();
299
300 assert_eq!(trigger.name, "test_trigger");
301 assert_eq!(trigger.trigger_type, TriggerType::Webhook);
302
303 match trigger.config {
304 TriggerTypeConfig::Webhook { url, method, .. } => {
305 assert_eq!(url.as_ref().to_string(), "https://api.example.com/webhook");
306 assert_eq!(method, Some("POST".to_string()));
307 }
308 _ => panic!("Expected webhook config"),
309 }
310 }
311
312 #[test]
313 fn test_trigger_with_config() {
314 let trigger = TriggerBuilder::new()
315 .name("my_trigger")
316 .config(TriggerTypeConfig::Webhook {
317 url: SecretValue::Plain(SecretString::new(
318 "https://api.example.com/webhook".to_string(),
319 )),
320 secret: Some(SecretValue::Plain(SecretString::new("secret".to_string()))),
321 method: Some("POST".to_string()),
322 headers: None,
323 message: NotificationMessage {
324 title: "Alert".to_string(),
325 body: "Test message".to_string(),
326 },
327 retry_policy: HttpRetryConfig::default(),
328 })
329 .build();
330
331 assert_eq!(trigger.name, "my_trigger");
332 assert_eq!(trigger.trigger_type, TriggerType::Webhook);
333
334 match trigger.config {
335 TriggerTypeConfig::Webhook { url, method, .. } => {
336 assert_eq!(url.as_ref().to_string(), "https://api.example.com/webhook");
337 assert_eq!(method, Some("POST".to_string()));
338 }
339 _ => panic!("Expected webhook config"),
340 }
341 }
342
343 #[test]
344 fn test_webhook_trigger() {
345 let trigger = TriggerBuilder::new()
346 .name("my_webhook")
347 .webhook("https://webhook.example.com")
348 .message("Custom Alert", "Something happened!")
349 .build();
350
351 assert_eq!(trigger.name, "my_webhook");
352 assert_eq!(trigger.trigger_type, TriggerType::Webhook);
353
354 match trigger.config {
355 TriggerTypeConfig::Webhook { url, message, .. } => {
356 assert_eq!(url.as_ref().to_string(), "https://webhook.example.com");
357 assert_eq!(message.title, "Custom Alert");
358 assert_eq!(message.body, "Something happened!");
359 }
360 _ => panic!("Expected webhook config"),
361 }
362 }
363
364 #[test]
365 fn test_webhook_trigger_with_config() {
366 let mut headers = std::collections::HashMap::new();
367 headers.insert("Content-Type".to_string(), "application/json".to_string());
368
369 let trigger = TriggerBuilder::new()
370 .name("my_webhook")
371 .webhook("https://webhook.example.com")
372 .webhook_method("POST")
373 .webhook_secret(SecretValue::Plain(SecretString::new(
374 "secret123".to_string(),
375 )))
376 .webhook_headers(headers.clone())
377 .message("Custom Alert", "Something happened!")
378 .build();
379
380 assert_eq!(trigger.name, "my_webhook");
381 assert_eq!(trigger.trigger_type, TriggerType::Webhook);
382
383 match trigger.config {
384 TriggerTypeConfig::Webhook {
385 url,
386 method,
387 secret,
388 headers: h,
389 message,
390 retry_policy: _,
391 } => {
392 assert_eq!(url.as_ref().to_string(), "https://webhook.example.com");
393 assert_eq!(method, Some("POST".to_string()));
394 assert_eq!(
395 secret.as_ref().map(|s| s.as_ref().to_string()),
396 Some("secret123".to_string())
397 );
398 assert_eq!(h, Some(headers));
399 assert_eq!(message.title, "Custom Alert");
400 assert_eq!(message.body, "Something happened!");
401 }
402 _ => panic!("Expected webhook config"),
403 }
404 }
405
406 #[test]
407 fn test_slack_trigger() {
408 let trigger = TriggerBuilder::new()
409 .name("slack_alert")
410 .slack("https://slack.webhook.com")
411 .message("Alert", "Test message")
412 .build();
413
414 assert_eq!(trigger.trigger_type, TriggerType::Slack);
415 match trigger.config {
416 TriggerTypeConfig::Slack {
417 slack_url,
418 message,
419 retry_policy: _,
420 } => {
421 assert_eq!(slack_url.as_ref().to_string(), "https://slack.webhook.com");
422 assert_eq!(message.title, "Alert");
423 assert_eq!(message.body, "Test message");
424 }
425 _ => panic!("Expected slack config"),
426 }
427 }
428
429 #[test]
430 fn test_discord_trigger() {
431 let trigger = TriggerBuilder::new()
432 .name("discord_alert")
433 .discord("https://discord.webhook.com")
434 .message("Alert", "Test message")
435 .build();
436
437 assert_eq!(trigger.trigger_type, TriggerType::Discord);
438 match trigger.config {
439 TriggerTypeConfig::Discord {
440 discord_url,
441 message,
442 retry_policy: _,
443 } => {
444 assert_eq!(
445 discord_url.as_ref().to_string(),
446 "https://discord.webhook.com"
447 );
448 assert_eq!(message.title, "Alert");
449 assert_eq!(message.body, "Test message");
450 }
451 _ => panic!("Expected discord config"),
452 }
453 }
454
455 #[test]
456 fn test_script_trigger() {
457 let trigger = TriggerBuilder::new()
458 .name("script_trigger")
459 .script("test.py", ScriptLanguage::Python)
460 .build();
461
462 assert_eq!(trigger.trigger_type, TriggerType::Script);
463 match trigger.config {
464 TriggerTypeConfig::Script {
465 script_path,
466 language,
467 timeout_ms,
468 ..
469 } => {
470 assert_eq!(script_path, "test.py");
471 assert_eq!(language, ScriptLanguage::Python);
472 assert_eq!(timeout_ms, 1000);
473 }
474 _ => panic!("Expected script config"),
475 }
476 }
477
478 #[test]
479 fn test_script_trigger_with_arguments() {
480 let trigger = TriggerBuilder::new()
481 .name("script_trigger")
482 .script("test.py", ScriptLanguage::Python)
483 .script_arguments(vec!["arg1".to_string()])
484 .build();
485
486 assert_eq!(trigger.trigger_type, TriggerType::Script);
487 match trigger.config {
488 TriggerTypeConfig::Script { arguments, .. } => {
489 assert_eq!(arguments, Some(vec!["arg1".to_string()]));
490 }
491 _ => panic!("Expected script config"),
492 }
493 }
494
495 #[test]
496 fn test_script_trigger_with_timeout() {
497 let trigger = TriggerBuilder::new()
498 .name("script_trigger")
499 .script("test.py", ScriptLanguage::Python)
500 .script_timeout_ms(2000)
501 .build();
502
503 assert_eq!(trigger.trigger_type, TriggerType::Script);
504 match trigger.config {
505 TriggerTypeConfig::Script { timeout_ms, .. } => {
506 assert_eq!(timeout_ms, 2000);
507 }
508 _ => panic!("Expected script config"),
509 }
510 }
511
512 #[test]
513 fn test_telegram_trigger() {
514 let trigger = TriggerBuilder::new()
515 .name("telegram_alert")
516 .telegram(
517 "1234567890:ABCdefGHIjklMNOpqrSTUvwxYZ123456789", "1234567890",
519 false,
520 )
521 .message("Alert", "Test message")
522 .build();
523
524 assert_eq!(trigger.trigger_type, TriggerType::Telegram);
525 match trigger.config {
526 TriggerTypeConfig::Telegram {
527 token,
528 chat_id,
529 message,
530 ..
531 } => {
532 assert_eq!(
533 token.as_ref().to_string(),
534 "1234567890:ABCdefGHIjklMNOpqrSTUvwxYZ123456789".to_string() );
536 assert_eq!(chat_id, "1234567890");
537 assert_eq!(message.title, "Alert");
538 assert_eq!(message.body, "Test message");
539 }
540 _ => panic!("Expected telegram config"),
541 }
542 }
543
544 #[test]
545 fn test_email_trigger() {
546 let trigger = TriggerBuilder::new()
547 .name("email_alert")
548 .email(
549 "smtp.example.com",
550 "user",
551 "pass",
552 "sender@example.com",
553 vec!["recipient@example.com"],
554 )
555 .email_port(465)
556 .email_subject("Custom Subject")
557 .build();
558
559 assert_eq!(trigger.trigger_type, TriggerType::Email);
560 match trigger.config {
561 TriggerTypeConfig::Email {
562 host,
563 port,
564 username,
565 password,
566 message,
567 sender,
568 recipients,
569 ..
570 } => {
571 assert_eq!(host, "smtp.example.com");
572 assert_eq!(port, Some(465));
573 assert_eq!(username.as_ref().to_string(), "user");
574 assert_eq!(password.as_ref().to_string(), "pass");
575 assert_eq!(message.title, "Custom Subject");
576 assert_eq!(sender.as_str(), "sender@example.com");
577 assert_eq!(recipients.len(), 1);
578 assert_eq!(recipients[0].as_str(), "recipient@example.com");
579 }
580 _ => panic!("Expected email config"),
581 }
582 }
583
584 #[test]
585 fn test_telegram_token() {
586 let token = SecretValue::Environment("TELEGRAM_TOKEN".to_string());
587 let trigger = TriggerBuilder::new()
588 .name("telegram_alert")
589 .telegram("dummy_token", "1234567890", false)
590 .telegram_token(token.clone())
591 .build();
592
593 assert_eq!(trigger.trigger_type, TriggerType::Telegram);
594 match trigger.config {
595 TriggerTypeConfig::Telegram { token: t, .. } => {
596 assert_eq!(t, token);
597 }
598 _ => panic!("Expected telegram config"),
599 }
600 }
601
602 #[test]
603 fn test_email_username() {
604 let username = SecretValue::Environment("SMTP_USERNAME".to_string());
605 let trigger = TriggerBuilder::new()
606 .name("email_alert")
607 .email(
608 "smtp.example.com",
609 "dummy_user",
610 "pass",
611 "sender@example.com",
612 vec!["recipient@example.com"],
613 )
614 .email_username(username.clone())
615 .build();
616
617 assert_eq!(trigger.trigger_type, TriggerType::Email);
618 match trigger.config {
619 TriggerTypeConfig::Email { username: u, .. } => {
620 assert_eq!(u, username);
621 }
622 _ => panic!("Expected email config"),
623 }
624 }
625
626 #[test]
627 fn test_email_password() {
628 let password = SecretValue::Environment("SMTP_PASSWORD".to_string());
629 let trigger = TriggerBuilder::new()
630 .name("email_alert")
631 .email(
632 "smtp.example.com",
633 "user",
634 "dummy_pass",
635 "sender@example.com",
636 vec!["recipient@example.com"],
637 )
638 .email_password(password.clone())
639 .build();
640
641 assert_eq!(trigger.trigger_type, TriggerType::Email);
642 match trigger.config {
643 TriggerTypeConfig::Email { password: p, .. } => {
644 assert_eq!(p, password);
645 }
646 _ => panic!("Expected email config"),
647 }
648 }
649
650 #[test]
651 fn test_url() {
652 let url = SecretValue::Environment("WEBHOOK_URL".to_string());
653
654 let webhook_trigger = TriggerBuilder::new()
656 .name("webhook_alert")
657 .webhook("dummy_url")
658 .url(url.clone())
659 .build();
660
661 assert_eq!(webhook_trigger.trigger_type, TriggerType::Webhook);
662 match webhook_trigger.config {
663 TriggerTypeConfig::Webhook { url: u, .. } => {
664 assert_eq!(u, url);
665 }
666 _ => panic!("Expected webhook config"),
667 }
668
669 let discord_trigger = TriggerBuilder::new()
671 .name("discord_alert")
672 .discord("dummy_url")
673 .url(url.clone())
674 .build();
675
676 assert_eq!(discord_trigger.trigger_type, TriggerType::Discord);
677 match discord_trigger.config {
678 TriggerTypeConfig::Discord { discord_url: u, .. } => {
679 assert_eq!(u, url);
680 }
681 _ => panic!("Expected discord config"),
682 }
683
684 let slack_trigger = TriggerBuilder::new()
686 .name("slack_alert")
687 .slack("dummy_url")
688 .url(url.clone())
689 .build();
690
691 assert_eq!(slack_trigger.trigger_type, TriggerType::Slack);
692 match slack_trigger.config {
693 TriggerTypeConfig::Slack { slack_url: u, .. } => {
694 assert_eq!(u, url);
695 }
696 _ => panic!("Expected slack config"),
697 }
698 }
699}