Skip to main content

kafrust_protocol/
consumer_group.rs

1use crate::codec::{Decoder, Encoder};
2use crate::error::{Error, Result};
3
4#[derive(Debug, Clone, PartialEq, Eq)]
5pub struct ConsumerProtocolSubscriptionV0 {
6    pub topics: Vec<String>,
7    pub user_data: Option<Vec<u8>>,
8}
9
10/// Consumer protocol subscription version 1 with previously owned partitions.
11///
12/// Kafka added `OwnedPartitions` in version 1 so cooperative assignors can
13/// stage ownership transfers across rebalances.
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct ConsumerProtocolSubscriptionV1 {
16    pub topics: Vec<String>,
17    pub user_data: Option<Vec<u8>>,
18    pub owned_partitions: Vec<ConsumerProtocolTopicAssignment>,
19}
20
21impl ConsumerProtocolSubscriptionV1 {
22    pub fn encode(&self) -> Result<Vec<u8>> {
23        let mut encoder = Encoder::new();
24        encoder.write_i16(1);
25        encoder.write_array(Some(self.topics.as_slice()), |encoder, topic| {
26            encoder.write_string(topic)
27        })?;
28        encoder.write_nullable_bytes(self.user_data.as_deref())?;
29        encoder.write_array(
30            Some(self.owned_partitions.as_slice()),
31            |encoder, assignment| assignment.encode(encoder),
32        )?;
33        Ok(encoder.into_bytes())
34    }
35
36    pub fn decode(bytes: &[u8]) -> Result<Self> {
37        let mut decoder = Decoder::new(bytes);
38        expect_version("consumer protocol subscription", decoder.read_i16()?, 1)?;
39        Ok(Self {
40            topics: decoder
41                .read_array(
42                    "consumer protocol subscription topics",
43                    Decoder::read_string,
44                )?
45                .unwrap_or_default(),
46            user_data: decoder.read_nullable_bytes()?,
47            owned_partitions: decoder
48                .read_array(
49                    "consumer protocol subscription owned partitions",
50                    ConsumerProtocolTopicAssignment::decode,
51                )?
52                .unwrap_or_default(),
53        })
54    }
55}
56
57impl ConsumerProtocolSubscriptionV0 {
58    pub fn encode(&self) -> Result<Vec<u8>> {
59        let mut encoder = Encoder::new();
60        encoder.write_i16(0);
61        encoder.write_array(Some(self.topics.as_slice()), |encoder, topic| {
62            encoder.write_string(topic)
63        })?;
64        encoder.write_nullable_bytes(self.user_data.as_deref())?;
65        Ok(encoder.into_bytes())
66    }
67
68    pub fn decode(bytes: &[u8]) -> Result<Self> {
69        let mut decoder = Decoder::new(bytes);
70        expect_version_v0("consumer protocol subscription", decoder.read_i16()?)?;
71        let subscription = Self {
72            topics: decoder
73                .read_array(
74                    "consumer protocol subscription topics",
75                    Decoder::read_string,
76                )?
77                .unwrap_or_default(),
78            user_data: decoder.read_nullable_bytes()?,
79        };
80        Ok(subscription)
81    }
82}
83
84#[derive(Debug, Clone, PartialEq, Eq)]
85pub struct ConsumerProtocolAssignmentV0 {
86    pub assignments: Vec<ConsumerProtocolTopicAssignment>,
87    pub user_data: Option<Vec<u8>>,
88}
89
90impl ConsumerProtocolAssignmentV0 {
91    pub fn encode(&self) -> Result<Vec<u8>> {
92        let mut encoder = Encoder::new();
93        encoder.write_i16(0);
94        encoder.write_array(Some(self.assignments.as_slice()), |encoder, assignment| {
95            assignment.encode(encoder)
96        })?;
97        encoder.write_nullable_bytes(self.user_data.as_deref())?;
98        Ok(encoder.into_bytes())
99    }
100
101    pub fn decode(bytes: &[u8]) -> Result<Self> {
102        let mut decoder = Decoder::new(bytes);
103        expect_version_v0("consumer protocol assignment", decoder.read_i16()?)?;
104        let assignment = Self {
105            assignments: decoder
106                .read_array(
107                    "consumer protocol assignment topics",
108                    ConsumerProtocolTopicAssignment::decode,
109                )?
110                .unwrap_or_default(),
111            user_data: decoder.read_nullable_bytes()?,
112        };
113        Ok(assignment)
114    }
115}
116
117fn expect_version_v0(kind: &'static str, version: i16) -> Result<()> {
118    expect_version(kind, version, 0)
119}
120
121fn expect_version(kind: &'static str, version: i16, expected: i16) -> Result<()> {
122    if version == expected {
123        return Ok(());
124    }
125
126    Err(Error::UnsupportedVersion { kind, version })
127}
128
129#[derive(Debug, Clone, PartialEq, Eq)]
130pub struct ConsumerProtocolTopicAssignment {
131    pub topic: String,
132    pub partitions: Vec<i32>,
133}
134
135impl ConsumerProtocolTopicAssignment {
136    fn encode(&self, encoder: &mut Encoder) -> Result<()> {
137        encoder.write_string(&self.topic)?;
138        encoder.write_array(Some(self.partitions.as_slice()), |encoder, partition| {
139            encoder.write_i32(*partition);
140            Ok(())
141        })
142    }
143
144    fn decode(decoder: &mut Decoder<'_>) -> Result<Self> {
145        Ok(Self {
146            topic: decoder.read_string()?,
147            partitions: decoder
148                .read_array("consumer protocol assignment partitions", |decoder| {
149                    decoder.read_i32()
150                })?
151                .unwrap_or_default(),
152        })
153    }
154}
155
156#[cfg(test)]
157#[allow(clippy::unwrap_used)]
158mod tests {
159    use super::{
160        ConsumerProtocolAssignmentV0, ConsumerProtocolSubscriptionV0,
161        ConsumerProtocolSubscriptionV1, ConsumerProtocolTopicAssignment,
162    };
163
164    #[test]
165    fn encodes_consumer_protocol_subscription_v0() {
166        let subscription = ConsumerProtocolSubscriptionV0 {
167            topics: vec!["orders".to_owned(), "payments".to_owned()],
168            user_data: None,
169        };
170
171        assert_eq!(
172            subscription.encode().unwrap(),
173            [
174                0, 0, // version
175                0, 0, 0, 2, // topic count
176                0, 6, b'o', b'r', b'd', b'e', b'r', b's', // orders
177                0, 8, b'p', b'a', b'y', b'm', b'e', b'n', b't', b's', // payments
178                0xff, 0xff, 0xff, 0xff, // null user data
179            ]
180        );
181    }
182
183    #[test]
184    fn decodes_consumer_protocol_subscription_v0() {
185        let encoded = ConsumerProtocolSubscriptionV0 {
186            topics: vec!["orders".to_owned()],
187            user_data: Some(vec![1, 2, 3]),
188        }
189        .encode()
190        .unwrap();
191
192        let decoded = ConsumerProtocolSubscriptionV0::decode(&encoded).unwrap();
193
194        assert_eq!(
195            decoded,
196            ConsumerProtocolSubscriptionV0 {
197                topics: vec!["orders".to_owned()],
198                user_data: Some(vec![1, 2, 3]),
199            }
200        );
201    }
202
203    #[test]
204    fn rejects_unsupported_subscription_version() {
205        let error = ConsumerProtocolSubscriptionV0::decode(&[0, 1]).unwrap_err();
206
207        assert_eq!(
208            error,
209            crate::Error::UnsupportedVersion {
210                kind: "consumer protocol subscription",
211                version: 1,
212            }
213        );
214    }
215
216    #[test]
217    fn encodes_and_decodes_consumer_protocol_subscription_v1_with_owned_partitions() {
218        let subscription = ConsumerProtocolSubscriptionV1 {
219            topics: vec!["orders".to_owned()],
220            user_data: Some(vec![9]),
221            owned_partitions: vec![ConsumerProtocolTopicAssignment {
222                topic: "orders".to_owned(),
223                partitions: vec![1, 3],
224            }],
225        };
226
227        let encoded = subscription.encode().unwrap();
228        let decoded = ConsumerProtocolSubscriptionV1::decode(&encoded).unwrap();
229
230        assert_eq!(decoded, subscription);
231    }
232
233    #[test]
234    fn encodes_consumer_protocol_assignment_v0() {
235        let assignment = ConsumerProtocolAssignmentV0 {
236            assignments: vec![ConsumerProtocolTopicAssignment {
237                topic: "orders".to_owned(),
238                partitions: vec![0, 2],
239            }],
240            user_data: Some(vec![9]),
241        };
242
243        assert_eq!(
244            assignment.encode().unwrap(),
245            [
246                0, 0, // version
247                0, 0, 0, 1, // topic count
248                0, 6, b'o', b'r', b'd', b'e', b'r', b's', // topic
249                0, 0, 0, 2, // partition count
250                0, 0, 0, 0, // partition 0
251                0, 0, 0, 2, // partition 2
252                0, 0, 0, 1, 9, // user data
253            ]
254        );
255    }
256
257    #[test]
258    fn decodes_consumer_protocol_assignment_v0() {
259        let encoded = ConsumerProtocolAssignmentV0 {
260            assignments: vec![ConsumerProtocolTopicAssignment {
261                topic: "orders".to_owned(),
262                partitions: vec![0, 2],
263            }],
264            user_data: None,
265        }
266        .encode()
267        .unwrap();
268
269        let decoded = ConsumerProtocolAssignmentV0::decode(&encoded).unwrap();
270
271        assert_eq!(
272            decoded,
273            ConsumerProtocolAssignmentV0 {
274                assignments: vec![ConsumerProtocolTopicAssignment {
275                    topic: "orders".to_owned(),
276                    partitions: vec![0, 2],
277                }],
278                user_data: None,
279            }
280        );
281    }
282}