1extern crate alloc;
4use alloc::string::String;
5use alloc::vec::Vec;
6
7use grafos_std::error::{FabricError, Result};
8use grafos_std::host;
9
10use serde::{Deserialize, Serialize};
11
12use crate::bucket_config::{BucketConfig, BucketTier};
13use crate::bucket_handle::BucketHandle;
14use crate::bucket_locator::BucketLocator;
15
16#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
18pub struct BucketInfo {
19 pub name: String,
21 pub pool: String,
23 pub created_at: u64,
25}
26
27pub struct BucketManager {
45 pool: String,
46 buckets: Vec<BucketInfo>,
47 generation_counter: u64,
48 published: Vec<BucketLocator>,
49}
50
51impl BucketManager {
52 pub fn new(pool: &str) -> Self {
54 BucketManager {
55 pool: String::from(pool),
56 buckets: Vec::new(),
57 generation_counter: 0,
58 published: Vec::new(),
59 }
60 }
61
62 pub fn create(&mut self, name: &str) -> Result<&BucketInfo> {
64 if self.exists(name) {
65 return Err(FabricError::IoError(-11));
66 }
67 let now = host::unix_time_secs();
68 let info = BucketInfo {
69 name: String::from(name),
70 pool: self.pool.clone(),
71 created_at: now,
72 };
73 self.buckets.push(info);
74
75 #[cfg(feature = "observe")]
76 crate::observe_hooks::on_bucket_created(name);
77
78 Ok(self.buckets.last().unwrap())
79 }
80
81 pub fn exists(&self, name: &str) -> bool {
83 self.buckets.iter().any(|b| b.name == name)
84 }
85
86 pub fn get(&self, name: &str) -> Option<&BucketInfo> {
88 self.buckets.iter().find(|b| b.name == name)
89 }
90
91 pub fn drop_bucket(&mut self, name: &str) -> bool {
93 let len_before = self.buckets.len();
94 self.buckets.retain(|b| b.name != name);
95 self.buckets.len() < len_before
96 }
97
98 pub fn list(&self) -> Vec<&str> {
100 self.buckets.iter().map(|b| b.name.as_str()).collect()
101 }
102
103 pub fn pool(&self) -> &str {
105 &self.pool
106 }
107
108 pub fn create_bucket(&mut self, config: BucketConfig) -> Result<BucketHandle> {
110 if self.exists(&config.name) {
111 return Err(FabricError::IoError(-11));
112 }
113 let now = host::unix_time_secs();
114 self.generation_counter += 1;
115 let info = BucketInfo {
116 name: config.name.clone(),
117 pool: self.pool.clone(),
118 created_at: now,
119 };
120 self.buckets.push(info);
121 Ok(BucketHandle {
122 name: config.name,
123 pool: self.pool.clone(),
124 tier: config.tier,
125 created_at: now,
126 generation: self.generation_counter,
127 })
128 }
129
130 pub fn open_bucket(&self, name: &str) -> Result<BucketHandle> {
132 let info = self.get(name).ok_or(FabricError::IoError(-4))?;
133 Ok(BucketHandle {
134 name: info.name.clone(),
135 pool: info.pool.clone(),
136 tier: BucketTier::Memory, created_at: info.created_at,
138 generation: self.generation_counter,
139 })
140 }
141
142 pub fn publish(&mut self, handle: &BucketHandle) -> BucketLocator {
144 let locator = BucketLocator {
145 version: 1,
146 name: handle.name.clone(),
147 pool: handle.pool.clone(),
148 tier: handle.tier.clone(),
149 generation: handle.generation,
150 created_at: handle.created_at,
151 };
152 if let Some(pos) = self.published.iter().position(|l| l.name == locator.name) {
154 self.published[pos] = locator.clone();
155 } else {
156 self.published.push(locator.clone());
157 }
158 locator
159 }
160
161 pub fn discover(&self, name: &str) -> Option<&BucketLocator> {
163 self.published.iter().find(|l| l.name == name)
164 }
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170 use grafos_std::host;
171
172 fn setup() {
173 host::reset_mock();
174 }
175
176 #[test]
177 fn create_and_exists() {
178 setup();
179 let mut mgr = BucketManager::new("pool");
180 assert!(!mgr.exists("b1"));
181
182 mgr.create("b1").unwrap();
183 assert!(mgr.exists("b1"));
184 }
185
186 #[test]
187 fn create_duplicate_fails() {
188 setup();
189 let mut mgr = BucketManager::new("pool");
190 mgr.create("b1").unwrap();
191 assert!(mgr.create("b1").is_err());
192 }
193
194 #[test]
195 fn drop_bucket() {
196 setup();
197 let mut mgr = BucketManager::new("pool");
198 mgr.create("b1").unwrap();
199 mgr.create("b2").unwrap();
200
201 assert!(mgr.drop_bucket("b1"));
202 assert!(!mgr.exists("b1"));
203 assert!(mgr.exists("b2"));
204 assert!(!mgr.drop_bucket("b1")); }
206
207 #[test]
208 fn list_buckets() {
209 setup();
210 let mut mgr = BucketManager::new("pool");
211 mgr.create("alpha").unwrap();
212 mgr.create("beta").unwrap();
213 mgr.create("gamma").unwrap();
214
215 let mut names = mgr.list();
216 names.sort();
217 assert_eq!(names, vec!["alpha", "beta", "gamma"]);
218 }
219
220 #[test]
221 fn get_bucket_info() {
222 setup();
223 let mut mgr = BucketManager::new("mypool");
224 mgr.create("data").unwrap();
225
226 let info = mgr.get("data").unwrap();
227 assert_eq!(info.name, "data");
228 assert_eq!(info.pool, "mypool");
229 }
230
231 #[test]
232 fn pool_name() {
233 let mgr = BucketManager::new("test");
234 assert_eq!(mgr.pool(), "test");
235 }
236
237 #[test]
238 fn create_bucket_from_config() {
239 setup();
240 use crate::bucket_config::{BucketConfig, BucketTier};
241 let mut mgr = BucketManager::new("pool");
242 let config = BucketConfig::new("data").tier(BucketTier::Block);
243 let handle = mgr.create_bucket(config).unwrap();
244 assert_eq!(handle.name(), "data");
245 assert_eq!(handle.pool(), "pool");
246 assert_eq!(*handle.tier(), BucketTier::Block);
247 assert_eq!(handle.generation(), 1);
248 assert!(mgr.exists("data"));
249 }
250
251 #[test]
252 fn create_bucket_duplicate_fails() {
253 setup();
254 use crate::bucket_config::BucketConfig;
255 let mut mgr = BucketManager::new("pool");
256 mgr.create_bucket(BucketConfig::new("dup")).unwrap();
257 assert!(mgr.create_bucket(BucketConfig::new("dup")).is_err());
258 }
259
260 #[test]
261 fn open_bucket() {
262 setup();
263 use crate::bucket_config::BucketConfig;
264 let mut mgr = BucketManager::new("pool");
265 mgr.create_bucket(BucketConfig::new("open-me")).unwrap();
266
267 let handle = mgr.open_bucket("open-me").unwrap();
268 assert_eq!(handle.name(), "open-me");
269 assert_eq!(handle.pool(), "pool");
270 }
271
272 #[test]
273 fn open_bucket_not_found() {
274 let mgr = BucketManager::new("pool");
275 assert!(mgr.open_bucket("nope").is_err());
276 }
277
278 #[test]
279 fn publish_and_discover() {
280 setup();
281 use crate::bucket_config::{BucketConfig, BucketTier};
282 let mut mgr = BucketManager::new("pool");
283 let handle = mgr
284 .create_bucket(BucketConfig::new("shared").tier(BucketTier::Memory))
285 .unwrap();
286
287 let locator = mgr.publish(&handle);
288 assert_eq!(locator.version, 1);
289 assert_eq!(locator.name, "shared");
290 assert_eq!(locator.pool, "pool");
291 assert_eq!(locator.tier, BucketTier::Memory);
292 assert_eq!(locator.generation, 1);
293
294 let found = mgr.discover("shared").unwrap();
295 assert_eq!(found, &locator);
296 assert!(mgr.discover("missing").is_none());
297 }
298
299 #[test]
300 fn publish_replaces_existing() {
301 setup();
302 use crate::bucket_config::BucketConfig;
303 let mut mgr = BucketManager::new("pool");
304 let h1 = mgr.create_bucket(BucketConfig::new("b")).unwrap();
305 mgr.publish(&h1);
306
307 mgr.drop_bucket("b");
309 let h2 = mgr.create_bucket(BucketConfig::new("b")).unwrap();
310 mgr.publish(&h2);
311
312 let found = mgr.discover("b").unwrap();
313 assert_eq!(found.generation, 2);
314 }
315
316 #[test]
317 fn bucket_handle_uri() {
318 setup();
319 use crate::bucket_config::BucketConfig;
320 let mut mgr = BucketManager::new("pool");
321 let handle = mgr.create_bucket(BucketConfig::new("bkt")).unwrap();
322 let uri = handle.uri("my-key").unwrap();
323 assert_eq!(uri.pool(), "pool");
324 assert_eq!(uri.bucket(), "bkt");
325 assert_eq!(uri.key(), "my-key");
326 }
327
328 #[test]
329 fn generation_increments() {
330 setup();
331 use crate::bucket_config::BucketConfig;
332 let mut mgr = BucketManager::new("pool");
333 let h1 = mgr.create_bucket(BucketConfig::new("a")).unwrap();
334 let h2 = mgr.create_bucket(BucketConfig::new("b")).unwrap();
335 assert_eq!(h1.generation(), 1);
336 assert_eq!(h2.generation(), 2);
337 }
338}