1use dynamic::{Dynamic, FromJson, FromYaml, MsgPack, MsgUnpack, ToJson};
2use rand::random_range;
3use scc::HashMap;
4use smol_str::SmolStr;
5
6use anyhow::{Result, anyhow};
7
8use super::{Object, sync_await};
9use crate::directory;
10use crate::node::Node;
11use fjall::{KeyspaceCreateOptions, OptimisticTxDatabase, OptimisticTxKeyspace, PersistMode};
12use redis::AsyncCommands;
13use redis::Commands;
14use std::path::{Path, PathBuf};
15use std::sync::Arc;
16
17use rslock::LockManager;
18
19pub enum Mount<T> {
20 Memory(Arc<HashMap<SmolStr, Node<T>>>),
21 Redis {
22 client: redis::Client,
23 rl: LockManager,
24 },
25 Fjall {
26 db: OptimisticTxDatabase,
27 values: OptimisticTxKeyspace,
28 write_lock: Arc<std::sync::Mutex<()>>,
29 },
30 Dir {
37 base: PathBuf,
38 },
39}
40
41impl<T: std::fmt::Debug + MsgPack + MsgUnpack + Default + Send> Mount<T> {
42 fn redis_idx(idx: usize) -> Result<isize> {
46 isize::try_from(idx).map_err(|_| anyhow!("索引 {} 超出 isize 范围", idx))
47 }
48
49 pub fn memory() -> Self {
50 Self::Memory(Arc::new(HashMap::new()))
51 }
52
53 pub fn redis(url: &str) -> Result<Self> {
54 let client = redis::Client::open(url)?;
55 let mut conn = client.get_connection()?;
56 directory::rebuild_once(&mut conn)?;
57 let rl = LockManager::new(vec![url]);
58 Ok(Self::Redis { client, rl })
59 }
60
61 pub fn fjall(data_dir: &str) -> Result<Self> {
62 let db = OptimisticTxDatabase::builder(data_dir).open()?;
63 let values = db.keyspace("root", KeyspaceCreateOptions::default)?;
64 Ok(Self::Fjall { db, values, write_lock: Arc::new(std::sync::Mutex::new(())) })
65 }
66
67 pub fn add(&self, name: &str, value: T) -> bool {
68 match self {
69 Self::Memory(m) => {
70 m.upsert_sync(name.into(), Node::Object(value));
71 true
72 }
73 Self::Redis { client, rl: _ } => {
74 let mut buf = Vec::new();
75 value.encode(&mut buf);
76 let Ok(mut conn) = client.get_connection() else {
77 return false;
78 };
79 conn.set::<&str, Vec<u8>, ()>(name, buf).is_ok() && directory::add_path(&mut conn, name).is_ok()
80 }
81 Self::Fjall { db, values, write_lock } => {
82 let mut buf = Vec::new();
83 value.encode(&mut buf);
84 let Ok(_guard) = write_lock.lock() else {
85 return false;
86 };
87 if fjall_clear_node(values, name).is_err() || values.insert(fjall_object_key(name), buf).is_err() {
88 return false;
89 }
90 fjall_persist(db).is_ok()
91 }
92 Self::Dir { .. } => false,
95 }
96 }
97
98 pub fn contains(&self, name: &str) -> bool {
99 match self {
100 Self::Memory(m) => m.contains_sync(name),
101 Self::Redis { client, rl: _ } => client.get_connection().and_then(|mut conn| conn.exists::<&str, bool>(name)).unwrap_or(false),
102 Self::Fjall { values, .. } => values.contains_key(fjall_object_key(name)).unwrap_or(false) || values.contains_key(fjall_type_key(name)).unwrap_or(false),
103 Self::Dir { base } => match safe_path(base, name) {
104 Some(p) => p.is_file(),
105 None => false,
106 },
107 }
108 }
109
110 pub fn get_mut<R: Send + 'static, F: FnMut(&mut T) -> R>(&self, name: &str, mut f: F) -> Result<R>
111 where
112 F: Send + 'static,
113 {
114 match self {
115 Self::Memory(m) => m
116 .update_sync(name, |_, v| match v {
117 Node::Object(v) => Some(f(v)),
118 _ => None,
119 })
120 .flatten()
121 .ok_or(anyhow!("{} 不存在", name)),
122 Self::Redis { client, rl } => {
123 let name = String::from(name);
124 let rl = rl.clone();
125 let client = client.clone();
126 sync_await!(async move {
127 loop {
128 if let Ok(lock) = rl.lock(name.as_str(), std::time::Duration::from_millis(5000)).await {
131 let mut conn = client.get_multiplexed_async_connection().await?;
132 let mut buf: Vec<u8> = conn.get(name.as_str()).await?;
133 let (mut v, _) = T::decode(buf.as_slice())?;
134 let r = f(&mut v);
135 buf.clear();
136 v.encode(&mut buf);
137 conn.set::<&str, Vec<u8>, ()>(name.as_str(), buf).await?;
138 rl.unlock(&lock).await;
139 break Ok(r);
140 }
141 tokio::time::sleep(std::time::Duration::from_millis(random_range(1..10))).await;
143 }
144 })
145 }
146 Self::Fjall { db, values, write_lock } => {
147 let _guard = write_lock.lock().map_err(|e| anyhow!("无法获取 fjall 写锁: {}", e))?;
148 let mut v = fjall_get_object::<T>(values, name)?;
149 let r = f(&mut v);
150 fjall_insert_object(values, name, &v)?;
151 fjall_persist(db)?;
152 Ok(r)
153 }
154 Self::Dir { .. } => Err(anyhow!("Mount::Dir 的原子更新请用 dir_update")),
156 }
157 }
158
159 pub fn get<R, F: FnOnce(&T) -> R>(&self, name: &str, f: F) -> Result<R> {
160 match self {
161 Self::Memory(m) => m
162 .read_sync(name, |_, v| match v {
163 Node::Object(v) => Some(f(v)),
164 _ => None,
165 })
166 .flatten()
167 .ok_or(anyhow!("{} 不存在", name)),
168 Self::Redis { client, rl: _ } => {
169 let mut conn = client.get_connection()?;
170 let buf: Vec<u8> = conn.get(name)?;
171 let (v, _) = T::decode(buf.as_slice())?;
172 Ok(f(&v))
173 }
174 Self::Fjall { values, .. } => fjall_get_object(values, name).map(|v| f(&v)),
175 Self::Dir { .. } => Err(anyhow!("Mount::Dir 的 get 请用 impl Mount<Object>::get")),
179 }
180 }
181
182 pub fn get_key_mut<'a, R: Send + 'static, F: FnOnce(&mut T) -> R>(&'a self, name: &'a str, key: &'a str, f: F) -> Result<R>
183 where
184 F: Send + 'static,
185 {
186 match self {
187 Self::Memory(m) => m
188 .update_sync(name, |_, v| match v {
189 Node::Map(m) => m.update_sync(key, |_, v| f(v)),
190 _ => None,
191 })
192 .flatten()
193 .ok_or(anyhow!("{} 不存在", name)),
194 Self::Redis { client, rl } => {
195 let name = String::from(name);
196 let key = String::from(key);
197 let rl = rl.clone();
198 let client = client.clone();
199 sync_await!(async move {
200 loop {
201 let lock_name = format!("{}::{}", name, key); if let Ok(lock) = rl.lock(lock_name.as_str(), std::time::Duration::from_millis(5000)).await {
204 let mut conn = client.get_multiplexed_async_connection().await?;
205 let mut buf: Vec<u8> = conn.hget(name.as_str(), key.as_str()).await?;
206 let (mut v, _) = T::decode(buf.as_slice())?;
207 let r = f(&mut v);
208 buf.clear();
209 v.encode(&mut buf);
210 conn.hset::<&str, &str, Vec<u8>, ()>(name.as_str(), key.as_str(), buf).await?;
211 rl.unlock(&lock).await;
212 break Ok(r);
213 }
214 tokio::time::sleep(std::time::Duration::from_millis(random_range(1..10))).await;
216 }
217 })
218 }
219 Self::Fjall { db, values, write_lock } => {
220 let _guard = write_lock.lock().map_err(|e| anyhow!("无法获取 fjall 写锁: {}", e))?;
221 let mut v = fjall_get_map_item::<T>(values, name, key)?;
222 let r = f(&mut v);
223 fjall_insert_map_item(values, name, key, &v)?;
224 fjall_persist(db)?;
225 Ok(r)
226 }
227 Self::Dir { .. } => Err(anyhow!("Mount::Dir 不支持 map 内 key 操作")),
229 }
230 }
231
232 pub fn dir(&self, name: &str) -> Result<Vec<SmolStr>> {
233 if let Self::Redis { client, rl: _ } = self {
234 let mut conn = client.get_connection()?;
235 return directory::children(&mut conn, name);
236 }
237 if let Self::Fjall { values, .. } = self {
238 return fjall_dir(values, name);
239 }
240 if let Self::Dir { .. } = self {
242 return Err(anyhow!("Mount::Dir 的目录列表请用 dir_list"));
243 }
244
245 let prefix = if name.is_empty() || name.ends_with('/') { name.to_string() } else { format!("{name}/") };
246 let raw = self.dir_raw(&prefix)?;
247 Ok(Self::dir_entries_from_raw(&prefix, raw))
248 }
249
250 fn dir_raw_entries_from_children(prefix: &str, children: Vec<SmolStr>) -> Vec<SmolStr> {
251 children.into_iter().map(|child| if prefix.is_empty() { child } else { format!("{prefix}{child}").into() }).collect()
252 }
253
254 fn dir_entries_from_raw(prefix: &str, raw: Vec<SmolStr>) -> Vec<SmolStr> {
255 let mut seen = std::collections::HashSet::new();
256 let mut names = Vec::new();
257 for key in raw {
258 let Some(rest) = key.strip_prefix(&prefix) else {
259 continue;
260 };
261 let end = rest.find('/').unwrap_or(rest.len());
262 let entry = &rest[..end];
263 if !entry.is_empty() && seen.insert(entry.to_string()) {
264 names.push(entry.into());
265 }
266 }
267 names
268 }
269
270 pub fn dir_raw(&self, name: &str) -> Result<Vec<SmolStr>> {
271 let mut names = Vec::new();
272 match self {
273 Self::Memory(m) => {
278 m.iter_sync(|key, _| {
279 if key.starts_with(name) {
280 names.push(key.clone())
281 }
282 true
283 });
284 }
285 Self::Redis { client, rl: _ } => {
286 let mut conn = client.get_connection()?;
287 let prefix = if name.is_empty() || name.ends_with('/') { name.to_string() } else { format!("{name}/") };
288 names.append(&mut Self::dir_raw_entries_from_children(&prefix, directory::children(&mut conn, name)?));
289 }
290 Self::Fjall { values, .. } => {
291 names.append(&mut fjall_paths_with_prefix(values, name)?);
292 }
293 Self::Dir { base } => {
295 let path = safe_path(base, name).ok_or_else(|| anyhow!("path 非法: {}", name))?;
296 if !path.exists() {
297 return Err(anyhow!("{} 不存在", name));
298 }
299 if !path.is_dir() {
300 return Err(anyhow!("{} 不是目录", name));
301 }
302 for entry in std::fs::read_dir(&path).map_err(|e| anyhow!("读取 {} 失败: {}", path.display(), e))? {
303 let entry = entry.map_err(|e| anyhow!("读取目录项失败: {}", e))?;
304 if let Some(s) = entry.file_name().to_str() {
305 names.push(SmolStr::new(s));
306 }
307 }
308 names.sort();
309 }
310 }
311 Ok(names)
312 }
313
314 pub fn len(&self, name: &str) -> Result<usize> {
315 match self {
316 Self::Memory(m) => m.read_sync(name, |_, v| v.len()).ok_or(anyhow!("{} 不是列表", name)),
317 Self::Redis { client, rl: _ } => {
318 let mut conn = client.get_connection()?;
319 let ty: String = conn.key_type(name)?;
320 match ty.as_str() {
321 "list" => Ok(conn.llen(name)?),
322 "hash" => Ok(conn.hlen(name)?),
323 _ => Ok(1),
324 }
325 }
326 Self::Fjall { values, .. } => match fjall_node_type(values, name)? {
327 Some(FjallNodeType::List) => Ok(fjall_count_prefix(values, fjall_list_prefix(name))?),
328 Some(FjallNodeType::Map) => Ok(fjall_count_prefix(values, fjall_map_prefix(name))?),
329 None if values.contains_key(fjall_object_key(name))? => Ok(1),
330 None => Err(anyhow!("{} 不存在", name)),
331 },
332 Self::Dir { base } => {
334 let path = safe_path(base, name).ok_or_else(|| anyhow!("path 非法: {}", name))?;
335 let metadata = std::fs::metadata(&path).map_err(|e| anyhow!("{} 不存在: {}", name, e))?;
336 if metadata.is_dir() { Ok(0) } else { Ok(metadata.len() as usize) }
337 }
338 }
339 }
340
341 pub fn remove(&self, name: &str) -> Result<T> {
342 match self {
343 Self::Memory(m) => m.remove_sync(name).map(|(_, v)| v.into_object().unwrap_or_default()).ok_or(anyhow!("{} 不存在", name)),
347 Self::Redis { client, rl: _ } => {
348 let mut conn = client.get_connection()?;
349 let buf: Vec<u8> = conn.get(name)?;
350 let (v, _) = T::decode(buf.as_slice())?;
351 let removed: usize = conn.del(name)?;
352 if removed != 0 {
353 directory::remove_path(&mut conn, name)?;
354 }
355 Ok(v)
356 }
357 Self::Fjall { db, values, .. } => {
358 let v = fjall_get_object(values, name)?;
359 values.remove(fjall_object_key(name))?;
360 fjall_persist(db)?;
361 Ok(v)
362 }
363 Self::Dir { .. } => Err(anyhow!("Mount::Dir 的删除请用 dir_remove")),
365 }
366 }
367
368 pub fn add_list(&self, name: &str) {
369 match self {
370 Self::Memory(m) => {
371 m.upsert_sync(name.into(), Node::<T>::list());
372 } Self::Redis { client: _, rl: _ } => {}
374 Self::Fjall { db, values, write_lock } => {
375 if let Ok(_guard) = write_lock.lock() {
376 let _ = fjall_clear_node(values, name).and_then(|_| fjall_set_node_type(values, name, FjallNodeType::List)).and_then(|_| fjall_persist(db));
377 }
378 }
379 Self::Dir { .. } => {
380 log::warn!("Mount::Dir 不支持 add_list,忽略 {}", name);
381 }
382 }
383 }
384
385 pub fn add_map(&self, name: &str) {
386 match self {
387 Self::Memory(m) => {
388 m.upsert_sync(name.into(), Node::<T>::map());
389 } Self::Redis { client: _, rl: _ } => {}
391 Self::Fjall { db, values, write_lock } => {
392 if let Ok(_guard) = write_lock.lock() {
393 let _ = fjall_clear_node(values, name).and_then(|_| fjall_set_node_type(values, name, FjallNodeType::Map)).and_then(|_| fjall_persist(db));
394 }
395 }
396 Self::Dir { .. } => {
397 log::warn!("Mount::Dir 不支持 add_map,忽略 {}", name);
398 }
399 }
400 }
401
402 pub fn push(&self, name: &str, value: T) -> Result<usize> {
403 match self {
404 Self::Memory(m) => m.update_sync(name, |_, v| v.push(value)).flatten().ok_or(anyhow!("push {} 失败", name)),
405 Self::Redis { client, rl: _ } => {
406 let mut conn = client.get_connection()?;
407 let mut buf = Vec::new();
408 value.encode(&mut buf);
409 let len = conn.rpush(name, buf)?;
410 directory::add_path(&mut conn, name)?;
411 Ok(len)
412 }
413 Self::Fjall { db, values, write_lock } => {
414 let _guard = write_lock.lock().map_err(|e| anyhow!("无法获取 fjall 写锁: {}", e))?;
415 match fjall_node_type(values, name)? {
416 Some(FjallNodeType::List) => {}
417 Some(FjallNodeType::Map) => return Err(anyhow!("push {} 失败", name)),
418 None => fjall_set_node_type(values, name, FjallNodeType::List)?,
419 }
420 let idx = fjall_next_list_idx(values, name)?;
421 fjall_insert_list_item(values, name, idx, &value)?;
422 fjall_persist(db)?;
423 Ok(idx)
424 }
425 Self::Dir { .. } => Err(anyhow!("Mount::Dir 不支持 push/List 语义")),
426 }
427 }
428
429 pub fn get_idx<R, F: FnOnce(&T) -> R>(&self, name: &str, idx: usize, f: F) -> Result<R> {
430 match self {
431 Self::Memory(m) => m.read_sync(name, |_, v| v.get_idx(idx, f)).flatten().ok_or(anyhow!("get_idx {} 失败", name)),
432 Self::Redis { client, rl: _ } => {
433 let mut conn = client.get_connection()?;
434 let buf: Vec<u8> = conn.lindex(name, Self::redis_idx(idx)?)?;
435 let (v, _) = T::decode(buf.as_slice()).unwrap_or_else(|_| (T::default(), 0));
439 Ok(f(&v))
440 }
441 Self::Fjall { values, .. } => fjall_get_list_item(values, name, idx).map(|v| f(&v)),
442 Self::Dir { .. } => Err(anyhow!("Mount::Dir 不支持 get_idx/List 语义")),
443 }
444 }
445
446 pub fn get_idx_mut<R, F: FnMut(&mut T) -> R>(&self, name: &str, idx: usize, mut f: F) -> Result<R> {
447 match self {
448 Self::Memory(m) => m.update_sync(name, |_, v| v.get_idx_mut(idx, f)).flatten().ok_or(anyhow!("get_idx {} 失败", name)),
449 Self::Redis { client, rl: _ } => {
450 let mut conn = client.get_connection()?;
451 let buf: Vec<u8> = conn.lindex(name, Self::redis_idx(idx)?)?;
452 let (mut v, _) = T::decode(buf.as_slice())?;
453 Ok(f(&mut v))
454 }
455 Self::Fjall { db, values, write_lock } => {
456 let _guard = write_lock.lock().map_err(|e| anyhow!("无法获取 fjall 写锁: {}", e))?;
457 let mut v = fjall_get_list_item::<T>(values, name, idx)?;
458 let r = f(&mut v);
459 fjall_insert_list_item(values, name, idx, &v)?;
460 fjall_persist(db)?;
461 Ok(r)
462 }
463 Self::Dir { .. } => Err(anyhow!("Mount::Dir 不支持 get_idx_mut/List 语义")),
464 }
465 }
466
467 pub fn remove_idx(&self, name: &str, idx: usize) -> Result<T> {
468 match self {
469 Self::Memory(m) => m.update_sync(name, |_, v| v.remove_idx(idx)).flatten().ok_or(anyhow!("remove_idx {} 失败", name)),
470 Self::Redis { client, rl: _ } => {
471 let mut conn = client.get_connection()?;
472 let idx = Self::redis_idx(idx)?;
473 let buf: Vec<u8> = conn.lindex(name, idx)?;
474 let v = T::decode(buf.as_slice()).map(|(v, _)| v).unwrap_or(T::default());
475 let _: () = conn.lset(name, idx, Vec::new())?;
476 Ok(v)
477 }
478 Self::Fjall { db, values, .. } => {
479 let key = fjall_list_item_key(name, idx);
480 let buf = values.get(&key)?.ok_or(anyhow!("remove_idx {} 失败", name))?;
481 let (v, _) = T::decode(buf.as_ref())?;
482 values.remove(key)?;
483 fjall_persist(db)?;
484 Ok(v)
485 }
486 Self::Dir { .. } => Err(anyhow!("Mount::Dir 不支持 remove_idx/List 语义")),
487 }
488 }
489
490 pub fn insert(&self, name: &str, key: &str, value: T) -> Option<T> {
491 match self {
492 Self::Memory(m) => m.update_sync(name, |_, v| v.insert(key.into(), value)).flatten(),
493 Self::Redis { client, rl: _ } => {
494 if let Ok(mut conn) = client.get_connection() {
495 let mut buf = Vec::new();
496 value.encode(&mut buf);
497 if conn.hset::<&str, &str, Vec<u8>, ()>(name, key, buf).is_ok() {
498 let _ = directory::add_path(&mut conn, name);
499 }
500 }
501 None
502 }
503 Self::Fjall { db, values, write_lock } => {
504 let _guard = write_lock.lock().ok()?;
505 match fjall_node_type(values, name).ok()? {
506 Some(FjallNodeType::Map) => {}
507 Some(FjallNodeType::List) => return None,
508 None => fjall_set_node_type(values, name, FjallNodeType::Map).ok()?,
509 }
510 let old = fjall_get_map_item(values, name, key).ok();
511 fjall_insert_map_item(values, name, key, &value).ok()?;
512 fjall_persist(db).ok()?;
513 old
514 }
515 Self::Dir { .. } => {
516 log::warn!("Mount::Dir 不支持 insert/Map 语义,忽略 {}/{}", name, key);
517 None
518 }
519 }
520 }
521
522 pub fn get_key<R, F: FnOnce(&T) -> R>(&self, name: &str, key: &str, f: F) -> Result<R> {
523 match self {
524 Self::Memory(m) => m.read_sync(name, |_, v| v.get_key(key, f)).flatten().ok_or(anyhow!("get_key {} 失败", name)),
525 Self::Redis { client, rl: _ } => {
526 let mut conn = client.get_connection()?;
527 let buf: Vec<u8> = conn.hget(name, key)?;
528 let (v, _) = T::decode(buf.as_slice())?;
529 Ok(f(&v))
530 }
531 Self::Fjall { values, .. } => fjall_get_map_item(values, name, key).map(|v| f(&v)),
532 Self::Dir { .. } => Err(anyhow!("Mount::Dir 不支持 get_key/Map 语义")),
533 }
534 }
535
536 pub fn keys(&self, name: &str) -> Result<Vec<SmolStr>> {
537 match self {
538 Self::Memory(m) => m.read_sync(name, |_, v| v.keys()).flatten().ok_or(anyhow!("keys {} 失败", name)),
539 Self::Redis { client, rl: _ } => {
540 let mut conn = client.get_connection()?;
541 let keys: Vec<String> = conn.hkeys(name)?;
542 Ok(keys.into_iter().map(|k| k.into()).collect())
543 }
544 Self::Fjall { values, .. } => fjall_map_keys(values, name),
545 Self::Dir { .. } => Err(anyhow!("Mount::Dir 不支持 keys/Map 语义")),
546 }
547 }
548
549 pub fn remove_key(&self, name: &str, key: &str) -> Result<T> {
550 match self {
551 Self::Memory(m) => m.update_sync(name, |_, v| v.remove_key(key)).flatten().ok_or(anyhow!("remove_key {} 失败", name)),
552 Self::Redis { client, rl: _ } => {
553 let mut conn = client.get_connection()?;
554 let buf: Vec<u8> = conn.hget(name, key)?;
555 let v = T::decode(buf.as_slice())?.0;
556 let _: usize = conn.hdel(name, key)?;
557 if !conn.exists::<_, bool>(name)? {
558 directory::remove_path(&mut conn, name)?;
559 }
560 Ok(v)
561 }
562 Self::Fjall { db, values, .. } => {
563 let item_key = fjall_map_item_key(name, key);
564 let buf = values.get(&item_key)?.ok_or(anyhow!("remove_key {} 失败", name))?;
565 let (v, _) = T::decode(buf.as_ref())?;
566 values.remove(item_key)?;
567 fjall_persist(db)?;
568 Ok(v)
569 }
570 Self::Dir { .. } => Err(anyhow!("Mount::Dir 不支持 remove_key/Map 语义")),
571 }
572 }
573}
574
575#[derive(Clone, Copy)]
576enum FjallNodeType {
577 List,
578 Map,
579}
580
581const FJALL_OBJECT_PREFIX: u8 = b'o';
582const FJALL_TYPE_PREFIX: u8 = b't';
583const FJALL_LIST_PREFIX: u8 = b'l';
584const FJALL_LIST_COUNTER_PREFIX: u8 = b'c';
585const FJALL_MAP_PREFIX: u8 = b'm';
586const FJALL_SEPARATOR: u8 = 0;
587
588fn fjall_key(prefix: u8, name: &str) -> Vec<u8> {
589 let mut key = Vec::with_capacity(2 + name.len());
590 key.push(prefix);
591 key.push(FJALL_SEPARATOR);
592 key.extend_from_slice(name.as_bytes());
593 key
594}
595
596fn fjall_object_key(name: &str) -> Vec<u8> {
597 fjall_key(FJALL_OBJECT_PREFIX, name)
598}
599
600fn fjall_type_key(name: &str) -> Vec<u8> {
601 fjall_key(FJALL_TYPE_PREFIX, name)
602}
603
604fn fjall_list_counter_key(name: &str) -> Vec<u8> {
605 fjall_key(FJALL_LIST_COUNTER_PREFIX, name)
606}
607
608fn fjall_item_prefix(prefix: u8, name: &str) -> Vec<u8> {
609 let mut key = fjall_key(prefix, name);
610 key.push(FJALL_SEPARATOR);
611 key
612}
613
614fn fjall_list_prefix(name: &str) -> Vec<u8> {
615 fjall_item_prefix(FJALL_LIST_PREFIX, name)
616}
617
618fn fjall_map_prefix(name: &str) -> Vec<u8> {
619 fjall_item_prefix(FJALL_MAP_PREFIX, name)
620}
621
622fn fjall_list_item_key(name: &str, idx: usize) -> Vec<u8> {
623 let mut key = fjall_list_prefix(name);
624 key.extend_from_slice(&(idx as u64).to_be_bytes());
625 key
626}
627
628fn fjall_map_item_key(name: &str, key: &str) -> Vec<u8> {
629 let mut item_key = fjall_map_prefix(name);
630 item_key.extend_from_slice(key.as_bytes());
631 item_key
632}
633
634fn fjall_prefix_end(prefix: &[u8]) -> Option<Vec<u8>> {
635 let mut end = prefix.to_vec();
636 for idx in (0..end.len()).rev() {
637 if end[idx] < 255 {
638 end[idx] += 1;
639 end.truncate(idx + 1);
640 return Some(end);
641 }
642 }
643 None
644}
645
646fn fjall_decode_value<T: MsgUnpack>(buf: &[u8]) -> Result<T> {
647 T::decode(buf).map(|(value, _)| value)
648}
649
650fn fjall_encode_value<T: MsgPack>(value: &T) -> Vec<u8> {
651 let mut buf = Vec::new();
652 value.encode(&mut buf);
653 buf
654}
655
656fn fjall_persist(db: &OptimisticTxDatabase) -> Result<()> {
657 Ok(db.persist(PersistMode::Buffer)?)
664}
665
666fn fjall_get_object<T: MsgUnpack>(values: &OptimisticTxKeyspace, name: &str) -> Result<T> {
667 let buf = values.get(fjall_object_key(name))?.ok_or(anyhow!("{} 不存在", name))?;
668 fjall_decode_value(buf.as_ref())
669}
670
671fn fjall_insert_object<T: MsgPack>(values: &OptimisticTxKeyspace, name: &str, value: &T) -> Result<()> {
672 Ok(values.insert(fjall_object_key(name), fjall_encode_value(value))?)
673}
674
675fn fjall_get_list_item<T: MsgUnpack>(values: &OptimisticTxKeyspace, name: &str, idx: usize) -> Result<T> {
676 let buf = values.get(fjall_list_item_key(name, idx))?.ok_or(anyhow!("get_idx {} 失败", name))?;
677 fjall_decode_value(buf.as_ref())
678}
679
680fn fjall_insert_list_item<T: MsgPack>(values: &OptimisticTxKeyspace, name: &str, idx: usize, value: &T) -> Result<()> {
681 Ok(values.insert(fjall_list_item_key(name, idx), fjall_encode_value(value))?)
682}
683
684fn fjall_get_map_item<T: MsgUnpack>(values: &OptimisticTxKeyspace, name: &str, key: &str) -> Result<T> {
685 let buf = values.get(fjall_map_item_key(name, key))?.ok_or(anyhow!("get_key {} 失败", name))?;
686 fjall_decode_value(buf.as_ref())
687}
688
689fn fjall_insert_map_item<T: MsgPack>(values: &OptimisticTxKeyspace, name: &str, key: &str, value: &T) -> Result<()> {
690 Ok(values.insert(fjall_map_item_key(name, key), fjall_encode_value(value))?)
691}
692
693fn fjall_node_type(values: &OptimisticTxKeyspace, name: &str) -> Result<Option<FjallNodeType>> {
694 Ok(values.get(fjall_type_key(name))?.and_then(|value| match value.as_ref() {
695 b"L" => Some(FjallNodeType::List),
696 b"M" => Some(FjallNodeType::Map),
697 _ => None,
698 }))
699}
700
701fn fjall_set_node_type(values: &OptimisticTxKeyspace, name: &str, node_type: FjallNodeType) -> Result<()> {
702 let value = match node_type {
703 FjallNodeType::List => b"L".as_slice(),
704 FjallNodeType::Map => b"M".as_slice(),
705 };
706 Ok(values.insert(fjall_type_key(name), value)?)
707}
708
709fn fjall_clear_node(values: &OptimisticTxKeyspace, name: &str) -> Result<()> {
710 values.remove(fjall_object_key(name))?;
711 values.remove(fjall_type_key(name))?;
712 values.remove(fjall_list_counter_key(name))?;
713 fjall_remove_prefix(values, fjall_list_prefix(name))?;
714 fjall_remove_prefix(values, fjall_map_prefix(name))?;
715 Ok(())
716}
717
718fn fjall_remove_prefix(values: &OptimisticTxKeyspace, prefix: Vec<u8>) -> Result<()> {
719 let mut keys = Vec::new();
720 for item in values.inner().prefix(prefix) {
721 keys.push(item.key()?);
722 }
723 for key in keys {
724 values.remove(key)?;
725 }
726 Ok(())
727}
728
729fn fjall_next_list_idx(values: &OptimisticTxKeyspace, name: &str) -> Result<usize> {
730 let key = fjall_list_counter_key(name);
731 let current = values.get(&key)?.and_then(|buf| buf.as_ref().try_into().ok().map(u64::from_be_bytes)).unwrap_or(0);
732 values.insert(key, (current + 1).to_be_bytes())?;
733 Ok(current as usize)
734}
735
736fn fjall_count_prefix(values: &OptimisticTxKeyspace, prefix: Vec<u8>) -> Result<usize> {
737 let mut count = 0;
738 for item in values.inner().prefix(prefix) {
739 item.key()?;
740 count += 1;
741 }
742 Ok(count)
743}
744
745fn fjall_paths_with_prefix(values: &OptimisticTxKeyspace, prefix: &str) -> Result<Vec<SmolStr>> {
746 let mut names = Vec::new();
747 names.extend(fjall_paths_for_key_prefix(values, FJALL_OBJECT_PREFIX, prefix)?);
748 names.extend(fjall_paths_for_key_prefix(values, FJALL_TYPE_PREFIX, prefix)?);
749 names.sort();
750 names.dedup();
751 Ok(names)
752}
753
754fn fjall_paths_for_key_prefix(values: &OptimisticTxKeyspace, key_prefix: u8, path_prefix: &str) -> Result<Vec<SmolStr>> {
755 let mut scan_prefix = fjall_key(key_prefix, path_prefix);
756 if path_prefix.is_empty() {
757 scan_prefix.truncate(2);
758 }
759 let mut names = Vec::new();
760 for item in values.inner().prefix(scan_prefix) {
761 let key = item.key()?;
762 let Some(path) = key.get(2..) else {
763 continue;
764 };
765 if let Ok(path) = std::str::from_utf8(path) {
766 names.push(path.into());
767 }
768 }
769 Ok(names)
770}
771
772fn fjall_dir(values: &OptimisticTxKeyspace, name: &str) -> Result<Vec<SmolStr>> {
773 let path_prefix = if name.is_empty() || name.ends_with('/') { name.to_string() } else { format!("{name}/") };
774 let mut names = Vec::new();
775 names.extend(fjall_dir_for_key_prefix(values, FJALL_OBJECT_PREFIX, &path_prefix)?);
776 names.extend(fjall_dir_for_key_prefix(values, FJALL_TYPE_PREFIX, &path_prefix)?);
777 names.sort();
778 names.dedup();
779 Ok(names)
780}
781
782fn fjall_dir_for_key_prefix(values: &OptimisticTxKeyspace, key_prefix: u8, path_prefix: &str) -> Result<Vec<SmolStr>> {
783 use std::ops::Bound::{Excluded, Included, Unbounded};
784
785 let scan_start = fjall_key(key_prefix, path_prefix);
786 let scan_end = fjall_prefix_end(&scan_start);
787 let mut start = scan_start;
788 let mut names = Vec::new();
789
790 loop {
791 if scan_end.as_ref().is_some_and(|end| start >= *end) {
792 break;
793 }
794
795 let end = scan_end.as_deref().map(Excluded).unwrap_or(Unbounded);
796 let iter = values.inner().range::<&[u8], _>((Included(start.as_slice()), end));
797 let mut restart_at = None;
798
799 for item in iter {
800 let key = item.key()?;
801 let Some(path) = key.get(2..) else {
802 continue;
803 };
804 let Ok(path) = std::str::from_utf8(path) else {
805 continue;
806 };
807 let Some(rest) = path.strip_prefix(path_prefix) else {
808 continue;
809 };
810 let Some(end) = rest.find('/') else {
811 if !rest.is_empty() {
812 names.push(rest.into());
813 }
814 continue;
815 };
816
817 let entry = &rest[..end];
818 if !entry.is_empty() {
819 names.push(entry.into());
820 restart_at = fjall_prefix_end(&fjall_key(key_prefix, &format!("{path_prefix}{entry}/")));
821 break;
822 }
823 }
824
825 let Some(next_start) = restart_at else {
826 break;
827 };
828 start = next_start;
829 }
830
831 Ok(names)
832}
833
834fn fjall_map_keys(values: &OptimisticTxKeyspace, name: &str) -> Result<Vec<SmolStr>> {
835 let prefix = fjall_map_prefix(name);
836 let mut keys = Vec::new();
837 for item in values.inner().prefix(&prefix) {
838 let key = item.key()?;
839 let Some(map_key) = key.get(prefix.len()..) else {
840 continue;
841 };
842 if let Ok(map_key) = std::str::from_utf8(map_key) {
843 keys.push(map_key.into());
844 }
845 }
846 Ok(keys)
847}
848
849use std::sync::RwLock;
850
851#[derive(Debug)]
852pub struct Root<T> {
853 mounts: Arc<RwLock<Vec<(SmolStr, Mount<T>)>>>,
854}
855
856impl<T: std::fmt::Debug> std::fmt::Debug for Mount<T> {
857 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
858 match self {
859 Self::Memory(_) => write!(f, "Mount::Memory"),
860 Self::Redis { client: _, rl: _ } => write!(f, "Mount::Redis"),
861 Self::Fjall { .. } => write!(f, "Mount::Fjall"),
862 Self::Dir { base } => write!(f, "Mount::Dir({})", base.display()),
863 }
864 }
865}
866
867impl<T> Clone for Mount<T> {
868 fn clone(&self) -> Self {
869 match self {
870 Self::Memory(m) => Self::Memory(m.clone()),
871 Self::Redis { client, rl } => Self::Redis { client: client.clone(), rl: rl.clone() },
872 Self::Fjall { db, values, write_lock } => Self::Fjall { db: db.clone(), values: values.clone(), write_lock: write_lock.clone() },
873 Self::Dir { base } => Self::Dir { base: base.clone() },
874 }
875 }
876}
877
878impl<T: std::fmt::Debug + MsgPack + MsgUnpack + Default + Send> Root<T> {
879 pub fn new() -> Self {
880 Self { mounts: Arc::new(RwLock::new(vec![("local".into(), Mount::<T>::memory())])) }
881 }
882
883 pub fn mount_memory(&self, name: &str) -> bool {
884 let mounts = self.mounts.write();
885 match mounts {
886 Ok(mut mounts) => {
887 if mounts.iter().any(|(n, _)| n == name) {
888 return false;
889 }
890 mounts.push((name.into(), Mount::<T>::memory()));
891 true
892 }
893 Err(_) => false,
894 }
895 }
896
897 pub fn mount_redis(&self, name: &str, url: &str) -> Result<bool> {
898 let mounts = self.mounts.write();
899 match mounts {
900 Ok(mut mounts) => {
901 if mounts.iter().any(|(n, _)| n == name) {
902 return Ok(false);
903 }
904 mounts.push((name.into(), Mount::<T>::redis(url)?));
905 Ok(true)
906 }
907 Err(e) => Err(anyhow!("无法获取写锁: {}", e)),
908 }
909 }
910
911 pub fn mount_fjall(&self, name: &str, data_dir: &str) -> Result<bool> {
912 let mounts = self.mounts.write();
913 match mounts {
914 Ok(mut mounts) => {
915 if mounts.iter().any(|(n, _)| n == name) {
916 return Ok(false);
917 }
918 mounts.push((name.into(), Mount::<T>::fjall(data_dir)?));
919 Ok(true)
920 }
921 Err(e) => Err(anyhow!("无法获取写锁: {}", e)),
922 }
923 }
924
925 pub fn mount_dir(&self, name: &str, host_dir: &str) -> Result<bool> {
934 let base = std::fs::canonicalize(host_dir).map_err(|e| anyhow!("mount_dir {} 失败:无法解析 host_dir={}: {}", name, host_dir, e))?;
935 let mounts = self.mounts.write();
936 match mounts {
937 Ok(mut mounts) => {
938 if mounts.iter().any(|(n, _)| n == name) {
939 return Ok(false);
940 }
941 mounts.push((name.into(), Mount::<T>::Dir { base }));
942 Ok(true)
943 }
944 Err(e) => Err(anyhow!("无法获取写锁: {}", e)),
945 }
946 }
947
948 pub fn get_mount<'a>(&self, name: &'a str) -> Result<(Mount<T>, &'a str)> {
949 let (mount, name) = name.split_once('/').ok_or(anyhow!("{} 没有 root 路径", name))?;
950 let mounts = self.mounts.read().map_err(|e| anyhow!("无法获取读锁: {}", e))?;
951 let m = mounts.iter().find_map(|m| if m.0 == mount { Some(m.1.clone()) } else { None }).ok_or(anyhow!("没有找到 {}", name))?;
952 Ok((m, name))
953 }
954}
955
956pub(super) fn safe_path(base: &Path, name: &str) -> Option<PathBuf> {
963 let mut path = base.to_path_buf();
964 for component in Path::new(name).components() {
965 match component {
966 std::path::Component::Normal(c) => path.push(c),
967 std::path::Component::CurDir => {}
968 std::path::Component::ParentDir | std::path::Component::RootDir | std::path::Component::Prefix(_) => return None,
970 }
971 }
972 Some(path)
973}
974
975fn encode_value(name: &str, value: &Dynamic) -> Result<Vec<u8>> {
979 let ext = Path::new(name).extension().and_then(|e| e.to_str()).unwrap_or("");
980 match ext {
981 "json" => {
982 let mut s = String::new();
983 value.to_json(&mut s);
984 Ok(s.into_bytes())
985 }
986 "yaml" | "yml" => Ok(value.to_yaml_string().into_bytes()),
987 "md" => Ok(value.to_markdown().into_bytes()),
988 _ => {
989 let s = value.as_str();
992 Ok(s.as_bytes().to_vec())
993 }
994 }
995}
996
997fn decode_value(name: &str, bytes: &[u8]) -> Result<Dynamic> {
1001 let ext = Path::new(name).extension().and_then(|e| e.to_str()).unwrap_or("");
1002 match ext {
1003 "json" => {
1004 let (v, _) = Dynamic::from_json(bytes)?;
1005 Ok(v)
1006 }
1007 "yaml" | "yml" => {
1008 let (v, _) = Dynamic::from_yaml(bytes)?;
1009 Ok(v)
1010 }
1011 _ => {
1012 let s = std::str::from_utf8(bytes).map_err(|e| anyhow!("非 UTF-8 文本: {}", e))?;
1013 Ok(Dynamic::from(s.to_string()))
1014 }
1015 }
1016}
1017
1018impl Mount<Object> {
1025 pub fn dir_add(&self, name: &str, value: Object) -> bool {
1026 let base = match self {
1027 Self::Dir { base } => base,
1028 _ => return false,
1029 };
1030 let Object::Value(dynamic) = value else { return false };
1031 let Some(path) = safe_path(base, name) else { return false };
1032 let Ok(bytes) = encode_value(name, &dynamic) else { return false };
1033 if let Some(parent) = path.parent() {
1034 if std::fs::create_dir_all(parent).is_err() {
1035 return false;
1036 }
1037 }
1038 std::fs::write(&path, bytes).is_ok()
1039 }
1040
1041 pub fn dir_get(&self, name: &str) -> Result<Dynamic> {
1044 let base = match self {
1045 Self::Dir { base } => base,
1046 _ => return Err(anyhow!("dir_get 仅在 Dir 后端可用")),
1047 };
1048 let path = safe_path(base, name).ok_or_else(|| anyhow!("path 非法: {}", name))?;
1049 if !path.exists() {
1050 return Err(anyhow!("{} 不存在", name));
1051 }
1052 if path.is_dir() {
1053 return Ok(Dynamic::Null);
1054 }
1055 let bytes = std::fs::read(&path).map_err(|e| anyhow!("读取 {} 失败: {}", path.display(), e))?;
1056 decode_value(name, &bytes).map_err(|e| anyhow!("解析 {} 失败: {}", name, e))
1057 }
1058
1059 pub fn dir_remove(&self, name: &str) -> Result<Dynamic> {
1060 let base = match self {
1061 Self::Dir { base } => base,
1062 _ => return Err(anyhow!("dir_remove 仅在 Dir 后端可用")),
1063 };
1064 let path = safe_path(base, name).ok_or_else(|| anyhow!("path 非法: {}", name))?;
1065 if !path.is_file() {
1066 return Err(anyhow!("{} 不是文件", name));
1067 }
1068 let bytes = std::fs::read(&path).map_err(|e| anyhow!("读取 {} 失败: {}", path.display(), e))?;
1069 let dynamic = decode_value(name, &bytes).map_err(|e| anyhow!("解析 {} 失败: {}", name, e))?;
1070 std::fs::remove_file(&path).map_err(|e| anyhow!("删除 {} 失败: {}", path.display(), e))?;
1071 Ok(dynamic)
1072 }
1073
1074 pub fn dir_contains(&self, name: &str) -> bool {
1075 let base = match self {
1076 Self::Dir { base } => base,
1077 _ => return false,
1078 };
1079 match safe_path(base, name) {
1080 Some(p) => p.exists(),
1081 None => false,
1082 }
1083 }
1084
1085 pub fn dir_update<F>(&self, name: &str, f: F) -> Result<Dynamic>
1089 where
1090 F: FnOnce(Dynamic) -> Dynamic,
1091 {
1092 let base = match self {
1093 Self::Dir { base } => base,
1094 _ => return Err(anyhow!("dir_update 仅在 Dir 后端可用")),
1095 };
1096 let path = safe_path(base, name).ok_or_else(|| anyhow!("path 非法: {}", name))?;
1097 if path.is_dir() {
1098 return Err(anyhow!("{} 是目录,不能 update", name));
1099 }
1100 let bytes = std::fs::read(&path).map_err(|e| anyhow!("读取 {} 失败: {}", path.display(), e))?;
1101 let current = decode_value(name, &bytes).map_err(|e| anyhow!("解析 {} 失败: {}", name, e))?;
1102 let next = f(current);
1103 let new_bytes = encode_value(name, &next)?;
1104 if let Some(parent) = path.parent() {
1105 std::fs::create_dir_all(parent).map_err(|e| anyhow!("创建父目录失败: {}", e))?;
1106 }
1107 std::fs::write(&path, new_bytes).map_err(|e| anyhow!("写入 {} 失败: {}", path.display(), e))?;
1108 Ok(next)
1109 }
1110}
1111
1112impl Mount<Object> {
1117 pub fn get_for_object<R, F: FnOnce(&Object) -> R>(&self, name: &str, f: F) -> Result<R> {
1120 match self {
1121 Self::Dir { .. } => {
1122 let dynamic = self.dir_get(name)?;
1123 let mut value = Object::default();
1124 if let Object::Value(slot) = &mut value {
1125 *slot = dynamic;
1126 }
1127 Ok(f(&value))
1128 }
1129 _ => self.get(name, |v| f(v)),
1131 }
1132 }
1133}
1134
1135#[cfg(test)]
1136mod tests {
1137 use super::*;
1138 use dynamic::Dynamic;
1139
1140 #[test]
1141 fn dir_returns_only_immediate_children() {
1142 let root = Root::<Dynamic>::new();
1143 let (mount, name) = root.get_mount("local/test/dir/a").unwrap();
1144 assert!(mount.add(name, 1.into()));
1145 let (mount, name) = root.get_mount("local/test/dir/sub/item").unwrap();
1146 assert!(mount.add(name, 2.into()));
1147 let (mount, name) = root.get_mount("local/test/dir2/x").unwrap();
1148 assert!(mount.add(name, 3.into()));
1149
1150 let (mount, name) = root.get_mount("local/test/dir").unwrap();
1151 let mut entries = mount.dir(name).unwrap();
1152 entries.sort();
1153
1154 assert_eq!(entries, vec![SmolStr::new("a"), SmolStr::new("sub")]);
1155 }
1156
1157 #[test]
1158 fn dir_accepts_trailing_slash() {
1159 let root = Root::<Dynamic>::new();
1160 let (mount, name) = root.get_mount("local/test/slash/a").unwrap();
1161 assert!(mount.add(name, 1.into()));
1162
1163 let (mount, name) = root.get_mount("local/test/slash/").unwrap();
1164 assert_eq!(mount.dir(name).unwrap(), vec![SmolStr::new("a")]);
1165 }
1166
1167 #[test]
1168 fn fjall_mount_persists_values_and_dirs() {
1169 let data_dir = std::env::temp_dir().join(format!("zust-root-fjall-{}", uuid::Uuid::new_v4()));
1170 let data_dir_str = data_dir.to_str().unwrap();
1171
1172 {
1173 let root = Root::<Dynamic>::new();
1174 assert!(root.mount_fjall("fjall", data_dir_str).unwrap());
1175 let (mount, name) = root.get_mount("fjall/test/kv/a").unwrap();
1176 assert!(mount.add(name, 42.into()));
1177 let (mount, name) = root.get_mount("fjall/test/kv/b").unwrap();
1178 assert!(mount.add(name, "persisted".into()));
1179 let (mount, name) = root.get_mount("fjall/test/list").unwrap();
1180 mount.add_list(name);
1181 assert_eq!(mount.push(name, 7.into()).unwrap(), 0);
1182 let (mount, name) = root.get_mount("fjall/test/map").unwrap();
1183 mount.add_map(name);
1184 mount.insert(name, "answer", 42.into());
1185 mount.insert(name, "0", "first".into());
1186 mount.insert(name, "1", "second".into());
1187 }
1188
1189 {
1190 let root = Root::<Dynamic>::new();
1191 assert!(root.mount_fjall("fjall", data_dir_str).unwrap());
1192 let (mount, name) = root.get_mount("fjall/test/kv/a").unwrap();
1193 assert_eq!(mount.get(name, |v| v.as_int()).unwrap(), Some(42));
1194 let (mount, name) = root.get_mount("fjall/test").unwrap();
1195 let mut entries = mount.dir(name).unwrap();
1196 entries.sort();
1197 assert_eq!(entries, vec![SmolStr::new("kv"), SmolStr::new("list"), SmolStr::new("map")]);
1198 let (mount, name) = root.get_mount("fjall/test/list").unwrap();
1199 assert_eq!(mount.get_idx(name, 0, |v| v.as_int()).unwrap(), Some(7));
1200 let (mount, name) = root.get_mount("fjall/test/map").unwrap();
1201 assert_eq!(mount.get_key(name, "answer", |v| v.as_int()).unwrap(), Some(42));
1202 let mut keys = mount.keys(name).unwrap();
1203 keys.sort();
1204 assert_eq!(keys, vec![SmolStr::new("0"), SmolStr::new("1"), SmolStr::new("answer")]);
1205 }
1206
1207 let _ = std::fs::remove_dir_all(data_dir);
1208 }
1209
1210 #[test]
1211 fn fjall_dir_skips_deep_children_without_missing_siblings() {
1212 let data_dir = std::env::temp_dir().join(format!("zust-root-fjall-dir-{}", uuid::Uuid::new_v4()));
1213 let data_dir_str = data_dir.to_str().unwrap();
1214 let root = Root::<Dynamic>::new();
1215 assert!(root.mount_fjall("fjall", data_dir_str).unwrap());
1216
1217 for path in ["fjall/root/a/deep/one", "fjall/root/a/deep/two", "fjall/root/a-/leaf", "fjall/root/a./leaf", "fjall/root/a0/leaf", "fjall/root/b"] {
1218 let (mount, name) = root.get_mount(path).unwrap();
1219 assert!(mount.add(name, 1.into()));
1220 }
1221
1222 let (mount, name) = root.get_mount("fjall/root").unwrap();
1223 let mut entries = mount.dir(name).unwrap();
1224 entries.sort();
1225
1226 assert_eq!(entries, vec![SmolStr::new("a"), SmolStr::new("a-"), SmolStr::new("a."), SmolStr::new("a0"), SmolStr::new("b")]);
1227
1228 let _ = std::fs::remove_dir_all(data_dir);
1229 }
1230
1231 fn fresh_dir_mount(name: &str) -> (Mount<Object>, std::path::PathBuf) {
1238 let dir = std::env::temp_dir().join(format!("zust-root-dir-{}", uuid::Uuid::new_v4()));
1239 std::fs::create_dir_all(&dir).unwrap();
1240 let root = Root::<Object>::new();
1241 assert!(root.mount_dir(name, dir.to_str().unwrap()).unwrap());
1242 let (mount, _) = root.get_mount(&format!("{}/_ignored", name)).unwrap();
1243 (mount, dir)
1244 }
1245
1246 #[test]
1250 fn mount_dir_yaml_round_trip() {
1251 let (mount, base) = fresh_dir_mount("ddd");
1252 let value = Dynamic::map(Default::default());
1253 let value = value;
1254 value.insert("name", "zust");
1255 value.insert("age", 18);
1256
1257 assert!(mount.dir_add("x.yaml", Object::Value(value)));
1258 assert!(base.join("x.yaml").is_file());
1259
1260 let got = mount.dir_get("x.yaml").unwrap();
1261 assert_eq!(got.get_dynamic("name").map(|v| v.as_str().to_string()), Some("zust".to_string()));
1262 assert_eq!(got.get_dynamic("age").and_then(|v| v.as_int()), Some(18));
1263
1264 let _ = std::fs::remove_dir_all(base);
1265 }
1266
1267 #[test]
1268 fn mount_dir_json_round_trip() {
1269 let (mount, base) = fresh_dir_mount("ddd");
1270 let value = Dynamic::map(Default::default());
1271 value.insert("k", "v");
1272 value.insert("n", 42);
1273
1274 assert!(mount.dir_add("data.json", Object::Value(value)));
1275 let got = mount.dir_get("data.json").unwrap();
1276 assert_eq!(got.get_dynamic("k").map(|v| v.as_str().to_string()), Some("v".to_string()));
1277 assert_eq!(got.get_dynamic("n").and_then(|v| v.as_int()), Some(42));
1278
1279 let _ = std::fs::remove_dir_all(base);
1280 }
1281
1282 #[test]
1283 fn mount_dir_md_writes_markdown_reads_string() {
1284 let (mount, base) = fresh_dir_mount("ddd");
1285 let value = Dynamic::map(Default::default());
1286 value.insert("count", 42);
1290 assert!(mount.dir_add("notes.md", Object::Value(value)));
1291 let content = std::fs::read_to_string(base.join("notes.md")).unwrap();
1292 assert!(content.contains("count"));
1293
1294 let got = mount.dir_get("notes.md").unwrap();
1296 assert!(got.as_str().contains("count"));
1297
1298 let _ = std::fs::remove_dir_all(base);
1299 }
1300
1301 #[test]
1302 fn mount_dir_raw_string_extension() {
1303 let (mount, base) = fresh_dir_mount("ddd");
1304 let value = Dynamic::from("hello world".to_string());
1305 assert!(mount.dir_add("readme.txt", Object::Value(value)));
1306 let got = mount.dir_get("readme.txt").unwrap();
1307 assert_eq!(got.as_str(), "hello world");
1308
1309 let _ = std::fs::remove_dir_all(base);
1310 }
1311
1312 #[test]
1313 fn mount_dir_len_file_is_size_dir_is_zero() {
1314 let (mount, base) = fresh_dir_mount("ddd");
1315 let payload = "x".repeat(123);
1316 assert!(mount.dir_add("a.yaml", Object::Value(Dynamic::from(payload))));
1317
1318 let file_len = mount.len("a.yaml").unwrap();
1320 assert!(file_len >= 123);
1321
1322 std::fs::create_dir_all(base.join("sub")).unwrap();
1324 let dir_len = mount.len("sub").unwrap();
1325 assert_eq!(dir_len, 0);
1326
1327 assert!(mount.len("missing").is_err());
1329
1330 let _ = std::fs::remove_dir_all(base);
1331 }
1332
1333 #[test]
1334 fn mount_dir_dir_lists_files_and_dirs_undistinguished() {
1335 let (mount, base) = fresh_dir_mount("ddd");
1336 assert!(mount.dir_add("a.yaml", Object::Value(Dynamic::from(1i64))));
1337 assert!(mount.dir_add("b.txt", Object::Value(Dynamic::from("x".to_string()))));
1338 std::fs::create_dir_all(base.join("sub")).unwrap();
1339 std::fs::create_dir_all(base.join("nested")).unwrap();
1340
1341 let mut names = mount.dir_raw("").unwrap();
1342 names.sort();
1343 assert_eq!(names, vec![SmolStr::new("a.yaml"), SmolStr::new("b.txt"), SmolStr::new("nested"), SmolStr::new("sub")]);
1344
1345 let _ = std::fs::remove_dir_all(base);
1346 }
1347
1348 #[test]
1349 fn mount_dir_get_directory_returns_null() {
1350 let (mount, base) = fresh_dir_mount("ddd");
1351 std::fs::create_dir_all(base.join("subdir")).unwrap();
1352
1353 let got = mount.dir_get("subdir").unwrap();
1355 assert!(got.is_null());
1356
1357 let _ = std::fs::remove_dir_all(base);
1358 }
1359
1360 #[test]
1361 fn mount_dir_get_missing_returns_err() {
1362 let (mount, base) = fresh_dir_mount("ddd");
1363 assert!(mount.dir_get("missing.yaml").is_err());
1364 let _ = std::fs::remove_dir_all(base);
1365 }
1366
1367 #[test]
1368 fn mount_dir_contains_file_and_dir() {
1369 let (mount, base) = fresh_dir_mount("ddd");
1370 assert!(mount.dir_add("a.yaml", Object::Value(Dynamic::from(1i64))));
1371 std::fs::create_dir_all(base.join("sub")).unwrap();
1372
1373 assert!(mount.dir_contains("a.yaml"));
1374 assert!(mount.dir_contains("sub"));
1375 assert!(!mount.dir_contains("missing"));
1376
1377 let _ = std::fs::remove_dir_all(base);
1378 }
1379
1380 #[test]
1381 fn mount_dir_remove_returns_decoded_value() {
1382 let (mount, base) = fresh_dir_mount("ddd");
1383 let v = Dynamic::map(Default::default());
1384 v.insert("hello", "world");
1385 assert!(mount.dir_add("x.json", Object::Value(v)));
1386
1387 let removed = mount.dir_remove("x.json").unwrap();
1388 assert_eq!(removed.get_dynamic("hello").map(|d| d.as_str().to_string()), Some("world".to_string()));
1389 assert!(!base.join("x.json").exists());
1390
1391 let _ = std::fs::remove_dir_all(base);
1392 }
1393
1394 #[test]
1395 fn mount_dir_path_traversal_rejected() {
1396 let (mount, base) = fresh_dir_mount("ddd");
1397 assert!(!mount.dir_add("../escape.txt", Object::Value(Dynamic::from("pwned"))));
1399 let escape = base.parent().unwrap().join("escape.txt");
1401 assert!(!escape.exists());
1402
1403 assert!(!mount.dir_add("/etc/passwd", Object::Value(Dynamic::from("x"))));
1405
1406 let _ = std::fs::remove_dir_all(base);
1407 }
1408
1409 #[test]
1410 fn mount_dir_update_read_modify_write() {
1411 let (mount, base) = fresh_dir_mount("ddd");
1412 let v = Dynamic::map(Default::default());
1413 v.insert("count", 1);
1414 assert!(mount.dir_add("c.json", Object::Value(v)));
1415
1416 let next = mount
1417 .dir_update("c.json", |cur| {
1418 if let Some(n) = cur.get_dynamic("count").and_then(|d| d.as_int()) {
1419 cur.insert("count", n + 10);
1420 }
1421 cur
1422 })
1423 .unwrap();
1424 assert_eq!(next.get_dynamic("count").and_then(|d| d.as_int()), Some(11));
1425
1426 let _ = std::fs::remove_dir_all(base);
1427 }
1428
1429 #[test]
1430 fn mount_dir_rejects_nonexistent_host_dir() {
1431 let root = Root::<Object>::new();
1432 let missing = std::env::temp_dir().join(format!("zust-root-missing-{}", uuid::Uuid::new_v4()));
1433 assert!(root.mount_dir("ddd", missing.to_str().unwrap()).is_err());
1434 }
1435
1436 #[test]
1437 fn mount_dir_rejects_list_and_map_operations() {
1438 let (mount, base) = fresh_dir_mount("ddd");
1439 mount.add_list("lst");
1442 assert!(mount.push("lst", Object::Value(Dynamic::from(1i64))).is_err());
1443 mount.insert("mp", "k", Object::Value(Dynamic::from(1i64)));
1444 assert!(mount.get_idx("lst", 0, |v| v.value()).is_err());
1445 assert!(mount.get_key("mp", "k", |v| v.value()).is_err());
1446 assert!(mount.keys("mp").is_err());
1447
1448 let _ = std::fs::remove_dir_all(base);
1449 }
1450}