1use crate::{
6 models::{
7 NotificationMessage, ScriptLanguage, SecretString, SecretValue, Trigger, TriggerType,
8 TriggerTypeConfig,
9 },
10 utils::RetryConfig,
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: RetryConfig::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: RetryConfig::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: RetryConfig::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: RetryConfig::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: RetryConfig::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 retry_policy: RetryConfig::default(),
191 };
192 self
193 }
194
195 pub fn email_port(mut self, port: u16) -> Self {
196 if let TriggerTypeConfig::Email { port: p, .. } = &mut self.config {
197 *p = Some(port);
198 }
199 self
200 }
201
202 pub fn email_subject(mut self, subject: &str) -> Self {
203 if let TriggerTypeConfig::Email { message, .. } = &mut self.config {
204 message.title = subject.to_string();
205 }
206 self
207 }
208
209 pub fn email_username(mut self, username: SecretValue) -> Self {
210 if let TriggerTypeConfig::Email { username: u, .. } = &mut self.config {
211 *u = username;
212 }
213 self
214 }
215
216 pub fn email_password(mut self, password: SecretValue) -> Self {
217 if let TriggerTypeConfig::Email { password: p, .. } = &mut self.config {
218 *p = password;
219 }
220 self
221 }
222
223 pub fn webhook_method(mut self, method: &str) -> Self {
224 if let TriggerTypeConfig::Webhook { method: m, .. } = &mut self.config {
225 *m = Some(method.to_string());
226 }
227 self
228 }
229
230 pub fn webhook_secret(mut self, secret: SecretValue) -> Self {
231 if let TriggerTypeConfig::Webhook { secret: s, .. } = &mut self.config {
232 *s = Some(secret);
233 }
234 self
235 }
236
237 pub fn webhook_headers(mut self, headers: std::collections::HashMap<String, String>) -> Self {
238 if let TriggerTypeConfig::Webhook { headers: h, .. } = &mut self.config {
239 *h = Some(headers);
240 }
241 self
242 }
243
244 pub fn url(mut self, url: SecretValue) -> Self {
245 self.config = match self.config {
246 TriggerTypeConfig::Webhook {
247 url: _,
248 method,
249 headers,
250 secret,
251 message,
252 retry_policy,
253 } => TriggerTypeConfig::Webhook {
254 url,
255 method,
256 headers,
257 secret,
258 message,
259 retry_policy,
260 },
261 TriggerTypeConfig::Discord {
262 discord_url: _,
263 message,
264 retry_policy,
265 } => TriggerTypeConfig::Discord {
266 discord_url: url,
267 message,
268 retry_policy,
269 },
270 TriggerTypeConfig::Slack {
271 slack_url: _,
272 message,
273 retry_policy,
274 } => TriggerTypeConfig::Slack {
275 slack_url: url,
276 message,
277 retry_policy,
278 },
279 config => config,
280 };
281 self
282 }
283
284 pub fn build(self) -> Trigger {
285 Trigger {
286 name: self.name,
287 trigger_type: self.trigger_type,
288 config: self.config,
289 }
290 }
291}
292
293#[cfg(test)]
294mod tests {
295 use super::*;
296
297 #[test]
298 fn test_default_trigger() {
299 let trigger = TriggerBuilder::new().build();
300
301 assert_eq!(trigger.name, "test_trigger");
302 assert_eq!(trigger.trigger_type, TriggerType::Webhook);
303
304 match trigger.config {
305 TriggerTypeConfig::Webhook { url, method, .. } => {
306 assert_eq!(url.as_ref().to_string(), "https://api.example.com/webhook");
307 assert_eq!(method, Some("POST".to_string()));
308 }
309 _ => panic!("Expected webhook config"),
310 }
311 }
312
313 #[test]
314 fn test_trigger_with_config() {
315 let trigger = TriggerBuilder::new()
316 .name("my_trigger")
317 .config(TriggerTypeConfig::Webhook {
318 url: SecretValue::Plain(SecretString::new(
319 "https://api.example.com/webhook".to_string(),
320 )),
321 secret: Some(SecretValue::Plain(SecretString::new("secret".to_string()))),
322 method: Some("POST".to_string()),
323 headers: None,
324 message: NotificationMessage {
325 title: "Alert".to_string(),
326 body: "Test message".to_string(),
327 },
328 retry_policy: RetryConfig::default(),
329 })
330 .build();
331
332 assert_eq!(trigger.name, "my_trigger");
333 assert_eq!(trigger.trigger_type, TriggerType::Webhook);
334
335 match trigger.config {
336 TriggerTypeConfig::Webhook { url, method, .. } => {
337 assert_eq!(url.as_ref().to_string(), "https://api.example.com/webhook");
338 assert_eq!(method, Some("POST".to_string()));
339 }
340 _ => panic!("Expected webhook config"),
341 }
342 }
343
344 #[test]
345 fn test_webhook_trigger() {
346 let trigger = TriggerBuilder::new()
347 .name("my_webhook")
348 .webhook("https://webhook.example.com")
349 .message("Custom Alert", "Something happened!")
350 .build();
351
352 assert_eq!(trigger.name, "my_webhook");
353 assert_eq!(trigger.trigger_type, TriggerType::Webhook);
354
355 match trigger.config {
356 TriggerTypeConfig::Webhook { url, message, .. } => {
357 assert_eq!(url.as_ref().to_string(), "https://webhook.example.com");
358 assert_eq!(message.title, "Custom Alert");
359 assert_eq!(message.body, "Something happened!");
360 }
361 _ => panic!("Expected webhook config"),
362 }
363 }
364
365 #[test]
366 fn test_webhook_trigger_with_config() {
367 let mut headers = std::collections::HashMap::new();
368 headers.insert("Content-Type".to_string(), "application/json".to_string());
369
370 let trigger = TriggerBuilder::new()
371 .name("my_webhook")
372 .webhook("https://webhook.example.com")
373 .webhook_method("POST")
374 .webhook_secret(SecretValue::Plain(SecretString::new(
375 "secret123".to_string(),
376 )))
377 .webhook_headers(headers.clone())
378 .message("Custom Alert", "Something happened!")
379 .build();
380
381 assert_eq!(trigger.name, "my_webhook");
382 assert_eq!(trigger.trigger_type, TriggerType::Webhook);
383
384 match trigger.config {
385 TriggerTypeConfig::Webhook {
386 url,
387 method,
388 secret,
389 headers: h,
390 message,
391 retry_policy: _,
392 } => {
393 assert_eq!(url.as_ref().to_string(), "https://webhook.example.com");
394 assert_eq!(method, Some("POST".to_string()));
395 assert_eq!(
396 secret.as_ref().map(|s| s.as_ref().to_string()),
397 Some("secret123".to_string())
398 );
399 assert_eq!(h, Some(headers));
400 assert_eq!(message.title, "Custom Alert");
401 assert_eq!(message.body, "Something happened!");
402 }
403 _ => panic!("Expected webhook config"),
404 }
405 }
406
407 #[test]
408 fn test_slack_trigger() {
409 let trigger = TriggerBuilder::new()
410 .name("slack_alert")
411 .slack("https://slack.webhook.com")
412 .message("Alert", "Test message")
413 .build();
414
415 assert_eq!(trigger.trigger_type, TriggerType::Slack);
416 match trigger.config {
417 TriggerTypeConfig::Slack {
418 slack_url,
419 message,
420 retry_policy: _,
421 } => {
422 assert_eq!(slack_url.as_ref().to_string(), "https://slack.webhook.com");
423 assert_eq!(message.title, "Alert");
424 assert_eq!(message.body, "Test message");
425 }
426 _ => panic!("Expected slack config"),
427 }
428 }
429
430 #[test]
431 fn test_discord_trigger() {
432 let trigger = TriggerBuilder::new()
433 .name("discord_alert")
434 .discord("https://discord.webhook.com")
435 .message("Alert", "Test message")
436 .build();
437
438 assert_eq!(trigger.trigger_type, TriggerType::Discord);
439 match trigger.config {
440 TriggerTypeConfig::Discord {
441 discord_url,
442 message,
443 retry_policy: _,
444 } => {
445 assert_eq!(
446 discord_url.as_ref().to_string(),
447 "https://discord.webhook.com"
448 );
449 assert_eq!(message.title, "Alert");
450 assert_eq!(message.body, "Test message");
451 }
452 _ => panic!("Expected discord config"),
453 }
454 }
455
456 #[test]
457 fn test_script_trigger() {
458 let trigger = TriggerBuilder::new()
459 .name("script_trigger")
460 .script("test.py", ScriptLanguage::Python)
461 .build();
462
463 assert_eq!(trigger.trigger_type, TriggerType::Script);
464 match trigger.config {
465 TriggerTypeConfig::Script {
466 script_path,
467 language,
468 timeout_ms,
469 ..
470 } => {
471 assert_eq!(script_path, "test.py");
472 assert_eq!(language, ScriptLanguage::Python);
473 assert_eq!(timeout_ms, 1000);
474 }
475 _ => panic!("Expected script config"),
476 }
477 }
478
479 #[test]
480 fn test_script_trigger_with_arguments() {
481 let trigger = TriggerBuilder::new()
482 .name("script_trigger")
483 .script("test.py", ScriptLanguage::Python)
484 .script_arguments(vec!["arg1".to_string()])
485 .build();
486
487 assert_eq!(trigger.trigger_type, TriggerType::Script);
488 match trigger.config {
489 TriggerTypeConfig::Script { arguments, .. } => {
490 assert_eq!(arguments, Some(vec!["arg1".to_string()]));
491 }
492 _ => panic!("Expected script config"),
493 }
494 }
495
496 #[test]
497 fn test_script_trigger_with_timeout() {
498 let trigger = TriggerBuilder::new()
499 .name("script_trigger")
500 .script("test.py", ScriptLanguage::Python)
501 .script_timeout_ms(2000)
502 .build();
503
504 assert_eq!(trigger.trigger_type, TriggerType::Script);
505 match trigger.config {
506 TriggerTypeConfig::Script { timeout_ms, .. } => {
507 assert_eq!(timeout_ms, 2000);
508 }
509 _ => panic!("Expected script config"),
510 }
511 }
512
513 #[test]
514 fn test_telegram_trigger() {
515 let trigger = TriggerBuilder::new()
516 .name("telegram_alert")
517 .telegram(
518 "1234567890:ABCdefGHIjklMNOpqrSTUvwxYZ123456789", "1234567890",
520 false,
521 )
522 .message("Alert", "Test message")
523 .build();
524
525 assert_eq!(trigger.trigger_type, TriggerType::Telegram);
526 match trigger.config {
527 TriggerTypeConfig::Telegram {
528 token,
529 chat_id,
530 message,
531 ..
532 } => {
533 assert_eq!(
534 token.as_ref().to_string(),
535 "1234567890:ABCdefGHIjklMNOpqrSTUvwxYZ123456789".to_string() );
537 assert_eq!(chat_id, "1234567890");
538 assert_eq!(message.title, "Alert");
539 assert_eq!(message.body, "Test message");
540 }
541 _ => panic!("Expected telegram config"),
542 }
543 }
544
545 #[test]
546 fn test_email_trigger() {
547 let trigger = TriggerBuilder::new()
548 .name("email_alert")
549 .email(
550 "smtp.example.com",
551 "user",
552 "pass",
553 "sender@example.com",
554 vec!["recipient@example.com"],
555 )
556 .email_port(465)
557 .email_subject("Custom Subject")
558 .build();
559
560 assert_eq!(trigger.trigger_type, TriggerType::Email);
561 match trigger.config {
562 TriggerTypeConfig::Email {
563 host,
564 port,
565 username,
566 password,
567 message,
568 sender,
569 recipients,
570 ..
571 } => {
572 assert_eq!(host, "smtp.example.com");
573 assert_eq!(port, Some(465));
574 assert_eq!(username.as_ref().to_string(), "user");
575 assert_eq!(password.as_ref().to_string(), "pass");
576 assert_eq!(message.title, "Custom Subject");
577 assert_eq!(sender.as_str(), "sender@example.com");
578 assert_eq!(recipients.len(), 1);
579 assert_eq!(recipients[0].as_str(), "recipient@example.com");
580 }
581 _ => panic!("Expected email config"),
582 }
583 }
584
585 #[test]
586 fn test_telegram_token() {
587 let token = SecretValue::Environment("TELEGRAM_TOKEN".to_string());
588 let trigger = TriggerBuilder::new()
589 .name("telegram_alert")
590 .telegram("dummy_token", "1234567890", false)
591 .telegram_token(token.clone())
592 .build();
593
594 assert_eq!(trigger.trigger_type, TriggerType::Telegram);
595 match trigger.config {
596 TriggerTypeConfig::Telegram { token: t, .. } => {
597 assert_eq!(t, token);
598 }
599 _ => panic!("Expected telegram config"),
600 }
601 }
602
603 #[test]
604 fn test_email_username() {
605 let username = SecretValue::Environment("SMTP_USERNAME".to_string());
606 let trigger = TriggerBuilder::new()
607 .name("email_alert")
608 .email(
609 "smtp.example.com",
610 "dummy_user",
611 "pass",
612 "sender@example.com",
613 vec!["recipient@example.com"],
614 )
615 .email_username(username.clone())
616 .build();
617
618 assert_eq!(trigger.trigger_type, TriggerType::Email);
619 match trigger.config {
620 TriggerTypeConfig::Email { username: u, .. } => {
621 assert_eq!(u, username);
622 }
623 _ => panic!("Expected email config"),
624 }
625 }
626
627 #[test]
628 fn test_email_password() {
629 let password = SecretValue::Environment("SMTP_PASSWORD".to_string());
630 let trigger = TriggerBuilder::new()
631 .name("email_alert")
632 .email(
633 "smtp.example.com",
634 "user",
635 "dummy_pass",
636 "sender@example.com",
637 vec!["recipient@example.com"],
638 )
639 .email_password(password.clone())
640 .build();
641
642 assert_eq!(trigger.trigger_type, TriggerType::Email);
643 match trigger.config {
644 TriggerTypeConfig::Email { password: p, .. } => {
645 assert_eq!(p, password);
646 }
647 _ => panic!("Expected email config"),
648 }
649 }
650
651 #[test]
652 fn test_url() {
653 let url = SecretValue::Environment("WEBHOOK_URL".to_string());
654
655 let webhook_trigger = TriggerBuilder::new()
657 .name("webhook_alert")
658 .webhook("dummy_url")
659 .url(url.clone())
660 .build();
661
662 assert_eq!(webhook_trigger.trigger_type, TriggerType::Webhook);
663 match webhook_trigger.config {
664 TriggerTypeConfig::Webhook { url: u, .. } => {
665 assert_eq!(u, url);
666 }
667 _ => panic!("Expected webhook config"),
668 }
669
670 let discord_trigger = TriggerBuilder::new()
672 .name("discord_alert")
673 .discord("dummy_url")
674 .url(url.clone())
675 .build();
676
677 assert_eq!(discord_trigger.trigger_type, TriggerType::Discord);
678 match discord_trigger.config {
679 TriggerTypeConfig::Discord { discord_url: u, .. } => {
680 assert_eq!(u, url);
681 }
682 _ => panic!("Expected discord config"),
683 }
684
685 let slack_trigger = TriggerBuilder::new()
687 .name("slack_alert")
688 .slack("dummy_url")
689 .url(url.clone())
690 .build();
691
692 assert_eq!(slack_trigger.trigger_type, TriggerType::Slack);
693 match slack_trigger.config {
694 TriggerTypeConfig::Slack { slack_url: u, .. } => {
695 assert_eq!(u, url);
696 }
697 _ => panic!("Expected slack config"),
698 }
699 }
700}