Skip to main content

jscpd_rs/detector/
store.rs

1use std::{collections::HashMap, error::Error, fmt};
2
3/// Error returned when a key is missing from a [`MemoryStore`] namespace.
4#[derive(Clone, Debug, Eq, PartialEq)]
5pub struct MemoryStoreError {
6    namespace: String,
7    key: String,
8}
9
10impl MemoryStoreError {
11    /// Create a missing-key error for a namespace/key pair.
12    pub fn new(namespace: impl Into<String>, key: impl Into<String>) -> Self {
13        Self {
14            namespace: namespace.into(),
15            key: key.into(),
16        }
17    }
18
19    /// Namespace searched by the failed lookup.
20    pub fn namespace(&self) -> &str {
21        &self.namespace
22    }
23
24    /// Key searched by the failed lookup.
25    pub fn key(&self) -> &str {
26        &self.key
27    }
28}
29
30impl fmt::Display for MemoryStoreError {
31    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
32        write!(
33            formatter,
34            "key '{}' not found in namespace '{}'",
35            self.key, self.namespace
36        )
37    }
38}
39
40impl Error for MemoryStoreError {}
41
42/// Simple namespace-aware in-memory store compatible with upstream concepts.
43#[derive(Clone, Debug)]
44pub struct MemoryStore<T> {
45    namespace: String,
46    values: HashMap<String, HashMap<String, T>>,
47}
48
49impl<T> Default for MemoryStore<T> {
50    fn default() -> Self {
51        Self::new()
52    }
53}
54
55impl<T> MemoryStore<T> {
56    /// Create an empty memory store.
57    pub fn new() -> Self {
58        Self {
59            namespace: String::new(),
60            values: HashMap::new(),
61        }
62    }
63
64    /// Switch the active namespace, creating it if needed.
65    pub fn namespace(&mut self, namespace: impl Into<String>) {
66        self.namespace = namespace.into();
67        self.values.entry(self.namespace.clone()).or_default();
68    }
69
70    /// Return the active namespace.
71    pub fn current_namespace(&self) -> &str {
72        &self.namespace
73    }
74
75    /// Get a value by key from the active namespace.
76    pub fn get(&self, key: impl AsRef<str>) -> Result<&T, MemoryStoreError> {
77        let key = key.as_ref();
78        self.values
79            .get(&self.namespace)
80            .and_then(|namespace| namespace.get(key))
81            .ok_or_else(|| MemoryStoreError::new(self.namespace.clone(), key))
82    }
83
84    /// Set a value in the active namespace and return a shared reference to it.
85    pub fn set(&mut self, key: impl Into<String>, value: T) -> &T {
86        let key = key.into();
87        self.values
88            .entry(self.namespace.clone())
89            .or_default()
90            .entry(key)
91            .insert_entry(value)
92            .into_mut()
93    }
94
95    /// Remove all namespaces and values.
96    pub fn close(&mut self) {
97        self.values.clear();
98    }
99
100    /// Return true when all namespaces are empty.
101    pub fn is_empty(&self) -> bool {
102        self.values.values().all(HashMap::is_empty)
103    }
104
105    /// Return the total number of stored values across namespaces.
106    pub fn len(&self) -> usize {
107        self.values.values().map(HashMap::len).sum()
108    }
109}