Skip to main content

root/
mount.rs

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    /// 把真实文件系统目录挂到 ROOT 树:`base` 是 host 路径(canonicalize 后)。
31    /// 与 Memory/Redis/Fjall 不同,Dir 后端**不支持** List/Map 语义(`add_list`、
32    /// `add_map`、`push`、`get_idx`、`insert` 等都返回 Err),只支持标量文件读写,
33    /// 且序列化方式按文件扩展名 dispatch:`.json` 走 to_json/from_json,
34    /// `.yaml`/`.yml` 走 to_yaml/from_yaml,`.md` 写用 to_markdown / 读回 String,
35    /// 其他后缀按 String 处理。
36    Dir { base: PathBuf },
37}
38
39impl<T: std::fmt::Debug + MsgPack + MsgUnpack + Default + Send> Mount<T> {
40    /// usize 索引转 isize 供 Redis lindex/lset 使用。
41    /// idx > isize::MAX 时回绕成负数会被 Redis 当成"从末尾倒数",与 zust 的绝对索引语义冲突,
42    /// 报错而非静默错误索引。
43    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            // Dir 后端的 add 由 `impl Mount<Object>::dir_add` 处理,
91            // lib.rs 在调用本方法前会先 match Dir 分发,这里不会真的执行到。
92            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                        // TTL 必须覆盖一次完整 RMW(GET+decode+闭包+encode+SET),
127                        // 原先 0..999ms 可能短于一次 Redis 往返,导致锁提前过期丢更新。
128                        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                        // 锁被占用时退避,避免 CPU 100% 空转。
140                        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            // Dir 后端的 update 走 `impl Mount<Object>::dir_update`。
153            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            // Dir 后端的 get 走下面 `impl Mount<Object>::get` 的特化实现——
174            // 泛型 T 不一定能包成 Object::Value,需要单独处理。
175            // 兜底仍报 Err,让上层知道这个 T 走不了 Dir。
176            Self::Dir { .. } => Err(anyhow!("Mount::Dir 的 get 请用 impl Mount<Object>::get")),
177        }
178    }
179
180    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>
181    where
182        F: Send + 'static,
183    {
184        match self {
185            Self::Memory(m) => m
186                .update_sync(name, |_, v| match v {
187                    Node::Map(m) => m.update_sync(key, |_, v| f(v)),
188                    _ => None,
189                })
190                .flatten()
191                .ok_or(anyhow!("{} 不存在", name)),
192            Self::Redis { client, rl } => {
193                let name = String::from(name);
194                let key = String::from(key);
195                let rl = rl.clone();
196                let client = client.clone();
197                sync_await!(async move {
198                    loop {
199                        // TTL 必须覆盖一次完整 RMW,原先 0..999ms 会丢更新(见 get_mut 注释)。
200                        let lock_name = format!("{}::{}", name, key); //为这个 name 里面的 key 单独上锁
201                        if let Ok(lock) = rl.lock(lock_name.as_str(), std::time::Duration::from_millis(5000)).await {
202                            let mut conn = client.get_multiplexed_async_connection().await?;
203                            let mut buf: Vec<u8> = conn.hget(name.as_str(), key.as_str()).await?;
204                            let (mut v, _) = T::decode(buf.as_slice())?;
205                            let r = f(&mut v);
206                            buf.clear();
207                            v.encode(&mut buf);
208                            conn.hset::<&str, &str, Vec<u8>, ()>(name.as_str(), key.as_str(), buf).await?;
209                            rl.unlock(&lock).await;
210                            break Ok(r);
211                        }
212                        // 锁被占用时退避,避免 CPU 100% 空转。
213                        tokio::time::sleep(std::time::Duration::from_millis(random_range(1..10))).await;
214                    }
215                })
216            }
217            Self::Fjall { db, values, write_lock } => {
218                let _guard = write_lock.lock().map_err(|e| anyhow!("无法获取 fjall 写锁: {}", e))?;
219                let mut v = fjall_get_map_item::<T>(values, name, key)?;
220                let r = f(&mut v);
221                fjall_insert_map_item(values, name, key, &v)?;
222                fjall_persist(db)?;
223                Ok(r)
224            }
225            // Dir 后端无 map 内 key 概念。
226            Self::Dir { .. } => Err(anyhow!("Mount::Dir 不支持 map 内 key 操作")),
227        }
228    }
229
230    pub fn dir(&self, name: &str) -> Result<Vec<SmolStr>> {
231        if let Self::Redis { client, rl: _ } = self {
232            let mut conn = client.get_connection()?;
233            return directory::children(&mut conn, name);
234        }
235        if let Self::Fjall { values, .. } = self {
236            return fjall_dir(values, name);
237        }
238        // Dir 后端的目录列表由 `impl Mount<Object>::dir_list` 处理。
239        if let Self::Dir { .. } = self {
240            return Err(anyhow!("Mount::Dir 的目录列表请用 dir_list"));
241        }
242
243        let prefix = if name.is_empty() || name.ends_with('/') { name.to_string() } else { format!("{name}/") };
244        let raw = self.dir_raw(&prefix)?;
245        Ok(Self::dir_entries_from_raw(&prefix, raw))
246    }
247
248    fn dir_raw_entries_from_children(prefix: &str, children: Vec<SmolStr>) -> Vec<SmolStr> {
249        children.into_iter().map(|child| if prefix.is_empty() { child } else { format!("{prefix}{child}").into() }).collect()
250    }
251
252    fn dir_entries_from_raw(prefix: &str, raw: Vec<SmolStr>) -> Vec<SmolStr> {
253        let mut seen = std::collections::HashSet::new();
254        let mut names = Vec::new();
255        for key in raw {
256            let Some(rest) = key.strip_prefix(&prefix) else {
257                continue;
258            };
259            let end = rest.find('/').unwrap_or(rest.len());
260            let entry = &rest[..end];
261            if !entry.is_empty() && seen.insert(entry.to_string()) {
262                names.push(entry.into());
263            }
264        }
265        names
266    }
267
268    pub fn dir_raw(&self, name: &str) -> Result<Vec<SmolStr>> {
269        let mut names = Vec::new();
270        match self {
271            // 性能:Memory 后端用 scc::HashMap(无序),dir_raw 只能 O(全表) starts_with 扫描。
272            // Redis 用 directory::children 索引,Fjall 用 prefix scan,都是有界的。
273            // http_module::dynamic_api_route 每请求递归调 dir,在 Memory 后端 + 大量节点时是热点。
274            // 根治需把 Memory 后端换成 BTreeMap 或维护前缀二级索引——属架构改动,暂不做。
275            Self::Memory(m) => {
276                m.iter_sync(|key, _| {
277                    if key.starts_with(name) {
278                        names.push(key.clone())
279                    }
280                    true
281                });
282            }
283            Self::Redis { client, rl: _ } => {
284                let mut conn = client.get_connection()?;
285                let prefix = if name.is_empty() || name.ends_with('/') { name.to_string() } else { format!("{name}/") };
286                names.append(&mut Self::dir_raw_entries_from_children(&prefix, directory::children(&mut conn, name)?));
287            }
288            Self::Fjall { values, .. } => {
289                names.append(&mut fjall_paths_with_prefix(values, name)?);
290            }
291            // Dir 后端直接列一级:不区分文件/目录,调用方用 len 区分(目录=0)。
292            Self::Dir { base } => {
293                let path = safe_path(base, name).ok_or_else(|| anyhow!("path 非法: {}", name))?;
294                if !path.exists() {
295                    return Err(anyhow!("{} 不存在", name));
296                }
297                if !path.is_dir() {
298                    return Err(anyhow!("{} 不是目录", name));
299                }
300                for entry in std::fs::read_dir(&path).map_err(|e| anyhow!("读取 {} 失败: {}", path.display(), e))? {
301                    let entry = entry.map_err(|e| anyhow!("读取目录项失败: {}", e))?;
302                    if let Some(s) = entry.file_name().to_str() {
303                        names.push(SmolStr::new(s));
304                    }
305                }
306                names.sort();
307            }
308        }
309        Ok(names)
310    }
311
312    pub fn len(&self, name: &str) -> Result<usize> {
313        match self {
314            Self::Memory(m) => m.read_sync(name, |_, v| v.len()).ok_or(anyhow!("{} 不是列表", name)),
315            Self::Redis { client, rl: _ } => {
316                let mut conn = client.get_connection()?;
317                let ty: String = conn.key_type(name)?;
318                match ty.as_str() {
319                    "list" => Ok(conn.llen(name)?),
320                    "hash" => Ok(conn.hlen(name)?),
321                    _ => Ok(1),
322                }
323            }
324            Self::Fjall { values, .. } => match fjall_node_type(values, name)? {
325                Some(FjallNodeType::List) => Ok(fjall_count_prefix(values, fjall_list_prefix(name))?),
326                Some(FjallNodeType::Map) => Ok(fjall_count_prefix(values, fjall_map_prefix(name))?),
327                None if values.contains_key(fjall_object_key(name))? => Ok(1),
328                None => Err(anyhow!("{} 不存在", name)),
329            },
330            // Dir 后端:文件 = 字节数,目录 = 0,不存在 = 报错。
331            Self::Dir { base } => {
332                let path = safe_path(base, name).ok_or_else(|| anyhow!("path 非法: {}", name))?;
333                let metadata = std::fs::metadata(&path).map_err(|e| anyhow!("{} 不存在: {}", name, e))?;
334                if metadata.is_dir() {
335                    Ok(0)
336                } else {
337                    Ok(metadata.len() as usize)
338                }
339            }
340        }
341    }
342
343    pub fn remove(&self, name: &str) -> Result<T> {
344        match self {
345            // into_object 对 List/Map 节点返回 None(只能取标量 Object),
346            // 但 Redis/Fjall 的 remove 按 name 直接删任意类型。三后端行为统一:
347            // List/Map 节点删除成功,返回 default(节点本身无标量值,default 表示"已删除")。
348            Self::Memory(m) => m.remove_sync(name).map(|(_, v)| v.into_object().unwrap_or_default()).ok_or(anyhow!("{} 不存在", name)),
349            Self::Redis { client, rl: _ } => {
350                let mut conn = client.get_connection()?;
351                let buf: Vec<u8> = conn.get(name)?;
352                let (v, _) = T::decode(buf.as_slice())?;
353                let removed: usize = conn.del(name)?;
354                if removed != 0 {
355                    directory::remove_path(&mut conn, name)?;
356                }
357                Ok(v)
358            }
359            Self::Fjall { db, values, .. } => {
360                let v = fjall_get_object(values, name)?;
361                values.remove(fjall_object_key(name))?;
362                fjall_persist(db)?;
363                Ok(v)
364            }
365            // Dir 后端的 remove 由 `impl Mount<Object>::dir_remove` 处理。
366            Self::Dir { .. } => Err(anyhow!("Mount::Dir 的删除请用 dir_remove")),
367        }
368    }
369
370    pub fn add_list(&self, name: &str) {
371        match self {
372            Self::Memory(m) => {
373                m.upsert_sync(name.into(), Node::<T>::list());
374            } // 强制插入 肯定成功
375            Self::Redis { client: _, rl: _ } => {}
376            Self::Fjall { db, values, write_lock } => {
377                if let Ok(_guard) = write_lock.lock() {
378                    let _ = fjall_clear_node(values, name)
379                        .and_then(|_| fjall_set_node_type(values, name, FjallNodeType::List))
380                        .and_then(|_| fjall_persist(db));
381                }
382            }
383            Self::Dir { .. } => {
384                log::warn!("Mount::Dir 不支持 add_list,忽略 {}", name);
385            }
386        }
387    }
388
389    pub fn add_map(&self, name: &str) {
390        match self {
391            Self::Memory(m) => {
392                m.upsert_sync(name.into(), Node::<T>::map());
393            } // 强制插入 肯定成功
394            Self::Redis { client: _, rl: _ } => {}
395            Self::Fjall { db, values, write_lock } => {
396                if let Ok(_guard) = write_lock.lock() {
397                    let _ = fjall_clear_node(values, name)
398                        .and_then(|_| fjall_set_node_type(values, name, FjallNodeType::Map))
399                        .and_then(|_| fjall_persist(db));
400                }
401            }
402            Self::Dir { .. } => {
403                log::warn!("Mount::Dir 不支持 add_map,忽略 {}", name);
404            }
405        }
406    }
407
408    pub fn push(&self, name: &str, value: T) -> Result<usize> {
409        match self {
410            Self::Memory(m) => m.update_sync(name, |_, v| v.push(value)).flatten().ok_or(anyhow!("push {} 失败", name)),
411            Self::Redis { client, rl: _ } => {
412                let mut conn = client.get_connection()?;
413                let mut buf = Vec::new();
414                value.encode(&mut buf);
415                let len = conn.rpush(name, buf)?;
416                directory::add_path(&mut conn, name)?;
417                Ok(len)
418            }
419            Self::Fjall { db, values, write_lock } => {
420                let _guard = write_lock.lock().map_err(|e| anyhow!("无法获取 fjall 写锁: {}", e))?;
421                match fjall_node_type(values, name)? {
422                    Some(FjallNodeType::List) => {}
423                    Some(FjallNodeType::Map) => return Err(anyhow!("push {} 失败", name)),
424                    None => fjall_set_node_type(values, name, FjallNodeType::List)?,
425                }
426                let idx = fjall_next_list_idx(values, name)?;
427                fjall_insert_list_item(values, name, idx, &value)?;
428                fjall_persist(db)?;
429                Ok(idx)
430            }
431            Self::Dir { .. } => Err(anyhow!("Mount::Dir 不支持 push/List 语义")),
432        }
433    }
434
435    pub fn get_idx<R, F: FnOnce(&T) -> R>(&self, name: &str, idx: usize, f: F) -> Result<R> {
436        match self {
437            Self::Memory(m) => m.read_sync(name, |_, v| v.get_idx(idx, f)).flatten().ok_or(anyhow!("get_idx {} 失败", name)),
438            Self::Redis { client, rl: _ } => {
439                let mut conn = client.get_connection()?;
440                let buf: Vec<u8> = conn.lindex(name, Self::redis_idx(idx)?)?;
441                // remove_idx 用 lset 写空 Vec 保留槽位(sparse slot 设计)。
442                // 空槽 decode 失败时返回 default,与 remove_idx 和 Memory 后端行为一致,
443                // 而非中断调用方。
444                let (v, _) = T::decode(buf.as_slice()).unwrap_or_else(|_| (T::default(), 0));
445                Ok(f(&v))
446            }
447            Self::Fjall { values, .. } => fjall_get_list_item(values, name, idx).map(|v| f(&v)),
448            Self::Dir { .. } => Err(anyhow!("Mount::Dir 不支持 get_idx/List 语义")),
449        }
450    }
451
452    pub fn get_idx_mut<R, F: FnMut(&mut T) -> R>(&self, name: &str, idx: usize, mut f: F) -> Result<R> {
453        match self {
454            Self::Memory(m) => m.update_sync(name, |_, v| v.get_idx_mut(idx, f)).flatten().ok_or(anyhow!("get_idx {} 失败", name)),
455            Self::Redis { client, rl: _ } => {
456                let mut conn = client.get_connection()?;
457                let buf: Vec<u8> = conn.lindex(name, Self::redis_idx(idx)?)?;
458                let (mut v, _) = T::decode(buf.as_slice())?;
459                Ok(f(&mut v))
460            }
461            Self::Fjall { db, values, write_lock } => {
462                let _guard = write_lock.lock().map_err(|e| anyhow!("无法获取 fjall 写锁: {}", e))?;
463                let mut v = fjall_get_list_item::<T>(values, name, idx)?;
464                let r = f(&mut v);
465                fjall_insert_list_item(values, name, idx, &v)?;
466                fjall_persist(db)?;
467                Ok(r)
468            }
469            Self::Dir { .. } => Err(anyhow!("Mount::Dir 不支持 get_idx_mut/List 语义")),
470        }
471    }
472
473    pub fn remove_idx(&self, name: &str, idx: usize) -> Result<T> {
474        match self {
475            Self::Memory(m) => m.update_sync(name, |_, v| v.remove_idx(idx)).flatten().ok_or(anyhow!("remove_idx {} 失败", name)),
476            Self::Redis { client, rl: _ } => {
477                let mut conn = client.get_connection()?;
478                let idx = Self::redis_idx(idx)?;
479                let buf: Vec<u8> = conn.lindex(name, idx)?;
480                let v = T::decode(buf.as_slice()).map(|(v, _)| v).unwrap_or(T::default());
481                let _: () = conn.lset(name, idx, Vec::new())?;
482                Ok(v)
483            }
484            Self::Fjall { db, values, .. } => {
485                let key = fjall_list_item_key(name, idx);
486                let buf = values.get(&key)?.ok_or(anyhow!("remove_idx {} 失败", name))?;
487                let (v, _) = T::decode(buf.as_ref())?;
488                values.remove(key)?;
489                fjall_persist(db)?;
490                Ok(v)
491            }
492            Self::Dir { .. } => Err(anyhow!("Mount::Dir 不支持 remove_idx/List 语义")),
493        }
494    }
495
496    pub fn insert(&self, name: &str, key: &str, value: T) -> Option<T> {
497        match self {
498            Self::Memory(m) => m.update_sync(name, |_, v| v.insert(key.into(), value)).flatten(),
499            Self::Redis { client, rl: _ } => {
500                if let Ok(mut conn) = client.get_connection() {
501                    let mut buf = Vec::new();
502                    value.encode(&mut buf);
503                    if conn.hset::<&str, &str, Vec<u8>, ()>(name, key, buf).is_ok() {
504                        let _ = directory::add_path(&mut conn, name);
505                    }
506                }
507                None
508            }
509            Self::Fjall { db, values, write_lock } => {
510                let _guard = write_lock.lock().ok()?;
511                match fjall_node_type(values, name).ok()? {
512                    Some(FjallNodeType::Map) => {}
513                    Some(FjallNodeType::List) => return None,
514                    None => fjall_set_node_type(values, name, FjallNodeType::Map).ok()?,
515                }
516                let old = fjall_get_map_item(values, name, key).ok();
517                fjall_insert_map_item(values, name, key, &value).ok()?;
518                fjall_persist(db).ok()?;
519                old
520            }
521            Self::Dir { .. } => {
522                log::warn!("Mount::Dir 不支持 insert/Map 语义,忽略 {}/{}", name, key);
523                None
524            }
525        }
526    }
527
528    pub fn get_key<R, F: FnOnce(&T) -> R>(&self, name: &str, key: &str, f: F) -> Result<R> {
529        match self {
530            Self::Memory(m) => m.read_sync(name, |_, v| v.get_key(key, f)).flatten().ok_or(anyhow!("get_key {} 失败", name)),
531            Self::Redis { client, rl: _ } => {
532                let mut conn = client.get_connection()?;
533                let buf: Vec<u8> = conn.hget(name, key)?;
534                let (v, _) = T::decode(buf.as_slice())?;
535                Ok(f(&v))
536            }
537            Self::Fjall { values, .. } => fjall_get_map_item(values, name, key).map(|v| f(&v)),
538            Self::Dir { .. } => Err(anyhow!("Mount::Dir 不支持 get_key/Map 语义")),
539        }
540    }
541
542    pub fn keys(&self, name: &str) -> Result<Vec<SmolStr>> {
543        match self {
544            Self::Memory(m) => m.read_sync(name, |_, v| v.keys()).flatten().ok_or(anyhow!("keys {} 失败", name)),
545            Self::Redis { client, rl: _ } => {
546                let mut conn = client.get_connection()?;
547                let keys: Vec<String> = conn.hkeys(name)?;
548                Ok(keys.into_iter().map(|k| k.into()).collect())
549            }
550            Self::Fjall { values, .. } => fjall_map_keys(values, name),
551            Self::Dir { .. } => Err(anyhow!("Mount::Dir 不支持 keys/Map 语义")),
552        }
553    }
554
555    pub fn remove_key(&self, name: &str, key: &str) -> Result<T> {
556        match self {
557            Self::Memory(m) => m.update_sync(name, |_, v| v.remove_key(key)).flatten().ok_or(anyhow!("remove_key {} 失败", name)),
558            Self::Redis { client, rl: _ } => {
559                let mut conn = client.get_connection()?;
560                let buf: Vec<u8> = conn.hget(name, key)?;
561                let v = T::decode(buf.as_slice())?.0;
562                let _: usize = conn.hdel(name, key)?;
563                if !conn.exists::<_, bool>(name)? {
564                    directory::remove_path(&mut conn, name)?;
565                }
566                Ok(v)
567            }
568            Self::Fjall { db, values, .. } => {
569                let item_key = fjall_map_item_key(name, key);
570                let buf = values.get(&item_key)?.ok_or(anyhow!("remove_key {} 失败", name))?;
571                let (v, _) = T::decode(buf.as_ref())?;
572                values.remove(item_key)?;
573                fjall_persist(db)?;
574                Ok(v)
575            }
576            Self::Dir { .. } => Err(anyhow!("Mount::Dir 不支持 remove_key/Map 语义")),
577        }
578    }
579}
580
581#[derive(Clone, Copy)]
582enum FjallNodeType {
583    List,
584    Map,
585}
586
587const FJALL_OBJECT_PREFIX: u8 = b'o';
588const FJALL_TYPE_PREFIX: u8 = b't';
589const FJALL_LIST_PREFIX: u8 = b'l';
590const FJALL_LIST_COUNTER_PREFIX: u8 = b'c';
591const FJALL_MAP_PREFIX: u8 = b'm';
592const FJALL_SEPARATOR: u8 = 0;
593
594fn fjall_key(prefix: u8, name: &str) -> Vec<u8> {
595    let mut key = Vec::with_capacity(2 + name.len());
596    key.push(prefix);
597    key.push(FJALL_SEPARATOR);
598    key.extend_from_slice(name.as_bytes());
599    key
600}
601
602fn fjall_object_key(name: &str) -> Vec<u8> {
603    fjall_key(FJALL_OBJECT_PREFIX, name)
604}
605
606fn fjall_type_key(name: &str) -> Vec<u8> {
607    fjall_key(FJALL_TYPE_PREFIX, name)
608}
609
610fn fjall_list_counter_key(name: &str) -> Vec<u8> {
611    fjall_key(FJALL_LIST_COUNTER_PREFIX, name)
612}
613
614fn fjall_item_prefix(prefix: u8, name: &str) -> Vec<u8> {
615    let mut key = fjall_key(prefix, name);
616    key.push(FJALL_SEPARATOR);
617    key
618}
619
620fn fjall_list_prefix(name: &str) -> Vec<u8> {
621    fjall_item_prefix(FJALL_LIST_PREFIX, name)
622}
623
624fn fjall_map_prefix(name: &str) -> Vec<u8> {
625    fjall_item_prefix(FJALL_MAP_PREFIX, name)
626}
627
628fn fjall_list_item_key(name: &str, idx: usize) -> Vec<u8> {
629    let mut key = fjall_list_prefix(name);
630    key.extend_from_slice(&(idx as u64).to_be_bytes());
631    key
632}
633
634fn fjall_map_item_key(name: &str, key: &str) -> Vec<u8> {
635    let mut item_key = fjall_map_prefix(name);
636    item_key.extend_from_slice(key.as_bytes());
637    item_key
638}
639
640fn fjall_prefix_end(prefix: &[u8]) -> Option<Vec<u8>> {
641    let mut end = prefix.to_vec();
642    for idx in (0..end.len()).rev() {
643        if end[idx] < 255 {
644            end[idx] += 1;
645            end.truncate(idx + 1);
646            return Some(end);
647        }
648    }
649    None
650}
651
652fn fjall_decode_value<T: MsgUnpack>(buf: &[u8]) -> Result<T> {
653    T::decode(buf).map(|(value, _)| value)
654}
655
656fn fjall_encode_value<T: MsgPack>(value: &T) -> Vec<u8> {
657    let mut buf = Vec::new();
658    value.encode(&mut buf);
659    buf
660}
661
662fn fjall_persist(db: &OptimisticTxDatabase) -> Result<()> {
663    // fjall 官方明确:persist 只影响 durability(断电后是否丢数据),不影响 consistency。
664    // 即使不 flush,fjall 也是 crash-safe 的——进程崩溃不丢已写入数据。
665    // 原先用 SyncAll(fsync)对每个单 key 写入强制落盘,I/O 开销是内存写的 100-1000 倍,
666    // 而 fjall 文档(db.rs:332)明确:"Even without flushing data is crash-safe."
667    // 改用 Buffer:数据进 OS page cache,进程崩溃安全,仅断电可能丢 journal 末尾少量写入。
668    // 若需断电级 durability,应使用 Redis/PostgreSQL 而非单 key fsync。
669    Ok(db.persist(PersistMode::Buffer)?)
670}
671
672fn fjall_get_object<T: MsgUnpack>(values: &OptimisticTxKeyspace, name: &str) -> Result<T> {
673    let buf = values.get(fjall_object_key(name))?.ok_or(anyhow!("{} 不存在", name))?;
674    fjall_decode_value(buf.as_ref())
675}
676
677fn fjall_insert_object<T: MsgPack>(values: &OptimisticTxKeyspace, name: &str, value: &T) -> Result<()> {
678    Ok(values.insert(fjall_object_key(name), fjall_encode_value(value))?)
679}
680
681fn fjall_get_list_item<T: MsgUnpack>(values: &OptimisticTxKeyspace, name: &str, idx: usize) -> Result<T> {
682    let buf = values.get(fjall_list_item_key(name, idx))?.ok_or(anyhow!("get_idx {} 失败", name))?;
683    fjall_decode_value(buf.as_ref())
684}
685
686fn fjall_insert_list_item<T: MsgPack>(values: &OptimisticTxKeyspace, name: &str, idx: usize, value: &T) -> Result<()> {
687    Ok(values.insert(fjall_list_item_key(name, idx), fjall_encode_value(value))?)
688}
689
690fn fjall_get_map_item<T: MsgUnpack>(values: &OptimisticTxKeyspace, name: &str, key: &str) -> Result<T> {
691    let buf = values.get(fjall_map_item_key(name, key))?.ok_or(anyhow!("get_key {} 失败", name))?;
692    fjall_decode_value(buf.as_ref())
693}
694
695fn fjall_insert_map_item<T: MsgPack>(values: &OptimisticTxKeyspace, name: &str, key: &str, value: &T) -> Result<()> {
696    Ok(values.insert(fjall_map_item_key(name, key), fjall_encode_value(value))?)
697}
698
699fn fjall_node_type(values: &OptimisticTxKeyspace, name: &str) -> Result<Option<FjallNodeType>> {
700    Ok(values.get(fjall_type_key(name))?.and_then(|value| match value.as_ref() {
701        b"L" => Some(FjallNodeType::List),
702        b"M" => Some(FjallNodeType::Map),
703        _ => None,
704    }))
705}
706
707fn fjall_set_node_type(values: &OptimisticTxKeyspace, name: &str, node_type: FjallNodeType) -> Result<()> {
708    let value = match node_type {
709        FjallNodeType::List => b"L".as_slice(),
710        FjallNodeType::Map => b"M".as_slice(),
711    };
712    Ok(values.insert(fjall_type_key(name), value)?)
713}
714
715fn fjall_clear_node(values: &OptimisticTxKeyspace, name: &str) -> Result<()> {
716    values.remove(fjall_object_key(name))?;
717    values.remove(fjall_type_key(name))?;
718    values.remove(fjall_list_counter_key(name))?;
719    fjall_remove_prefix(values, fjall_list_prefix(name))?;
720    fjall_remove_prefix(values, fjall_map_prefix(name))?;
721    Ok(())
722}
723
724fn fjall_remove_prefix(values: &OptimisticTxKeyspace, prefix: Vec<u8>) -> Result<()> {
725    let mut keys = Vec::new();
726    for item in values.inner().prefix(prefix) {
727        keys.push(item.key()?);
728    }
729    for key in keys {
730        values.remove(key)?;
731    }
732    Ok(())
733}
734
735fn fjall_next_list_idx(values: &OptimisticTxKeyspace, name: &str) -> Result<usize> {
736    let key = fjall_list_counter_key(name);
737    let current = values.get(&key)?.and_then(|buf| buf.as_ref().try_into().ok().map(u64::from_be_bytes)).unwrap_or(0);
738    values.insert(key, (current + 1).to_be_bytes())?;
739    Ok(current as usize)
740}
741
742fn fjall_count_prefix(values: &OptimisticTxKeyspace, prefix: Vec<u8>) -> Result<usize> {
743    let mut count = 0;
744    for item in values.inner().prefix(prefix) {
745        item.key()?;
746        count += 1;
747    }
748    Ok(count)
749}
750
751fn fjall_paths_with_prefix(values: &OptimisticTxKeyspace, prefix: &str) -> Result<Vec<SmolStr>> {
752    let mut names = Vec::new();
753    names.extend(fjall_paths_for_key_prefix(values, FJALL_OBJECT_PREFIX, prefix)?);
754    names.extend(fjall_paths_for_key_prefix(values, FJALL_TYPE_PREFIX, prefix)?);
755    names.sort();
756    names.dedup();
757    Ok(names)
758}
759
760fn fjall_paths_for_key_prefix(values: &OptimisticTxKeyspace, key_prefix: u8, path_prefix: &str) -> Result<Vec<SmolStr>> {
761    let mut scan_prefix = fjall_key(key_prefix, path_prefix);
762    if path_prefix.is_empty() {
763        scan_prefix.truncate(2);
764    }
765    let mut names = Vec::new();
766    for item in values.inner().prefix(scan_prefix) {
767        let key = item.key()?;
768        let Some(path) = key.get(2..) else {
769            continue;
770        };
771        if let Ok(path) = std::str::from_utf8(path) {
772            names.push(path.into());
773        }
774    }
775    Ok(names)
776}
777
778fn fjall_dir(values: &OptimisticTxKeyspace, name: &str) -> Result<Vec<SmolStr>> {
779    let path_prefix = if name.is_empty() || name.ends_with('/') { name.to_string() } else { format!("{name}/") };
780    let mut names = Vec::new();
781    names.extend(fjall_dir_for_key_prefix(values, FJALL_OBJECT_PREFIX, &path_prefix)?);
782    names.extend(fjall_dir_for_key_prefix(values, FJALL_TYPE_PREFIX, &path_prefix)?);
783    names.sort();
784    names.dedup();
785    Ok(names)
786}
787
788fn fjall_dir_for_key_prefix(values: &OptimisticTxKeyspace, key_prefix: u8, path_prefix: &str) -> Result<Vec<SmolStr>> {
789    use std::ops::Bound::{Excluded, Included, Unbounded};
790
791    let scan_start = fjall_key(key_prefix, path_prefix);
792    let scan_end = fjall_prefix_end(&scan_start);
793    let mut start = scan_start;
794    let mut names = Vec::new();
795
796    loop {
797        if scan_end.as_ref().is_some_and(|end| start >= *end) {
798            break;
799        }
800
801        let end = scan_end.as_deref().map(Excluded).unwrap_or(Unbounded);
802        let iter = values.inner().range::<&[u8], _>((Included(start.as_slice()), end));
803        let mut restart_at = None;
804
805        for item in iter {
806            let key = item.key()?;
807            let Some(path) = key.get(2..) else {
808                continue;
809            };
810            let Ok(path) = std::str::from_utf8(path) else {
811                continue;
812            };
813            let Some(rest) = path.strip_prefix(path_prefix) else {
814                continue;
815            };
816            let Some(end) = rest.find('/') else {
817                if !rest.is_empty() {
818                    names.push(rest.into());
819                }
820                continue;
821            };
822
823            let entry = &rest[..end];
824            if !entry.is_empty() {
825                names.push(entry.into());
826                restart_at = fjall_prefix_end(&fjall_key(key_prefix, &format!("{path_prefix}{entry}/")));
827                break;
828            }
829        }
830
831        let Some(next_start) = restart_at else {
832            break;
833        };
834        start = next_start;
835    }
836
837    Ok(names)
838}
839
840fn fjall_map_keys(values: &OptimisticTxKeyspace, name: &str) -> Result<Vec<SmolStr>> {
841    let prefix = fjall_map_prefix(name);
842    let mut keys = Vec::new();
843    for item in values.inner().prefix(&prefix) {
844        let key = item.key()?;
845        let Some(map_key) = key.get(prefix.len()..) else {
846            continue;
847        };
848        if let Ok(map_key) = std::str::from_utf8(map_key) {
849            keys.push(map_key.into());
850        }
851    }
852    Ok(keys)
853}
854
855use std::sync::RwLock;
856
857#[derive(Debug)]
858pub struct Root<T> {
859    mounts: Arc<RwLock<Vec<(SmolStr, Mount<T>)>>>,
860}
861
862impl<T: std::fmt::Debug> std::fmt::Debug for Mount<T> {
863    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
864        match self {
865            Self::Memory(_) => write!(f, "Mount::Memory"),
866            Self::Redis { client: _, rl: _ } => write!(f, "Mount::Redis"),
867            Self::Fjall { .. } => write!(f, "Mount::Fjall"),
868            Self::Dir { base } => write!(f, "Mount::Dir({})", base.display()),
869        }
870    }
871}
872
873impl<T> Clone for Mount<T> {
874    fn clone(&self) -> Self {
875        match self {
876            Self::Memory(m) => Self::Memory(m.clone()),
877            Self::Redis { client, rl } => Self::Redis { client: client.clone(), rl: rl.clone() },
878            Self::Fjall { db, values, write_lock } => Self::Fjall { db: db.clone(), values: values.clone(), write_lock: write_lock.clone() },
879            Self::Dir { base } => Self::Dir { base: base.clone() },
880        }
881    }
882}
883
884impl<T: std::fmt::Debug + MsgPack + MsgUnpack + Default + Send> Root<T> {
885    pub fn new() -> Self {
886        Self { mounts: Arc::new(RwLock::new(vec![("local".into(), Mount::<T>::memory())])) }
887    }
888
889    pub fn mount_memory(&self, name: &str) -> bool {
890        let mounts = self.mounts.write();
891        match mounts {
892            Ok(mut mounts) => {
893                if mounts.iter().any(|(n, _)| n == name) {
894                    return false;
895                }
896                mounts.push((name.into(), Mount::<T>::memory()));
897                true
898            }
899            Err(_) => false,
900        }
901    }
902
903    pub fn mount_redis(&self, name: &str, url: &str) -> Result<bool> {
904        let mounts = self.mounts.write();
905        match mounts {
906            Ok(mut mounts) => {
907                if mounts.iter().any(|(n, _)| n == name) {
908                    return Ok(false);
909                }
910                mounts.push((name.into(), Mount::<T>::redis(url)?));
911                Ok(true)
912            }
913            Err(e) => Err(anyhow!("无法获取写锁: {}", e)),
914        }
915    }
916
917    pub fn mount_fjall(&self, name: &str, data_dir: &str) -> Result<bool> {
918        let mounts = self.mounts.write();
919        match mounts {
920            Ok(mut mounts) => {
921                if mounts.iter().any(|(n, _)| n == name) {
922                    return Ok(false);
923                }
924                mounts.push((name.into(), Mount::<T>::fjall(data_dir)?));
925                Ok(true)
926            }
927            Err(e) => Err(anyhow!("无法获取写锁: {}", e)),
928        }
929    }
930
931    /// 把 host 文件系统目录挂到 ROOT 树。
932    ///
933    /// 安全模型:必须显式 `mount_dir` 才能让脚本访问 host 文件,**默认拒绝**
934    /// 任何未挂载的 host 路径。`host_dir` 必须存在;不存在返回 Err,不自动创建,
935    /// 避免脚本无意中写错路径就在磁盘上建出一坨空目录。
936    ///
937    /// 挂载后的 root 路径只能访问 `host_dir` 之下的文件:
938    /// 拒绝 `..` 跳出,也拒绝绝对路径段。
939    pub fn mount_dir(&self, name: &str, host_dir: &str) -> Result<bool> {
940        let base = std::fs::canonicalize(host_dir).map_err(|e| anyhow!("mount_dir {} 失败:无法解析 host_dir={}: {}", name, host_dir, e))?;
941        let mounts = self.mounts.write();
942        match mounts {
943            Ok(mut mounts) => {
944                if mounts.iter().any(|(n, _)| n == name) {
945                    return Ok(false);
946                }
947                mounts.push((name.into(), Mount::<T>::Dir { base }));
948                Ok(true)
949            }
950            Err(e) => Err(anyhow!("无法获取写锁: {}", e)),
951        }
952    }
953
954    pub fn get_mount<'a>(&self, name: &'a str) -> Result<(Mount<T>, &'a str)> {
955        let (mount, name) = name.split_once('/').ok_or(anyhow!("{} 没有 root 路径", name))?;
956        let mounts = self.mounts.read().map_err(|e| anyhow!("无法获取读锁: {}", e))?;
957        let m = mounts.iter().find_map(|m| if m.0 == mount { Some(m.1.clone()) } else { None }).ok_or(anyhow!("没有找到 {}", name))?;
958        Ok((m, name))
959    }
960}
961
962// ----------------------------------------------------------------------------
963// Mount::Dir 路径安全 + 序列化/反序列化 helpers
964// ----------------------------------------------------------------------------
965
966/// 拒绝 `..` 跳出 mount 边界,也拒绝绝对路径段(防 caller 用 `name` 直接给
967/// `/etc/passwd` 之类越界访问)。`./` 当前目录前缀忽略,只 push 真实段。
968pub(super) fn safe_path(base: &Path, name: &str) -> Option<PathBuf> {
969    let mut path = base.to_path_buf();
970    for component in Path::new(name).components() {
971        match component {
972            std::path::Component::Normal(c) => path.push(c),
973            std::path::Component::CurDir => {}
974            // ParentDir (..)、RootDir (/)、Prefix (C:\) 一律拒绝。
975            std::path::Component::ParentDir | std::path::Component::RootDir | std::path::Component::Prefix(_) => return None,
976        }
977    }
978    Some(path)
979}
980
981/// 按文件扩展名把 `Dynamic` 编码成字节流:
982/// `.json` → `to_json`、`.yaml`/`.yml` → `to_yaml`、`.md` → `to_markdown`,
983/// 其他 → `value.as_str()` 字节流。`add` 写入文件失败时调用方据此报错。
984fn encode_value(name: &str, value: &Dynamic) -> Result<Vec<u8>> {
985    let ext = Path::new(name).extension().and_then(|e| e.to_str()).unwrap_or("");
986    match ext {
987        "json" => {
988            let mut s = String::new();
989            value.to_json(&mut s);
990            Ok(s.into_bytes())
991        }
992        "yaml" | "yml" => Ok(value.to_yaml_string().into_bytes()),
993        "md" => Ok(value.to_markdown().into_bytes()),
994        _ => {
995            // 默认按 String 处理:Dynamic 必须能转成 &str,
996            // 否则调用方得到一个 non-UTF-8 空字节序列,写文件无意义。
997            let s = value.as_str();
998            Ok(s.as_bytes().to_vec())
999        }
1000    }
1001}
1002
1003/// 按文件扩展名把字节流反序列化成 `Dynamic`。`.md` 没有反向 parser,
1004/// 统一回退到 String(用户原文:`json md yaml 之外就是直接按 String 读取`,
1005/// md 也归到 String 这一边,符合 dynamic 库目前的能力边界)。
1006fn decode_value(name: &str, bytes: &[u8]) -> Result<Dynamic> {
1007    let ext = Path::new(name).extension().and_then(|e| e.to_str()).unwrap_or("");
1008    match ext {
1009        "json" => {
1010            let (v, _) = Dynamic::from_json(bytes)?;
1011            Ok(v)
1012        }
1013        "yaml" | "yml" => {
1014            let (v, _) = Dynamic::from_yaml(bytes)?;
1015            Ok(v)
1016        }
1017        _ => {
1018            let s = std::str::from_utf8(bytes).map_err(|e| anyhow!("非 UTF-8 文本: {}", e))?;
1019            Ok(Dynamic::from(s.to_string()))
1020        }
1021    }
1022}
1023
1024/// 把 `Mount<Object>` 的 Dir 后端特有的文件读写方法集中在一处。
1025///
1026/// `impl<T: ...> Mount<T>` 通用方法对 Dir 分支只返回 Err/占位,真正的
1027/// 文件 I/O 与序列化由这里的 `dir_*` 方法处理。`root::lib.rs` 在调度
1028/// `add`/`get`/`remove`/`update`/`contains` 时,先 match `Mount::Dir { .. }`
1029/// 走这些方法。
1030impl Mount<Object> {
1031    pub fn dir_add(&self, name: &str, value: Object) -> bool {
1032        let base = match self {
1033            Self::Dir { base } => base,
1034            _ => return false,
1035        };
1036        let Object::Value(dynamic) = value else { return false };
1037        let Some(path) = safe_path(base, name) else { return false };
1038        let Ok(bytes) = encode_value(name, &dynamic) else { return false };
1039        if let Some(parent) = path.parent() {
1040            if std::fs::create_dir_all(parent).is_err() {
1041                return false;
1042            }
1043        }
1044        std::fs::write(&path, bytes).is_ok()
1045    }
1046
1047    /// 读取文件 / 探测目录:目录 → Null(不报错),文件 → 按扩展名 decode,
1048    /// 不存在 → Err。调用方拿到的 `Ok(Null)` 即可区分"这是目录"。
1049    pub fn dir_get(&self, name: &str) -> Result<Dynamic> {
1050        let base = match self {
1051            Self::Dir { base } => base,
1052            _ => return Err(anyhow!("dir_get 仅在 Dir 后端可用")),
1053        };
1054        let path = safe_path(base, name).ok_or_else(|| anyhow!("path 非法: {}", name))?;
1055        if !path.exists() {
1056            return Err(anyhow!("{} 不存在", name));
1057        }
1058        if path.is_dir() {
1059            return Ok(Dynamic::Null);
1060        }
1061        let bytes = std::fs::read(&path).map_err(|e| anyhow!("读取 {} 失败: {}", path.display(), e))?;
1062        decode_value(name, &bytes).map_err(|e| anyhow!("解析 {} 失败: {}", name, e))
1063    }
1064
1065    pub fn dir_remove(&self, name: &str) -> Result<Dynamic> {
1066        let base = match self {
1067            Self::Dir { base } => base,
1068            _ => return Err(anyhow!("dir_remove 仅在 Dir 后端可用")),
1069        };
1070        let path = safe_path(base, name).ok_or_else(|| anyhow!("path 非法: {}", name))?;
1071        if !path.is_file() {
1072            return Err(anyhow!("{} 不是文件", name));
1073        }
1074        let bytes = std::fs::read(&path).map_err(|e| anyhow!("读取 {} 失败: {}", path.display(), e))?;
1075        let dynamic = decode_value(name, &bytes).map_err(|e| anyhow!("解析 {} 失败: {}", name, e))?;
1076        std::fs::remove_file(&path).map_err(|e| anyhow!("删除 {} 失败: {}", path.display(), e))?;
1077        Ok(dynamic)
1078    }
1079
1080
1081    pub fn dir_contains(&self, name: &str) -> bool {
1082        let base = match self {
1083            Self::Dir { base } => base,
1084            _ => return false,
1085        };
1086        match safe_path(base, name) {
1087            Some(p) => p.exists(),
1088            None => false,
1089        }
1090    }
1091
1092    /// read + apply f + write。**不保证原子**:两次 syscall 之间进程崩溃
1093    /// 会丢更新。Memory/Redis/Fjall 的 `update` 同样不保证跨进程原子,
1094    /// 这是 ROOT 抽象层的整体选择。
1095    pub fn dir_update<F>(&self, name: &str, f: F) -> Result<Dynamic>
1096    where
1097        F: FnOnce(Dynamic) -> Dynamic,
1098    {
1099        let base = match self {
1100            Self::Dir { base } => base,
1101            _ => return Err(anyhow!("dir_update 仅在 Dir 后端可用")),
1102        };
1103        let path = safe_path(base, name).ok_or_else(|| anyhow!("path 非法: {}", name))?;
1104        if path.is_dir() {
1105            return Err(anyhow!("{} 是目录,不能 update", name));
1106        }
1107        let bytes = std::fs::read(&path).map_err(|e| anyhow!("读取 {} 失败: {}", path.display(), e))?;
1108        let current = decode_value(name, &bytes).map_err(|e| anyhow!("解析 {} 失败: {}", name, e))?;
1109        let next = f(current);
1110        let new_bytes = encode_value(name, &next)?;
1111        if let Some(parent) = path.parent() {
1112            std::fs::create_dir_all(parent).map_err(|e| anyhow!("创建父目录失败: {}", e))?;
1113        }
1114        std::fs::write(&path, new_bytes).map_err(|e| anyhow!("写入 {} 失败: {}", path.display(), e))?;
1115        Ok(next)
1116    }
1117}
1118
1119// ----------------------------------------------------------------------------
1120// `impl Mount<Object>` 的特化方法
1121// ----------------------------------------------------------------------------
1122
1123impl Mount<Object> {
1124    /// Mount::Dir 后端的 `get` 特化:从文件 decode,wrap 成 Object::Value。
1125    /// 解决 mount_dir 写完 YAML 后 `root::get` 拿不到 round-trip Dynamic 的问题。
1126    pub fn get_for_object<R, F: FnOnce(&Object) -> R>(
1127        &self,
1128        name: &str,
1129        f: F,
1130    ) -> Result<R> {
1131        match self {
1132            Self::Dir { .. } => {
1133                let dynamic = self.dir_get(name)?;
1134                let mut value = Object::default();
1135                if let Object::Value(slot) = &mut value {
1136                    *slot = dynamic;
1137                }
1138                Ok(f(&value))
1139            }
1140            // 其它后端走通用 get,但 callback 类型是 `&Object`(非泛型)。
1141            _ => self.get(name, |v| f(v)),
1142        }
1143    }
1144}
1145
1146#[cfg(test)]
1147mod tests {
1148    use super::*;
1149    use dynamic::Dynamic;
1150
1151    #[test]
1152    fn dir_returns_only_immediate_children() {
1153        let root = Root::<Dynamic>::new();
1154        let (mount, name) = root.get_mount("local/test/dir/a").unwrap();
1155        assert!(mount.add(name, 1.into()));
1156        let (mount, name) = root.get_mount("local/test/dir/sub/item").unwrap();
1157        assert!(mount.add(name, 2.into()));
1158        let (mount, name) = root.get_mount("local/test/dir2/x").unwrap();
1159        assert!(mount.add(name, 3.into()));
1160
1161        let (mount, name) = root.get_mount("local/test/dir").unwrap();
1162        let mut entries = mount.dir(name).unwrap();
1163        entries.sort();
1164
1165        assert_eq!(entries, vec![SmolStr::new("a"), SmolStr::new("sub")]);
1166    }
1167
1168    #[test]
1169    fn dir_accepts_trailing_slash() {
1170        let root = Root::<Dynamic>::new();
1171        let (mount, name) = root.get_mount("local/test/slash/a").unwrap();
1172        assert!(mount.add(name, 1.into()));
1173
1174        let (mount, name) = root.get_mount("local/test/slash/").unwrap();
1175        assert_eq!(mount.dir(name).unwrap(), vec![SmolStr::new("a")]);
1176    }
1177
1178    #[test]
1179    fn fjall_mount_persists_values_and_dirs() {
1180        let data_dir = std::env::temp_dir().join(format!("zust-root-fjall-{}", uuid::Uuid::new_v4()));
1181        let data_dir_str = data_dir.to_str().unwrap();
1182
1183        {
1184            let root = Root::<Dynamic>::new();
1185            assert!(root.mount_fjall("fjall", data_dir_str).unwrap());
1186            let (mount, name) = root.get_mount("fjall/test/kv/a").unwrap();
1187            assert!(mount.add(name, 42.into()));
1188            let (mount, name) = root.get_mount("fjall/test/kv/b").unwrap();
1189            assert!(mount.add(name, "persisted".into()));
1190            let (mount, name) = root.get_mount("fjall/test/list").unwrap();
1191            mount.add_list(name);
1192            assert_eq!(mount.push(name, 7.into()).unwrap(), 0);
1193            let (mount, name) = root.get_mount("fjall/test/map").unwrap();
1194            mount.add_map(name);
1195            mount.insert(name, "answer", 42.into());
1196            mount.insert(name, "0", "first".into());
1197            mount.insert(name, "1", "second".into());
1198        }
1199
1200        {
1201            let root = Root::<Dynamic>::new();
1202            assert!(root.mount_fjall("fjall", data_dir_str).unwrap());
1203            let (mount, name) = root.get_mount("fjall/test/kv/a").unwrap();
1204            assert_eq!(mount.get(name, |v| v.as_int()).unwrap(), Some(42));
1205            let (mount, name) = root.get_mount("fjall/test").unwrap();
1206            let mut entries = mount.dir(name).unwrap();
1207            entries.sort();
1208            assert_eq!(entries, vec![SmolStr::new("kv"), SmolStr::new("list"), SmolStr::new("map")]);
1209            let (mount, name) = root.get_mount("fjall/test/list").unwrap();
1210            assert_eq!(mount.get_idx(name, 0, |v| v.as_int()).unwrap(), Some(7));
1211            let (mount, name) = root.get_mount("fjall/test/map").unwrap();
1212            assert_eq!(mount.get_key(name, "answer", |v| v.as_int()).unwrap(), Some(42));
1213            let mut keys = mount.keys(name).unwrap();
1214            keys.sort();
1215            assert_eq!(keys, vec![SmolStr::new("0"), SmolStr::new("1"), SmolStr::new("answer")]);
1216        }
1217
1218        let _ = std::fs::remove_dir_all(data_dir);
1219    }
1220
1221    #[test]
1222    fn fjall_dir_skips_deep_children_without_missing_siblings() {
1223        let data_dir = std::env::temp_dir().join(format!("zust-root-fjall-dir-{}", uuid::Uuid::new_v4()));
1224        let data_dir_str = data_dir.to_str().unwrap();
1225        let root = Root::<Dynamic>::new();
1226        assert!(root.mount_fjall("fjall", data_dir_str).unwrap());
1227
1228        for path in [
1229            "fjall/root/a/deep/one",
1230            "fjall/root/a/deep/two",
1231            "fjall/root/a-/leaf",
1232            "fjall/root/a./leaf",
1233            "fjall/root/a0/leaf",
1234            "fjall/root/b",
1235        ] {
1236            let (mount, name) = root.get_mount(path).unwrap();
1237            assert!(mount.add(name, 1.into()));
1238        }
1239
1240        let (mount, name) = root.get_mount("fjall/root").unwrap();
1241        let mut entries = mount.dir(name).unwrap();
1242        entries.sort();
1243
1244        assert_eq!(entries, vec![SmolStr::new("a"), SmolStr::new("a-"), SmolStr::new("a."), SmolStr::new("a0"), SmolStr::new("b")]);
1245
1246        let _ = std::fs::remove_dir_all(data_dir);
1247    }
1248
1249    // ---- Mount::Dir 测试 -----------------------------------------------------
1250    //
1251    // 每个测试用独立 temp dir,测完清理。Mount::Object 的 dir_* 方法被 Root<Object>
1252    // 的全局调度调用,所以这些测试也覆盖 lib.rs 的 add/get/contains/remove/update
1253    // 的 Dir 分支。
1254
1255    fn fresh_dir_mount(name: &str) -> (Mount<Object>, std::path::PathBuf) {
1256        let dir = std::env::temp_dir().join(format!("zust-root-dir-{}", uuid::Uuid::new_v4()));
1257        std::fs::create_dir_all(&dir).unwrap();
1258        let root = Root::<Object>::new();
1259        assert!(root.mount_dir(name, dir.to_str().unwrap()).unwrap());
1260        let (mount, _) = root.get_mount(&format!("{}/_ignored", name)).unwrap();
1261        (mount, dir)
1262    }
1263
1264    /// `Mount<Object>` 的 dir_* 方法测试——直接覆盖文件读写与边界条件。
1265    /// lib.rs 集成测试由 zust-vm 的脚本测试覆盖。
1266
1267    #[test]
1268    fn mount_dir_yaml_round_trip() {
1269        let (mount, base) = fresh_dir_mount("ddd");
1270        let value = Dynamic::map(Default::default());
1271        let mut value = value;
1272        value.insert("name", "zust");
1273        value.insert("age", 18);
1274
1275        assert!(mount.dir_add("x.yaml", Object::Value(value)));
1276        assert!(base.join("x.yaml").is_file());
1277
1278        let got = mount.dir_get("x.yaml").unwrap();
1279        assert_eq!(got.get_dynamic("name").map(|v| v.as_str().to_string()), Some("zust".to_string()));
1280        assert_eq!(got.get_dynamic("age").and_then(|v| v.as_int()), Some(18));
1281
1282        let _ = std::fs::remove_dir_all(base);
1283    }
1284
1285    #[test]
1286    fn mount_dir_json_round_trip() {
1287        let (mount, base) = fresh_dir_mount("ddd");
1288        let mut value = Dynamic::map(Default::default());
1289        value.insert("k", "v");
1290        value.insert("n", 42);
1291
1292        assert!(mount.dir_add("data.json", Object::Value(value)));
1293        let got = mount.dir_get("data.json").unwrap();
1294        assert_eq!(got.get_dynamic("k").map(|v| v.as_str().to_string()), Some("v".to_string()));
1295        assert_eq!(got.get_dynamic("n").and_then(|v| v.as_int()), Some(42));
1296
1297        let _ = std::fs::remove_dir_all(base);
1298    }
1299
1300    #[test]
1301    fn mount_dir_md_writes_markdown_reads_string() {
1302        let (mount, base) = fresh_dir_mount("ddd");
1303        let mut value = Dynamic::map(Default::default());
1304        // 用 i64 值绕开 to_markdown 把 String 当 char list 迭代的现有行为:
1305        // 当前 dynamic 实现把 Dynamic::String 视作字符序列,直接 panic 在 unwrap。
1306        // 这里只验证 .md 写/读路径正常,不强求 markdown 内容细节。
1307        value.insert("count", 42);
1308        assert!(mount.dir_add("notes.md", Object::Value(value)));
1309        let content = std::fs::read_to_string(base.join("notes.md")).unwrap();
1310        assert!(content.contains("count"));
1311
1312        // .md 没有 from_markdown,读取按 String 处理。
1313        let got = mount.dir_get("notes.md").unwrap();
1314        assert!(got.as_str().contains("count"));
1315
1316        let _ = std::fs::remove_dir_all(base);
1317    }
1318
1319    #[test]
1320    fn mount_dir_raw_string_extension() {
1321        let (mount, base) = fresh_dir_mount("ddd");
1322        let value = Dynamic::from("hello world".to_string());
1323        assert!(mount.dir_add("readme.txt", Object::Value(value)));
1324        let got = mount.dir_get("readme.txt").unwrap();
1325        assert_eq!(got.as_str(), "hello world");
1326
1327        let _ = std::fs::remove_dir_all(base);
1328    }
1329
1330    #[test]
1331    fn mount_dir_len_file_is_size_dir_is_zero() {
1332        let (mount, base) = fresh_dir_mount("ddd");
1333        let payload = "x".repeat(123);
1334        assert!(mount.dir_add("a.yaml", Object::Value(Dynamic::from(payload))));
1335
1336        // 文件 = 字节数(yaml 序列化长度,>= 原始 123)。
1337        let file_len = mount.len("a.yaml").unwrap();
1338        assert!(file_len >= 123);
1339
1340        // 创建子目录后,目录长度 = 0。
1341        std::fs::create_dir_all(base.join("sub")).unwrap();
1342        let dir_len = mount.len("sub").unwrap();
1343        assert_eq!(dir_len, 0);
1344
1345        // 不存在 → Err。
1346        assert!(mount.len("missing").is_err());
1347
1348        let _ = std::fs::remove_dir_all(base);
1349    }
1350
1351    #[test]
1352    fn mount_dir_dir_lists_files_and_dirs_undistinguished() {
1353        let (mount, base) = fresh_dir_mount("ddd");
1354        assert!(mount.dir_add("a.yaml", Object::Value(Dynamic::from(1i64))));
1355        assert!(mount.dir_add("b.txt", Object::Value(Dynamic::from("x".to_string()))));
1356        std::fs::create_dir_all(base.join("sub")).unwrap();
1357        std::fs::create_dir_all(base.join("nested")).unwrap();
1358
1359        let mut names = mount.dir_raw("").unwrap();
1360        names.sort();
1361        assert_eq!(names, vec![SmolStr::new("a.yaml"), SmolStr::new("b.txt"), SmolStr::new("nested"), SmolStr::new("sub")]);
1362
1363        let _ = std::fs::remove_dir_all(base);
1364    }
1365
1366    #[test]
1367    fn mount_dir_get_directory_returns_null() {
1368        let (mount, base) = fresh_dir_mount("ddd");
1369        std::fs::create_dir_all(base.join("subdir")).unwrap();
1370
1371        // get 目录 → Null(不报错)。
1372        let got = mount.dir_get("subdir").unwrap();
1373        assert!(got.is_null());
1374
1375        let _ = std::fs::remove_dir_all(base);
1376    }
1377
1378    #[test]
1379    fn mount_dir_get_missing_returns_err() {
1380        let (mount, base) = fresh_dir_mount("ddd");
1381        assert!(mount.dir_get("missing.yaml").is_err());
1382        let _ = std::fs::remove_dir_all(base);
1383    }
1384
1385    #[test]
1386    fn mount_dir_contains_file_and_dir() {
1387        let (mount, base) = fresh_dir_mount("ddd");
1388        assert!(mount.dir_add("a.yaml", Object::Value(Dynamic::from(1i64))));
1389        std::fs::create_dir_all(base.join("sub")).unwrap();
1390
1391        assert!(mount.dir_contains("a.yaml"));
1392        assert!(mount.dir_contains("sub"));
1393        assert!(!mount.dir_contains("missing"));
1394
1395        let _ = std::fs::remove_dir_all(base);
1396    }
1397
1398    #[test]
1399    fn mount_dir_remove_returns_decoded_value() {
1400        let (mount, base) = fresh_dir_mount("ddd");
1401        let mut v = Dynamic::map(Default::default());
1402        v.insert("hello", "world");
1403        assert!(mount.dir_add("x.json", Object::Value(v)));
1404
1405        let removed = mount.dir_remove("x.json").unwrap();
1406        assert_eq!(removed.get_dynamic("hello").map(|d| d.as_str().to_string()), Some("world".to_string()));
1407        assert!(!base.join("x.json").exists());
1408
1409        let _ = std::fs::remove_dir_all(base);
1410    }
1411
1412    #[test]
1413    fn mount_dir_path_traversal_rejected() {
1414        let (mount, base) = fresh_dir_mount("ddd");
1415        // 尝试用 .. 跳出:应该被 safe_path 拒绝。
1416        assert!(!mount.dir_add("../escape.txt", Object::Value(Dynamic::from("pwned"))));
1417        // 跳不出去的标志:base 之外没有 escape.txt 文件。
1418        let escape = base.parent().unwrap().join("escape.txt");
1419        assert!(!escape.exists());
1420
1421        // 绝对路径段也拒绝。
1422        assert!(!mount.dir_add("/etc/passwd", Object::Value(Dynamic::from("x"))));
1423
1424        let _ = std::fs::remove_dir_all(base);
1425    }
1426
1427    #[test]
1428    fn mount_dir_update_read_modify_write() {
1429        let (mount, base) = fresh_dir_mount("ddd");
1430        let mut v = Dynamic::map(Default::default());
1431        v.insert("count", 1);
1432        assert!(mount.dir_add("c.json", Object::Value(v)));
1433
1434        let next = mount.dir_update("c.json", |mut cur| {
1435            if let Some(n) = cur.get_dynamic("count").and_then(|d| d.as_int()) {
1436                cur.insert("count", n + 10);
1437            }
1438            cur
1439        }).unwrap();
1440        assert_eq!(next.get_dynamic("count").and_then(|d| d.as_int()), Some(11));
1441
1442        let _ = std::fs::remove_dir_all(base);
1443    }
1444
1445    #[test]
1446    fn mount_dir_rejects_nonexistent_host_dir() {
1447        let root = Root::<Object>::new();
1448        let missing = std::env::temp_dir().join(format!("zust-root-missing-{}", uuid::Uuid::new_v4()));
1449        assert!(root.mount_dir("ddd", missing.to_str().unwrap()).is_err());
1450    }
1451
1452    #[test]
1453    fn mount_dir_rejects_list_and_map_operations() {
1454        let (mount, base) = fresh_dir_mount("ddd");
1455        // add_list / add_map / push / get_idx / insert / keys 在 Dir 后端必须报错或 no-op,
1456        // 不能像 Memory 后端那样静默成功。
1457        mount.add_list("lst");
1458        assert!(mount.push("lst", Object::Value(Dynamic::from(1i64))).is_err());
1459        mount.insert("mp", "k", Object::Value(Dynamic::from(1i64)));
1460        assert!(mount.get_idx("lst", 0, |v| v.value()).is_err());
1461        assert!(mount.get_key("mp", "k", |v| v.value()).is_err());
1462        assert!(mount.keys("mp").is_err());
1463
1464        let _ = std::fs::remove_dir_all(base);
1465    }
1466}