Skip to main content

harn_vm/
runtime_paths.rs

1use std::path::{Path, PathBuf};
2
3pub const HARN_STATE_DIR_ENV: &str = "HARN_STATE_DIR";
4pub const HARN_RUN_DIR_ENV: &str = "HARN_RUN_DIR";
5pub const HARN_WORKTREE_DIR_ENV: &str = "HARN_WORKTREE_DIR";
6
7#[cfg(test)]
8pub(crate) fn test_env_lock() -> &'static std::sync::Mutex<()> {
9    use std::sync::{Mutex, OnceLock};
10    static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
11    LOCK.get_or_init(|| Mutex::new(()))
12}
13
14fn resolve_root_value(base_dir: &Path, env_value: Option<&str>, default_relative: &str) -> PathBuf {
15    match env_value {
16        Some(value) if !value.trim().is_empty() => {
17            let candidate = PathBuf::from(value);
18            if candidate.is_absolute() {
19                candidate
20            } else {
21                base_dir.join(candidate)
22            }
23        }
24        _ => base_dir.join(default_relative),
25    }
26}
27
28fn resolve_root(base_dir: &Path, env_key: &str, default_relative: &str) -> PathBuf {
29    let env_value = std::env::var(env_key).ok();
30    resolve_root_value(base_dir, env_value.as_deref(), default_relative)
31}
32
33pub fn state_root(base_dir: &Path) -> PathBuf {
34    resolve_root(base_dir, HARN_STATE_DIR_ENV, ".harn")
35}
36
37pub fn run_root(base_dir: &Path) -> PathBuf {
38    resolve_root(base_dir, HARN_RUN_DIR_ENV, ".harn-runs")
39}
40
41fn worktree_root_value(
42    base_dir: &Path,
43    state_env_value: Option<&str>,
44    worktree_env_value: Option<&str>,
45) -> PathBuf {
46    match worktree_env_value {
47        Some(value) if !value.trim().is_empty() => {
48            let candidate = PathBuf::from(value);
49            if candidate.is_absolute() {
50                candidate
51            } else {
52                base_dir.join(candidate)
53            }
54        }
55        _ => resolve_root_value(base_dir, state_env_value, ".harn").join("worktrees"),
56    }
57}
58
59pub fn worktree_root(base_dir: &Path) -> PathBuf {
60    let state_env_value = std::env::var(HARN_STATE_DIR_ENV).ok();
61    let worktree_env_value = std::env::var(HARN_WORKTREE_DIR_ENV).ok();
62    worktree_root_value(
63        base_dir,
64        state_env_value.as_deref(),
65        worktree_env_value.as_deref(),
66    )
67}
68
69pub fn store_path(base_dir: &Path) -> PathBuf {
70    state_root(base_dir).join("store.json")
71}
72
73pub fn checkpoint_dir(base_dir: &Path) -> PathBuf {
74    state_root(base_dir).join("checkpoints")
75}
76
77pub fn metadata_dir(base_dir: &Path) -> PathBuf {
78    state_root(base_dir).join("metadata")
79}
80
81pub fn event_log_dir(base_dir: &Path) -> PathBuf {
82    state_root(base_dir).join("events")
83}
84
85pub fn event_log_sqlite_path(base_dir: &Path) -> PathBuf {
86    state_root(base_dir).join("events.sqlite")
87}
88
89pub fn workflow_dir(base_dir: &Path) -> PathBuf {
90    state_root(base_dir).join("workflows")
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96
97    #[test]
98    fn defaults_resolve_under_base_dir() {
99        let base = Path::new("/tmp/harn-runtime-paths");
100        assert_eq!(resolve_root_value(base, None, ".harn"), base.join(".harn"));
101        assert_eq!(
102            resolve_root_value(base, None, ".harn-runs"),
103            base.join(".harn-runs")
104        );
105        assert_eq!(
106            worktree_root_value(base, None, None),
107            base.join(".harn").join("worktrees")
108        );
109        assert_eq!(
110            resolve_root_value(base, None, ".harn").join("events"),
111            base.join(".harn").join("events")
112        );
113        assert_eq!(
114            resolve_root_value(base, None, ".harn").join("workflows"),
115            base.join(".harn").join("workflows")
116        );
117        assert_eq!(
118            resolve_root_value(base, None, ".harn").join("events.sqlite"),
119            base.join(".harn").join("events.sqlite")
120        );
121    }
122}