Skip to main content

wanning_core/
paths.rs

1//! W-43a 产品化:默认路径与 WAL 目录自动创建。
2//!
3//! 产品化的「零配置」体验:用户什么都不配置时,审计账本落在用户家目录的
4//! `.wanning/wal.jsonl` 下(Windows = `%USERPROFILE%\.wanning\wal.jsonl`),
5//! 父目录不存在就自动创建。两件事都刻意**保守**:
6//!
7//! - 家目录解析顺序固定为 `WANNING_HOME`(测试/隔离开关,不是用户要配的东西)
8//!   → `USERPROFILE`(Windows 标准)→ `HOME`(Unix 标准);三处都拿不到就返回
9//!   `None`,调用方 fail-closed 报错并给「显式路径」逃生门——**绝不猜一个落点**,
10//!   也绝不静默落到当前目录。
11//! - 只自动建 WAL 的父目录这一个目录,不建「.wanning 之外的任何东西」;裸文件名
12//!   (无父目录)是 no-op。
13
14use std::env;
15use std::path::{Path, PathBuf};
16
17use crate::error::CoreError;
18
19/// 家目录环境变量,按优先级排列(测试隔离 > Windows 标准 > Unix 标准)。
20const HOME_VARS: [&str; 3] = ["WANNING_HOME", "USERPROFILE", "HOME"];
21
22/// 解析家目录。三处都拿不到 = `None`(fail-closed,调用方报错,绝不猜落点)。
23pub fn home_dir() -> Option<PathBuf> {
24    for key in HOME_VARS {
25        match env::var_os(key) {
26            Some(value) if !value.is_empty() => return Some(PathBuf::from(value)),
27            _ => continue,
28        }
29    }
30    None
31}
32
33/// 默认配置/账本根目录:`<home>/.wanning`(Windows = `%USERPROFILE%\.wanning`)。
34pub fn default_home() -> Option<PathBuf> {
35    home_dir().map(|home| home.join(".wanning"))
36}
37
38/// 默认审计账本路径:`<home>/.wanning/wal.jsonl`。纯路径计算,零 IO。
39pub fn default_wal_path() -> Option<PathBuf> {
40    default_home().map(|home| home.join("wal.jsonl"))
41}
42
43/// 确保 WAL 的父目录存在(不存在则递归创建;已存在或裸文件名 = no-op)。
44///
45/// 在 [`crate::wal::Wal::open`] 里于拿锁**之前**调用:锁文件 `<wal>.lock` 要落在
46/// 刚建出来的目录里。Windows 的裸文件名 `Path::new("a.jsonl").parent()` 返回
47/// `Some("")`,空串按「无目录」处理。
48pub fn ensure_wal_parent(wal_path: &Path) -> Result<(), CoreError> {
49    let Some(parent) = wal_path.parent() else {
50        return Ok(());
51    };
52    if parent.as_os_str().is_empty() {
53        return Ok(());
54    }
55    std::fs::create_dir_all(parent).map_err(|e| {
56        CoreError::WalIo(format!(
57            "创建审计账本目录 {parent:?} 失败: {e}(可用显式路径绕开默认位置)"
58        ))
59    })
60}