rocketmq_protocol/protocol/header_codec/
sink.rs1use bytes::BufMut;
16use bytes::BytesMut;
17use cheetah_string::CheetahString;
18
19use super::private::Sealed;
20use super::write_json_string;
21use super::HeaderFieldContext;
22use super::HeaderValue;
23use super::ProtocolContractViolation;
24use crate::protocol::command_custom_header::HeaderMap;
25
26pub trait EncodeSink: Sealed {
32 fn write<V: HeaderValue>(
42 &mut self,
43 key: &'static str,
44 value: &V,
45 context: HeaderFieldContext,
46 ) -> Result<(), ProtocolContractViolation>;
47}
48
49pub struct MapSink<'a> {
51 out: &'a mut HeaderMap,
52}
53
54impl<'a> MapSink<'a> {
55 #[inline]
57 pub const fn new(out: &'a mut HeaderMap) -> Self {
58 Self { out }
59 }
60
61 #[inline]
63 pub fn into_inner(self) -> &'a mut HeaderMap {
64 self.out
65 }
66}
67
68impl Sealed for MapSink<'_> {}
69
70impl EncodeSink for MapSink<'_> {
71 #[inline]
72 fn write<V: HeaderValue>(
73 &mut self,
74 key: &'static str,
75 value: &V,
76 _context: HeaderFieldContext,
77 ) -> Result<(), ProtocolContractViolation> {
78 self.out
79 .insert(CheetahString::from_static_str(key), value.to_map_value());
80 Ok(())
81 }
82}
83
84pub struct JsonSink<'a> {
89 out: &'a mut BytesMut,
90 first: bool,
91}
92
93impl<'a> JsonSink<'a> {
94 #[inline]
96 pub fn new(out: &'a mut BytesMut) -> Self {
97 out.extend_from_slice(b"{");
98 Self { out, first: true }
99 }
100
101 #[inline]
103 pub fn finish(self) -> &'a mut BytesMut {
104 self.out.extend_from_slice(b"}");
105 self.out
106 }
107}
108
109impl Sealed for JsonSink<'_> {}
110
111impl EncodeSink for JsonSink<'_> {
112 #[inline]
113 fn write<V: HeaderValue>(
114 &mut self,
115 key: &'static str,
116 value: &V,
117 _context: HeaderFieldContext,
118 ) -> Result<(), ProtocolContractViolation> {
119 if self.first {
120 self.first = false;
121 } else {
122 self.out.extend_from_slice(b",");
123 }
124 write_json_string(self.out, key);
125 self.out.extend_from_slice(b":");
126 value.write_json_string(self.out);
127 Ok(())
128 }
129}
130
131pub struct BinarySink<'a> {
138 out: &'a mut BytesMut,
139}
140
141impl<'a> BinarySink<'a> {
142 #[inline]
144 pub fn new(out: &'a mut BytesMut) -> Self {
145 Self { out }
146 }
147
148 #[inline]
150 pub fn into_inner(self) -> &'a mut BytesMut {
151 self.out
152 }
153}
154
155impl Sealed for BinarySink<'_> {}
156
157impl EncodeSink for BinarySink<'_> {
158 #[inline]
159 fn write<V: HeaderValue>(
160 &mut self,
161 key: &'static str,
162 value: &V,
163 context: HeaderFieldContext,
164 ) -> Result<(), ProtocolContractViolation> {
165 let key_len = u16::try_from(key.len()).map_err(|_| ProtocolContractViolation::KeyLengthOverflow {
166 header: context.header,
167 key: context.key,
168 })?;
169 if !V::ALWAYS_FITS_WIRE_LENGTH && value.encoded_len() > i32::MAX as usize {
170 return Err(ProtocolContractViolation::ValueLengthOverflow {
171 header: context.header,
172 key: context.key,
173 });
174 }
175
176 let pair_start = self.out.len();
177 self.out.put_u16(key_len);
178 self.out.extend_from_slice(key.as_bytes());
179
180 let value_len_offset = self.out.len();
181 self.out.put_i32(0);
182 let value_offset = self.out.len();
183 value.write_ascii(self.out);
184 let actual_value_len = self.out.len() - value_offset;
185 let actual_value_len = i32::try_from(actual_value_len).map_err(|_| {
186 self.out.truncate(pair_start);
187 ProtocolContractViolation::ValueLengthOverflow {
188 header: context.header,
189 key: context.key,
190 }
191 })?;
192 self.out[value_len_offset..value_len_offset + 4].copy_from_slice(&actual_value_len.to_be_bytes());
193 Ok(())
194 }
195}
196
197#[cfg(test)]
198mod tests {
199 use super::*;
200 use crate::protocol::header_codec::HeaderValueKind;
201
202 const CONTEXT: HeaderFieldContext =
203 HeaderFieldContext::new("ExampleHeader", "topic", HeaderValueKind::String, None);
204
205 #[test]
206 fn writes_into_existing_map_without_an_intermediate_map() {
207 let mut fields = HeaderMap::with_capacity(2);
208 fields.insert(
209 CheetahString::from_static_str("existing"),
210 CheetahString::from_static_str("value"),
211 );
212
213 let mut sink = MapSink::new(&mut fields);
214 sink.write("topic", &CheetahString::from("测试-topic"), CONTEXT)
215 .unwrap();
216 let fields = sink.into_inner();
217
218 assert_eq!(fields.len(), 2);
219 assert_eq!(fields.get("topic").map(CheetahString::as_str), Some("测试-topic"));
220 assert_eq!(fields.get("existing").map(CheetahString::as_str), Some("value"));
221 }
222
223 #[test]
224 fn canonical_write_replaces_an_existing_value() {
225 let mut fields = HeaderMap::new();
226 fields.insert(
227 CheetahString::from_static_str("topic"),
228 CheetahString::from_static_str("old"),
229 );
230
231 MapSink::new(&mut fields)
232 .write("topic", &String::from("new"), CONTEXT)
233 .unwrap();
234
235 assert_eq!(fields.get("topic").map(CheetahString::as_str), Some("new"));
236 }
237
238 #[test]
239 fn scalar_write_uses_the_header_value_canonical_form() {
240 let mut fields = HeaderMap::new();
241 let mut sink = MapSink::new(&mut fields);
242
243 sink.write(
244 "queueOffset",
245 &u64::MAX,
246 HeaderFieldContext::new("ExampleHeader", "queueOffset", HeaderValueKind::U64, None),
247 )
248 .unwrap();
249
250 assert_eq!(
251 fields.get("queueOffset").map(CheetahString::as_str),
252 Some("18446744073709551615")
253 );
254 }
255
256 #[test]
257 fn binary_sink_appends_canonical_pairs_without_replacing_existing_bytes() {
258 let mut out = BytesMut::from(&b"prefix"[..]);
259 let mut sink = BinarySink::new(&mut out);
260 sink.write("queueOffset", &-42_i64, CONTEXT).unwrap();
261 let out = sink.into_inner();
262
263 assert_eq!(out.len() - 6, 2 + 11 + 4 + 3);
264 assert_eq!(&out[..6], b"prefix");
265 assert_eq!(u16::from_be_bytes(out[6..8].try_into().unwrap()), 11);
266 assert_eq!(&out[8..19], b"queueOffset");
267 assert_eq!(i32::from_be_bytes(out[19..23].try_into().unwrap()), 3);
268 assert_eq!(&out[23..], b"-42");
269 }
270
271 #[test]
272 fn binary_sink_rejects_an_oversized_key_without_mutating_the_destination() {
273 let key = Box::leak("k".repeat(u16::MAX as usize + 1).into_boxed_str());
274 let mut out = BytesMut::from(&b"prefix"[..]);
275 let error = BinarySink::new(&mut out).write(key, &true, CONTEXT).unwrap_err();
276
277 assert!(matches!(error, ProtocolContractViolation::KeyLengthOverflow { .. }));
278 assert_eq!(out.as_ref(), b"prefix");
279 }
280
281 #[test]
282 fn json_sink_writes_string_scalars_and_escapes_text_without_allocating_values() {
283 let mut out = BytesMut::new();
284 let mut sink = JsonSink::new(&mut out);
285 sink.write("topic", &CheetahString::from("主题\"\\\n"), CONTEXT)
286 .unwrap();
287 sink.write(
288 "queueOffset",
289 &-42_i64,
290 HeaderFieldContext::new("ExampleHeader", "queueOffset", HeaderValueKind::I64, None),
291 )
292 .unwrap();
293 sink.write(
294 "enabled",
295 &true,
296 HeaderFieldContext::new("ExampleHeader", "enabled", HeaderValueKind::Bool, None),
297 )
298 .unwrap();
299 sink.finish();
300
301 let value: serde_json::Value = serde_json::from_slice(&out).unwrap();
302 assert_eq!(value["topic"], "主题\"\\\n");
303 assert_eq!(value["queueOffset"], "-42");
304 assert_eq!(value["enabled"], "true");
305 }
306}