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