1use std::collections::HashMap;
2use std::collections::HashSet;
3use std::sync::Mutex;
4use std::sync::OnceLock;
5use std::io;
6
7use crate::db::Db;
8
9#[derive(Default)]
10pub struct Store {
11 pub(crate) dbs: HashSet<Db>,
12 pub(crate) entries: HashMap<Db, 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(db: &Db) -> 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.dbs.insert(db.clone());
27 st.entries.entry(db.clone()).or_insert_with(HashMap::new);
28 Ok(())
29}
30
31pub fn remove_if_exists(db: &Db) -> 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.dbs.remove(db);
36 st.entries.remove(db);
37 Ok(())
38}
39
40pub fn exists(db: &Db) -> 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.dbs.contains(db))
45}
46
47pub fn list_keys(db: &Db) -> 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_db_exists_locked(&st, db)?;
53
54 let map: &HashMap<String, String> = st
55 .entries
56 .get(db)
57 .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "DB not found"))?;
58
59 Ok(map.keys().cloned().collect())
60}
61
62pub(crate) fn ensure_db_exists_locked(st: &Store, db: &Db) -> io::Result<()> {
63 if !st.dbs.contains(db) {
64 return Err(io::Error::new(io::ErrorKind::NotFound, "DB 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;