Skip to main content

gen_memory/
store.rs

1use std::collections::HashMap;
2use std::collections::HashSet;
3use std::sync::Mutex;
4use std::sync::OnceLock;
5use std::io;
6
7use crate::bucket::Bucket;
8
9#[derive(Default)]
10pub struct Store {
11    pub(crate) buckets: HashSet<Bucket>,
12    pub(crate) entries: HashMap<Bucket, HashMap<String, String>>,
13}
14
15static STORE: OnceLock<Mutex<Store>> = OnceLock::new();
16
17pub(crate) fn store() -> &'static Mutex<Store> {
18    STORE.get_or_init(|| Mutex::new(Store::default()))
19}
20
21pub fn create(bucket: &Bucket) -> io::Result<()> {
22    let mut st: std::sync::MutexGuard<'_, Store> = store().lock().map_err(|_| {
23        io::Error::new(io::ErrorKind::Other, "ram store poisoned")
24    })?;
25
26    st.buckets.insert(bucket.clone());
27    st.entries.entry(bucket.clone()).or_insert_with(HashMap::new);
28    Ok(())
29}
30
31pub fn remove_if_exists(bucket: &Bucket) -> io::Result<()> {
32    let mut st: std::sync::MutexGuard<'_, Store> = store().lock().map_err(|_| {
33        io::Error::new(io::ErrorKind::Other, "ram store poisoned")
34    })?;
35    st.buckets.remove(bucket);
36    st.entries.remove(bucket);
37    Ok(())
38}
39
40pub fn exists(bucket: &Bucket) -> io::Result<bool> {
41    let st: std::sync::MutexGuard<'_, Store> = store().lock().map_err(|_| {
42        io::Error::new(io::ErrorKind::Other, "ram store poisoned")
43    })?;
44    Ok(st.buckets.contains(bucket))
45}
46
47pub fn list_keys(bucket: &Bucket) -> io::Result<Vec<String>> {
48    let st: std::sync::MutexGuard<'_, Store> = store().lock().map_err(|_| {
49        io::Error::new(io::ErrorKind::Other, "ram store poisoned")
50    })?;
51
52    ensure_bucket_exists_locked(&st, bucket)?;
53
54    let map: &HashMap<String, String> = st
55        .entries
56        .get(bucket)
57        .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "Bucket not found"))?;
58
59    Ok(map.keys().cloned().collect())
60}
61
62pub(crate) fn ensure_bucket_exists_locked(st: &Store, bucket: &Bucket) -> io::Result<()> {
63    if !st.buckets.contains(bucket) {
64        return Err(io::Error::new(io::ErrorKind::NotFound, "Bucket not found"));
65    }
66    Ok(())
67}
68
69pub(crate) fn validate_key(key: &str) -> io::Result<()> {
70    if key.is_empty() {
71        return Err(io::Error::new(
72            io::ErrorKind::InvalidInput,
73            "Entry key is empty",
74        ));
75    }
76    if key == "." || key == ".." {
77        return Err(io::Error::new(
78            io::ErrorKind::InvalidInput,
79            "Entry key cannot be '.' or '..'",
80        ));
81    }
82    let ok: bool = key.chars().all(|c| {
83        c.is_ascii_alphanumeric() || c == '_' || c == '-' || c == '.'
84    });
85    if !ok {
86        return Err(io::Error::new(
87            io::ErrorKind::InvalidInput,
88            "Entry key must match [A-Za-z0-9_.-]+",
89        ));
90    }
91    Ok(())
92}
93
94#[cfg(test)]
95#[path = "../tests/unit_tests/store.rs"]
96pub mod tests;