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}