use crate::{
custom_serde::{deserialize_nullish, float_unix_epoch},
streams::DynamoDbBatchItemFailure,
time_window::*,
};
#[cfg(feature = "builders")]
use bon::Builder;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[cfg(feature = "catch-all-fields")]
use serde_json::Value;
use std::fmt;
#[cfg(test)]
mod attributes;
#[non_exhaustive]
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum StreamViewType {
NewImage,
OldImage,
NewAndOldImages,
#[default]
KeysOnly,
}
impl fmt::Display for StreamViewType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let val = match self {
StreamViewType::NewImage => "NEW_IMAGE",
StreamViewType::OldImage => "OLD_IMAGE",
StreamViewType::NewAndOldImages => "NEW_AND_OLD_IMAGES",
StreamViewType::KeysOnly => "KEYS_ONLY",
};
write!(f, "{val}")
}
}
#[non_exhaustive]
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum StreamStatus {
Enabling,
Enabled,
Disabling,
#[default]
Disabled,
}
impl fmt::Display for StreamStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let val = match self {
StreamStatus::Enabling => "ENABLING",
StreamStatus::Enabled => "ENABLED",
StreamStatus::Disabling => "DISABLING",
StreamStatus::Disabled => "DISABLED",
};
write!(f, "{val}")
}
}
#[non_exhaustive]
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum SharedIteratorType {
TrimHorizon,
#[default]
Latest,
AtSequenceNumber,
AfterSequenceNumber,
}
impl fmt::Display for SharedIteratorType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let val = match self {
SharedIteratorType::TrimHorizon => "TRIM_HORIZON",
SharedIteratorType::Latest => "LATEST",
SharedIteratorType::AtSequenceNumber => "AT_SEQUENCE_NUMBER",
SharedIteratorType::AfterSequenceNumber => "AFTER_SEQUENCE_NUMBER",
};
write!(f, "{val}")
}
}
#[non_exhaustive]
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum OperationType {
#[default]
Insert,
Modify,
Remove,
}
impl fmt::Display for OperationType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let val = match self {
OperationType::Insert => "INSERT",
OperationType::Modify => "MODIFY",
OperationType::Remove => "REMOVE",
};
write!(f, "{val}")
}
}
#[non_exhaustive]
#[derive(Clone, Default, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum KeyType {
#[default]
Hash,
Range,
}
impl fmt::Display for KeyType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let val = match self {
KeyType::Hash => "HASH",
KeyType::Range => "RANGE",
};
write!(f, "{val}")
}
}
#[non_exhaustive]
#[cfg_attr(feature = "builders", derive(Builder))]
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
pub struct Event {
#[serde(rename = "Records")]
pub records: Vec<EventRecord>,
#[cfg(feature = "catch-all-fields")]
#[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))]
#[serde(flatten)]
#[cfg_attr(feature = "builders", builder(default))]
pub other: serde_json::Map<String, Value>,
}
#[non_exhaustive]
#[cfg_attr(feature = "builders", derive(Builder))]
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct TimeWindowEvent {
#[serde(rename = "DynamoDBEvent")]
#[serde(flatten)]
pub dynamo_db_event: Event,
#[serde(rename = "TimeWindowProperties")]
#[serde(flatten)]
pub time_window_properties: TimeWindowProperties,
#[cfg(feature = "catch-all-fields")]
#[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))]
#[serde(flatten)]
#[cfg_attr(feature = "builders", builder(default))]
pub other: serde_json::Map<String, Value>,
}
#[non_exhaustive]
#[cfg_attr(feature = "builders", derive(Builder))]
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct TimeWindowEventResponse {
#[serde(rename = "TimeWindowEventResponseProperties")]
#[serde(flatten)]
pub time_window_event_response_properties: TimeWindowEventResponseProperties,
pub batch_item_failures: Vec<DynamoDbBatchItemFailure>,
#[cfg(feature = "catch-all-fields")]
#[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))]
#[serde(flatten)]
#[cfg_attr(feature = "builders", builder(default))]
pub other: serde_json::Map<String, Value>,
}
#[non_exhaustive]
#[cfg_attr(feature = "builders", derive(Builder))]
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct EventRecord {
pub aws_region: String,
#[serde(rename = "dynamodb")]
pub change: StreamRecord,
#[serde(rename = "eventID")]
pub event_id: String,
pub event_name: String,
#[serde(default)]
pub event_source: Option<String>,
#[serde(default)]
pub event_version: Option<String>,
#[serde(rename = "eventSourceARN")]
#[serde(default)]
pub event_source_arn: Option<String>,
#[serde(default)]
pub user_identity: Option<UserIdentity>,
#[serde(default)]
pub record_format: Option<String>,
#[serde(default)]
pub table_name: Option<String>,
#[cfg(feature = "catch-all-fields")]
#[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))]
#[serde(flatten)]
#[cfg_attr(feature = "builders", builder(default))]
pub other: serde_json::Map<String, Value>,
}
#[non_exhaustive]
#[cfg_attr(feature = "builders", derive(Builder))]
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct UserIdentity {
#[serde(default)]
pub type_: String,
#[serde(default)]
pub principal_id: String,
#[cfg(feature = "catch-all-fields")]
#[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))]
#[serde(flatten)]
#[cfg_attr(feature = "builders", builder(default))]
pub other: serde_json::Map<String, Value>,
}
#[non_exhaustive]
#[cfg_attr(feature = "builders", derive(Builder))]
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct StreamRecord {
#[serde(rename = "ApproximateCreationDateTime")]
#[serde(with = "float_unix_epoch")]
#[serde(default)]
pub approximate_creation_date_time: DateTime<Utc>,
#[serde(deserialize_with = "deserialize_nullish")]
#[serde(default)]
#[serde(rename = "Keys")]
pub keys: serde_dynamo::Item,
#[serde(deserialize_with = "deserialize_nullish")]
#[serde(default)]
#[serde(rename = "NewImage")]
pub new_image: serde_dynamo::Item,
#[serde(deserialize_with = "deserialize_nullish")]
#[serde(default)]
#[serde(rename = "OldImage")]
pub old_image: serde_dynamo::Item,
#[serde(default)]
#[serde(rename = "SequenceNumber")]
pub sequence_number: Option<String>,
#[serde(rename = "SizeBytes")]
pub size_bytes: i64,
#[serde(default)]
#[serde(rename = "StreamViewType")]
pub stream_view_type: Option<StreamViewType>,
#[cfg(feature = "catch-all-fields")]
#[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))]
#[serde(flatten)]
#[cfg_attr(feature = "builders", builder(default))]
pub other: serde_json::Map<String, Value>,
}
#[cfg(test)]
#[allow(deprecated)]
mod test {
use super::*;
use crate::fixtures::verify_serde_roundtrip;
use chrono::TimeZone;
#[test]
#[cfg(feature = "dynamodb")]
fn example_dynamodb_event() {
let data = include_bytes!("../../fixtures/example-dynamodb-event.json");
let mut parsed: Event = serde_json::from_slice(data).unwrap();
let output: String = serde_json::to_string(&parsed).unwrap();
let reparsed: Event = serde_json::from_slice(output.as_bytes()).unwrap();
assert_eq!(parsed, reparsed);
let event = parsed.records.pop().unwrap();
let date = Utc.ymd(2016, 12, 2).and_hms(1, 27, 0);
assert_eq!(date, event.change.approximate_creation_date_time);
}
#[test]
#[cfg(feature = "dynamodb")]
fn example_dynamodb_event_null_items() {
let data = include_bytes!("../../fixtures/example-dynamodb-event-null-items.json");
let event = verify_serde_roundtrip::<Event>(data);
assert_eq!(0, event.records[0].change.keys.len());
assert_eq!(0, event.records[0].change.new_image.len());
assert_eq!(0, event.records[0].change.old_image.len());
}
#[test]
#[cfg(feature = "dynamodb")]
fn example_dynamodb_event_with_optional_fields() {
let data = include_bytes!("../../fixtures/example-dynamodb-event-record-with-optional-fields.json");
let parsed: EventRecord = serde_json::from_slice(data).unwrap();
let output: String = serde_json::to_string(&parsed).unwrap();
let reparsed: EventRecord = serde_json::from_slice(output.as_bytes()).unwrap();
assert_eq!(parsed, reparsed);
let date = Utc.timestamp_micros(0).unwrap(); assert_eq!(date, reparsed.change.approximate_creation_date_time);
}
}