Skip to main content

mcpr_integrations/store/
path.rs

1//! Database file path resolution.
2//!
3//! The storage engine needs to know where to put the SQLite file. This module
4//! resolves the path from three sources, in priority order:
5//!
6//! 1. **Explicit config**: `[store] path` in `mcpr.toml` — user chose a specific location.
7//! 2. **Environment variable**: `MCPR_DB` — useful for CI, Docker, or per-session overrides.
8//! 3. **Default**: `~/.mcpr/store.db` — all mcpr state lives under `~/.mcpr/`.
9//!
10//! The parent directory is created automatically if it doesn't exist.
11
12use std::path::PathBuf;
13
14/// Environment variable name for overriding the database path.
15const MCPR_DB_ENV: &str = "MCPR_DB";
16
17/// Database filename within the mcpr data directory.
18const DB_FILENAME: &str = "store.db";
19
20/// Resolve the database file path.
21///
22/// Priority: `config_path` > `$MCPR_DB` env > platform default.
23///
24/// Returns `None` only if no platform data directory can be determined
25/// (extremely rare — means `$HOME` is unset on a headless system).
26pub fn resolve_db_path(config_path: Option<&str>) -> Option<PathBuf> {
27    // 1. Explicit config takes priority — the user made a deliberate choice.
28    if let Some(p) = config_path {
29        return Some(PathBuf::from(p));
30    }
31
32    // 2. Environment variable — useful for CI, Docker, or scripting.
33    if let Ok(p) = std::env::var(MCPR_DB_ENV)
34        && !p.is_empty()
35    {
36        return Some(PathBuf::from(p));
37    }
38
39    // 3. Default — all mcpr state under ~/.mcpr/
40    dirs::home_dir().map(|h| h.join(".mcpr").join(DB_FILENAME))
41}
42
43/// Ensure the parent directory of the given path exists.
44///
45/// Called before opening the database. Creates intermediate directories
46/// as needed. Returns an error if directory creation fails (e.g., permissions).
47pub fn ensure_parent_dir(path: &std::path::Path) -> std::io::Result<()> {
48    if let Some(parent) = path.parent() {
49        std::fs::create_dir_all(parent)?;
50    }
51    Ok(())
52}
53
54#[cfg(test)]
55#[allow(non_snake_case)]
56mod tests {
57    use super::*;
58
59    #[test]
60    fn resolve_db_path__explicit_config_wins() {
61        let result = resolve_db_path(Some("/custom/path/my.db"));
62        assert_eq!(result, Some(PathBuf::from("/custom/path/my.db")));
63    }
64
65    #[test]
66    fn resolve_db_path__platform_default_returns_some() {
67        let result = resolve_db_path(None);
68        assert!(result.is_some());
69        let path = result.unwrap();
70        assert!(path.to_str().unwrap().contains(".mcpr"));
71        assert!(path.to_str().unwrap().ends_with("store.db"));
72    }
73
74    #[test]
75    fn ensure_parent_dir__creates_dirs() {
76        let dir = tempfile::tempdir().unwrap();
77        let db_path = dir.path().join("sub").join("dir").join("mcpr.db");
78        assert!(!db_path.parent().unwrap().exists());
79
80        ensure_parent_dir(&db_path).unwrap();
81        assert!(db_path.parent().unwrap().exists());
82    }
83}