1use chrono::{DateTime, Utc};
2use serde::{de::DeserializeOwned, Deserialize, Serialize};
3use std::collections::HashMap;
4
5use crate::custom_serde::{deserialize_lambda_map, deserialize_nullish_boolean};
6
7#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
11#[serde(rename_all = "PascalCase")]
12pub struct SnsEvent {
13 pub records: Vec<SnsRecord>,
14}
15
16#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
18#[serde(rename_all = "PascalCase")]
19pub struct SnsRecord {
20 pub event_source: String,
22
23 pub event_version: String,
25
26 pub event_subscription_arn: String,
28
29 pub sns: SnsMessage,
31}
32
33#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
35#[serde(rename_all = "PascalCase")]
36pub struct SnsMessage {
37 #[serde(rename = "Type")]
39 pub sns_message_type: String,
40
41 pub message_id: String,
43
44 pub topic_arn: String,
46
47 #[serde(default)]
53 pub subject: Option<String>,
54
55 pub timestamp: DateTime<Utc>,
57
58 pub signature_version: String,
60
61 pub signature: String,
63
64 #[serde(alias = "SigningCertURL")]
66 pub signing_cert_url: String,
67
68 #[serde(alias = "UnsubscribeURL")]
70 pub unsubscribe_url: String,
71
72 pub message: String,
74
75 #[serde(deserialize_with = "deserialize_lambda_map")]
77 #[serde(default)]
78 pub message_attributes: HashMap<String, MessageAttribute>,
79}
80
81#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
85#[serde(rename_all = "PascalCase")]
86#[serde(bound(deserialize = "T: DeserializeOwned"))]
87pub struct SnsEventObj<T: Serialize> {
88 pub records: Vec<SnsRecordObj<T>>,
89}
90
91#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
93#[serde(rename_all = "PascalCase")]
94#[serde(bound(deserialize = "T: DeserializeOwned"))]
95pub struct SnsRecordObj<T: Serialize> {
96 pub event_source: String,
98
99 pub event_version: String,
101
102 pub event_subscription_arn: String,
104
105 pub sns: SnsMessageObj<T>,
107}
108
109#[serde_with::serde_as]
111#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
112#[serde(rename_all = "PascalCase")]
113#[serde(bound(deserialize = "T: DeserializeOwned"))]
114pub struct SnsMessageObj<T: Serialize> {
115 #[serde(rename = "Type")]
117 pub sns_message_type: String,
118
119 pub message_id: String,
121
122 pub topic_arn: String,
124
125 #[serde(default)]
131 pub subject: Option<String>,
132
133 pub timestamp: DateTime<Utc>,
135
136 pub signature_version: String,
138
139 pub signature: String,
141
142 #[serde(alias = "SigningCertURL")]
144 pub signing_cert_url: String,
145
146 #[serde(alias = "UnsubscribeURL")]
148 pub unsubscribe_url: String,
149
150 #[serde_as(as = "serde_with::json::JsonString")]
152 #[serde(bound(deserialize = "T: DeserializeOwned"))]
153 pub message: T,
154
155 #[serde(deserialize_with = "deserialize_lambda_map")]
157 #[serde(default)]
158 pub message_attributes: HashMap<String, MessageAttribute>,
159}
160
161#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
167pub struct MessageAttribute {
168 #[serde(rename = "Type")]
170 pub data_type: String,
171
172 #[serde(rename = "Value")]
174 pub value: String,
175}
176
177#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
178#[serde(rename_all = "PascalCase")]
179pub struct CloudWatchAlarmPayload {
180 pub alarm_name: String,
181 pub alarm_description: String,
182 #[serde(rename = "AWSAccountId")]
183 pub aws_account_id: String,
184 pub new_state_value: String,
185 pub new_state_reason: String,
186 pub state_change_time: String,
187 pub region: String,
188 pub alarm_arn: String,
189 pub old_state_value: String,
190 pub trigger: CloudWatchAlarmTrigger,
191}
192
193#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
194#[serde(rename_all = "PascalCase")]
195pub struct CloudWatchAlarmTrigger {
196 pub period: i64,
197 pub evaluation_periods: i64,
198 pub comparison_operator: String,
199 pub threshold: f64,
200 pub treat_missing_data: String,
201 pub evaluate_low_sample_count_percentile: String,
202 #[serde(default)]
203 pub metrics: Vec<CloudWatchMetricDataQuery>,
204 pub metric_name: Option<String>,
205 pub namespace: Option<String>,
206 pub statistic_type: Option<String>,
207 pub statistic: Option<String>,
208 pub unit: Option<String>,
209 #[serde(default)]
210 pub dimensions: Vec<CloudWatchDimension>,
211}
212
213#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
214#[serde(rename_all = "PascalCase")]
215pub struct CloudWatchMetricDataQuery {
216 pub id: String,
217 pub expression: Option<String>,
218 pub label: Option<String>,
219 pub metric_stat: Option<CloudWatchMetricStat>,
220 pub period: Option<i64>,
221 #[serde(default, deserialize_with = "deserialize_nullish_boolean")]
222 pub return_data: bool,
223}
224
225#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
226#[serde(rename_all = "PascalCase")]
227pub struct CloudWatchMetricStat {
228 pub metric: CloudWatchMetric,
229 pub period: i64,
230 pub stat: String,
231 pub unit: Option<String>,
232}
233
234#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
235#[serde(rename_all = "PascalCase")]
236pub struct CloudWatchMetric {
237 #[serde(default)]
238 pub dimensions: Vec<CloudWatchDimension>,
239 pub metric_name: Option<String>,
240 pub namespace: Option<String>,
241}
242
243#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
244pub struct CloudWatchDimension {
245 pub name: String,
246 pub value: String,
247}
248
249#[cfg(test)]
250mod test {
251 use super::*;
252
253 #[test]
254 #[cfg(feature = "sns")]
255 fn my_example_sns_event() {
256 let data = include_bytes!("../../fixtures/example-sns-event.json");
257 let parsed: SnsEvent = serde_json::from_slice(data).unwrap();
258 let output: String = serde_json::to_string(&parsed).unwrap();
259 let reparsed: SnsEvent = serde_json::from_slice(output.as_bytes()).unwrap();
260 assert_eq!(parsed, reparsed);
261 }
262
263 #[test]
264 #[cfg(feature = "sns")]
265 fn my_example_sns_event_pascal_case() {
266 let data = include_bytes!("../../fixtures/example-sns-event-pascal-case.json");
267 let parsed: SnsEvent = serde_json::from_slice(data).unwrap();
268 let output: String = serde_json::to_string(&parsed).unwrap();
269 let reparsed: SnsEvent = serde_json::from_slice(output.as_bytes()).unwrap();
270 assert_eq!(parsed, reparsed);
271 }
272
273 #[test]
274 #[cfg(feature = "sns")]
275 fn my_example_sns_event_cloudwatch_single_metric() {
276 let data = include_bytes!("../../fixtures/example-cloudwatch-alarm-sns-payload-single-metric.json");
277 let parsed: SnsEvent = serde_json::from_slice(data).unwrap();
278 assert_eq!(1, parsed.records.len());
279
280 let output: String = serde_json::to_string(&parsed).unwrap();
281 let reparsed: SnsEvent = serde_json::from_slice(output.as_bytes()).unwrap();
282 assert_eq!(parsed, reparsed);
283
284 let parsed: SnsEventObj<CloudWatchAlarmPayload> =
285 serde_json::from_slice(data).expect("failed to parse CloudWatch Alarm payload");
286
287 let record = parsed.records.first().unwrap();
288 assert_eq!("EXAMPLE", record.sns.message.alarm_name);
289 }
290
291 #[test]
292 #[cfg(feature = "sns")]
293 fn my_example_sns_event_cloudwatch_multiple_metrics() {
294 let data = include_bytes!("../../fixtures/example-cloudwatch-alarm-sns-payload-multiple-metrics.json");
295 let parsed: SnsEvent = serde_json::from_slice(data).unwrap();
296 assert_eq!(2, parsed.records.len());
297
298 let output: String = serde_json::to_string(&parsed).unwrap();
299 let reparsed: SnsEvent = serde_json::from_slice(output.as_bytes()).unwrap();
300 assert_eq!(parsed, reparsed);
301 }
302
303 #[test]
304 #[cfg(feature = "sns")]
305 fn my_example_sns_obj_event() {
306 let data = include_bytes!("../../fixtures/example-sns-event-obj.json");
307
308 #[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
309 struct CustStruct {
310 foo: String,
311 bar: i32,
312 }
313
314 let parsed: SnsEventObj<CustStruct> = serde_json::from_slice(data).unwrap();
315 println!("{:?}", parsed);
316
317 assert_eq!(parsed.records[0].sns.message.foo, "Hello world!");
318 assert_eq!(parsed.records[0].sns.message.bar, 123);
319
320 let output: String = serde_json::to_string(&parsed).unwrap();
321 let reparsed: SnsEventObj<CustStruct> = serde_json::from_slice(output.as_bytes()).unwrap();
322 assert_eq!(parsed, reparsed);
323 }
324}