1use std::collections::HashMap;
8use std::iter::IntoIterator;
9use std::time::Duration;
10use typed_builder::TypedBuilder;
11
12#[derive(Debug, Clone, PartialEq, Eq, Hash)]
14pub struct TlsSettings {
15 pub cert_path: String,
17
18 pub key_path: String,
20
21 pub ca_path: Option<String>,
23}
24
25#[derive(Debug, Clone, PartialEq, Eq, Hash)]
27pub struct ProxyConfig {
28 pub proxy_url: String,
30
31 pub username: Option<String>,
33
34 pub password: Option<String>,
36}
37
38#[derive(TypedBuilder, Debug, PartialEq, Default)]
39pub struct EnvelopeData {
40 #[builder(default, setter(strip_option))]
42 pub from: Option<String>,
43
44 #[builder(default)]
46 pub rcpt: Vec<String>,
47
48 #[builder(default, setter(strip_option))]
50 pub ip: Option<String>,
51
52 #[builder(default, setter(strip_option))]
54 pub user: Option<String>,
55
56 #[builder(default, setter(strip_option))]
58 pub helo: Option<String>,
59
60 #[builder(default, setter(strip_option))]
62 pub hostname: Option<String>,
63
64 #[builder(default, setter(strip_option))]
68 pub file_path: Option<String>,
69
70 #[builder(default)]
75 pub body_block: bool,
76
77 #[builder(default)]
79 pub additional_headers: HashMap<String, String>,
80}
81
82impl IntoIterator for EnvelopeData {
83 type Item = (String, String);
84 type IntoIter = std::vec::IntoIter<(String, String)>;
85
86 fn into_iter(self) -> Self::IntoIter {
89 let mut headers = Vec::with_capacity(self.rcpt.len() + self.additional_headers.len() + 7);
90 if let Some(from) = self.from {
91 headers.push(("From".to_string(), from));
92 }
93 if let Some(ip) = self.ip {
94 headers.push(("IP".to_string(), ip));
95 }
96 if let Some(user) = self.user {
97 headers.push(("User".to_string(), user));
98 }
99 if let Some(helo) = self.helo {
100 headers.push(("Helo".to_string(), helo));
101 }
102 if let Some(hostname) = self.hostname {
103 headers.push(("Hostname".to_string(), hostname));
104 }
105 if let Some(file_path) = self.file_path {
106 headers.push(("File".to_string(), file_path));
107 }
108 if self.body_block {
109 headers.push(("Flags".to_string(), "body_block".to_string()));
110 }
111 for rcpt in self.rcpt {
112 headers.push(("Rcpt".to_string(), rcpt));
113 }
114 headers.extend(self.additional_headers);
115 headers.into_iter()
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 #[test]
124 fn envelope_emits_one_rcpt_header_per_recipient() {
125 let envelope = EnvelopeData::builder()
126 .from("sender@example.com".to_string())
127 .rcpt(vec![
128 "one@example.com".to_string(),
129 "two@example.com".to_string(),
130 "three@example.com".to_string(),
131 ])
132 .build();
133
134 let headers: Vec<(String, String)> = envelope.into_iter().collect();
135 let rcpts: Vec<&str> = headers
136 .iter()
137 .filter(|(k, _)| k == "Rcpt")
138 .map(|(_, v)| v.as_str())
139 .collect();
140 assert_eq!(
141 rcpts,
142 ["one@example.com", "two@example.com", "three@example.com"]
143 );
144 }
145}
146
147#[derive(TypedBuilder, Debug, Clone, PartialEq, Eq, Hash)]
152pub struct Config {
153 pub base_url: String,
155
156 #[builder(default, setter(strip_option))]
158 pub password: Option<String>,
159
160 #[builder(default = Duration::from_secs(30))]
162 pub timeout: Duration,
163
164 #[builder(default = 1)]
166 pub retries: u32,
167
168 #[builder(default, setter(strip_option))]
170 pub tls_settings: Option<TlsSettings>,
171
172 #[builder(default, setter(strip_option))]
174 pub proxy_config: Option<ProxyConfig>,
175
176 #[builder(default = true)]
178 pub zstd: bool,
179
180 #[builder(default, setter(strip_option))]
182 pub encryption_key: Option<String>,
183}