1use chrono::{DateTime, Utc};
2use serde::{de::DeserializeOwned, Deserialize, Serialize};
3#[cfg(feature = "catch-all-fields")]
4use serde_json::Value;
5use std::collections::HashMap;
6
7use crate::custom_serde::{deserialize_lambda_map, deserialize_nullish_boolean};
8
9#[non_exhaustive]
13#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
14#[serde(rename_all = "PascalCase")]
15pub struct SnsEvent {
16 pub records: Vec<SnsRecord>,
17
18 #[cfg(feature = "catch-all-fields")]
22 #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))]
23 #[serde(flatten)]
24 pub other: serde_json::Map<String, Value>,
25}
26
27#[non_exhaustive]
29#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
30#[serde(rename_all = "PascalCase")]
31pub struct SnsRecord {
32 pub event_source: String,
34
35 pub event_version: String,
37
38 pub event_subscription_arn: String,
40
41 pub sns: SnsMessage,
43
44 #[cfg(feature = "catch-all-fields")]
48 #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))]
49 #[serde(flatten)]
50 pub other: serde_json::Map<String, Value>,
51}
52
53#[non_exhaustive]
55#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
56#[serde(rename_all = "PascalCase")]
57pub struct SnsMessage {
58 #[serde(rename = "Type")]
60 pub sns_message_type: String,
61
62 pub message_id: String,
64
65 pub topic_arn: String,
67
68 #[serde(default)]
74 pub subject: Option<String>,
75
76 pub timestamp: DateTime<Utc>,
78
79 pub signature_version: String,
81
82 pub signature: String,
84
85 #[serde(alias = "SigningCertURL")]
87 pub signing_cert_url: String,
88
89 #[serde(alias = "UnsubscribeURL")]
91 pub unsubscribe_url: String,
92
93 pub message: String,
95
96 #[serde(deserialize_with = "deserialize_lambda_map")]
98 #[serde(default)]
99 pub message_attributes: HashMap<String, MessageAttribute>,
100
101 #[cfg(feature = "catch-all-fields")]
105 #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))]
106 #[serde(flatten)]
107 pub other: serde_json::Map<String, Value>,
108}
109
110#[non_exhaustive]
114#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
115#[serde(rename_all = "PascalCase")]
116#[serde(bound(deserialize = "T: DeserializeOwned"))]
117pub struct SnsEventObj<T: Serialize> {
118 pub records: Vec<SnsRecordObj<T>>,
119
120 #[cfg(feature = "catch-all-fields")]
124 #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))]
125 #[serde(flatten)]
126 pub other: serde_json::Map<String, Value>,
127}
128
129#[non_exhaustive]
131#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
132#[serde(rename_all = "PascalCase")]
133#[serde(bound(deserialize = "T: DeserializeOwned"))]
134pub struct SnsRecordObj<T: Serialize> {
135 pub event_source: String,
137
138 pub event_version: String,
140
141 pub event_subscription_arn: String,
143
144 pub sns: SnsMessageObj<T>,
146
147 #[cfg(feature = "catch-all-fields")]
151 #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))]
152 #[serde(flatten)]
153 pub other: serde_json::Map<String, Value>,
154}
155
156#[non_exhaustive]
158#[serde_with::serde_as]
159#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
160#[serde(rename_all = "PascalCase")]
161#[serde(bound(deserialize = "T: DeserializeOwned"))]
162pub struct SnsMessageObj<T: Serialize> {
163 #[serde(rename = "Type")]
165 pub sns_message_type: String,
166
167 pub message_id: String,
169
170 pub topic_arn: String,
172
173 #[serde(default)]
179 pub subject: Option<String>,
180
181 pub timestamp: DateTime<Utc>,
183
184 pub signature_version: String,
186
187 pub signature: String,
189
190 #[serde(alias = "SigningCertURL")]
192 pub signing_cert_url: String,
193
194 #[serde(alias = "UnsubscribeURL")]
196 pub unsubscribe_url: String,
197
198 #[serde_as(as = "serde_with::json::JsonString")]
200 #[serde(bound(deserialize = "T: DeserializeOwned"))]
201 pub message: T,
202
203 #[serde(deserialize_with = "deserialize_lambda_map")]
205 #[serde(default)]
206 pub message_attributes: HashMap<String, MessageAttribute>,
207
208 #[cfg(feature = "catch-all-fields")]
212 #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))]
213 #[serde(flatten)]
214 pub other: serde_json::Map<String, Value>,
215}
216
217#[non_exhaustive]
223#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
224pub struct MessageAttribute {
225 #[serde(rename = "Type")]
227 pub data_type: String,
228
229 #[serde(rename = "Value")]
231 pub value: String,
232
233 #[cfg(feature = "catch-all-fields")]
237 #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))]
238 #[serde(flatten)]
239 pub other: serde_json::Map<String, Value>,
240}
241
242#[non_exhaustive]
243#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
244#[serde(rename_all = "PascalCase")]
245pub struct CloudWatchAlarmPayload {
246 pub alarm_name: String,
247 pub alarm_description: String,
248 #[serde(rename = "AWSAccountId")]
249 pub aws_account_id: String,
250 pub new_state_value: String,
251 pub new_state_reason: String,
252 pub state_change_time: String,
253 pub region: String,
254 pub alarm_arn: String,
255 pub old_state_value: String,
256 pub trigger: CloudWatchAlarmTrigger,
257 #[cfg(feature = "catch-all-fields")]
261 #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))]
262 #[serde(flatten)]
263 pub other: serde_json::Map<String, Value>,
264}
265
266#[non_exhaustive]
267#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
268#[serde(rename_all = "PascalCase")]
269pub struct CloudWatchAlarmTrigger {
270 pub period: i64,
271 pub evaluation_periods: i64,
272 pub comparison_operator: String,
273 pub threshold: f64,
274 pub treat_missing_data: String,
275 pub evaluate_low_sample_count_percentile: String,
276 #[serde(default)]
277 pub metrics: Vec<CloudWatchMetricDataQuery>,
278 pub metric_name: Option<String>,
279 pub namespace: Option<String>,
280 pub statistic_type: Option<String>,
281 pub statistic: Option<String>,
282 pub unit: Option<String>,
283 #[serde(default)]
284 pub dimensions: Vec<CloudWatchDimension>,
285 #[cfg(feature = "catch-all-fields")]
289 #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))]
290 #[serde(flatten)]
291 pub other: serde_json::Map<String, Value>,
292}
293
294#[non_exhaustive]
295#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
296#[serde(rename_all = "PascalCase")]
297pub struct CloudWatchMetricDataQuery {
298 pub id: String,
299 pub expression: Option<String>,
300 pub label: Option<String>,
301 pub metric_stat: Option<CloudWatchMetricStat>,
302 pub period: Option<i64>,
303 #[serde(default, deserialize_with = "deserialize_nullish_boolean")]
304 pub return_data: bool,
305 #[cfg(feature = "catch-all-fields")]
309 #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))]
310 #[serde(flatten)]
311 pub other: serde_json::Map<String, Value>,
312}
313
314#[non_exhaustive]
315#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
316#[serde(rename_all = "PascalCase")]
317pub struct CloudWatchMetricStat {
318 pub metric: CloudWatchMetric,
319 pub period: i64,
320 pub stat: String,
321 pub unit: Option<String>,
322 #[cfg(feature = "catch-all-fields")]
326 #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))]
327 #[serde(flatten)]
328 pub other: serde_json::Map<String, Value>,
329}
330
331#[non_exhaustive]
332#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
333#[serde(rename_all = "PascalCase")]
334pub struct CloudWatchMetric {
335 #[serde(default)]
336 pub dimensions: Vec<CloudWatchDimension>,
337 pub metric_name: Option<String>,
338 pub namespace: Option<String>,
339 #[cfg(feature = "catch-all-fields")]
343 #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))]
344 #[serde(flatten)]
345 pub other: serde_json::Map<String, Value>,
346}
347
348#[non_exhaustive]
349#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
350pub struct CloudWatchDimension {
351 pub name: String,
352 pub value: String,
353 #[cfg(feature = "catch-all-fields")]
357 #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))]
358 #[serde(flatten)]
359 pub other: serde_json::Map<String, Value>,
360}
361
362#[cfg(test)]
363mod test {
364 use super::*;
365
366 #[test]
367 #[cfg(feature = "sns")]
368 fn my_example_sns_event() {
369 let data = include_bytes!("../../fixtures/example-sns-event.json");
370 let parsed: SnsEvent = serde_json::from_slice(data).unwrap();
371 let output: String = serde_json::to_string(&parsed).unwrap();
372 let reparsed: SnsEvent = serde_json::from_slice(output.as_bytes()).unwrap();
373 assert_eq!(parsed, reparsed);
374 }
375
376 #[test]
377 #[cfg(feature = "sns")]
378 fn my_example_sns_event_pascal_case() {
379 let data = include_bytes!("../../fixtures/example-sns-event-pascal-case.json");
380 let parsed: SnsEvent = serde_json::from_slice(data).unwrap();
381 let output: String = serde_json::to_string(&parsed).unwrap();
382 let reparsed: SnsEvent = serde_json::from_slice(output.as_bytes()).unwrap();
383 assert_eq!(parsed, reparsed);
384 }
385
386 #[test]
387 #[cfg(feature = "sns")]
388 fn my_example_sns_event_cloudwatch_single_metric() {
389 let data = include_bytes!("../../fixtures/example-cloudwatch-alarm-sns-payload-single-metric.json");
390 let parsed: SnsEvent = serde_json::from_slice(data).unwrap();
391 assert_eq!(1, parsed.records.len());
392
393 let output: String = serde_json::to_string(&parsed).unwrap();
394 let reparsed: SnsEvent = serde_json::from_slice(output.as_bytes()).unwrap();
395 assert_eq!(parsed, reparsed);
396
397 let parsed: SnsEventObj<CloudWatchAlarmPayload> =
398 serde_json::from_slice(data).expect("failed to parse CloudWatch Alarm payload");
399
400 let record = parsed.records.first().unwrap();
401 assert_eq!("EXAMPLE", record.sns.message.alarm_name);
402 }
403
404 #[test]
405 #[cfg(feature = "sns")]
406 fn my_example_sns_event_cloudwatch_multiple_metrics() {
407 let data = include_bytes!("../../fixtures/example-cloudwatch-alarm-sns-payload-multiple-metrics.json");
408 let parsed: SnsEvent = serde_json::from_slice(data).unwrap();
409 assert_eq!(2, parsed.records.len());
410
411 let output: String = serde_json::to_string(&parsed).unwrap();
412 let reparsed: SnsEvent = serde_json::from_slice(output.as_bytes()).unwrap();
413 assert_eq!(parsed, reparsed);
414 }
415
416 #[test]
417 #[cfg(feature = "sns")]
418 fn my_example_sns_obj_event() {
419 let data = include_bytes!("../../fixtures/example-sns-event-obj.json");
420
421 #[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
422 struct CustStruct {
423 foo: String,
424 bar: i32,
425 }
426
427 let parsed: SnsEventObj<CustStruct> = serde_json::from_slice(data).unwrap();
428 println!("{parsed:?}");
429
430 assert_eq!(parsed.records[0].sns.message.foo, "Hello world!");
431 assert_eq!(parsed.records[0].sns.message.bar, 123);
432
433 let output: String = serde_json::to_string(&parsed).unwrap();
434 let reparsed: SnsEventObj<CustStruct> = serde_json::from_slice(output.as_bytes()).unwrap();
435 assert_eq!(parsed, reparsed);
436 }
437}