#![allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum ThriftType {
Stop = 0,
Bool = 2,
Byte = 3,
Double = 4,
I16 = 6,
I32 = 8,
I64 = 10,
String = 11,
Struct = 12,
Map = 13,
Set = 14,
List = 15,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ThriftValue {
Bool(bool),
Byte(u8),
I16(i16),
I32(i32),
I64(i64),
Double(f64),
String(String),
Struct(Vec<ThriftField>),
List(ThriftType, Vec<ThriftValue>),
}
#[derive(Debug, Clone, PartialEq)]
pub struct ThriftField {
pub field_id: i16,
pub field_type: ThriftType,
pub value: ThriftValue,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ThriftError {
UnexpectedEnd,
UnknownType(u8),
InvalidFieldId,
}
impl std::fmt::Display for ThriftError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::UnexpectedEnd => write!(f, "unexpected end of Thrift buffer"),
Self::UnknownType(t) => write!(f, "unknown Thrift type: {t}"),
Self::InvalidFieldId => write!(f, "invalid Thrift field ID"),
}
}
}
pub fn encode_i32(value: i32, buf: &mut Vec<u8>) {
buf.extend_from_slice(&value.to_be_bytes());
}
pub fn decode_i32(buf: &[u8], offset: usize) -> Result<i32, ThriftError> {
if offset + 4 > buf.len() {
return Err(ThriftError::UnexpectedEnd);
}
let bytes: [u8; 4] = buf[offset..offset + 4].try_into().unwrap_or_default();
Ok(i32::from_be_bytes(bytes))
}
pub fn encode_string(s: &str, buf: &mut Vec<u8>) {
encode_i32(s.len() as i32, buf);
buf.extend_from_slice(s.as_bytes());
}
pub fn encode_field_header(field_type: ThriftType, field_id: i16, buf: &mut Vec<u8>) {
buf.push(field_type as u8);
buf.extend_from_slice(&field_id.to_be_bytes());
}
pub fn struct_field_count(val: &ThriftValue) -> usize {
if let ThriftValue::Struct(fields) = val {
fields.len()
} else {
0
}
}
pub fn is_struct(val: &ThriftValue) -> bool {
matches!(val, ThriftValue::Struct(_))
}
pub fn type_of(val: &ThriftValue) -> ThriftType {
match val {
ThriftValue::Bool(_) => ThriftType::Bool,
ThriftValue::Byte(_) => ThriftType::Byte,
ThriftValue::I16(_) => ThriftType::I16,
ThriftValue::I32(_) => ThriftType::I32,
ThriftValue::I64(_) => ThriftType::I64,
ThriftValue::Double(_) => ThriftType::Double,
ThriftValue::String(_) => ThriftType::String,
ThriftValue::Struct(_) => ThriftType::Struct,
ThriftValue::List(_, _) => ThriftType::List,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_encode_i32_zero() {
let mut buf = vec![];
encode_i32(0, &mut buf);
assert_eq!(buf, &[0, 0, 0, 0]);
}
#[test]
fn test_encode_decode_i32_roundtrip() {
let mut buf = vec![];
encode_i32(12345, &mut buf);
assert_eq!(decode_i32(&buf, 0).expect("should succeed"), 12345);
}
#[test]
fn test_decode_i32_short_buffer() {
assert!(decode_i32(&[0, 0], 0).is_err());
}
#[test]
fn test_encode_string() {
let mut buf = vec![];
encode_string("hi", &mut buf);
assert_eq!(decode_i32(&buf, 0).expect("should succeed"), 2);
assert_eq!(&buf[4..], b"hi");
}
#[test]
fn test_encode_field_header() {
let mut buf = vec![];
encode_field_header(ThriftType::I32, 1, &mut buf);
assert_eq!(buf.len(), 3);
assert_eq!(buf[0], ThriftType::I32 as u8);
}
#[test]
fn test_struct_field_count() {
let val = ThriftValue::Struct(vec![ThriftField {
field_id: 1,
field_type: ThriftType::I32,
value: ThriftValue::I32(0),
}]);
assert_eq!(struct_field_count(&val), 1);
}
#[test]
fn test_is_struct_true() {
let val = ThriftValue::Struct(vec![]);
assert!(is_struct(&val));
}
#[test]
fn test_type_of_bool() {
assert_eq!(type_of(&ThriftValue::Bool(true)), ThriftType::Bool);
}
#[test]
fn test_type_of_string() {
assert_eq!(
type_of(&ThriftValue::String("x".to_string())),
ThriftType::String
);
}
#[test]
fn test_decode_i32_offset() {
let mut buf = vec![0u8; 4];
buf.extend_from_slice(&42i32.to_be_bytes());
assert_eq!(decode_i32(&buf, 4).expect("should succeed"), 42);
}
}