use bytes::BytesMut;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EncodingError {
pub message: String,
}
impl std::error::Error for EncodingError {}
impl std::fmt::Display for EncodingError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.message)
}
}
impl From<super::DeserializeError> for EncodingError {
fn from(err: super::DeserializeError) -> Self {
EncodingError {
message: err.message,
}
}
}
pub fn encode_utf8(s: &str, buf: &mut BytesMut) {
let bytes = s.as_bytes();
let len = bytes.len();
if len > u16::MAX as usize {
panic!("String too long for UTF-8 encoding: {} bytes", len);
}
buf.extend_from_slice(&(len as u16).to_le_bytes());
buf.extend_from_slice(bytes);
}
pub fn decode_utf8(buf: &mut &[u8]) -> Result<String, EncodingError> {
if buf.len() < 2 {
return Err(EncodingError {
message: "Buffer too short for UTF-8 length".to_string(),
});
}
let len = u16::from_le_bytes([buf[0], buf[1]]) as usize;
*buf = &buf[2..];
if buf.len() < len {
return Err(EncodingError {
message: format!(
"Buffer too short for UTF-8 payload: need {} bytes, have {}",
len,
buf.len()
),
});
}
let bytes = &buf[..len];
*buf = &buf[len..];
String::from_utf8(bytes.to_vec()).map_err(|e| EncodingError {
message: format!("Invalid UTF-8: {}", e),
})
}
pub fn encode_optional_utf8(opt: Option<&str>, buf: &mut BytesMut) {
match opt {
Some(s) => encode_utf8(s, buf),
None => {
buf.extend_from_slice(&0u16.to_le_bytes());
}
}
}
pub fn decode_optional_utf8(buf: &mut &[u8]) -> Result<Option<String>, EncodingError> {
if buf.len() < 2 {
return Err(EncodingError {
message: "Buffer too short for optional UTF-8 length".to_string(),
});
}
let len = u16::from_le_bytes([buf[0], buf[1]]);
if len == 0 {
*buf = &buf[2..];
return Ok(None);
}
decode_utf8(buf).map(Some)
}
pub fn decode_array_count(buf: &mut &[u8]) -> Result<usize, EncodingError> {
if buf.len() < 2 {
return Err(EncodingError {
message: "Buffer too short for array count".to_string(),
});
}
let count = u16::from_le_bytes([buf[0], buf[1]]) as usize;
*buf = &buf[2..];
Ok(count)
}
pub fn encode_array_count(count: usize, buf: &mut BytesMut) {
if count > u16::MAX as usize {
panic!("Array too long: {} items", count);
}
buf.extend_from_slice(&(count as u16).to_le_bytes());
}
pub fn validate_fixed_element_array_len(
buf_len: usize,
element_size: usize,
) -> Result<usize, EncodingError> {
if !buf_len.is_multiple_of(element_size) {
return Err(EncodingError {
message: format!(
"Buffer length {} is not divisible by element size {}",
buf_len, element_size
),
});
}
Ok(buf_len / element_size)
}
pub fn encode_u64(value: u64, buf: &mut BytesMut) {
buf.extend_from_slice(&value.to_le_bytes());
}
pub fn decode_u64(buf: &mut &[u8]) -> Result<u64, EncodingError> {
if buf.len() < 8 {
return Err(EncodingError {
message: format!("Buffer too short for u64: need 8 bytes, have {}", buf.len()),
});
}
let bytes: [u8; 8] = buf[..8].try_into().map_err(|_| EncodingError {
message: "Failed to extract 8 bytes for u64".to_string(),
})?;
*buf = &buf[8..];
Ok(u64::from_le_bytes(bytes))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn should_encode_and_decode_utf8() {
let s = "Hello, World!";
let mut buf = BytesMut::new();
encode_utf8(s, &mut buf);
let mut slice = buf.as_ref();
let decoded = decode_utf8(&mut slice).unwrap();
assert_eq!(decoded, s);
assert!(slice.is_empty());
}
#[test]
fn should_encode_and_decode_utf8_with_unicode() {
let s = "Hello, 世界!";
let mut buf = BytesMut::new();
encode_utf8(s, &mut buf);
let mut slice = buf.as_ref();
let decoded = decode_utf8(&mut slice).unwrap();
assert_eq!(decoded, s);
assert!(slice.is_empty());
}
#[test]
fn should_encode_and_decode_optional_utf8_some() {
let s = Some("test");
let mut buf = BytesMut::new();
encode_optional_utf8(s, &mut buf);
let mut slice = buf.as_ref();
let decoded = decode_optional_utf8(&mut slice).unwrap();
assert_eq!(decoded, s.map(|s| s.to_string()));
assert!(slice.is_empty());
}
#[test]
fn should_encode_and_decode_optional_utf8_none() {
let s: Option<&str> = None;
let mut buf = BytesMut::new();
encode_optional_utf8(s, &mut buf);
let mut slice = buf.as_ref();
let decoded = decode_optional_utf8(&mut slice).unwrap();
assert_eq!(decoded, None);
assert!(slice.is_empty());
}
#[test]
fn should_return_error_for_truncated_utf8() {
let mut buf = BytesMut::new();
buf.extend_from_slice(&10u16.to_le_bytes()); buf.extend_from_slice(b"short");
let mut slice = buf.as_ref();
let result = decode_utf8(&mut slice);
assert!(result.is_err());
assert!(result.unwrap_err().message.contains("Buffer too short"));
}
#[test]
fn should_encode_and_decode_u64() {
let value = 0x0123456789ABCDEFu64;
let mut buf = BytesMut::new();
encode_u64(value, &mut buf);
let mut slice = buf.as_ref();
let decoded = decode_u64(&mut slice).unwrap();
assert_eq!(decoded, value);
assert!(slice.is_empty());
}
#[test]
fn should_encode_and_decode_u64_zero() {
let value = 0u64;
let mut buf = BytesMut::new();
encode_u64(value, &mut buf);
let mut slice = buf.as_ref();
let decoded = decode_u64(&mut slice).unwrap();
assert_eq!(decoded, value);
assert!(slice.is_empty());
}
#[test]
fn should_encode_and_decode_u64_max() {
let value = u64::MAX;
let mut buf = BytesMut::new();
encode_u64(value, &mut buf);
let mut slice = buf.as_ref();
let decoded = decode_u64(&mut slice).unwrap();
assert_eq!(decoded, value);
assert!(slice.is_empty());
}
#[test]
fn should_return_error_for_truncated_u64() {
let mut buf = BytesMut::new();
buf.extend_from_slice(&[1, 2, 3]);
let mut slice = buf.as_ref();
let result = decode_u64(&mut slice);
assert!(result.is_err());
assert!(result.unwrap_err().message.contains("Buffer too short"));
}
}