krabka_client_producer/
record.rs1use std::{
6 future::Future,
7 pin::Pin,
8 task::{Context, Poll},
9};
10
11use bytes::Bytes;
12use tokio::sync::oneshot;
13
14use crate::error::ProducerError;
15
16#[derive(Debug, Clone, Default, PartialEq, Eq)]
17pub struct ProducerRecord {
18 pub topic: String,
19 pub partition: Option<i32>,
23 pub key: Option<Bytes>,
24 pub value: Option<Bytes>,
25 pub headers: Vec<Header>,
26 pub timestamp_ms: Option<i64>,
30}
31
32impl ProducerRecord {
33 pub(crate) fn validate(&self) -> Result<(), ProducerError> {
41 if let Some(timestamp) = self.timestamp_ms.filter(|timestamp| *timestamp < 0) {
42 return Err(ProducerError::InvalidTimestamp(timestamp));
43 }
44 if let Some(partition) = self.partition.filter(|partition| *partition < 0) {
45 return Err(ProducerError::InvalidPartition(partition));
46 }
47 Ok(())
48 }
49}
50
51#[derive(Debug, Clone, PartialEq, Eq)]
52pub struct Header {
53 pub key: String,
54 pub value: Option<Bytes>,
55}
56
57#[derive(Debug, Clone, PartialEq, Eq)]
61pub struct RecordMetadata {
62 pub topic: String,
64 pub partition: i32,
65 pub offset: i64,
70 pub timestamp_ms: i64,
74 pub serialized_key_size: i32,
76 pub serialized_value_size: i32,
78}
79
80#[derive(Debug)]
85#[must_use = "await the handle to observe delivery errors"]
86pub struct DeliveryHandle {
87 receiver: oneshot::Receiver<Result<RecordMetadata, ProducerError>>,
88}
89
90impl DeliveryHandle {
91 pub(crate) fn new(receiver: oneshot::Receiver<Result<RecordMetadata, ProducerError>>) -> Self {
92 Self { receiver }
93 }
94}
95
96impl Future for DeliveryHandle {
97 type Output = Result<RecordMetadata, ProducerError>;
98
99 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
100 Pin::new(&mut self.receiver)
101 .poll(cx)
102 .map(|result| result.unwrap_or(Err(ProducerError::Closed)))
103 }
104}
105
106#[cfg(test)]
107mod tests {
108
109 use super::*;
110
111 #[test]
112 fn producer_record_default_is_empty() {
113 let r = ProducerRecord::default();
114 assert2::assert!(
115 r == ProducerRecord {
116 topic: String::new(),
117 partition: None,
118 key: None,
119 value: None,
120 headers: vec![],
121 timestamp_ms: None,
122 }
123 );
124 }
125
126 #[tokio::test]
127 async fn delivery_handle_reports_metadata_errors_and_closed_sender() {
128 let metadata = RecordMetadata {
129 topic: "events".into(),
130 partition: 2,
131 offset: 42,
132 timestamp_ms: 7,
133 serialized_key_size: -1,
134 serialized_value_size: 5,
135 };
136 let (sender, receiver) = oneshot::channel();
137 let mut handle = DeliveryHandle::new(receiver);
138 assert2::assert!(futures::FutureExt::now_or_never(&mut handle).is_none());
139 sender.send(Ok(metadata.clone())).expect("handle is open");
140 assert2::assert!(handle.await.expect("delivery succeeds") == metadata);
141
142 for (result, expected) in [
143 (Some(Err(ProducerError::Server(42))), "broker error_code 42"),
144 (None, "producer closed"),
145 ] {
146 let (sender, receiver) = oneshot::channel();
147 let handle = DeliveryHandle::new(receiver);
148 if let Some(result) = result {
149 sender.send(result).expect("handle is open");
150 } else {
151 drop(sender);
152 }
153 assert2::assert!(handle.await.expect_err("delivery fails").to_string() == expected);
154 }
155 }
156}