crystal 0.0.0

Crystal, a simple database.
Documentation
use std::collections::HashMap;
use std::fs;
use std::path::Path;
use bincode::{serialize, deserialize};
use std::io::{Error, ErrorKind};

#[derive(Debug)]
pub struct KvStore {
    keys: HashMap<String, String>,
    storage_path: String,
}

impl KvStore {
    pub fn new(storage_path: &str) -> Result<KvStore, Error> {
        fs::create_dir_all(storage_path)?;
        Ok(KvStore {
            keys: HashMap::new(),
            storage_path: storage_path.to_string(),
        })
    }

    pub fn set(&mut self, key: String, value: String) -> Result<(), Error> {
        let file_path = format!("{}/{}.bin", self.storage_path, key);
        let encoded: Vec<u8> = serialize(&value).map_err(|e| Error::new(ErrorKind::Other, e))?;
        fs::write(file_path, encoded)?;
        self.keys.insert(key, value);
        Ok(())
    }

    pub fn get(&self, key: &str) -> Result<Option<String>, Error> {
        if let Some(value) = self.keys.get(key) {
            return Ok(Some(value.clone()));
        }

        let file_path = format!("{}/{}.bin", self.storage_path, key);
        if !Path::new(&file_path).exists() {
            return Ok(None);
        }

        let data = fs::read(file_path)?;
        let decoded: String = deserialize(&data).map_err(|e| Error::new(ErrorKind::Other, e))?;
        Ok(Some(decoded))
    }

    pub fn remove(&mut self, key: &str) -> Result<(), Error> {
        let file_path = format!("{}/{}.bin", self.storage_path, key);
        if Path::new(&file_path).exists() {
            fs::remove_file(file_path)?;
        }
        self.keys.remove(key);
        Ok(())
    }
}

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

    #[test]
    fn test_set_get() {
        let dir = tempdir().unwrap();
        let mut store = KvStore::new(dir.path().to_str().unwrap()).unwrap();
        
        store.set("key1".to_string(), "value1".to_string()).unwrap();
        assert_eq!(store.get("key1").unwrap(), Some("value1".to_string()));
    }

    #[test]
    fn test_get_non_existent() {
        let dir = tempdir().unwrap();
        let store = KvStore::new(dir.path().to_str().unwrap()).unwrap();
        
        assert_eq!(store.get("nonexistent").unwrap(), None);
    }

    #[test]
    fn test_remove() {
        let dir = tempdir().unwrap();
        let mut store = KvStore::new(dir.path().to_str().unwrap()).unwrap();
        
        store.set("key1".to_string(), "value1".to_string()).unwrap();
        store.remove("key1").unwrap();
        assert_eq!(store.get("key1").unwrap(), None);
    }

    #[test]
    fn test_persistence() {
        let dir = tempdir().unwrap();
        let path = dir.path().to_str().unwrap();
        
        {
            let mut store = KvStore::new(path).unwrap();
            store.set("key1".to_string(), "value1".to_string()).unwrap();
        }
        
        {
            let store = KvStore::new(path).unwrap();
            assert_eq!(store.get("key1").unwrap(), Some("value1".to_string()));
        }
    }

    #[test]
    fn test_overwrite() {
        let dir = tempdir().unwrap();
        let mut store = KvStore::new(dir.path().to_str().unwrap()).unwrap();
        
        store.set("key1".to_string(), "value1".to_string()).unwrap();
        store.set("key1".to_string(), "value2".to_string()).unwrap();
        assert_eq!(store.get("key1").unwrap(), Some("value2".to_string()));
    }
}