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, facet::Facet, Debug)]
100pub struct AlertTargets {
101 pub targets: Vec<ExternalTarget>,
102}
103
104#[derive(serde::Deserialize, serde::Serialize, facet::Facet, Clone, Debug)]
105#[facet(rename_all = "snake_case")]
106#[serde(rename_all = "snake_case")]
107pub struct ExternalTarget {
108 pub id: String,
109 #[serde(flatten)]
110 pub conn: TargetEmail,
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116
117 #[test]
118 fn test_send_target_simple_format() {
119 let yaml = r#"
120id: test-target
121subject: Test Subject
122template: Test template
123"#;
124 let target: SendTarget = serde_yaml::from_str(yaml).unwrap();
125 assert_eq!(target.id(), "test-target");
126 assert_eq!(target.subject(), &Some("Test Subject".to_string()));
127 assert_eq!(target.template(), "Test template");
128 }
129
130 #[test]
131 fn test_send_target_external_format() {
132 let yaml = r#"
133target: external
134id: test-target
135subject: Test Subject
136template: Test template
137"#;
138 let target: SendTarget = serde_yaml::from_str(yaml).unwrap();
139 assert_eq!(target.id(), "test-target");
140 assert_eq!(target.subject(), &Some("Test Subject".to_string()));
141 assert_eq!(target.template(), "Test template");
142 }
143
144 #[test]
145 fn test_send_target_without_subject() {
146 let yaml = r#"
147id: test-target
148template: Test template
149"#;
150 let target: SendTarget = serde_yaml::from_str(yaml).unwrap();
151 assert_eq!(target.id(), "test-target");
152 assert_eq!(target.subject(), &None);
153 assert_eq!(target.template(), "Test template");
154 }
155}