1use crate::codec::{Decoder, Encoder};
2use crate::error::Result;
3use crate::header::RequestHeader;
4
5pub const API_KEY: i16 = 11;
6
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct JoinGroupRequestV2 {
9 pub correlation_id: i32,
10 pub client_id: Option<String>,
11 pub group_id: String,
12 pub session_timeout_ms: i32,
13 pub rebalance_timeout_ms: i32,
14 pub member_id: String,
15 pub protocol_type: String,
16 pub protocols: Vec<JoinGroupProtocol>,
17}
18
19impl JoinGroupRequestV2 {
20 pub fn encode(&self) -> Result<Vec<u8>> {
21 let mut encoder = Encoder::new();
22 RequestHeader {
23 api_key: API_KEY,
24 api_version: 2,
25 correlation_id: self.correlation_id,
26 client_id: self.client_id.clone(),
27 }
28 .encode_v1(&mut encoder)?;
29 encoder.write_string(&self.group_id)?;
30 encoder.write_i32(self.session_timeout_ms);
31 encoder.write_i32(self.rebalance_timeout_ms);
32 encoder.write_string(&self.member_id)?;
33 encoder.write_string(&self.protocol_type)?;
34 encoder.write_array(Some(self.protocols.as_slice()), |encoder, protocol| {
35 protocol.encode(encoder)
36 })?;
37 Ok(encoder.into_bytes())
38 }
39}
40
41#[derive(Debug, Clone, PartialEq, Eq)]
42pub struct JoinGroupRequestV5 {
43 pub correlation_id: i32,
44 pub client_id: Option<String>,
45 pub group_id: String,
46 pub session_timeout_ms: i32,
47 pub rebalance_timeout_ms: i32,
48 pub member_id: String,
49 pub group_instance_id: Option<String>,
50 pub protocol_type: String,
51 pub protocols: Vec<JoinGroupProtocol>,
52}
53
54impl JoinGroupRequestV5 {
55 pub fn encode(&self) -> Result<Vec<u8>> {
56 let mut encoder = Encoder::new();
57 RequestHeader {
58 api_key: API_KEY,
59 api_version: 5,
60 correlation_id: self.correlation_id,
61 client_id: self.client_id.clone(),
62 }
63 .encode_v1(&mut encoder)?;
64 encoder.write_string(&self.group_id)?;
65 encoder.write_i32(self.session_timeout_ms);
66 encoder.write_i32(self.rebalance_timeout_ms);
67 encoder.write_string(&self.member_id)?;
68 encoder.write_nullable_string(self.group_instance_id.as_deref())?;
69 encoder.write_string(&self.protocol_type)?;
70 encoder.write_array(Some(self.protocols.as_slice()), |encoder, protocol| {
71 protocol.encode(encoder)
72 })?;
73 Ok(encoder.into_bytes())
74 }
75}
76
77#[derive(Debug, Clone, PartialEq, Eq)]
78pub struct JoinGroupProtocol {
79 pub name: String,
80 pub metadata: Vec<u8>,
81}
82
83impl JoinGroupProtocol {
84 fn encode(&self, encoder: &mut Encoder) -> Result<()> {
85 encoder.write_string(&self.name)?;
86 encoder.write_bytes(&self.metadata)
87 }
88}
89
90#[derive(Debug, Clone, PartialEq, Eq)]
91pub struct JoinGroupResponseV2 {
92 pub throttle_time_ms: i32,
93 pub error_code: i16,
94 pub generation_id: i32,
95 pub protocol_name: String,
96 pub leader: String,
97 pub member_id: String,
98 pub members: Vec<JoinGroupMember>,
99}
100
101impl JoinGroupResponseV2 {
102 pub fn decode_body(decoder: &mut Decoder<'_>) -> Result<Self> {
103 Ok(Self {
104 throttle_time_ms: decoder.read_i32()?,
105 error_code: decoder.read_i16()?,
106 generation_id: decoder.read_i32()?,
107 protocol_name: decoder.read_string()?,
108 leader: decoder.read_string()?,
109 member_id: decoder.read_string()?,
110 members: decoder
111 .read_array("join group members", JoinGroupMember::decode)?
112 .unwrap_or_default(),
113 })
114 }
115}
116
117#[derive(Debug, Clone, PartialEq, Eq)]
118pub struct JoinGroupResponseV5 {
119 pub throttle_time_ms: i32,
120 pub error_code: i16,
121 pub generation_id: i32,
122 pub protocol_name: String,
123 pub leader: String,
124 pub member_id: String,
125 pub members: Vec<JoinGroupMemberV5>,
126}
127
128impl JoinGroupResponseV5 {
129 pub fn decode_body(decoder: &mut Decoder<'_>) -> Result<Self> {
130 Ok(Self {
131 throttle_time_ms: decoder.read_i32()?,
132 error_code: decoder.read_i16()?,
133 generation_id: decoder.read_i32()?,
134 protocol_name: decoder.read_string()?,
135 leader: decoder.read_string()?,
136 member_id: decoder.read_string()?,
137 members: decoder
138 .read_array("join group members", JoinGroupMemberV5::decode)?
139 .unwrap_or_default(),
140 })
141 }
142}
143
144#[derive(Debug, Clone, PartialEq, Eq)]
145pub struct JoinGroupMember {
146 pub member_id: String,
147 pub metadata: Vec<u8>,
148}
149
150impl JoinGroupMember {
151 fn decode(decoder: &mut Decoder<'_>) -> Result<Self> {
152 Ok(Self {
153 member_id: decoder.read_string()?,
154 metadata: decoder.read_bytes()?,
155 })
156 }
157}
158
159#[derive(Debug, Clone, PartialEq, Eq)]
160pub struct JoinGroupMemberV5 {
161 pub member_id: String,
162 pub group_instance_id: Option<String>,
163 pub metadata: Vec<u8>,
164}
165
166impl JoinGroupMemberV5 {
167 fn decode(decoder: &mut Decoder<'_>) -> Result<Self> {
168 Ok(Self {
169 member_id: decoder.read_string()?,
170 group_instance_id: decoder.read_nullable_string()?,
171 metadata: decoder.read_bytes()?,
172 })
173 }
174}
175
176#[cfg(test)]
177#[allow(clippy::unwrap_used)]
178mod tests {
179 use super::{
180 JoinGroupMember, JoinGroupMemberV5, JoinGroupProtocol, JoinGroupRequestV2,
181 JoinGroupRequestV5, JoinGroupResponseV2, JoinGroupResponseV5,
182 };
183 use crate::codec::{Decoder, Encoder};
184
185 #[test]
186 fn encodes_join_group_v2_request() {
187 let request = JoinGroupRequestV2 {
188 correlation_id: 13,
189 client_id: Some("kafrust".to_owned()),
190 group_id: "orders-group".to_owned(),
191 session_timeout_ms: 10_000,
192 rebalance_timeout_ms: 30_000,
193 member_id: String::new(),
194 protocol_type: "consumer".to_owned(),
195 protocols: vec![JoinGroupProtocol {
196 name: "range".to_owned(),
197 metadata: vec![1, 2, 3],
198 }],
199 };
200
201 assert_eq!(
202 request.encode().unwrap(),
203 [
204 0, 11, 0, 2, 0, 0, 0, 13, 0, 7, b'k', b'a', b'f', b'r', b'u', b's', b't', 0, 12, b'o', b'r', b'd', b'e', b'r', b's', b'-', b'g', b'r', b'o', b'u',
209 b'p', 0, 0, 39, 16, 0, 0, 117, 48, 0, 0, 0, 8, b'c', b'o', b'n', b's', b'u', b'm', b'e', b'r', 0, 0, 0, 1, 0, 5, b'r', b'a', b'n', b'g', b'e', 0, 0, 0, 3, 1, 2, 3, ]
218 );
219 }
220
221 #[test]
222 fn decodes_join_group_v2_response() {
223 let mut bytes = Encoder::new();
224 bytes.write_i32(0);
225 bytes.write_i16(0);
226 bytes.write_i32(7);
227 bytes.write_string("range").unwrap();
228 bytes.write_string("member-a").unwrap();
229 bytes.write_string("member-a").unwrap();
230 bytes.write_i32(1);
231 bytes.write_string("member-a").unwrap();
232 bytes.write_bytes(&[1, 2, 3]).unwrap();
233 let bytes = bytes.into_bytes();
234
235 let mut decoder = Decoder::new(&bytes);
236 let response = JoinGroupResponseV2::decode_body(&mut decoder).unwrap();
237
238 assert_eq!(response.throttle_time_ms, 0);
239 assert_eq!(response.error_code, 0);
240 assert_eq!(response.generation_id, 7);
241 assert_eq!(response.protocol_name, "range");
242 assert_eq!(response.leader, "member-a");
243 assert_eq!(response.member_id, "member-a");
244 assert_eq!(
245 response.members,
246 vec![JoinGroupMember {
247 member_id: "member-a".to_owned(),
248 metadata: vec![1, 2, 3],
249 }]
250 );
251 assert!(decoder.is_empty());
252 }
253
254 #[test]
255 fn encodes_join_group_v5_request_with_static_member() {
256 let request = JoinGroupRequestV5 {
257 correlation_id: 13,
258 client_id: Some("kafrust".to_owned()),
259 group_id: "orders-group".to_owned(),
260 session_timeout_ms: 10_000,
261 rebalance_timeout_ms: 30_000,
262 member_id: "member-a".to_owned(),
263 group_instance_id: Some("orders-reader-1".to_owned()),
264 protocol_type: "consumer".to_owned(),
265 protocols: vec![JoinGroupProtocol {
266 name: "range".to_owned(),
267 metadata: vec![1, 2, 3],
268 }],
269 };
270
271 let encoded = request.encode().unwrap();
272 assert_eq!(&encoded[0..4], &[0, 11, 0, 5]);
273 assert!(encoded
274 .windows(17)
275 .any(|bytes| bytes == b"\0\x0forders-reader-1"));
276 }
277
278 #[test]
279 fn decodes_join_group_v5_response_with_static_member() {
280 let mut bytes = Encoder::new();
281 bytes.write_i32(0);
282 bytes.write_i16(0);
283 bytes.write_i32(7);
284 bytes.write_string("range").unwrap();
285 bytes.write_string("member-a").unwrap();
286 bytes.write_string("member-a").unwrap();
287 bytes.write_i32(1);
288 bytes.write_string("member-a").unwrap();
289 bytes
290 .write_nullable_string(Some("orders-reader-1"))
291 .unwrap();
292 bytes.write_bytes(&[1, 2, 3]).unwrap();
293 let bytes = bytes.into_bytes();
294
295 let mut decoder = Decoder::new(&bytes);
296 let response = JoinGroupResponseV5::decode_body(&mut decoder).unwrap();
297
298 assert_eq!(
299 response.members,
300 vec![JoinGroupMemberV5 {
301 member_id: "member-a".to_owned(),
302 group_instance_id: Some("orders-reader-1".to_owned()),
303 metadata: vec![1, 2, 3],
304 }]
305 );
306 assert!(decoder.is_empty());
307 }
308}