bestool_alertd/
targets.rs1use std::collections::HashMap;
2
3use miette::Result;
4
5use crate::{
6 EmailConfig,
7 alert::AlertDefinition,
8 templates::{load_templates, render_alert},
9};
10
11mod default;
12mod email;
13
14pub use default::determine_default_target;
15pub use email::TargetEmail;
16
17#[derive(serde::Deserialize, Debug, Clone)]
18#[serde(rename_all = "snake_case")]
19#[serde(untagged)]
20pub enum SendTarget {
21 Simple {
23 id: String,
24 subject: Option<String>,
25 template: String,
26 },
27 External {
29 target: String, id: String,
31 subject: Option<String>,
32 template: String,
33 },
34}
35
36impl SendTarget {
37 pub fn id(&self) -> &str {
38 match self {
39 Self::Simple { id, .. } => id,
40 Self::External { id, .. } => id,
41 }
42 }
43
44 pub fn subject(&self) -> &Option<String> {
45 match self {
46 Self::Simple { subject, .. } => subject,
47 Self::External { subject, .. } => subject,
48 }
49 }
50
51 pub fn template(&self) -> &str {
52 match self {
53 Self::Simple { template, .. } => template,
54 Self::External { template, .. } => template,
55 }
56 }
57
58 pub fn resolve_external(
59 &self,
60 external_targets: &HashMap<String, Vec<ExternalTarget>>,
61 ) -> Vec<ResolvedTarget> {
62 external_targets
63 .get(self.id())
64 .map(|exts| {
65 exts.iter()
66 .map(|ext| ResolvedTarget {
67 subject: self.subject().clone(),
68 template: self.template().to_string(),
69 conn: ext.conn.clone(),
70 })
71 .collect()
72 })
73 .unwrap_or_default()
74 }
75}
76
77#[derive(Debug, Clone)]
78pub struct ResolvedTarget {
79 pub subject: Option<String>,
80 pub template: String,
81 pub conn: TargetEmail,
82}
83
84impl ResolvedTarget {
85 pub async fn send(
86 &self,
87 alert: &AlertDefinition,
88 tera_ctx: &mut tera::Context,
89 email: Option<&EmailConfig>,
90 dry_run: bool,
91 ) -> Result<()> {
92 let tera = load_templates(&self.subject, &self.template)?;
93 let (subject, body) = render_alert(&tera, tera_ctx)?;
94
95 self.conn.send(alert, email, &subject, &body, dry_run).await
96 }
97}
98
99#[derive(serde::Deserialize, Debug)]
100pub struct AlertTargets {
101 pub targets: Vec<ExternalTarget>,
102}
103
104#[derive(serde::Deserialize, serde::Serialize, Clone, Debug)]
105#[serde(rename_all = "snake_case")]
106pub struct ExternalTarget {
107 pub id: String,
108 #[serde(flatten)]
109 pub conn: TargetEmail,
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115
116 #[test]
117 fn test_send_target_simple_format() {
118 let yaml = r#"
119id: test-target
120subject: Test Subject
121template: Test template
122"#;
123 let target: SendTarget = serde_yaml::from_str(yaml).unwrap();
124 assert_eq!(target.id(), "test-target");
125 assert_eq!(target.subject(), &Some("Test Subject".to_string()));
126 assert_eq!(target.template(), "Test template");
127 }
128
129 #[test]
130 fn test_send_target_external_format() {
131 let yaml = r#"
132target: external
133id: test-target
134subject: Test Subject
135template: Test template
136"#;
137 let target: SendTarget = serde_yaml::from_str(yaml).unwrap();
138 assert_eq!(target.id(), "test-target");
139 assert_eq!(target.subject(), &Some("Test Subject".to_string()));
140 assert_eq!(target.template(), "Test template");
141 }
142
143 #[test]
144 fn test_send_target_without_subject() {
145 let yaml = r#"
146id: test-target
147template: Test template
148"#;
149 let target: SendTarget = serde_yaml::from_str(yaml).unwrap();
150 assert_eq!(target.id(), "test-target");
151 assert_eq!(target.subject(), &None);
152 assert_eq!(target.template(), "Test template");
153 }
154}