libvctrl_core 3.2.0

Reference implementations of the libvctrl contracts (in-memory store, SHA-512 hasher, binary codec)
Documentation
use alloc::vec::IntoIter;
use std::collections::HashMap;

use libvctrl_handler::{Hash, RefStore, VctrlError};

#[derive(Debug, Default)]
pub struct MemoryRefStore {
    refs: HashMap<String, Hash>,
}

impl MemoryRefStore {
    #[must_use]
    pub fn new() -> Self {
        Self {
            refs: HashMap::new(),
        }
    }
}

impl RefStore for MemoryRefStore {
    type RefsIterator = IntoIter<Result<String, VctrlError>>;

    fn set_ref(&mut self, name: &str, hash: &Hash) -> Result<(), VctrlError> {
        libvctrl_handler::validate_ref_name(name)?;
        let _ = self.refs.insert(name.to_string(), *hash);
        Ok(())
    }

    fn get_ref(&self, name: &str) -> Result<Hash, VctrlError> {
        self.refs
            .get(name)
            .copied()
            .ok_or_else(|| VctrlError::RefNotFound(name.into()))
    }

    fn delete_ref(&mut self, name: &str) -> Result<(), VctrlError> {
        let _ = self.refs.remove(name);
        Ok(())
    }

    fn list_refs(&self) -> Result<Self::RefsIterator, VctrlError> {
        let mut names: Vec<String> = self.refs.keys().cloned().collect();
        names.sort();
        Ok(names.into_iter().map(Ok).collect::<Vec<_>>().into_iter())
    }
}

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

    fn hash_byte(byte: u8) -> Result<Hash, VctrlError> {
        Hash::from_bytes(&[byte; 64])
    }

    #[test]
    fn set_and_get_ref_roundtrip() -> Result<(), VctrlError> {
        let mut store = MemoryRefStore::new();
        let hash = hash_byte(0xAB)?;

        store.set_ref("refs/heads/main", &hash)?;
        let got = store.get_ref("refs/heads/main")?;
        assert_eq!(got, hash);
        Ok(())
    }

    #[test]
    fn set_ref_invalid_name_errors() -> Result<(), VctrlError> {
        let mut store = MemoryRefStore::new();
        let hash = hash_byte(0xCD)?;
        assert!(store.set_ref("bad name", &hash).is_err());
        Ok(())
    }

    #[test]
    fn get_ref_missing_errors() {
        let store = MemoryRefStore::new();
        let result = store.get_ref("refs/heads/nope");
        assert!(matches!(result, Err(VctrlError::RefNotFound(_))));
    }

    #[test]
    fn delete_ref_removes_ref() -> Result<(), VctrlError> {
        let mut store = MemoryRefStore::new();
        let hash = hash_byte(0xEF)?;
        store.set_ref("refs/tags/v1", &hash)?;
        store.delete_ref("refs/tags/v1")?;
        assert!(store.get_ref("refs/tags/v1").is_err());
        Ok(())
    }

    #[test]
    fn list_refs_sorted() -> Result<(), VctrlError> {
        let mut store = MemoryRefStore::new();
        let h1 = hash_byte(0x01)?;
        let h2 = hash_byte(0x02)?;
        store.set_ref("refs/heads/b", &h1)?;
        store.set_ref("refs/heads/a", &h2)?;

        let names: Vec<String> = store.list_refs()?.collect::<Result<_, _>>()?;
        assert_eq!(
            names,
            vec!["refs/heads/a".to_string(), "refs/heads/b".to_string()]
        );
        Ok(())
    }
}