use chrono::{DateTime, Utc};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use crate::custom_serde::{deserialize_lambda_map, deserialize_nullish_boolean};
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "PascalCase")]
pub struct SnsEvent {
pub records: Vec<SnsRecord>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "PascalCase")]
pub struct SnsRecord {
pub event_source: String,
pub event_version: String,
pub event_subscription_arn: String,
pub sns: SnsMessage,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "PascalCase")]
pub struct SnsMessage {
#[serde(rename = "Type")]
pub sns_message_type: String,
pub message_id: String,
pub topic_arn: String,
#[serde(default)]
pub subject: Option<String>,
pub timestamp: DateTime<Utc>,
pub signature_version: String,
pub signature: String,
#[serde(alias = "SigningCertURL")]
pub signing_cert_url: String,
#[serde(alias = "UnsubscribeURL")]
pub unsubscribe_url: String,
pub message: String,
#[serde(deserialize_with = "deserialize_lambda_map")]
#[serde(default)]
pub message_attributes: HashMap<String, MessageAttribute>,
}
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "PascalCase")]
#[serde(bound(deserialize = "T: DeserializeOwned"))]
pub struct SnsEventObj<T: Serialize> {
pub records: Vec<SnsRecordObj<T>>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "PascalCase")]
#[serde(bound(deserialize = "T: DeserializeOwned"))]
pub struct SnsRecordObj<T: Serialize> {
pub event_source: String,
pub event_version: String,
pub event_subscription_arn: String,
pub sns: SnsMessageObj<T>,
}
#[serde_with::serde_as]
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "PascalCase")]
#[serde(bound(deserialize = "T: DeserializeOwned"))]
pub struct SnsMessageObj<T: Serialize> {
#[serde(rename = "Type")]
pub sns_message_type: String,
pub message_id: String,
pub topic_arn: String,
#[serde(default)]
pub subject: Option<String>,
pub timestamp: DateTime<Utc>,
pub signature_version: String,
pub signature: String,
#[serde(alias = "SigningCertURL")]
pub signing_cert_url: String,
#[serde(alias = "UnsubscribeURL")]
pub unsubscribe_url: String,
#[serde_as(as = "serde_with::json::JsonString")]
#[serde(bound(deserialize = "T: DeserializeOwned"))]
pub message: T,
#[serde(deserialize_with = "deserialize_lambda_map")]
#[serde(default)]
pub message_attributes: HashMap<String, MessageAttribute>,
}
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub struct MessageAttribute {
#[serde(rename = "Type")]
pub data_type: String,
#[serde(rename = "Value")]
pub value: String,
}
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "PascalCase")]
pub struct CloudWatchAlarmPayload {
pub alarm_name: String,
pub alarm_description: String,
#[serde(rename = "AWSAccountId")]
pub aws_account_id: String,
pub new_state_value: String,
pub new_state_reason: String,
pub state_change_time: String,
pub region: String,
pub alarm_arn: String,
pub old_state_value: String,
pub trigger: CloudWatchAlarmTrigger,
}
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "PascalCase")]
pub struct CloudWatchAlarmTrigger {
pub period: i64,
pub evaluation_periods: i64,
pub comparison_operator: String,
pub threshold: f64,
pub treat_missing_data: String,
pub evaluate_low_sample_count_percentile: String,
#[serde(default)]
pub metrics: Vec<CloudWatchMetricDataQuery>,
pub metric_name: Option<String>,
pub namespace: Option<String>,
pub statistic_type: Option<String>,
pub statistic: Option<String>,
pub unit: Option<String>,
#[serde(default)]
pub dimensions: Vec<CloudWatchDimension>,
}
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "PascalCase")]
pub struct CloudWatchMetricDataQuery {
pub id: String,
pub expression: Option<String>,
pub label: Option<String>,
pub metric_stat: Option<CloudWatchMetricStat>,
pub period: Option<i64>,
#[serde(default, deserialize_with = "deserialize_nullish_boolean")]
pub return_data: bool,
}
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "PascalCase")]
pub struct CloudWatchMetricStat {
pub metric: CloudWatchMetric,
pub period: i64,
pub stat: String,
pub unit: Option<String>,
}
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "PascalCase")]
pub struct CloudWatchMetric {
#[serde(default)]
pub dimensions: Vec<CloudWatchDimension>,
pub metric_name: Option<String>,
pub namespace: Option<String>,
}
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub struct CloudWatchDimension {
pub name: String,
pub value: String,
}
#[cfg(test)]
mod test {
use super::*;
#[test]
#[cfg(feature = "sns")]
fn my_example_sns_event() {
let data = include_bytes!("../../fixtures/example-sns-event.json");
let parsed: SnsEvent = serde_json::from_slice(data).unwrap();
let output: String = serde_json::to_string(&parsed).unwrap();
let reparsed: SnsEvent = serde_json::from_slice(output.as_bytes()).unwrap();
assert_eq!(parsed, reparsed);
}
#[test]
#[cfg(feature = "sns")]
fn my_example_sns_event_pascal_case() {
let data = include_bytes!("../../fixtures/example-sns-event-pascal-case.json");
let parsed: SnsEvent = serde_json::from_slice(data).unwrap();
let output: String = serde_json::to_string(&parsed).unwrap();
let reparsed: SnsEvent = serde_json::from_slice(output.as_bytes()).unwrap();
assert_eq!(parsed, reparsed);
}
#[test]
#[cfg(feature = "sns")]
fn my_example_sns_event_cloudwatch_single_metric() {
let data = include_bytes!("../../fixtures/example-cloudwatch-alarm-sns-payload-single-metric.json");
let parsed: SnsEvent = serde_json::from_slice(data).unwrap();
assert_eq!(1, parsed.records.len());
let output: String = serde_json::to_string(&parsed).unwrap();
let reparsed: SnsEvent = serde_json::from_slice(output.as_bytes()).unwrap();
assert_eq!(parsed, reparsed);
let parsed: SnsEventObj<CloudWatchAlarmPayload> =
serde_json::from_slice(data).expect("failed to parse CloudWatch Alarm payload");
let record = parsed.records.first().unwrap();
assert_eq!("EXAMPLE", record.sns.message.alarm_name);
}
#[test]
#[cfg(feature = "sns")]
fn my_example_sns_event_cloudwatch_multiple_metrics() {
let data = include_bytes!("../../fixtures/example-cloudwatch-alarm-sns-payload-multiple-metrics.json");
let parsed: SnsEvent = serde_json::from_slice(data).unwrap();
assert_eq!(2, parsed.records.len());
let output: String = serde_json::to_string(&parsed).unwrap();
let reparsed: SnsEvent = serde_json::from_slice(output.as_bytes()).unwrap();
assert_eq!(parsed, reparsed);
}
#[test]
#[cfg(feature = "sns")]
fn my_example_sns_obj_event() {
let data = include_bytes!("../../fixtures/example-sns-event-obj.json");
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
struct CustStruct {
foo: String,
bar: i32,
}
let parsed: SnsEventObj<CustStruct> = serde_json::from_slice(data).unwrap();
println!("{:?}", parsed);
assert_eq!(parsed.records[0].sns.message.foo, "Hello world!");
assert_eq!(parsed.records[0].sns.message.bar, 123);
let output: String = serde_json::to_string(&parsed).unwrap();
let reparsed: SnsEventObj<CustStruct> = serde_json::from_slice(output.as_bytes()).unwrap();
assert_eq!(parsed, reparsed);
}
}