rocketmq_protocol/protocol/header/
search_offset_request_header.rs1use cheetah_string::CheetahString;
16use rocketmq_macros::RequestHeaderCodec;
17use rocketmq_model::boundary_type::BoundaryType;
18use serde::Deserialize;
19use serde::Serialize;
20
21use crate::protocol::header::message_operation_header::TopicRequestHeaderTrait;
22use crate::rpc::topic_request_header::TopicRequestHeader;
23
24#[derive(Default, Debug, Serialize, Deserialize, RequestHeaderCodec)]
25#[header(
26 type_id = "rocketmq_protocol::protocol::header::search_offset_request_header::SearchOffsetRequestHeader",
27 java_class = "org.apache.rocketmq.remoting.protocol.header.SearchOffsetRequestHeader"
28)]
29#[serde(rename_all = "camelCase")]
30pub struct SearchOffsetRequestHeader {
31 #[header(required)]
32 pub topic: CheetahString,
33
34 pub lite_topic: Option<CheetahString>,
35
36 #[header(required)]
37 pub queue_id: i32,
38
39 #[header(required)]
40 pub timestamp: i64,
41
42 #[header(default, default_semantic = "literal:LOWER")]
43 pub boundary_type: BoundaryType,
44
45 #[serde(flatten)]
46 #[header(flatten, presence = "any")]
47 pub topic_request_header: Option<TopicRequestHeader>,
48}
49
50impl TopicRequestHeaderTrait for SearchOffsetRequestHeader {
51 fn set_lo(&mut self, lo: Option<bool>) {
52 if let Some(header) = self.topic_request_header.as_mut() {
53 header.lo = lo;
54 }
55 }
56
57 fn lo(&self) -> Option<bool> {
58 self.topic_request_header.as_ref().and_then(|h| h.lo)
59 }
60
61 fn set_topic(&mut self, topic: CheetahString) {
62 self.topic = topic;
63 }
64
65 fn topic(&self) -> &CheetahString {
66 &self.topic
67 }
68
69 fn broker_name(&self) -> Option<&CheetahString> {
70 self.topic_request_header
71 .as_ref()
72 .and_then(|h| h.rpc_request_header.as_ref())
73 .and_then(|h| h.broker_name.as_ref())
74 }
75
76 fn set_broker_name(&mut self, broker_name: CheetahString) {
77 if let Some(header) = self.topic_request_header.as_mut() {
78 if let Some(rpc_header) = header.rpc_request_header.as_mut() {
79 rpc_header.broker_name = Some(broker_name);
80 }
81 }
82 }
83
84 fn namespace(&self) -> Option<&str> {
85 self.topic_request_header
86 .as_ref()
87 .and_then(|h| h.rpc_request_header.as_ref())
88 .and_then(|r| r.namespace.as_deref())
89 }
90
91 fn set_namespace(&mut self, namespace: CheetahString) {
92 if let Some(header) = self.topic_request_header.as_mut() {
93 if let Some(rpc_header) = header.rpc_request_header.as_mut() {
94 rpc_header.namespace = Some(namespace);
95 }
96 }
97 }
98
99 fn namespaced(&self) -> Option<bool> {
100 self.topic_request_header
101 .as_ref()
102 .and_then(|h| h.rpc_request_header.as_ref())
103 .and_then(|r| r.namespaced)
104 }
105
106 fn set_namespaced(&mut self, namespaced: bool) {
107 if let Some(header) = self.topic_request_header.as_mut() {
108 if let Some(rpc_header) = header.rpc_request_header.as_mut() {
109 rpc_header.namespaced = Some(namespaced);
110 }
111 }
112 }
113
114 fn oneway(&self) -> Option<bool> {
115 self.topic_request_header
116 .as_ref()
117 .and_then(|h| h.rpc_request_header.as_ref())
118 .and_then(|r| r.oneway)
119 }
120
121 fn set_oneway(&mut self, oneway: bool) {
122 if let Some(header) = self.topic_request_header.as_mut() {
123 if let Some(rpc_header) = header.rpc_request_header.as_mut() {
124 rpc_header.oneway = Some(oneway);
125 }
126 }
127 }
128
129 fn queue_id(&self) -> i32 {
130 self.queue_id
131 }
132
133 fn set_queue_id(&mut self, queue_id: i32) {
134 self.queue_id = queue_id;
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141 use crate::protocol::command_custom_header::{CommandCustomHeader, FromMap};
142 use crate::rpc::rpc_request_header::RpcRequestHeader;
143
144 fn header_with_rpc_envelope() -> SearchOffsetRequestHeader {
145 SearchOffsetRequestHeader {
146 topic: CheetahString::from("topic-a"),
147 lite_topic: Some(CheetahString::from("lite-topic-a")),
148 queue_id: 1,
149 timestamp: i64::MAX,
150 boundary_type: BoundaryType::Upper,
151 topic_request_header: Some(TopicRequestHeader {
152 rpc_request_header: Some(RpcRequestHeader::default()),
153 lo: None,
154 }),
155 }
156 }
157
158 #[test]
159 fn request_header_codec_preserves_java_keys_and_numeric_boundaries() {
160 let header = SearchOffsetRequestHeader {
161 topic_request_header: None,
162 ..header_with_rpc_envelope()
163 };
164 let map = header.to_map().unwrap();
165
166 assert_eq!(map.get("topic").map(|value| value.as_str()), Some("topic-a"));
167 assert_eq!(map.get("liteTopic").map(|value| value.as_str()), Some("lite-topic-a"));
168
169 let decoded = <SearchOffsetRequestHeader as FromMap>::from(&map).unwrap();
170 assert_eq!(decoded.topic, "topic-a");
171 assert_eq!(decoded.lite_topic.as_deref(), Some("lite-topic-a"));
172 assert_eq!(decoded.queue_id, 1);
173 assert_eq!(decoded.timestamp, i64::MAX);
174 assert_eq!(decoded.boundary_type, BoundaryType::Upper);
175 }
176
177 #[test]
178 fn serde_uses_java_names_and_the_uppercase_boundary_value() {
179 let header = SearchOffsetRequestHeader {
180 topic_request_header: None,
181 ..header_with_rpc_envelope()
182 };
183 let value = serde_json::to_value(&header).unwrap();
184
185 assert_eq!(value["topic"], "topic-a");
186 assert_eq!(value["liteTopic"], "lite-topic-a");
187 assert_eq!(value["queueId"], 1);
188 assert_eq!(value["timestamp"], i64::MAX);
189 assert_eq!(value["boundaryType"], "UPPER");
190
191 let decoded: SearchOffsetRequestHeader = serde_json::from_value(value).unwrap();
192 assert_eq!(decoded.boundary_type, BoundaryType::Upper);
193 }
194
195 #[test]
196 fn serde_matches_java_fallback_for_non_upper_boundary_values() {
197 for boundary_type in ["LOWER", "lower", "invalid"] {
198 let json = format!(r#"{{"topic":"topic-a","queueId":1,"timestamp":1,"boundaryType":"{boundary_type}"}}"#);
199 let decoded: SearchOffsetRequestHeader = serde_json::from_str(&json).unwrap();
200
201 assert_eq!(decoded.boundary_type, BoundaryType::Lower);
202 }
203 }
204
205 #[test]
206 fn nested_setters_are_noops_without_an_rpc_envelope() {
207 let mut header = SearchOffsetRequestHeader::default();
208
209 header.set_lo(Some(true));
210 header.set_broker_name(CheetahString::from("broker-a"));
211 header.set_namespace(CheetahString::from("namespace-a"));
212 header.set_namespaced(true);
213 header.set_oneway(true);
214
215 assert_eq!(header.lo(), None);
216 assert_eq!(header.broker_name(), None);
217 assert_eq!(header.namespace(), None);
218 assert_eq!(header.namespaced(), None);
219 assert_eq!(header.oneway(), None);
220 }
221}