Skip to main content

oxihuman_core/
storage_backend.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Abstract in-memory storage backend with namespaced key-value buckets.
6
7use std::collections::HashMap;
8
9/// A single named storage bucket.
10#[allow(dead_code)]
11#[derive(Debug, Default)]
12pub struct Bucket {
13    pub name: String,
14    pub entries: HashMap<String, Vec<u8>>,
15    pub write_count: u64,
16}
17
18#[allow(dead_code)]
19impl Bucket {
20    pub fn new(name: &str) -> Self {
21        Self {
22            name: name.to_string(),
23            entries: HashMap::new(),
24            write_count: 0,
25        }
26    }
27
28    pub fn put(&mut self, key: &str, data: Vec<u8>) {
29        self.entries.insert(key.to_string(), data);
30        self.write_count += 1;
31    }
32
33    pub fn get(&self, key: &str) -> Option<&[u8]> {
34        self.entries.get(key).map(|v| v.as_slice())
35    }
36
37    pub fn remove(&mut self, key: &str) -> bool {
38        self.entries.remove(key).is_some()
39    }
40
41    pub fn contains(&self, key: &str) -> bool {
42        self.entries.contains_key(key)
43    }
44
45    pub fn entry_count(&self) -> usize {
46        self.entries.len()
47    }
48
49    pub fn total_bytes(&self) -> usize {
50        self.entries.values().map(|v| v.len()).sum()
51    }
52}
53
54/// An in-memory storage backend with multiple named buckets.
55#[allow(dead_code)]
56pub struct StorageBackend {
57    buckets: HashMap<String, Bucket>,
58    read_count: u64,
59}
60
61#[allow(dead_code)]
62impl StorageBackend {
63    pub fn new() -> Self {
64        Self {
65            buckets: HashMap::new(),
66            read_count: 0,
67        }
68    }
69
70    pub fn ensure_bucket(&mut self, name: &str) -> &mut Bucket {
71        self.buckets
72            .entry(name.to_string())
73            .or_insert_with(|| Bucket::new(name))
74    }
75
76    pub fn put(&mut self, bucket: &str, key: &str, data: Vec<u8>) {
77        self.ensure_bucket(bucket).put(key, data);
78    }
79
80    pub fn get(&mut self, bucket: &str, key: &str) -> Option<&[u8]> {
81        self.read_count += 1;
82        self.buckets
83            .get(bucket)?
84            .entries
85            .get(key)
86            .map(|v| v.as_slice())
87    }
88
89    pub fn remove(&mut self, bucket: &str, key: &str) -> bool {
90        self.buckets.get_mut(bucket).is_some_and(|b| b.remove(key))
91    }
92
93    pub fn contains(&self, bucket: &str, key: &str) -> bool {
94        self.buckets.get(bucket).is_some_and(|b| b.contains(key))
95    }
96
97    pub fn bucket_count(&self) -> usize {
98        self.buckets.len()
99    }
100
101    pub fn total_entries(&self) -> usize {
102        self.buckets.values().map(|b| b.entry_count()).sum()
103    }
104
105    pub fn total_bytes(&self) -> usize {
106        self.buckets.values().map(|b| b.total_bytes()).sum()
107    }
108
109    pub fn read_count(&self) -> u64 {
110        self.read_count
111    }
112
113    pub fn drop_bucket(&mut self, name: &str) -> bool {
114        self.buckets.remove(name).is_some()
115    }
116}
117
118impl Default for StorageBackend {
119    fn default() -> Self {
120        Self::new()
121    }
122}
123
124pub fn new_storage_backend() -> StorageBackend {
125    StorageBackend::new()
126}
127
128pub fn sb_put(sb: &mut StorageBackend, bucket: &str, key: &str, data: Vec<u8>) {
129    sb.put(bucket, key, data);
130}
131
132pub fn sb_get<'a>(sb: &'a mut StorageBackend, bucket: &str, key: &str) -> Option<&'a [u8]> {
133    sb.get(bucket, key)
134}
135
136pub fn sb_remove(sb: &mut StorageBackend, bucket: &str, key: &str) -> bool {
137    sb.remove(bucket, key)
138}
139
140pub fn sb_contains(sb: &StorageBackend, bucket: &str, key: &str) -> bool {
141    sb.contains(bucket, key)
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147
148    #[test]
149    fn empty_on_creation() {
150        let sb = new_storage_backend();
151        assert_eq!(sb.bucket_count(), 0);
152        assert_eq!(sb.total_entries(), 0);
153    }
154
155    #[test]
156    fn put_and_get() {
157        let mut sb = new_storage_backend();
158        sb_put(&mut sb, "assets", "mesh.bin", vec![1, 2, 3]);
159        let data = sb_get(&mut sb, "assets", "mesh.bin").expect("should succeed");
160        assert_eq!(data, &[1, 2, 3]);
161    }
162
163    #[test]
164    fn missing_key_returns_none() {
165        let mut sb = new_storage_backend();
166        assert!(sb_get(&mut sb, "bucket", "missing").is_none());
167    }
168
169    #[test]
170    fn remove_returns_true_once() {
171        let mut sb = new_storage_backend();
172        sb_put(&mut sb, "b", "k", vec![0]);
173        assert!(sb_remove(&mut sb, "b", "k"));
174        assert!(!sb_remove(&mut sb, "b", "k"));
175    }
176
177    #[test]
178    fn contains_check() {
179        let mut sb = new_storage_backend();
180        sb_put(&mut sb, "b", "key", vec![]);
181        assert!(sb_contains(&sb, "b", "key"));
182        assert!(!sb_contains(&sb, "b", "other"));
183    }
184
185    #[test]
186    fn total_bytes_sums_across_buckets() {
187        let mut sb = new_storage_backend();
188        sb_put(&mut sb, "a", "k1", vec![1, 2]);
189        sb_put(&mut sb, "b", "k2", vec![3, 4, 5]);
190        assert_eq!(sb.total_bytes(), 5);
191    }
192
193    #[test]
194    fn drop_bucket() {
195        let mut sb = new_storage_backend();
196        sb_put(&mut sb, "tmp", "k", vec![0]);
197        assert!(sb.drop_bucket("tmp"));
198        assert_eq!(sb.bucket_count(), 0);
199    }
200
201    #[test]
202    fn read_count_increments() {
203        let mut sb = new_storage_backend();
204        sb_put(&mut sb, "b", "k", vec![0]);
205        sb_get(&mut sb, "b", "k");
206        sb_get(&mut sb, "b", "k");
207        assert_eq!(sb.read_count(), 2);
208    }
209
210    #[test]
211    fn bucket_auto_created() {
212        let mut sb = new_storage_backend();
213        sb_put(&mut sb, "new_bucket", "key", vec![7]);
214        assert_eq!(sb.bucket_count(), 1);
215    }
216
217    #[test]
218    fn total_entries_across_buckets() {
219        let mut sb = new_storage_backend();
220        sb_put(&mut sb, "a", "k1", vec![]);
221        sb_put(&mut sb, "a", "k2", vec![]);
222        sb_put(&mut sb, "b", "k3", vec![]);
223        assert_eq!(sb.total_entries(), 3);
224    }
225}