1use crate::{
2 rpc::{typed_data::Data, TypedData},
3 send_grid::{
4 Attachment, Content, EmailAddress, MailSettings, MessageBuilder, Personalization,
5 TrackingSettings, UnsubscribeGroup,
6 },
7 FromVec,
8};
9use serde::{Deserialize, Serialize};
10use serde_json::{to_string, to_value, Value};
11use std::collections::HashMap;
12
13#[derive(Debug, Default, Clone, Serialize, Deserialize)]
48pub struct SendGridMessage {
49 #[serde(skip_serializing_if = "Option::is_none")]
51 pub from: Option<EmailAddress>,
52 #[serde(skip_serializing_if = "Option::is_none")]
54 pub subject: Option<String>,
55 #[serde(skip_serializing_if = "Vec::is_empty")]
57 pub personalizations: Vec<Personalization>,
58 #[serde(rename = "content", skip_serializing_if = "Vec::is_empty")]
60 pub contents: Vec<Content>,
61 #[serde(skip_serializing_if = "Vec::is_empty")]
63 pub attachments: Vec<Attachment>,
64 #[serde(skip_serializing_if = "Option::is_none")]
66 pub template_id: Option<String>,
67 #[serde(skip_serializing_if = "HashMap::is_empty")]
69 pub headers: HashMap<String, String>,
70 #[serde(skip_serializing_if = "HashMap::is_empty")]
72 pub sections: HashMap<String, String>,
73 #[serde(skip_serializing_if = "Vec::is_empty")]
75 pub categories: Vec<String>,
76 #[serde(skip_serializing_if = "HashMap::is_empty")]
78 pub custom_args: HashMap<String, String>,
79 #[serde(skip_serializing_if = "Option::is_none")]
81 pub send_at: Option<i64>,
82 #[serde(rename = "asm", skip_serializing_if = "Option::is_none")]
84 pub unsubscribe_group: Option<UnsubscribeGroup>,
85 #[serde(skip_serializing_if = "Option::is_none")]
87 pub batch_id: Option<String>,
88 #[serde(skip_serializing_if = "Option::is_none")]
90 pub ip_pool_name: Option<String>,
91 #[serde(skip_serializing_if = "Option::is_none")]
93 pub mail_settings: Option<MailSettings>,
94 #[serde(skip_serializing_if = "Option::is_none")]
96 pub tracking_settings: Option<TrackingSettings>,
97 #[serde(skip_serializing_if = "Option::is_none")]
99 pub reply_to: Option<EmailAddress>,
100}
101
102impl SendGridMessage {
103 pub fn build() -> MessageBuilder {
121 MessageBuilder::new()
122 }
123}
124
125#[doc(hidden)]
126impl Into<TypedData> for SendGridMessage {
127 fn into(self) -> TypedData {
128 TypedData {
129 data: Some(Data::Json(
130 to_string(&self).expect("failed to convert SendGrid message to JSON string"),
131 )),
132 }
133 }
134}
135
136#[doc(hidden)]
137impl FromVec<SendGridMessage> for TypedData {
138 fn from_vec(vec: Vec<SendGridMessage>) -> Self {
139 TypedData {
140 data: Some(Data::Json(
141 Value::Array(vec.into_iter().map(|m| to_value(m).unwrap()).collect()).to_string(),
142 )),
143 }
144 }
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150 use crate::send_grid::{
151 BccSettings, BypassListManagement, ClickTracking, FooterSettings, GoogleAnalytics,
152 OpenTracking, SandboxMode, SpamCheck, SubscriptionTracking,
153 };
154 use serde_json::Map;
155
156 #[test]
157 fn it_serializes_to_json() {
158 let mut headers = HashMap::new();
159 headers.insert("foo".to_owned(), "bar".to_owned());
160
161 let mut sections = HashMap::new();
162 sections.insert("foo".to_owned(), "bar".to_owned());
163
164 let mut substitutions = HashMap::new();
165 substitutions.insert("key".to_owned(), "value".to_owned());
166
167 let mut custom_args = HashMap::new();
168 custom_args.insert("baz".to_owned(), "jam".to_owned());
169
170 let mut template_data = Map::<String, Value>::new();
171 template_data.insert("hello".to_owned(), Value::String("world".to_owned()));
172
173 let json = to_string(&create_send_grid_message()).unwrap();
174
175 assert_eq!(json, expected_message_json());
176 }
177
178 #[test]
179 fn it_converts_to_typed_data() {
180 let message = create_send_grid_message();
181
182 let data: TypedData = message.into();
183 assert_eq!(
184 data.data,
185 Some(Data::Json(expected_message_json().to_owned()))
186 );
187 }
188
189 fn create_send_grid_message() -> SendGridMessage {
190 let mut headers = HashMap::new();
191 headers.insert("foo".to_owned(), "bar".to_owned());
192
193 let mut sections = HashMap::new();
194 sections.insert("foo".to_owned(), "bar".to_owned());
195
196 let mut substitutions = HashMap::new();
197 substitutions.insert("key".to_owned(), "value".to_owned());
198
199 let mut custom_args = HashMap::new();
200 custom_args.insert("baz".to_owned(), "jam".to_owned());
201
202 let mut template_data = Map::<String, Value>::new();
203 template_data.insert("hello".to_owned(), Value::String("world".to_owned()));
204
205 SendGridMessage {
206 from: Some(EmailAddress {
207 email: "foo@example.com".to_owned(),
208 name: Some("foo".to_owned()),
209 }),
210 subject: Some("hello world".to_owned()),
211 personalizations: vec![Personalization {
212 to: vec![
213 EmailAddress {
214 email: "foo@example.com".to_owned(),
215 ..Default::default()
216 },
217 EmailAddress {
218 email: "bar@example.com".to_owned(),
219 name: Some("Bar Baz".to_owned()),
220 },
221 ],
222 cc: vec![
223 EmailAddress {
224 email: "baz@example.com".to_owned(),
225 ..Default::default()
226 },
227 EmailAddress {
228 email: "jam@example.com".to_owned(),
229 name: Some("Jam".to_owned()),
230 },
231 ],
232 bcc: vec![
233 EmailAddress {
234 email: "cake@example.com".to_owned(),
235 ..Default::default()
236 },
237 EmailAddress {
238 email: "lie@example.com".to_owned(),
239 name: Some("Lie".to_owned()),
240 },
241 ],
242 subject: Some("hello world".to_owned()),
243 headers: headers.clone(),
244 substitutions,
245 custom_args: custom_args.clone(),
246 send_at: Some(12345),
247 template_data: Some(template_data),
248 }],
249 contents: vec![Content {
250 mime_type: "text/plain".to_owned(),
251 value: "hello world".to_owned(),
252 }],
253 attachments: vec![Attachment {
254 content: "aGVsbG8gd29ybGQ=".to_owned(),
255 mime_type: "text/plain".to_owned(),
256 filename: "foo.txt".to_owned(),
257 disposition: None,
258 content_id: None,
259 }],
260 template_id: Some("template".to_owned()),
261 headers,
262 sections,
263 categories: vec!["first".to_owned(), "second".to_owned()],
264 custom_args,
265 send_at: Some(12345),
266 unsubscribe_group: Some(UnsubscribeGroup {
267 group_id: 12345,
268 groups_to_display: Vec::new(),
269 }),
270 batch_id: Some("batch".to_owned()),
271 ip_pool_name: Some("pool".to_owned()),
272 mail_settings: Some(MailSettings {
273 bcc: Some(BccSettings {
274 enable: true,
275 email: "foo@example.com".to_owned(),
276 }),
277 bypass_list_management: Some(BypassListManagement { enable: true }),
278 footer: Some(FooterSettings {
279 enable: true,
280 text: "hello".to_owned(),
281 html: "world".to_owned(),
282 }),
283 sandbox_mode: Some(SandboxMode { enable: true }),
284 spam_check: Some(SpamCheck {
285 enable: true,
286 threshold: 7,
287 post_to_url: "https://example.com".to_owned(),
288 }),
289 }),
290 tracking_settings: Some(TrackingSettings {
291 click_tracking: Some(ClickTracking {
292 enable: true,
293 enable_text: false,
294 }),
295 open_tracking: Some(OpenTracking {
296 enable: true,
297 substitution_tag: Some("foo".to_owned()),
298 }),
299 subscription_tracking: Some(SubscriptionTracking {
300 enable: true,
301 text: "foo".to_owned(),
302 html: "bar".to_owned(),
303 substitution_tag: Some("baz".to_owned()),
304 }),
305 google_analytics: Some(GoogleAnalytics {
306 enable: true,
307 source: "foo".to_owned(),
308 medium: "bar".to_owned(),
309 term: "baz".to_owned(),
310 content: "jam".to_owned(),
311 campaign: "cake".to_owned(),
312 }),
313 }),
314 reply_to: Some(EmailAddress {
315 email: "bar@example.com".to_owned(),
316 name: Some("bar".to_owned()),
317 }),
318 }
319 }
320
321 fn expected_message_json() -> &'static str {
322 r#"{"from":{"email":"foo@example.com","name":"foo"},"subject":"hello world","personalizations":[{"to":[{"email":"foo@example.com"},{"email":"bar@example.com","name":"Bar Baz"}],"cc":[{"email":"baz@example.com"},{"email":"jam@example.com","name":"Jam"}],"bcc":[{"email":"cake@example.com"},{"email":"lie@example.com","name":"Lie"}],"subject":"hello world","headers":{"foo":"bar"},"substitutions":{"key":"value"},"custom_args":{"baz":"jam"},"send_at":12345,"dynamic_template_data":{"hello":"world"}}],"content":[{"type":"text/plain","value":"hello world"}],"attachments":[{"content":"aGVsbG8gd29ybGQ=","type":"text/plain","filename":"foo.txt"}],"template_id":"template","headers":{"foo":"bar"},"sections":{"foo":"bar"},"categories":["first","second"],"custom_args":{"baz":"jam"},"send_at":12345,"asm":{"group_id":12345},"batch_id":"batch","ip_pool_name":"pool","mail_settings":{"bcc":{"enable":true,"email":"foo@example.com"},"bypass_list_management":{"enable":true},"footer":{"enable":true,"text":"hello","html":"world"},"sandbox_mode":{"enable":true},"spam_check":{"enable":true,"threshold":7,"post_to_url":"https://example.com"}},"tracking_settings":{"click_tracking":{"enable":true,"enable_text":false},"open_tracking":{"enable":true,"substitution_tag":"foo"},"subscription_tracking":{"enable":true,"text":"foo","html":"bar","substitution_tag":"baz"},"ganalytics":{"enable":true,"utm_source":"foo","utm_medium":"bar","utm_term":"baz","utm_content":"jam","utm_campaign":"cake"}},"reply_to":{"email":"bar@example.com","name":"bar"}}"#
323 }
324}