1use cheetah_string::CheetahString;
16use rocketmq_macros::RequestHeaderCodec;
17use serde::Deserialize;
18use serde::Serialize;
19
20use crate::protocol::header::message_operation_header::TopicRequestHeaderTrait;
21use crate::protocol::header::namesrv::topic_operation_header::TopicRequestHeader;
22
23#[derive(Debug, Clone, Deserialize, Serialize, Default, RequestHeaderCodec)]
24#[header(
25 type_id = "rocketmq_protocol::protocol::header::pull_message_request_header::PullMessageRequestHeader",
26 java_class = "org.apache.rocketmq.remoting.protocol.header.PullMessageRequestHeader",
27 fast
28)]
29#[serde(rename_all = "camelCase")]
30pub struct PullMessageRequestHeader {
31 #[header(required)]
32 pub consumer_group: CheetahString,
33
34 #[header(required)]
35 pub topic: CheetahString,
36
37 pub lite_topic: Option<CheetahString>,
38
39 #[header(required)]
40 pub queue_id: i32,
41
42 #[header(required)]
43 pub queue_offset: i64,
44
45 #[header(required)]
46 pub max_msg_nums: i32,
47
48 #[header(required)]
49 pub sys_flag: i32,
50
51 #[header(required)]
52 pub commit_offset: i64,
53
54 #[header(required, range = "i64")]
55 pub suspend_timeout_millis: u64,
56
57 #[header(required, binary_order = 10)]
59 pub sub_version: i64,
60
61 #[header(binary_order = 9)]
62 pub subscription: Option<CheetahString>,
63 pub expression_type: Option<CheetahString>,
64 pub max_msg_bytes: Option<i32>,
65 pub request_source: Option<i32>,
66 #[serde(rename = "proxyFrowardClientId", alias = "proxyForwardClientId")]
67 #[header(
68 key = "proxyFrowardClientId",
69 alias = "proxyForwardClientId",
70 alias_conflict = "prefer_canonical"
71 )]
72 pub proxy_forward_client_id: Option<CheetahString>,
73 #[serde(flatten)]
74 #[header(flatten, presence = "always")]
75 pub topic_request: Option<TopicRequestHeader>,
76}
77
78impl TopicRequestHeaderTrait for PullMessageRequestHeader {
79 fn set_lo(&mut self, lo: Option<bool>) {
80 if let Some(header) = self.topic_request.as_mut() {
81 header.lo = lo;
82 }
83 }
84
85 fn lo(&self) -> Option<bool> {
86 self.topic_request.as_ref().and_then(|h| h.lo)
87 }
88
89 fn set_topic(&mut self, topic: CheetahString) {
90 self.topic = topic;
91 }
92
93 fn topic(&self) -> &CheetahString {
94 &self.topic
95 }
96
97 fn broker_name(&self) -> Option<&CheetahString> {
98 self.topic_request
99 .as_ref()
100 .and_then(|h| h.rpc.as_ref())
101 .and_then(|h| h.broker_name.as_ref())
102 }
103
104 fn set_broker_name(&mut self, broker_name: CheetahString) {
105 if let Some(header) = self.topic_request.as_mut() {
106 if let Some(rpc_header) = header.rpc.as_mut() {
107 rpc_header.broker_name = Some(broker_name);
108 }
109 }
110 }
111
112 fn namespace(&self) -> Option<&str> {
113 self.topic_request
114 .as_ref()
115 .and_then(|h| h.rpc.as_ref())
116 .and_then(|r| r.namespace.as_deref())
117 }
118
119 fn set_namespace(&mut self, namespace: CheetahString) {
120 if let Some(header) = self.topic_request.as_mut() {
121 if let Some(rpc_header) = header.rpc.as_mut() {
122 rpc_header.namespace = Some(namespace);
123 }
124 }
125 }
126
127 fn namespaced(&self) -> Option<bool> {
128 self.topic_request
129 .as_ref()
130 .and_then(|h| h.rpc.as_ref())
131 .and_then(|r| r.namespaced)
132 }
133
134 fn set_namespaced(&mut self, namespaced: bool) {
135 if let Some(header) = self.topic_request.as_mut() {
136 if let Some(rpc_header) = header.rpc.as_mut() {
137 rpc_header.namespaced = Some(namespaced);
138 }
139 }
140 }
141
142 fn oneway(&self) -> Option<bool> {
143 self.topic_request
144 .as_ref()
145 .and_then(|h| h.rpc.as_ref())
146 .and_then(|r| r.oneway)
147 }
148
149 fn set_oneway(&mut self, oneway: bool) {
150 if let Some(header) = self.topic_request.as_mut() {
151 if let Some(rpc_header) = header.rpc.as_mut() {
152 rpc_header.oneway = Some(oneway);
153 }
154 }
155 }
156
157 fn queue_id(&self) -> i32 {
158 self.queue_id
159 }
160
161 fn set_queue_id(&mut self, queue_id: i32) {
162 self.queue_id = queue_id;
163 }
164}
165
166#[cfg(test)]
167mod tests {
168 use std::collections::HashMap;
169
170 use cheetah_string::CheetahString;
171
172 use super::*;
173 use crate::protocol::command_custom_header::CommandCustomHeader;
174 use crate::protocol::command_custom_header::FromMap;
175
176 #[test]
177 fn pull_message_request_header_serializes_correctly() {
178 let header = PullMessageRequestHeader {
179 consumer_group: CheetahString::from_static_str("test_consumer_group"),
180 topic: CheetahString::from_static_str("test_topic"),
181 lite_topic: Some(CheetahString::from_static_str("test_lite_topic")),
182 queue_id: 1,
183 queue_offset: 100,
184 max_msg_nums: 10,
185 sys_flag: 0,
186 commit_offset: 50,
187 suspend_timeout_millis: 3000,
188 subscription: Some(CheetahString::from_static_str("test_subscription")),
189 sub_version: 1,
190 expression_type: Some(CheetahString::from_static_str("test_expression")),
191 max_msg_bytes: Some(1024),
192 request_source: Some(1),
193 proxy_forward_client_id: Some(CheetahString::from_static_str("test_client_id")),
194 topic_request: None,
195 };
196 let map: HashMap<CheetahString, CheetahString> = header.to_map().unwrap();
197 assert_eq!(
198 map.get(&CheetahString::from_static_str("consumerGroup")).unwrap(),
199 "test_consumer_group"
200 );
201 assert_eq!(map.get(&CheetahString::from_static_str("topic")).unwrap(), "test_topic");
202 assert_eq!(
203 map.get(&CheetahString::from_static_str("liteTopic")).unwrap(),
204 "test_lite_topic"
205 );
206 assert_eq!(map.get(&CheetahString::from_static_str("queueId")).unwrap(), "1");
207 assert_eq!(map.get(&CheetahString::from_static_str("queueOffset")).unwrap(), "100");
208 assert_eq!(map.get(&CheetahString::from_static_str("maxMsgNums")).unwrap(), "10");
209 assert_eq!(map.get(&CheetahString::from_static_str("sysFlag")).unwrap(), "0");
210 assert_eq!(map.get(&CheetahString::from_static_str("commitOffset")).unwrap(), "50");
211 assert_eq!(
212 map.get(&CheetahString::from_static_str("suspendTimeoutMillis"))
213 .unwrap(),
214 "3000"
215 );
216 assert_eq!(
217 map.get(&CheetahString::from_static_str("subscription")).unwrap(),
218 "test_subscription"
219 );
220 assert_eq!(map.get(&CheetahString::from_static_str("subVersion")).unwrap(), "1");
221 assert_eq!(
222 map.get(&CheetahString::from_static_str("expressionType")).unwrap(),
223 "test_expression"
224 );
225 assert_eq!(map.get(&CheetahString::from_static_str("maxMsgBytes")).unwrap(), "1024");
226 assert_eq!(map.get(&CheetahString::from_static_str("requestSource")).unwrap(), "1");
227 assert_eq!(
228 map.get(&CheetahString::from_static_str("proxyFrowardClientId"))
229 .unwrap(),
230 "test_client_id"
231 );
232 }
233
234 #[test]
235 fn pull_message_request_header_deserializes_correctly() {
236 let mut map = HashMap::new();
237 map.insert(
238 CheetahString::from_static_str("consumerGroup"),
239 CheetahString::from_static_str("test_consumer_group"),
240 );
241 map.insert(
242 CheetahString::from_static_str("topic"),
243 CheetahString::from_static_str("test_topic"),
244 );
245 map.insert(
246 CheetahString::from_static_str("liteTopic"),
247 CheetahString::from_static_str("test_lite_topic"),
248 );
249 map.insert(
250 CheetahString::from_static_str("queueId"),
251 CheetahString::from_static_str("1"),
252 );
253 map.insert(
254 CheetahString::from_static_str("queueOffset"),
255 CheetahString::from_static_str("100"),
256 );
257 map.insert(
258 CheetahString::from_static_str("maxMsgNums"),
259 CheetahString::from_static_str("10"),
260 );
261 map.insert(
262 CheetahString::from_static_str("sysFlag"),
263 CheetahString::from_static_str("0"),
264 );
265 map.insert(
266 CheetahString::from_static_str("commitOffset"),
267 CheetahString::from_static_str("50"),
268 );
269 map.insert(
270 CheetahString::from_static_str("suspendTimeoutMillis"),
271 CheetahString::from_static_str("3000"),
272 );
273 map.insert(
274 CheetahString::from_static_str("subscription"),
275 CheetahString::from_static_str("test_subscription"),
276 );
277 map.insert(
278 CheetahString::from_static_str("subVersion"),
279 CheetahString::from_static_str("1"),
280 );
281 map.insert(
282 CheetahString::from_static_str("expressionType"),
283 CheetahString::from_static_str("test_expression"),
284 );
285 map.insert(
286 CheetahString::from_static_str("maxMsgBytes"),
287 CheetahString::from_static_str("1024"),
288 );
289 map.insert(
290 CheetahString::from_static_str("requestSource"),
291 CheetahString::from_static_str("1"),
292 );
293 map.insert(
294 CheetahString::from_static_str("proxyFrowardClientId"),
295 CheetahString::from_static_str("test_client_id"),
296 );
297
298 let header = <PullMessageRequestHeader as FromMap>::from(&map).unwrap();
299 assert_eq!(header.consumer_group, "test_consumer_group");
300 assert_eq!(header.topic, "test_topic");
301 assert_eq!(header.lite_topic.unwrap(), "test_lite_topic");
302 assert_eq!(header.queue_id, 1);
303 assert_eq!(header.queue_offset, 100);
304 assert_eq!(header.max_msg_nums, 10);
305 assert_eq!(header.sys_flag, 0);
306 assert_eq!(header.commit_offset, 50);
307 assert_eq!(header.suspend_timeout_millis, 3000);
308 assert_eq!(header.subscription.unwrap(), "test_subscription");
309 assert_eq!(header.sub_version, 1);
310 assert_eq!(header.expression_type.unwrap(), "test_expression");
311 assert_eq!(header.max_msg_bytes.unwrap(), 1024);
312 assert_eq!(header.request_source.unwrap(), 1);
313 assert_eq!(header.proxy_forward_client_id.unwrap(), "test_client_id");
314 }
315
316 #[test]
317 fn pull_message_request_header_accepts_legacy_corrected_proxy_forward_key() {
318 let mut map = HashMap::new();
319 map.insert(
320 CheetahString::from_static_str("consumerGroup"),
321 CheetahString::from_static_str("test_consumer_group"),
322 );
323 map.insert(
324 CheetahString::from_static_str("topic"),
325 CheetahString::from_static_str("test_topic"),
326 );
327 map.insert(
328 CheetahString::from_static_str("liteTopic"),
329 CheetahString::from_static_str("test_lite_topic"),
330 );
331 map.insert(
332 CheetahString::from_static_str("queueId"),
333 CheetahString::from_static_str("1"),
334 );
335 map.insert(
336 CheetahString::from_static_str("queueOffset"),
337 CheetahString::from_static_str("100"),
338 );
339 map.insert(
340 CheetahString::from_static_str("maxMsgNums"),
341 CheetahString::from_static_str("10"),
342 );
343 map.insert(
344 CheetahString::from_static_str("sysFlag"),
345 CheetahString::from_static_str("0"),
346 );
347 map.insert(
348 CheetahString::from_static_str("commitOffset"),
349 CheetahString::from_static_str("50"),
350 );
351 map.insert(
352 CheetahString::from_static_str("suspendTimeoutMillis"),
353 CheetahString::from_static_str("3000"),
354 );
355 map.insert(
356 CheetahString::from_static_str("subVersion"),
357 CheetahString::from_static_str("1"),
358 );
359 map.insert(
360 CheetahString::from_static_str("proxyForwardClientId"),
361 CheetahString::from_static_str("legacy_client_id"),
362 );
363
364 let header = <PullMessageRequestHeader as FromMap>::from(&map).unwrap();
365
366 assert_eq!(header.proxy_forward_client_id.unwrap(), "legacy_client_id");
367 }
368
369 #[test]
370 fn pull_message_request_header_handles_missing_optional_fields() {
371 let mut map = HashMap::new();
372 map.insert(
373 CheetahString::from_static_str("consumerGroup"),
374 CheetahString::from_static_str("test_consumer_group"),
375 );
376 map.insert(
377 CheetahString::from_static_str("topic"),
378 CheetahString::from_static_str("test_topic"),
379 );
380 map.insert(
381 CheetahString::from_static_str("queueId"),
382 CheetahString::from_static_str("1"),
383 );
384 map.insert(
385 CheetahString::from_static_str("queueOffset"),
386 CheetahString::from_static_str("100"),
387 );
388 map.insert(
389 CheetahString::from_static_str("maxMsgNums"),
390 CheetahString::from_static_str("10"),
391 );
392 map.insert(
393 CheetahString::from_static_str("sysFlag"),
394 CheetahString::from_static_str("0"),
395 );
396 map.insert(
397 CheetahString::from_static_str("commitOffset"),
398 CheetahString::from_static_str("50"),
399 );
400 map.insert(
401 CheetahString::from_static_str("suspendTimeoutMillis"),
402 CheetahString::from_static_str("3000"),
403 );
404 map.insert(
405 CheetahString::from_static_str("subVersion"),
406 CheetahString::from_static_str("1"),
407 );
408
409 let header = <PullMessageRequestHeader as FromMap>::from(&map).unwrap();
410 assert_eq!(header.consumer_group, "test_consumer_group");
411 assert_eq!(header.topic, "test_topic");
412 assert!(header.lite_topic.is_none());
413 assert_eq!(header.queue_id, 1);
414 assert_eq!(header.queue_offset, 100);
415 assert_eq!(header.max_msg_nums, 10);
416 assert_eq!(header.sys_flag, 0);
417 assert_eq!(header.commit_offset, 50);
418 assert_eq!(header.suspend_timeout_millis, 3000);
419 assert_eq!(header.sub_version, 1);
420 assert!(header.subscription.is_none());
421 assert!(header.expression_type.is_none());
422 assert!(header.max_msg_bytes.is_none());
423 assert!(header.request_source.is_none());
424 assert!(header.proxy_forward_client_id.is_none());
425 }
426
427 #[test]
428 fn pull_message_request_header_handles_invalid_data() {
429 let mut map = HashMap::new();
430 map.insert(
431 CheetahString::from_static_str("consumerGroup"),
432 CheetahString::from_static_str("test_consumer_group"),
433 );
434 map.insert(
435 CheetahString::from_static_str("topic"),
436 CheetahString::from_static_str("test_topic"),
437 );
438 map.insert(
439 CheetahString::from_static_str("queueId"),
440 CheetahString::from_static_str("invalid"),
441 );
442 map.insert(
443 CheetahString::from_static_str("queueOffset"),
444 CheetahString::from_static_str("invalid"),
445 );
446 map.insert(
447 CheetahString::from_static_str("maxMsgNums"),
448 CheetahString::from_static_str("invalid"),
449 );
450 map.insert(
451 CheetahString::from_static_str("sysFlag"),
452 CheetahString::from_static_str("invalid"),
453 );
454 map.insert(
455 CheetahString::from_static_str("commitOffset"),
456 CheetahString::from_static_str("invalid"),
457 );
458 map.insert(
459 CheetahString::from_static_str("suspendTimeoutMillis"),
460 CheetahString::from_static_str("invalid"),
461 );
462 map.insert(
463 CheetahString::from_static_str("subVersion"),
464 CheetahString::from_static_str("invalid"),
465 );
466
467 let result = <PullMessageRequestHeader as FromMap>::from(&map);
468 assert!(result.is_err());
469 }
470}