Skip to main content

krabka_protocol/primitives/
string_bytes.rs

1use 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
11// ---- STRING (non-flexible) ----
12// Wire: INT16 length (>=0), then `length` bytes UTF-8. -1 = null.
13
14/// # Panics
15/// Panics if a value previously validated by the protocol type no longer satisfies its encoded-length or field-range invariant.
16pub 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
29/// # Errors
30/// Returns the underlying protocol error when input is truncated, contains an invalid length or tag, or cannot be encoded for the selected version.
31pub 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
38/// # Errors
39/// Returns the underlying protocol error when input is truncated, contains an invalid length or tag, or cannot be encoded for the selected version.
40/// # Panics
41/// Panics if a value previously validated by the protocol type no longer satisfies its encoded-length or field-range invariant.
42pub 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
68// ---- COMPACT_STRING (flexible) ----
69// Wire: UVARINT length+1 (0 = null), then `length` UTF-8 bytes.
70
71/// # Panics
72/// Panics if a value previously validated by the protocol type no longer satisfies its encoded-length or field-range invariant.
73pub 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
86/// # Errors
87/// Returns the underlying protocol error when input is truncated, contains an invalid length or tag, or cannot be encoded for the selected version.
88pub 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
97/// # Errors
98/// Returns the underlying protocol error when input is truncated, contains an invalid length or tag, or cannot be encoded for the selected version.
99pub 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]
119/// # Panics
120/// Panics if a value previously validated by the protocol type no longer satisfies its encoded-length or field-range invariant.
121pub 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
132// ---- BYTES / COMPACT_BYTES ----
133// BYTES: INT32 length, `length` bytes. -1 = null.
134// COMPACT_BYTES: UVARINT length+1 (0=null), `length` bytes.
135
136/// # Panics
137/// Panics if a value previously validated by the protocol type no longer satisfies its encoded-length or field-range invariant.
138pub 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
151/// # Errors
152/// Returns the underlying protocol error when input is truncated, contains an invalid length or tag, or cannot be encoded for the selected version.
153pub 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
160/// # Errors
161/// Returns the underlying protocol error when input is truncated, contains an invalid length or tag, or cannot be encoded for the selected version.
162/// # Panics
163/// Panics if a value previously validated by the protocol type no longer satisfies its encoded-length or field-range invariant.
164pub 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
189/// # Panics
190/// Panics if a value previously validated by the protocol type no longer satisfies its encoded-length or field-range invariant.
191pub 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]
205/// # Panics
206/// Panics if a value previously validated by the protocol type no longer satisfies its encoded-length or field-range invariant.
207pub fn compact_bytes_len(b: &[u8]) -> usize {
208    uvarint_len(u32::try_from(b.len() + 1).unwrap()) + b.len()
209}
210
211/// Like `compact_bytes_len`, but takes the byte count directly rather than a slice.
212///
213/// Use this when you know the content size and do not need to materialise the
214/// buffer.
215#[must_use]
216/// # Panics
217/// Panics if a value previously validated by the protocol type no longer satisfies its encoded-length or field-range invariant.
218pub 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
229/// # Errors
230/// Returns the underlying protocol error when input is truncated, contains an invalid length or tag, or cannot be encoded for the selected version.
231pub 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
240/// # Errors
241/// Returns the underlying protocol error when input is truncated, contains an invalid length or tag, or cannot be encoded for the selected version.
242pub 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        // INT16(5) + bytes
272        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        // UVARINT(6) + bytes
291        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]); // length = 1 means "0 bytes"
310        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        // INT16(2) + invalid UTF-8 byte sequence
326        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}