reddb-io-file 1.11.0

RedDB file artifact layer: single-file .rdb layout, WAL, snapshots, checkpoints, locks, and recovery.
Documentation
use super::*;

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ServerlessManifestEntry {
    pub kind: ServerlessPackKind,
    pub relative_path: PathBuf,
    pub bytes: u64,
    pub checksum: u32,
    pub content_hash: ServerlessContentHash,
}

impl ServerlessManifestEntry {
    pub fn new(
        kind: ServerlessPackKind,
        relative_path: impl Into<PathBuf>,
        bytes: u64,
        checksum: u32,
    ) -> Self {
        Self {
            kind,
            relative_path: relative_path.into(),
            bytes,
            checksum,
            content_hash: ServerlessContentHash::ZERO,
        }
    }

    pub fn from_bytes(
        kind: ServerlessPackKind,
        relative_path: impl Into<PathBuf>,
        payload: &[u8],
    ) -> Self {
        Self {
            kind,
            relative_path: relative_path.into(),
            bytes: payload.len() as u64,
            checksum: crc32(payload),
            content_hash: ServerlessContentHash::from_bytes(payload),
        }
    }

    pub fn with_content_hash(mut self, content_hash: ServerlessContentHash) -> Self {
        self.content_hash = content_hash;
        self
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ServerlessManifest {
    pub namespace: String,
    pub generation: u64,
    pub entries: Vec<ServerlessManifestEntry>,
}

impl ServerlessManifest {
    pub fn new(namespace: impl Into<String>, generation: u64) -> Self {
        Self {
            namespace: namespace.into(),
            generation,
            entries: Vec::new(),
        }
    }

    pub fn push(&mut self, entry: ServerlessManifestEntry) {
        self.entries.push(entry);
        self.entries.sort_by_key(|entry| {
            (
                u8::from(entry.kind),
                entry.relative_path.to_string_lossy().to_string(),
            )
        });
    }

    pub fn write_to_path(&self, path: impl AsRef<Path>) -> RdbFileResult<()> {
        write_bytes(path, &self.encode())
    }

    pub fn read_from_path(path: impl AsRef<Path>) -> RdbFileResult<Self> {
        Self::decode(&fs::read(path)?)
    }

    pub fn encode(&self) -> Vec<u8> {
        let mut out = Vec::new();
        out.extend_from_slice(SERVERLESS_MANIFEST_MAGIC);
        put_u16(&mut out, SERVERLESS_ARTIFACT_VERSION);
        put_u64(&mut out, self.generation);
        put_string(&mut out, &self.namespace);
        put_u32(&mut out, self.entries.len() as u32);
        for entry in &self.entries {
            out.push(u8::from(entry.kind));
            put_string(&mut out, &entry.relative_path.to_string_lossy());
            put_u64(&mut out, entry.bytes);
            put_u32(&mut out, entry.checksum);
            put_content_hash(&mut out, entry.content_hash);
        }
        let checksum = crc32(&out);
        put_u32(&mut out, checksum);
        out
    }

    pub fn decode(bytes: &[u8]) -> RdbFileResult<Self> {
        verify_checksum(bytes)?;
        let mut cursor = 0usize;
        expect_magic(bytes, &mut cursor, SERVERLESS_MANIFEST_MAGIC)?;
        let version = take_u16(bytes, &mut cursor)?;
        if version != SERVERLESS_ARTIFACT_VERSION {
            return Err(RdbFileError::InvalidOperation(format!(
                "unsupported serverless manifest version {version}"
            )));
        }
        let generation = take_u64(bytes, &mut cursor)?;
        let namespace = take_string(bytes, &mut cursor)?;
        let count = take_u32(bytes, &mut cursor)? as usize;
        let mut entries = Vec::with_capacity(count);
        for _ in 0..count {
            let kind = ServerlessPackKind::try_from(take_u8(bytes, &mut cursor)?)?;
            let relative_path = PathBuf::from(take_string(bytes, &mut cursor)?);
            let bytes_len = take_u64(bytes, &mut cursor)?;
            let checksum = take_u32(bytes, &mut cursor)?;
            let content_hash = take_content_hash(bytes, &mut cursor)?;
            entries.push(ServerlessManifestEntry {
                kind,
                relative_path,
                bytes: bytes_len,
                checksum,
                content_hash,
            });
        }
        reject_trailing_bytes(bytes, cursor)?;
        Ok(Self {
            namespace,
            generation,
            entries,
        })
    }
}