data_bucket 0.3.13

DataBucket is container for WorkTable's data
Documentation
use crate::Link;
use crate::Persistable;
use eyre::{eyre, Result};

#[derive(Debug)]
pub struct DataPage<const DATA_LENGTH: usize> {
    pub length: u32,
    pub data: [u8; DATA_LENGTH],
}

impl<const DATA_LENGTH: usize> DataPage<DATA_LENGTH> {
    pub fn update_at(&mut self, link: Link, new_data: &[u8]) -> Result<()> {
        if new_data.len() as u32 != link.length {
            return Err(eyre!(
                "New data length {} does not match link length {}",
                new_data.len(),
                link.length
            ));
        }

        if (link.offset + link.length) as usize > DATA_LENGTH {
            return Err(eyre!(
                "Link range (offset: {}, length: {}) exceeds data bounds ({})",
                link.offset,
                link.length,
                DATA_LENGTH
            ));
        }

        let start = link.offset as usize;
        let end = (link.offset + link.length) as usize;
        self.data[start..end].copy_from_slice(new_data);

        self.length = self.length.max(link.offset + link.length);
        Ok(())
    }

    pub fn get_at(&self, link: Link) -> Result<&[u8]> {
        if (link.offset + link.length) as usize > DATA_LENGTH {
            return Err(eyre!(
                "Link range (offset: {}, length: {}) exceeds data bounds ({})",
                link.offset,
                link.length,
                DATA_LENGTH
            ));
        }

        let start = link.offset as usize;
        let end = (link.offset + link.length) as usize;
        Ok(&self.data[start..end])
    }
}

impl<const DATA_LENGTH: usize> Persistable for DataPage<DATA_LENGTH> {
    fn as_bytes(&self) -> impl AsRef<[u8]> {
        &self.data[..self.length as usize]
    }

    fn from_bytes(bytes: &[u8]) -> Self {
        let mut data = [0; DATA_LENGTH];
        data.copy_from_slice(bytes);
        Self {
            length: bytes.len() as u32,
            data,
        }
    }
}

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

    #[test]
    fn test_update_at_success() {
        let mut data = DataPage {
            length: 0,
            data: [0; 100],
        };

        let link = Link {
            page_id: 1.into(),
            offset: 5,
            length: 3,
        };

        data.update_at(link, &[1, 2, 3]).unwrap();
        assert_eq!(data.get_at(link).unwrap(), &[1, 2, 3]);
        assert_eq!(data.length, 8);
    }

    #[test]
    fn test_update_at_wrong_length() {
        let mut data = DataPage {
            length: 0,
            data: [0; 100],
        };

        let link = Link {
            page_id: 1.into(),
            offset: 5,
            length: 3,
        };

        let err = data.update_at(link, &[1, 2]).unwrap_err();
        assert!(err
            .to_string()
            .contains("New data length 2 does not match link length 3"));
    }

    #[test]
    fn test_update_at_out_of_bounds() {
        let mut data = DataPage {
            length: 0,
            data: [0; 100],
        };

        let link = Link {
            page_id: 1.into(),
            offset: 98,
            length: 3,
        };

        let err = data.update_at(link, &[1, 2, 3]).unwrap_err();
        assert!(err
            .to_string()
            .contains("Link range (offset: 98, length: 3) exceeds data bounds (100)"));
    }

    #[test]
    fn test_get_at_out_of_bounds() {
        let data = DataPage {
            length: 0,
            data: [0; 100],
        };

        let link = Link {
            page_id: 1.into(),
            offset: 98,
            length: 3,
        };

        let err = data.get_at(link).unwrap_err();
        assert!(err
            .to_string()
            .contains("Link range (offset: 98, length: 3) exceeds data bounds (100)"));
    }
}