tbytes 0.1.0-alpha1

A tiny library for reading and writing typed data into buffers
Documentation
//! # Bytes writer.
//!
//! [`TBytesWriter`] is designed to write primitive types and fixed arrays of primitive types into
//! a provided [`TBytesWriterBackend`].
#![warn(missing_docs)]
#![deny(rustdoc::broken_intra_doc_links)]

use core::cell::Cell;

use super::errors::TBytesError;

/// Writes primitive types and arrays of primitive types into [`TBytesWriterBackend`].
#[derive(Clone, Debug)]
pub struct TBytesWriter<B: TBytesWriterBackend> {
    backend: B,
}

impl<B: TBytesWriterBackend> TBytesWriter<B> {
    /// Default constructor.
    pub fn new(backend: B) -> Self {
        Self { backend }
    }
}

/// Type-aware write interface for [`TBytesWriter`].
pub trait BytesWriterFor<T> {
    /// Writes value of type `T` and returns number of written bytes.
    fn write(&mut self, value: T) -> Result<usize, TBytesError>;
    /// Writes slice of values of type `T` and returns number of written bytes.
    fn write_slice(&mut self, values: &[T]) -> Result<usize, TBytesError>;
}

/// Implements [`BytesWriterFor`] for a specific type which implements [`num_traits::ToBytes`].
macro_rules! t_bytes_writer_for {
    ($type_: ident) => {
        impl<B: TBytesWriterBackend> BytesWriterFor<$type_> for TBytesWriter<B> {
            fn write(&mut self, value: $type_) -> Result<usize, TBytesError> {
                const SIZE: usize = core::mem::size_of::<$type_>();

                let bytes = value.to_ne_bytes();
                self.backend.write(&bytes)?;

                Ok(SIZE)
            }

            fn write_slice(&mut self, values: &[$type_]) -> Result<usize, TBytesError> {
                let mut num_bytes: usize = 0;

                for val in values {
                    let bytes = val.to_ne_bytes();
                    num_bytes += self.backend.write(&bytes)?;
                }

                Ok(num_bytes)
            }
        }
    };
}

// Implement bytes writer for primitive types
t_bytes_writer_for!(u8);
t_bytes_writer_for!(u16);
t_bytes_writer_for!(u32);
t_bytes_writer_for!(u64);
t_bytes_writer_for!(u128);
t_bytes_writer_for!(i8);
t_bytes_writer_for!(i16);
t_bytes_writer_for!(i32);
t_bytes_writer_for!(i64);
t_bytes_writer_for!(i128);
t_bytes_writer_for!(f32);
t_bytes_writer_for!(f64);

/// Backend for [`TBytesWriter`].
pub trait TBytesWriterBackend {
    /// Writes a sequence of bytes into buffer.
    fn write(&mut self, bytes: &[u8]) -> Result<usize, TBytesError>;
}

/// Backend for [`TBytesWriter`] which operates on mutable slices of bytes.
///
/// > This is not a thread-safe implementation!
#[derive(Debug)]
pub struct TBytesWriterSliceBackend<'a> {
    buffer: &'a mut [u8],
    pos: Cell<usize>,
}

impl<'a> TBytesWriterSliceBackend<'a> {
    /// Default constructor.
    pub fn new(buffer: &'a mut [u8]) -> Self {
        Self {
            buffer,
            pos: Default::default(),
        }
    }

    /// Current cursor position.
    pub fn pos(&self) -> usize {
        self.pos.get()
    }
}

impl<'a> TBytesWriterBackend for TBytesWriterSliceBackend<'a> {
    /// Writes `bytes` into slice.
    ///
    /// Returns number of written bytes or [`TBytesError`].
    fn write(&mut self, bytes: &[u8]) -> Result<usize, TBytesError> {
        let num_bytes = bytes.len();
        let pos = self.pos.get();

        if self.buffer.len() < pos + num_bytes {
            return Err(TBytesError::OutOfBounds);
        }

        let frame = &mut self.buffer[pos..pos + num_bytes];
        frame.copy_from_slice(bytes);
        self.pos.replace(pos + num_bytes);

        Ok(num_bytes)
    }
}

impl<'a> From<&'a mut [u8]> for TBytesWriter<TBytesWriterSliceBackend<'a>> {
    /// Constructs [`TBytesWriter`] from provided mutable slice.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use tbytes::{TBytesWriter, BytesWriterFor};
    ///
    /// let mut buffer = [0, 0];
    /// let mut writer = TBytesWriter::from(buffer.as_mut_slice());
    ///
    /// writer.write(511u16).unwrap();
    /// assert_eq!(&buffer, &[255, 1]);
    /// ```
    fn from(value: &'a mut [u8]) -> Self {
        TBytesWriter::new(TBytesWriterSliceBackend::new(value))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn slice_backend() {
        let mut buffer = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9u8];
        let mut backend = TBytesWriterSliceBackend::new(&mut buffer);

        let num_bytes = backend.write(&[9, 8, 7, 6, 5, 4, 3, 2, 1, 0u8]).unwrap();
        assert_eq!(num_bytes, 10);

        let err = backend.write(&[0u8]);
        assert!(err.is_err());
        assert!(matches!(err, Err(TBytesError::OutOfBounds)));

        assert_eq!(&buffer, &[9, 8, 7, 6, 5, 4, 3, 2, 1, 0u8]);
    }

    #[test]
    fn slice_backend_out_of_bouts_is_idempotent() {
        let mut buffer = [0; 10];
        let mut backend = TBytesWriterSliceBackend::new(&mut buffer);

        let num_bytes = backend.write(&[1; 10]).unwrap();
        assert_eq!(num_bytes, 10);

        let err = backend.write(&[0]);
        assert!(matches!(err, Err(TBytesError::OutOfBounds)));
        let err = backend.write(&[0]);
        assert!(matches!(err, Err(TBytesError::OutOfBounds)));

        assert_eq!(backend.pos(), buffer.len());
        assert_eq!(&buffer, &[1; 10]);
    }

    #[test]
    fn from_mut_slice() {
        let mut buffer = [0; 2];
        let mut writer = TBytesWriter::from(buffer.as_mut_slice());

        writer.write(511u16).unwrap();
        assert_eq!(&buffer, &[255, 1]);
    }

    #[test]
    fn bytes_writer_for() {
        let mut buffer = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9u8];
        let backend = TBytesWriterSliceBackend::new(&mut buffer);
        let mut writer = TBytesWriter::new(backend);

        let num_bytes = writer.write(0u64).unwrap();
        assert_eq!(num_bytes, 8);

        let num_bytes = writer.write_slice(&[1, 2u8]).unwrap();
        assert_eq!(num_bytes, 2);

        let err = writer.write(0u8);
        assert!(err.is_err());
        assert!(matches!(err, Err(TBytesError::OutOfBounds)));

        assert_eq!(&buffer, &[0, 0, 0, 0, 0, 0, 0, 0, 1, 2u8]);
    }
}