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