krabka_protocol/primitives/
string_bytes.rs1use bytes::{Buf, BufMut, Bytes};
2
3use crate::{
4 ProtocolError,
5 primitives::{
6 fixed::{get_i16, get_i32, put_i16, put_i32},
7 varint::{get_uvarint, put_uvarint, uvarint_len},
8 },
9};
10
11pub fn put_string<B: BufMut>(buf: &mut B, s: &str) {
17 let len = i16::try_from(s.len()).expect("string length must fit in i16");
18 put_i16(buf, len);
19 buf.put_slice(s.as_bytes());
20}
21
22pub fn put_nullable_string<B: BufMut>(buf: &mut B, s: Option<&str>) {
23 match s {
24 None => put_i16(buf, -1),
25 Some(s) => put_string(buf, s),
26 }
27}
28
29pub fn get_string_owned<B: Buf>(buf: &mut B) -> Result<String, ProtocolError> {
32 match get_nullable_string_owned(buf)? {
33 Some(s) => Ok(s),
34 None => Err(ProtocolError::InvalidValue("non-nullable STRING was null")),
35 }
36}
37
38pub fn get_nullable_string_owned<B: Buf>(buf: &mut B) -> Result<Option<String>, ProtocolError> {
43 let len = get_i16(buf)?;
44 if len < 0 {
45 return Ok(None);
46 }
47 let n = usize::try_from(len).expect("non-negative string length must fit in usize");
48 if buf.remaining() < n {
49 return Err(ProtocolError::UnexpectedEof {
50 needed: n - buf.remaining(),
51 });
52 }
53 let mut v = vec![0u8; n];
54 buf.copy_to_slice(&mut v);
55 let s = String::from_utf8(v).map_err(|e| ProtocolError::InvalidUtf8(e.utf8_error()))?;
56 Ok(Some(s))
57}
58
59#[must_use]
60pub fn string_len(s: &str) -> usize {
61 2 + s.len()
62}
63#[must_use]
64pub fn nullable_string_len(s: Option<&str>) -> usize {
65 2 + s.map_or(0, str::len)
66}
67
68pub fn put_compact_string<B: BufMut>(buf: &mut B, s: &str) {
74 let len = u32::try_from(s.len() + 1).expect("string length too large");
75 put_uvarint(buf, len);
76 buf.put_slice(s.as_bytes());
77}
78
79pub fn put_compact_nullable_string<B: BufMut>(buf: &mut B, s: Option<&str>) {
80 match s {
81 None => put_uvarint(buf, 0),
82 Some(s) => put_compact_string(buf, s),
83 }
84}
85
86pub fn get_compact_string_owned<B: Buf>(buf: &mut B) -> Result<String, ProtocolError> {
89 match get_compact_nullable_string_owned(buf)? {
90 Some(s) => Ok(s),
91 None => Err(ProtocolError::InvalidValue(
92 "non-nullable COMPACT_STRING was null",
93 )),
94 }
95}
96
97pub fn get_compact_nullable_string_owned<B: Buf>(
100 buf: &mut B,
101) -> Result<Option<String>, ProtocolError> {
102 let raw = get_uvarint(buf)?;
103 if raw == 0 {
104 return Ok(None);
105 }
106 let n = (raw - 1) as usize;
107 if buf.remaining() < n {
108 return Err(ProtocolError::UnexpectedEof {
109 needed: n - buf.remaining(),
110 });
111 }
112 let mut v = vec![0u8; n];
113 buf.copy_to_slice(&mut v);
114 let s = String::from_utf8(v).map_err(|e| ProtocolError::InvalidUtf8(e.utf8_error()))?;
115 Ok(Some(s))
116}
117
118#[must_use]
119pub fn compact_string_len(s: &str) -> usize {
122 uvarint_len(u32::try_from(s.len() + 1).unwrap()) + s.len()
123}
124#[must_use]
125pub fn compact_nullable_string_len(s: Option<&str>) -> usize {
126 match s {
127 None => uvarint_len(0),
128 Some(s) => compact_string_len(s),
129 }
130}
131
132pub fn put_bytes<B: BufMut>(buf: &mut B, b: &[u8]) {
139 let len = i32::try_from(b.len()).expect("bytes length must fit in i32");
140 put_i32(buf, len);
141 buf.put_slice(b);
142}
143
144pub fn put_nullable_bytes<B: BufMut>(buf: &mut B, b: Option<&[u8]>) {
145 match b {
146 None => put_i32(buf, -1),
147 Some(b) => put_bytes(buf, b),
148 }
149}
150
151pub fn get_bytes_owned<B: Buf>(buf: &mut B) -> Result<Bytes, ProtocolError> {
154 match get_nullable_bytes_owned(buf)? {
155 Some(b) => Ok(b),
156 None => Err(ProtocolError::InvalidValue("non-nullable BYTES was null")),
157 }
158}
159
160pub fn get_nullable_bytes_owned<B: Buf>(buf: &mut B) -> Result<Option<Bytes>, ProtocolError> {
165 let len = get_i32(buf)?;
166 if len < 0 {
167 return Ok(None);
168 }
169 let n = usize::try_from(len).expect("non-negative bytes length must fit in usize");
170 if buf.remaining() < n {
171 return Err(ProtocolError::UnexpectedEof {
172 needed: n - buf.remaining(),
173 });
174 }
175 let mut v = vec![0u8; n];
176 buf.copy_to_slice(&mut v);
177 Ok(Some(Bytes::from(v)))
178}
179
180#[must_use]
181pub fn bytes_len(b: &[u8]) -> usize {
182 4 + b.len()
183}
184#[must_use]
185pub fn nullable_bytes_len(b: Option<&[u8]>) -> usize {
186 4 + b.map_or(0, <[u8]>::len)
187}
188
189pub fn put_compact_bytes<B: BufMut>(buf: &mut B, b: &[u8]) {
192 let len = u32::try_from(b.len() + 1).expect("bytes length too large");
193 put_uvarint(buf, len);
194 buf.put_slice(b);
195}
196
197pub fn put_compact_nullable_bytes<B: BufMut>(buf: &mut B, b: Option<&[u8]>) {
198 match b {
199 None => put_uvarint(buf, 0),
200 Some(b) => put_compact_bytes(buf, b),
201 }
202}
203
204#[must_use]
205pub fn compact_bytes_len(b: &[u8]) -> usize {
208 uvarint_len(u32::try_from(b.len() + 1).unwrap()) + b.len()
209}
210
211#[must_use]
216pub fn compact_bytes_len_from_size(n: usize) -> usize {
219 uvarint_len(u32::try_from(n + 1).unwrap()) + n
220}
221#[must_use]
222pub fn compact_nullable_bytes_len(b: Option<&[u8]>) -> usize {
223 match b {
224 None => uvarint_len(0),
225 Some(b) => compact_bytes_len(b),
226 }
227}
228
229pub fn get_compact_bytes_owned<B: Buf>(buf: &mut B) -> Result<Bytes, ProtocolError> {
232 match get_compact_nullable_bytes_owned(buf)? {
233 Some(b) => Ok(b),
234 None => Err(ProtocolError::InvalidValue(
235 "non-nullable COMPACT_BYTES was null",
236 )),
237 }
238}
239
240pub fn get_compact_nullable_bytes_owned<B: Buf>(
243 buf: &mut B,
244) -> Result<Option<Bytes>, ProtocolError> {
245 let raw = get_uvarint(buf)?;
246 if raw == 0 {
247 return Ok(None);
248 }
249 let n = (raw - 1) as usize;
250 if buf.remaining() < n {
251 return Err(ProtocolError::UnexpectedEof {
252 needed: n - buf.remaining(),
253 });
254 }
255 let mut v = vec![0u8; n];
256 buf.copy_to_slice(&mut v);
257 Ok(Some(Bytes::from(v)))
258}
259
260#[cfg(test)]
261mod tests {
262
263 use bytes::BytesMut;
264
265 use super::*;
266
267 #[test]
268 fn string_roundtrip() {
269 let mut buf = BytesMut::new();
270 put_string(&mut buf, "kafka");
271 assert2::assert!(&buf[..] == &[0x00, 0x05, b'k', b'a', b'f', b'k', b'a']);
273 let mut cur = &buf[..];
274 assert2::assert!(get_string_owned(&mut cur).unwrap() == "kafka");
275 }
276
277 #[test]
278 fn nullable_string_null() {
279 let mut buf = BytesMut::new();
280 put_nullable_string(&mut buf, None);
281 assert2::assert!(&buf[..] == &[0xFF, 0xFF]);
282 let mut cur = &buf[..];
283 assert2::assert!(get_nullable_string_owned(&mut cur).unwrap() == None);
284 }
285
286 #[test]
287 fn compact_string_roundtrip() {
288 let mut buf = BytesMut::new();
289 put_compact_string(&mut buf, "kafka");
290 assert2::assert!(&buf[..] == &[0x06, b'k', b'a', b'f', b'k', b'a']);
292 let mut cur = &buf[..];
293 assert2::assert!(get_compact_string_owned(&mut cur).unwrap() == "kafka");
294 }
295
296 #[test]
297 fn compact_nullable_string_null() {
298 let mut buf = BytesMut::new();
299 put_compact_nullable_string(&mut buf, None);
300 assert2::assert!(&buf[..] == &[0x00]);
301 let mut cur = &buf[..];
302 assert2::assert!(get_compact_nullable_string_owned(&mut cur).unwrap() == None);
303 }
304
305 #[test]
306 fn empty_compact_string() {
307 let mut buf = BytesMut::new();
308 put_compact_string(&mut buf, "");
309 assert2::assert!(&buf[..] == &[0x01]); let mut cur = &buf[..];
311 assert2::assert!(get_compact_string_owned(&mut cur).unwrap() == "");
312 }
313
314 #[test]
315 fn bytes_roundtrip() {
316 let mut buf = BytesMut::new();
317 put_bytes(&mut buf, &[1, 2, 3]);
318 let mut cur = &buf[..];
319 let out = get_bytes_owned(&mut cur).unwrap();
320 assert2::assert!(out.as_ref() == &[1, 2, 3]);
321 }
322
323 #[test]
324 fn invalid_utf8_is_rejected() {
325 let bytes = [0x00, 0x02, 0xC3, 0x28];
327 let mut cur = &bytes[..];
328 assert2::assert!(matches!(
329 get_string_owned(&mut cur),
330 Err(ProtocolError::InvalidUtf8(_))
331 ));
332 }
333}