Skip to content

Commit 4205ca4

Browse files
committed
Add Kafka partial batch failure response types
1 parent 8c02edc commit 4205ca4

1 file changed

Lines changed: 134 additions & 0 deletions

File tree

  • lambda-events/src/event/kafka

lambda-events/src/event/kafka/mod.rs

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,90 @@ pub struct KafkaRecord {
5555
pub other: serde_json::Map<String, Value>,
5656
}
5757

58+
/// `KafkaEventResponse` is the outer structure to report batch item failures for `KafkaEvent`.
59+
#[non_exhaustive]
60+
#[cfg_attr(feature = "builders", derive(Builder))]
61+
#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)]
62+
#[serde(rename_all = "camelCase")]
63+
pub struct KafkaEventResponse {
64+
pub batch_item_failures: Vec<KafkaBatchItemFailure>,
65+
/// Catchall to catch any additional fields that were present but not explicitly defined by this struct.
66+
/// Enabled with Cargo feature `catch-all-fields`.
67+
/// If `catch-all-fields` is disabled, any additional fields that are present will be ignored.
68+
#[cfg(feature = "catch-all-fields")]
69+
#[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))]
70+
#[serde(flatten)]
71+
#[cfg_attr(feature = "builders", builder(default))]
72+
pub other: serde_json::Map<String, Value>,
73+
}
74+
75+
impl KafkaEventResponse {
76+
/// Add a failed Kafka item identifier to the batch response.
77+
///
78+
/// Lambda retries the identified records when `ReportBatchItemFailures` is enabled on the
79+
/// Kafka event source mapping. Returning an error from the handler still retries the whole
80+
/// batch.
81+
pub fn add_failure(&mut self, item_identifier: KafkaItemIdentifier) {
82+
self.batch_item_failures.push(KafkaBatchItemFailure {
83+
item_identifier,
84+
..Default::default()
85+
});
86+
}
87+
88+
/// Set all failed Kafka item identifiers in the batch response.
89+
///
90+
/// This replaces any previously registered failures.
91+
pub fn set_failures<I>(&mut self, item_identifiers: I)
92+
where
93+
I: IntoIterator<Item = KafkaItemIdentifier>,
94+
{
95+
self.batch_item_failures = item_identifiers
96+
.into_iter()
97+
.map(|item_identifier| KafkaBatchItemFailure {
98+
item_identifier,
99+
..Default::default()
100+
})
101+
.collect();
102+
}
103+
}
104+
105+
/// `KafkaBatchItemFailure` is an individual Kafka record which failed processing.
106+
#[non_exhaustive]
107+
#[cfg_attr(feature = "builders", derive(Builder))]
108+
#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)]
109+
#[serde(rename_all = "camelCase")]
110+
pub struct KafkaBatchItemFailure {
111+
pub item_identifier: KafkaItemIdentifier,
112+
/// Catchall to catch any additional fields that were present but not explicitly defined by this struct.
113+
/// Enabled with Cargo feature `catch-all-fields`.
114+
/// If `catch-all-fields` is disabled, any additional fields that are present will be ignored.
115+
#[cfg(feature = "catch-all-fields")]
116+
#[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))]
117+
#[serde(flatten)]
118+
#[cfg_attr(feature = "builders", builder(default))]
119+
pub other: serde_json::Map<String, Value>,
120+
}
121+
122+
/// `KafkaItemIdentifier` identifies a Kafka record for a partial batch response.
123+
#[non_exhaustive]
124+
#[cfg_attr(feature = "builders", derive(Builder))]
125+
#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)]
126+
#[serde(rename_all = "camelCase")]
127+
pub struct KafkaItemIdentifier {
128+
/// The topic-partition key from the Kafka event's `records` map.
129+
pub partition: String,
130+
/// The Kafka record offset.
131+
pub offset: i64,
132+
/// Catchall to catch any additional fields that were present but not explicitly defined by this struct.
133+
/// Enabled with Cargo feature `catch-all-fields`.
134+
/// If `catch-all-fields` is disabled, any additional fields that are present will be ignored.
135+
#[cfg(feature = "catch-all-fields")]
136+
#[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))]
137+
#[serde(flatten)]
138+
#[cfg_attr(feature = "builders", builder(default))]
139+
pub other: serde_json::Map<String, Value>,
140+
}
141+
58142
#[cfg(test)]
59143
mod test {
60144
use super::*;
@@ -68,4 +152,54 @@ mod test {
68152
let reparsed: KafkaEvent = serde_json::from_slice(output.as_bytes()).unwrap();
69153
assert_eq!(parsed, reparsed);
70154
}
155+
156+
#[test]
157+
#[cfg(feature = "kafka")]
158+
fn kafka_event_response_serializes_item_identifiers() {
159+
let mut response = KafkaEventResponse::default();
160+
response.add_failure(KafkaItemIdentifier {
161+
partition: String::from("some.topic-3"),
162+
offset: 42,
163+
..Default::default()
164+
});
165+
166+
let serialized = serde_json::to_value(response).unwrap();
167+
168+
assert_eq!(
169+
serialized,
170+
serde_json::json!({
171+
"batchItemFailures": [{
172+
"itemIdentifier": {
173+
"partition": "some.topic-3",
174+
"offset": 42,
175+
}
176+
}]
177+
})
178+
);
179+
}
180+
181+
#[test]
182+
#[cfg(feature = "kafka")]
183+
fn kafka_event_response_sets_failures() {
184+
let mut response = KafkaEventResponse::default();
185+
response.set_failures([
186+
KafkaItemIdentifier {
187+
partition: String::from("some.topic-3"),
188+
offset: 42,
189+
..Default::default()
190+
},
191+
KafkaItemIdentifier {
192+
partition: String::from("some.topic-4"),
193+
offset: 43,
194+
..Default::default()
195+
},
196+
]);
197+
198+
assert_eq!(response.batch_item_failures.len(), 2);
199+
assert_eq!(
200+
response.batch_item_failures[0].item_identifier.partition,
201+
"some.topic-3"
202+
);
203+
assert_eq!(response.batch_item_failures[1].item_identifier.offset, 43);
204+
}
71205
}

0 commit comments

Comments
 (0)