crystal/
lib.rs

1use std::collections::HashMap;
2use std::fs;
3use std::path::Path;
4use bincode::{serialize, deserialize};
5use std::io::{Error, ErrorKind};
6
7#[derive(Debug)]
8pub struct KvStore {
9    keys: HashMap<String, String>,
10    storage_path: String,
11}
12
13impl KvStore {
14    pub fn new(storage_path: &str) -> Result<KvStore, Error> {
15        fs::create_dir_all(storage_path)?;
16        Ok(KvStore {
17            keys: HashMap::new(),
18            storage_path: storage_path.to_string(),
19        })
20    }
21
22    pub fn set(&mut self, key: String, value: String) -> Result<(), Error> {
23        let file_path = format!("{}/{}.bin", self.storage_path, key);
24        let encoded: Vec<u8> = serialize(&value).map_err(|e| Error::new(ErrorKind::Other, e))?;
25        fs::write(file_path, encoded)?;
26        self.keys.insert(key, value);
27        Ok(())
28    }
29
30    pub fn get(&self, key: &str) -> Result<Option<String>, Error> {
31        if let Some(value) = self.keys.get(key) {
32            return Ok(Some(value.clone()));
33        }
34
35        let file_path = format!("{}/{}.bin", self.storage_path, key);
36        if !Path::new(&file_path).exists() {
37            return Ok(None);
38        }
39
40        let data = fs::read(file_path)?;
41        let decoded: String = deserialize(&data).map_err(|e| Error::new(ErrorKind::Other, e))?;
42        Ok(Some(decoded))
43    }
44
45    pub fn remove(&mut self, key: &str) -> Result<(), Error> {
46        let file_path = format!("{}/{}.bin", self.storage_path, key);
47        if Path::new(&file_path).exists() {
48            fs::remove_file(file_path)?;
49        }
50        self.keys.remove(key);
51        Ok(())
52    }
53}
54
55#[cfg(test)]
56mod tests {
57    use super::*;
58    use tempfile::tempdir;
59
60    #[test]
61    fn test_set_get() {
62        let dir = tempdir().unwrap();
63        let mut store = KvStore::new(dir.path().to_str().unwrap()).unwrap();
64        
65        store.set("key1".to_string(), "value1".to_string()).unwrap();
66        assert_eq!(store.get("key1").unwrap(), Some("value1".to_string()));
67    }
68
69    #[test]
70    fn test_get_non_existent() {
71        let dir = tempdir().unwrap();
72        let store = KvStore::new(dir.path().to_str().unwrap()).unwrap();
73        
74        assert_eq!(store.get("nonexistent").unwrap(), None);
75    }
76
77    #[test]
78    fn test_remove() {
79        let dir = tempdir().unwrap();
80        let mut store = KvStore::new(dir.path().to_str().unwrap()).unwrap();
81        
82        store.set("key1".to_string(), "value1".to_string()).unwrap();
83        store.remove("key1").unwrap();
84        assert_eq!(store.get("key1").unwrap(), None);
85    }
86
87    #[test]
88    fn test_persistence() {
89        let dir = tempdir().unwrap();
90        let path = dir.path().to_str().unwrap();
91        
92        {
93            let mut store = KvStore::new(path).unwrap();
94            store.set("key1".to_string(), "value1".to_string()).unwrap();
95        }
96        
97        {
98            let store = KvStore::new(path).unwrap();
99            assert_eq!(store.get("key1").unwrap(), Some("value1".to_string()));
100        }
101    }
102
103    #[test]
104    fn test_overwrite() {
105        let dir = tempdir().unwrap();
106        let mut store = KvStore::new(dir.path().to_str().unwrap()).unwrap();
107        
108        store.set("key1".to_string(), "value1".to_string()).unwrap();
109        store.set("key1".to_string(), "value2".to_string()).unwrap();
110        assert_eq!(store.get("key1").unwrap(), Some("value2".to_string()));
111    }
112}