compact-thrift-runtime 0.2.1

Runtime library for compact-thrift code generator
Documentation
use std::borrow::Cow;
use std::rc::Rc;
use std::sync::Arc;
use crate::protocol::*;
use crate::ThriftError;

/// For structs, the following field-types can be encoded:
///
///  - BOOLEAN_TRUE, encoded as 1
///  - BOOLEAN_FALSE, encoded as 2
///
/// For lists and sets the following element-types are used (see note 1 below):
///
///  - BOOL, encoded as 1 or 2 (see note below)
///
/// The only valid value in the original spec was 2, but due to an widespread implementation
/// bug the defacto standard across large parts of the library became 1 instead.
/// As a result, both values are now allowed.
///
/// Element values of type bool are sent as an int8; true as 1 and false as 2.
///
/// Previous versions of the spec said true is 1 and false is 0,
/// so we are a bit more lenient and support both when reading.
impl <'i> CompactThriftProtocol<'i> for bool {
    const FIELD_TYPE: u8 = 2; // TRUE = 1, FALSE = 2

    #[inline]
    fn fill_thrift<T: CompactThriftInput<'i>>(&mut self, input: &mut T) -> Result<(), ThriftError> {
        let byte = input.read_byte()?;
        *self = byte == 1;
        Ok(())
    }

    #[inline]
    fn fill_thrift_field<T: CompactThriftInput<'i>>(&mut self, _input: &mut T, field_type: u8) -> Result<(), ThriftError> {
        *self = field_type == 1;
        Ok(())
    }

    #[inline]
    fn write_thrift<T: CompactThriftOutput>(&self, output: &mut T) -> Result<(), ThriftError> {
        let value = 1 + (!*self) as u8;
        output.write_byte(value)
    }

    fn write_thrift_field<T: CompactThriftOutput>(&self, output: &mut T, field_id: i16, last_field_id: &mut i16) -> Result<(), ThriftError> {
        let field_type = 1 + (!*self) as u8;
        write_field_header(output, field_type, field_id, last_field_id)
    }
}

impl <'i> CompactThriftProtocol<'i> for i8 {
    const FIELD_TYPE: u8 = 3;

    #[inline]
    fn fill_thrift<T: CompactThriftInput<'i>>(&mut self, input: &mut T) -> Result<(), ThriftError> {
        *self = input.read_byte()? as i8;
        Ok(())
    }

    #[inline]
    fn write_thrift<T: CompactThriftOutput>(&self, output: &mut T) -> Result<(), ThriftError> {
        output.write_byte(*self as u8)
    }
}

impl <'i> CompactThriftProtocol<'i> for i16 {
    const FIELD_TYPE: u8 = 4;

    #[inline]
    fn fill_thrift<T: CompactThriftInput<'i>>(&mut self, input: &mut T) -> Result<(), ThriftError> {
        *self = input.read_i16()?;
        Ok(())
    }

    #[inline]
    fn write_thrift<T: CompactThriftOutput>(&self, output: &mut T) -> Result<(), ThriftError> {
        output.write_i16(*self)
    }
}

impl <'i> CompactThriftProtocol<'i> for i32 {
    const FIELD_TYPE: u8 = 5;

    #[inline]
    fn fill_thrift<T: CompactThriftInput<'i>>(&mut self, input: &mut T) -> Result<(), ThriftError> {
        *self = input.read_i32()?;
        Ok(())
    }

    #[inline]
    fn write_thrift<T: CompactThriftOutput>(&self, output: &mut T) -> Result<(), ThriftError> {
        output.write_i32(*self)
    }
}

impl <'i> CompactThriftProtocol<'i> for i64 {
    const FIELD_TYPE: u8 = 6;

    #[inline]
    fn fill_thrift<T: CompactThriftInput<'i>>(&mut self, input: &mut T) -> Result<(), ThriftError> {
        *self = input.read_i64()?;
        Ok(())
    }

    #[inline]
    fn write_thrift<T: CompactThriftOutput>(&self, output: &mut T) -> Result<(), ThriftError> {
        output.write_i64(*self)
    }
}

impl <'i> CompactThriftProtocol<'i> for f64 {
    const FIELD_TYPE: u8 = 7;

    #[inline]
    fn fill_thrift<T: CompactThriftInput<'i>>(&mut self, input: &mut T) -> Result<(), ThriftError> {
        *self = input.read_double()?;
        Ok(())
    }

    #[inline]
    fn write_thrift<T: CompactThriftOutput>(&self, output: &mut T) -> Result<(), ThriftError> {
        output.write_double(*self)
    }
}

impl <'i> CompactThriftProtocol<'i> for Vec<u8> {
    const FIELD_TYPE: u8 = 8;

    #[inline]
    fn fill_thrift<T: CompactThriftInput<'i>>(&mut self, input: &mut T) -> Result<(), ThriftError> {
        *self = input.read_binary()?.into_owned();
        Ok(())
    }

    #[inline]
    fn write_thrift<T: CompactThriftOutput>(&self, output: &mut T) -> Result<(), ThriftError> {
        output.write_binary(self.as_slice())
    }
}

impl <'i> CompactThriftProtocol<'i> for String {
    // Same type as for Binary.
    const FIELD_TYPE: u8 = 8;

    #[inline]
    fn fill_thrift<T: CompactThriftInput<'i>>(&mut self, input: &mut T) -> Result<(), ThriftError> {
        *self = input.read_string()?.into_owned();
        Ok(())
    }

    #[inline]
    fn write_thrift<T: CompactThriftOutput>(&self, output: &mut T) -> Result<(), ThriftError> {
        output.write_string(self.as_str())
    }
}

impl <'i> CompactThriftProtocol<'i> for Cow<'i, [u8]> {
    const FIELD_TYPE: u8 = 8;

    #[inline]
    fn fill_thrift<T: CompactThriftInput<'i>>(&mut self, input: &mut T) -> Result<(), ThriftError> {
        *self = input.read_binary()?;
        Ok(())
    }

    #[inline]
    fn write_thrift<T: CompactThriftOutput>(&self, output: &mut T) -> Result<(), ThriftError> {
        output.write_binary(self.as_ref())
    }
}

impl <'i> CompactThriftProtocol<'i> for Cow<'i, str> {
    const FIELD_TYPE: u8 = 8;

    #[inline]
    fn fill_thrift<T: CompactThriftInput<'i>>(&mut self, input: &mut T) -> Result<(), ThriftError> {
        *self = input.read_string()?;
        Ok(())
    }

    #[inline]
    fn write_thrift<T: CompactThriftOutput>(&self, output: &mut T) -> Result<(), ThriftError> {
        output.write_string(self.as_ref())
    }
}

impl <'i, P: CompactThriftProtocol<'i> + Default> CompactThriftProtocol<'i> for Vec<P> {
    const FIELD_TYPE: u8 = 9;

    fn fill_thrift<T: CompactThriftInput<'i>>(&mut self, input: &mut T) -> Result<(), ThriftError> {
        let (len, element_type) = read_collection_len_and_type(input)?;
        // Special case for boolean elements:
        // The only valid value in the original spec was 2, but due to a widespread implementation
        // bug the defacto standard across large parts of the library became 1 instead.
        if element_type != P::FIELD_TYPE && !(P::FIELD_TYPE == bool::FIELD_TYPE && element_type == 1) {
            return Err(ThriftError::InvalidType);
        }
        self.clear();
        self.try_reserve(len as usize).map_err(|_| ThriftError::ReserveError)?;

        for _ in 0..len as usize {
            // workaround for unnecessary memcpy calls when using Vec::push(P::default()) with larger structs
            // https://github.com/rust-lang/rust/issues/125632
            self.extend((0..1).map(|_| P::default()));
            // Safety: we just pushed an element
            unsafe {
                self.last_mut().unwrap_unchecked().fill_thrift(input)?;
            }
        }

        Ok(())
    }

    fn write_thrift<T: CompactThriftOutput>(&self, output: &mut T) -> Result<(), ThriftError> {
        let len = self.len();
        if len > MAX_COLLECTION_LEN {
            return Err(ThriftError::InvalidCollectionLen);
        }

        if len < 15 {
            let header = P::FIELD_TYPE | ((len as u8) << 4);
            output.write_byte(header)?;
        } else {
            output.write_byte(P::FIELD_TYPE | 0xF0)?;
            output.write_len(len)?;
        }
        self.iter().try_for_each(|value| {
            value.write_thrift(output)
        })
    }
}

impl <'i, P: CompactThriftProtocol<'i> + Default> CompactThriftProtocol<'i> for Option<P> {
    const FIELD_TYPE: u8 = P::FIELD_TYPE;

    #[inline]
    fn fill_thrift<T: CompactThriftInput<'i>>(&mut self, input: &mut T) -> Result<(), ThriftError> {
        if self.is_some() {
            return Err(ThriftError::DuplicateField);
        }
        // Safety: avoid generating drop calls, content is always None because of check above
        unsafe {
            std::ptr::write(self as *mut _, Some(P::default()));
            self.as_mut().unwrap_unchecked().fill_thrift(input)?;
        }
        Ok(())
    }

    #[inline]
    fn fill_thrift_field<T: CompactThriftInput<'i>>(&mut self, input: &mut T, field_type: u8) -> Result<(), ThriftError> {
        if self.is_some() {
            return Err(ThriftError::DuplicateField);
        }
        // Safety: avoid generating drop calls, content is always None because of check above
        unsafe {
            std::ptr::write(self as *mut _, Some(P::default()));
            self.as_mut().unwrap_unchecked().fill_thrift_field(input, field_type)?;
        }
        Ok(())
    }


    #[inline]
    fn write_thrift<T: CompactThriftOutput>(&self, output: &mut T) -> Result<(), ThriftError> {
        if let Some(value) = self.as_ref() {
            value.write_thrift(output)
        } else {
            Err(ThriftError::MissingValue)
        }
    }

    #[inline]
    fn write_thrift_field<T: CompactThriftOutput>(&self, output: &mut T, field_id: i16, last_field_id: &mut i16) -> Result<(), ThriftError> {
        // only write if present
        if let Some(value) = self.as_ref() {
            value.write_thrift_field(output, field_id, last_field_id)?;
        }
        Ok(())
    }
}

impl <'i, P: CompactThriftProtocol<'i> + Default> CompactThriftProtocol<'i> for Box<P> {
    const FIELD_TYPE: u8 = P::FIELD_TYPE;

    #[inline]
    fn fill_thrift<T: CompactThriftInput<'i>>(&mut self, input: &mut T) -> Result<(), ThriftError> {
        Box::as_mut(self).fill_thrift(input)
    }

    #[inline]
    fn write_thrift<T: CompactThriftOutput>(&self, output: &mut T) -> Result<(), ThriftError> {
        Box::as_ref(self).write_thrift(output)
    }
}

impl <'i, P: CompactThriftProtocol<'i> + Default> CompactThriftProtocol<'i> for Rc<P> {
    const FIELD_TYPE: u8 = P::FIELD_TYPE;

    #[inline]
    fn fill_thrift<T: CompactThriftInput<'i>>(&mut self, input: &mut T) -> Result<(), ThriftError> {
        Rc::get_mut(self).ok_or(ThriftError::DuplicateField)?.fill_thrift(input)
    }

    #[inline]
    fn write_thrift<T: CompactThriftOutput>(&self, output: &mut T) -> Result<(), ThriftError> {
        Rc::as_ref(self).write_thrift(output)
    }
}

impl <'i, P: CompactThriftProtocol<'i> + Default> CompactThriftProtocol<'i> for Arc<P> {
    const FIELD_TYPE: u8 = P::FIELD_TYPE;

    #[inline]
    fn fill_thrift<T: CompactThriftInput<'i>>(&mut self, input: &mut T) -> Result<(), ThriftError> {
        Arc::get_mut(self).ok_or(ThriftError::DuplicateField)?.fill_thrift(input)
    }

    #[inline]
    fn write_thrift<T: CompactThriftOutput>(&self, output: &mut T) -> Result<(), ThriftError> {
        Arc::as_ref(self).write_thrift(output)
    }
}

impl <'i> CompactThriftProtocol<'i> for Box<str> {
    const FIELD_TYPE: u8 = String::FIELD_TYPE;

    #[inline]
    fn fill_thrift<T: CompactThriftInput<'i>>(&mut self, input: &mut T) -> Result<(), ThriftError> {
        *self = input.read_string()?.into_owned().into_boxed_str();
        Ok(())
    }

    #[inline]
    fn write_thrift<T: CompactThriftOutput>(&self, output: &mut T) -> Result<(), ThriftError> {
        output.write_string(self.as_ref())
    }
}

impl <'i> CompactThriftProtocol<'i> for Rc<str> {
    const FIELD_TYPE: u8 = String::FIELD_TYPE;

    #[inline]
    fn fill_thrift<T: CompactThriftInput<'i>>(&mut self, input: &mut T) -> Result<(), ThriftError> {
        *self = match input.read_string()? {
            Cow::Borrowed(s) => Rc::from(s),
            Cow::Owned(s) => Rc::from(s),
        };
        Ok(())
    }

    #[inline]
    fn write_thrift<T: CompactThriftOutput>(&self, output: &mut T) -> Result<(), ThriftError> {
        output.write_string(self.as_ref())
    }
}

impl <'i> CompactThriftProtocol<'i> for Arc<str> {
    const FIELD_TYPE: u8 = String::FIELD_TYPE;

    #[inline]
    fn fill_thrift<T: CompactThriftInput<'i>>(&mut self, input: &mut T) -> Result<(), ThriftError> {
        *self = match input.read_string()? {
            Cow::Borrowed(s) => Arc::from(s),
            Cow::Owned(s) => Arc::from(s),
        };
        Ok(())
    }

    #[inline]
    fn write_thrift<T: CompactThriftOutput>(&self, output: &mut T) -> Result<(), ThriftError> {
        output.write_string(self.as_ref())
    }
}


#[cfg(test)]
mod tests {
    use crate::{CompactThriftInputSlice, CompactThriftProtocol};

    #[test]
    fn test_encode_boolean_list() {
        let mut output = vec![];
        vec![false, true, true].write_thrift(&mut output).unwrap();
        assert_eq!(&output, &[0b0011_0010, 2, 1, 1]);
    }
    #[test]
    fn test_decode_boolean_list_tag1() {
        let input = vec![0b0011_0001_u8, 2, 1, 0];
        let values = Vec::<bool>::read_thrift(&mut CompactThriftInputSlice::new(&input)).unwrap();
        assert_eq!(&values, &[false, true, false]);
    }
    #[test]
    fn test_decode_boolean_list_tag2() {
        let input = vec![0b0011_0010_u8, 2, 1, 0];
        let values = Vec::<bool>::read_thrift(&mut CompactThriftInputSlice::new(&input)).unwrap();
        assert_eq!(&values, &[false, true, false]);
    }
}