use std::path::{Path, PathBuf};
use super::InstallError;
pub const CACHE_DIR_ENV: &str = "FERROCV_CACHE_DIR";
pub fn cache_root() -> Result<PathBuf, InstallError> {
if let Ok(value) = std::env::var(CACHE_DIR_ENV) {
if value.is_empty() {
return Err(InstallError::CacheDirUnresolved);
}
return Ok(PathBuf::from(value));
}
dirs::cache_dir()
.map(|p| p.join("ferrocv"))
.ok_or(InstallError::CacheDirUnresolved)
}
pub fn preview_cache_root() -> Result<PathBuf, InstallError> {
Ok(cache_root()?.join("packages").join("preview"))
}
pub fn package_cache_dir(name: &str, version: &str) -> Result<PathBuf, InstallError> {
Ok(preview_cache_root()?.join(name).join(version))
}
pub fn ensure_parent_exists(final_path: &Path) -> Result<PathBuf, InstallError> {
let parent = final_path
.parent()
.ok_or_else(|| InstallError::Io {
context: format!("cache path has no parent: {}", final_path.display(),),
source: std::io::Error::other("cache path has no parent"),
})?
.to_path_buf();
std::fs::create_dir_all(&parent).map_err(|source| InstallError::Io {
context: format!("create cache parent {}", parent.display()),
source,
})?;
Ok(parent)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_env::ENV_LOCK;
struct EnvVarGuard {
key: String,
prior: Option<String>,
}
impl Drop for EnvVarGuard {
fn drop(&mut self) {
unsafe {
match &self.prior {
Some(v) => std::env::set_var(&self.key, v),
None => std::env::remove_var(&self.key),
}
}
}
}
fn with_env_var<F: FnOnce()>(key: &str, value: Option<&str>, body: F) {
let _lock = ENV_LOCK.lock().unwrap_or_else(|p| p.into_inner());
let _guard = EnvVarGuard {
key: key.to_owned(),
prior: std::env::var(key).ok(),
};
unsafe {
match value {
Some(v) => std::env::set_var(key, v),
None => std::env::remove_var(key),
}
}
body();
}
#[test]
fn env_var_override_is_honored() {
with_env_var(CACHE_DIR_ENV, Some("/tmp/ferrocv-test-cache"), || {
let path = cache_root().expect("explicit override resolves");
assert_eq!(path, PathBuf::from("/tmp/ferrocv-test-cache"));
});
}
#[test]
fn empty_env_var_is_rejected() {
with_env_var(CACHE_DIR_ENV, Some(""), || {
let err = cache_root()
.expect_err("empty FERROCV_CACHE_DIR must surface as CacheDirUnresolved");
assert!(matches!(err, InstallError::CacheDirUnresolved));
});
}
#[test]
fn preview_cache_root_appends_expected_suffix() {
with_env_var(CACHE_DIR_ENV, Some("/tmp/ferrocv-test-cache"), || {
let path = preview_cache_root().expect("resolves");
assert_eq!(
path,
PathBuf::from("/tmp/ferrocv-test-cache/packages/preview")
);
});
}
#[test]
fn package_cache_dir_layout_is_stable() {
with_env_var(CACHE_DIR_ENV, Some("/tmp/ferrocv-test-cache"), || {
let path = package_cache_dir("basic-resume", "0.2.8").expect("resolves");
assert_eq!(
path,
PathBuf::from("/tmp/ferrocv-test-cache/packages/preview/basic-resume/0.2.8")
);
});
}
}